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

This commit is contained in:
hayaksi1
2026-09-03 03:02:35 +03:00
1121 changed files with 7527 additions and 3389 deletions
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_settings_help_us_improve">"提供匿名的使用數據以協助我們釐清問題。"</string>
<string name="screen_analytics_settings_help_us_improve">"提供匿名的使用資料以協助我們釐清問題。"</string>
<string name="screen_analytics_settings_read_terms">"您可以到%1$s閱讀我們的條款。"</string>
<string name="screen_analytics_settings_read_terms_content_link">"這裡"</string>
<string name="screen_analytics_settings_share_data">"提供分析數據"</string>
<string name="screen_analytics_settings_share_data">"提供分析資料"</string>
</resources>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_prompt_data_usage">"Wir speichern oder profilieren keine personenbezogenen Daten."</string>
<string name="screen_analytics_prompt_data_usage">"Wir speichern oder profilieren keine personenbezogenen Daten"</string>
<string name="screen_analytics_prompt_help_us_improve">"Teile anonyme Nutzungsdaten, um uns bei der Identifizierung von Problemen zu helfen."</string>
<string name="screen_analytics_prompt_read_terms">"Weitere Informationen findest du %1$s."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"hier"</string>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_prompt_data_usage">"我們不會紀錄或剖繪您的個人資料"</string>
<string name="screen_analytics_prompt_help_us_improve">"提供匿名的使用數據以協助我們釐清問題。"</string>
<string name="screen_analytics_prompt_help_us_improve">"提供匿名的使用資料以協助我們釐清問題。"</string>
<string name="screen_analytics_prompt_read_terms">"您可以到%1$s閱讀我們的條款。"</string>
<string name="screen_analytics_prompt_read_terms_content_link">"這裡"</string>
<string name="screen_analytics_prompt_settings">"您可以在任何時候關閉它"</string>
@@ -54,10 +54,32 @@ class WebViewWidgetMessageInterceptor(
// objects in log lines.
view.evaluateJavascript(
"""
// Removing any parts that result in a circular structure. Circular structures crash JSON.stringify.
function safeStringify(object) {
const simpleObject = {};
for (const prop in object) {
if (!object.hasOwnProperty(prop)) {
continue;
}
if (typeof(object[prop]) == 'object') {
continue;
}
if (typeof(object[prop]) == 'function') {
continue;
}
simpleObject[prop] = object[prop];
}
try {
return JSON.stringify(simpleObject);
} catch {
return "{Failed to stringify object}";
}
}
function logFn(consoleLogFn, ...args) {
consoleLogFn(
args.map(
a => typeof a === "string" ? a : JSON.stringify(a)
a => typeof a === "string" ? a : safeStringify(a)
).join(' ')
);
};
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="call_foreground_service_channel_title_android">"Połączenie w trakcie"</string>
<string name="call_foreground_service_message_android">"Stuknij, aby wrócić do rozmowy"</string>
<string name="call_foreground_service_message_android">"Dotknij, aby wrócić do rozmowy"</string>
<string name="call_foreground_service_title_android">"☎️ Rozmowa w toku"</string>
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call nie obsługuje korzystania z urządzeń audio Bluetooth w tej wersji Androida. Wybierz inne urządzenie audio."</string>
<string name="screen_incoming_call_subtitle_android">"Przychodzące połączenie Element"</string>
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Neuer Chat"</string>
<string name="screen_create_room_add_people_title">"Nutzer einladen"</string>
<string name="screen_create_room_add_people_title">"Personen einladen"</string>
<string name="screen_create_room_error_creating_room">"Beim Erstellen des Chats ist ein Fehler aufgetreten"</string>
<string name="screen_create_room_error_creating_space">"Der Space konnte wegen eines unbekannten Fehlers nicht erstellt werden. Versuch\' es später nochmal."</string>
<string name="screen_create_room_error_creating_space">"Der Space konnte wegen eines unbekannten Fehlers nicht erstellt werden. Bitte versuche es später erneut."</string>
<string name="screen_create_room_name_placeholder">"Name hinzufügen…"</string>
<string name="screen_create_room_new_room_title">"Neuer Chat"</string>
<string name="screen_create_room_new_room_title">"Neue Gruppe"</string>
<string name="screen_create_room_new_space_title">"Neuer Space"</string>
<string name="screen_create_room_private_option_description">"Nur eingeladene Personen haben Zutritt zu diesem Chat."</string>
<string name="screen_create_room_private_option_title">"Privat"</string>
<string name="screen_create_room_public_option_description">"Jeder kann diesen Chat finden.
Du kannst dies jederzeit in den Einstellungen des Chats ändern."</string>
<string name="screen_create_room_public_option_short_description">"Jeder kann beitreten."</string>
<string name="screen_create_room_public_option_short_description">"Alle können beitreten."</string>
<string name="screen_create_room_public_option_title">"Öffentlich"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Jeder kann den Beitritt zum Chat erbitten, aber ein Admin oder Moderator muss die Anfrage akzeptieren."</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Anfrage zum Beitritt zulassen"</string>
@@ -19,18 +19,18 @@ Du kannst dies jederzeit in den Einstellungen des Chats ändern."</string>
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Beitritt anfragen"</string>
<string name="screen_create_room_room_access_section_private_option_description">"Nur eingeladene Personen können beitreten."</string>
<string name="screen_create_room_room_access_section_private_option_title">"Privat"</string>
<string name="screen_create_room_room_access_section_public_option_description">"Jeder darf diesem Chat beitreten."</string>
<string name="screen_create_room_room_access_section_public_option_description">"Alle können beitreten."</string>
<string name="screen_create_room_room_access_section_public_option_title">"Öffentlich"</string>
<string name="screen_create_room_room_access_section_restricted_option_description">"Jeder in %1$s kann beitreten."</string>
<string name="screen_create_room_room_access_section_restricted_option_title">"Standard"</string>
<string name="screen_create_room_room_access_section_title">"Wer hat Zugang"</string>
<string name="screen_create_room_room_address_section_footer">"Du benötigst eine Adresse, um diesen Chat im öffentlichen Verzeichnis sichtbar zu machen."</string>
<string name="screen_create_room_room_address_section_title">"Adresse"</string>
<string name="screen_create_room_room_visibility_section_title">" Sichtbarkeit des Chats"</string>
<string name="screen_create_room_room_visibility_section_title">"Sichtbarkeit des Chats"</string>
<string name="screen_create_room_space_selection_no_space_description">"(kein Space)"</string>
<string name="screen_create_room_space_selection_no_space_option">"Nicht zu einem Space hinzufügen"</string>
<string name="screen_create_room_space_selection_no_space_title">"Kein Space ausgewählt"</string>
<string name="screen_create_room_space_selection_sheet_title">"Space hinzufügen"</string>
<string name="screen_create_room_space_selection_sheet_title">"Zu Space hinzufügen"</string>
<string name="screen_create_room_topic_label">"Thema (optional)"</string>
<string name="screen_create_room_topic_placeholder">"Beschreibung hinzufügen…"</string>
</resources>
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.enterprise.api
import android.app.Activity
/**
* A hook that is called during app startup, from the `MainActivity`.
*
* Note that the hook is run in the background: it will not block startup of the app.
*/
fun interface AppStartupHook {
/** @param activity The activity which is active while the app starts (i.e. the `MainActivity`). */
suspend fun onAppStartup(activity: Activity)
}
@@ -53,8 +53,6 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
@@ -80,7 +78,7 @@ import io.element.android.libraries.designsystem.modifiers.backgroundVerticalGra
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.text.AdaptativeTitle
import io.element.android.libraries.designsystem.theme.components.DropdownMenu
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
import io.element.android.libraries.designsystem.theme.components.Icon
@@ -141,15 +139,21 @@ fun HomeTopBar(
else -> stringResource(selectedNavigationItem.labelRes)
}
}
HomeNavigationBarItem.Spaces -> stringResource(selectedNavigationItem.labelRes)
HomeNavigationBarItem.Spaces -> null
}
displayTitle?.let {
val style = when (spaceFiltersState) {
// Space name
is SpaceFiltersState.Selected -> ElementTheme.typography.fontHeadingSmMedium
// "Chats"
else -> ElementTheme.typography.fontHeadingLgBold
}
AdaptativeTitle(
title = displayTitle,
style = style,
twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium,
)
}
Text(
modifier = Modifier.semantics {
heading()
},
style = ElementTheme.typography.aliasScreenTitle,
text = displayTitle,
)
},
navigationIcon = {
NavigationIcon(
@@ -179,7 +183,9 @@ fun HomeTopBar(
TopAppBarScrollBehaviorLayout(scrollBehavior = scrollBehavior) {
RoomListFiltersView(
state = filtersState,
modifier = Modifier.padding(bottom = 16.dp).padding(contentPadding)
modifier = Modifier
.padding(bottom = 16.dp)
.padding(contentPadding)
)
}
}
@@ -348,45 +354,45 @@ private fun AccountIcon(
),
contentAlignment = Alignment.Center,
) {
val avatarData by remember(matrixUser) {
derivedStateOf {
matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar)
}
val avatarData by remember(matrixUser) {
derivedStateOf {
matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar)
}
val statusEmoji = matrixUser.displayedStatus?.toEmojiText()
val avatarModifier = if (statusEmoji != null) {
Modifier.eraseStatusEmojiBackground(
parentSize = AvatarSize.CurrentUserTopBar.dp,
layoutDirection = LocalLayoutDirection.current,
)
} else {
Modifier
}
Avatar(
avatarData = avatarData,
avatarType = AvatarType.User,
modifier = avatarModifier,
contentDescription = if (isCurrentAccount) {
if (showAvatarIndicator) {
stringResource(CommonStrings.a11y_settings_with_required_action)
} else {
stringResource(CommonStrings.common_settings)
}
} else {
null
},
}
val statusEmoji = matrixUser.displayedStatus?.toEmojiText()
val avatarModifier = if (statusEmoji != null) {
Modifier.eraseStatusEmojiBackground(
parentSize = AvatarSize.CurrentUserTopBar.dp,
layoutDirection = LocalLayoutDirection.current,
)
if (statusEmoji != null) {
StatusEmojiBadge(
emoji = statusEmoji,
modifier = Modifier.align(Alignment.BottomEnd),
)
}
if (showAvatarIndicator) {
RedIndicatorAtom(
modifier = Modifier.align(Alignment.TopEnd)
)
}
} else {
Modifier
}
Avatar(
avatarData = avatarData,
avatarType = AvatarType.User,
modifier = avatarModifier,
contentDescription = if (isCurrentAccount) {
if (showAvatarIndicator) {
stringResource(CommonStrings.a11y_settings_with_required_action)
} else {
stringResource(CommonStrings.common_settings)
}
} else {
null
},
)
if (statusEmoji != null) {
StatusEmojiBadge(
emoji = statusEmoji,
modifier = Modifier.align(Alignment.BottomEnd),
)
}
if (showAvatarIndicator) {
RedIndicatorAtom(
modifier = Modifier.align(Alignment.TopEnd)
)
}
}
}
@@ -19,6 +19,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter
import io.element.android.libraries.matrix.api.room.CallIntentConsensus
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.ui.model.dmUserStatus
@@ -47,7 +48,7 @@ class RoomListRoomSummaryFactory(
mode = DateFormatterMode.TimeOrDate,
useRelative = true,
),
latestEvent = computeLatestEvent(roomSummary.latestEvent, roomInfo.isDm),
latestEvent = computeLatestEvent(roomSummary.latestEvent, roomInfo.hasOnlyTwoMembers()),
avatarData = avatarData,
userDefinedNotificationMode = roomInfo.userDefinedNotificationMode,
hasRoomCall = roomInfo.hasRoomCall,
@@ -81,6 +82,10 @@ class RoomListRoomSummaryFactory(
)
}
private fun RoomInfo.hasOnlyTwoMembers(): Boolean {
return isDm || activeMembersCount <= 2
}
private fun computeLatestEvent(latestEvent: LatestEventValue, dm: Boolean): LatestEvent {
return when (latestEvent) {
is LatestEventValue.None -> {
@@ -143,7 +143,7 @@ private fun EmptySpaceHomeView(
)
Text(
text = stringResource(CommonStrings.screen_space_list_empty_state_title),
style = ElementTheme.typography.fontHeadingLgBold,
style = ElementTheme.typography.fontHeadingMdBold,
color = ElementTheme.colors.textPrimary,
textAlign = TextAlign.Center,
)
@@ -5,9 +5,9 @@
<string name="banner_battery_optimization_title_android">"Kommen die Benachrichtigungen nicht an?"</string>
<string name="banner_new_sound_message">"Dein Benachrichtigungs-Ping wurde aktualisiert – klarer, schneller und weniger störend."</string>
<string name="banner_new_sound_title">"Wir haben deine Sounds aktualisiert"</string>
<string name="banner_set_up_recovery_content">"Deine Chats werden automatisch gesichert und mit einer Ende-zu-Ende-Verschlüsselung geschützt. Um dieses Backup wiederherzustellen und deine digitale Identität zu bewahren, falls du den Zugriff auf alle deine Geräte verlierst, benötigst du deinen Wiederherstellungsschlüssel."</string>
<string name="banner_set_up_recovery_submit">"Wiederherstellungsschlüssel einrichten"</string>
<string name="banner_set_up_recovery_title">"Sichere deine Chats"</string>
<string name="banner_set_up_recovery_content">"Von deinen Chats wird automatisch ein Ende-zu-Ende-verschlüsseltes Backup erstellt. Wenn du den Zugriff auf alle deine Geräte verlierst, brauchst du deinen Wiederherstellungsschlüssel, um dieses Backup wiederherzustellen und deine digitale Identität zu behalten."</string>
<string name="banner_set_up_recovery_submit">"Wiederherstellungsschlüssel erstellen"</string>
<string name="banner_set_up_recovery_title">"Deine Chats sichern"</string>
<string name="confirm_recovery_key_banner_message">"Bestätige deinen Wiederherstellungsschlüssel, um weiterhin auf deinen Schlüsselspeicher und den Nachrichtenverlauf zugreifen zu können."</string>
<string name="confirm_recovery_key_banner_primary_button_title">"Gib deinen Wiederherstellungsschlüssel ein"</string>
<string name="confirm_recovery_key_banner_secondary_button_title">"Hast du deinen Wiederherstellungsschlüssel vergessen?"</string>
@@ -16,7 +16,7 @@
<string name="full_screen_intent_banner_title">"Verbessere dein Anruferlebnis"</string>
<string name="screen_home_tab_chats">"Chats"</string>
<string name="screen_home_tab_spaces">"Spaces"</string>
<string name="screen_invites_decline_chat_message">"Möchtest du die Einladung zum Betreten von %1$s wirklich ablehnen?"</string>
<string name="screen_invites_decline_chat_message">"Möchtest du die Einladung zu %1$s wirklich ablehnen?"</string>
<string name="screen_invites_decline_chat_title">"Einladung ablehnen"</string>
<string name="screen_invites_decline_direct_chat_message">"Bist du sicher, dass du diese Direktnachricht von %1$s ablehnen möchtest?"</string>
<string name="screen_invites_decline_direct_chat_title">"Einladung ablehnen"</string>
@@ -35,9 +35,9 @@ Deaktiviere den entsprechenden Filter, um deine anderen Chats zu sehen"</string>
<string name="screen_roomlist_filter_invites">"Einladungen"</string>
<string name="screen_roomlist_filter_invites_empty_state_title">"Du hast keine ausstehenden Einladungen."</string>
<string name="screen_roomlist_filter_low_priority">"Niedrige Priorität"</string>
<string name="screen_roomlist_filter_low_priority_empty_state_title">"Du hast noch keine Chats mit niedriger Priorität."</string>
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Wähle Filter ab, um Chats zu sehen."</string>
<string name="screen_roomlist_filter_mixed_empty_state_title">"Für diese Auswahl hast du keinen Chat."</string>
<string name="screen_roomlist_filter_low_priority_empty_state_title">"Du hast noch keine Chats mit niedriger Priorität"</string>
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Entferne Filter, um Chats zu sehen"</string>
<string name="screen_roomlist_filter_mixed_empty_state_title">"Für diese Auswahl hast du keinen Chat"</string>
<string name="screen_roomlist_filter_people">"Personen"</string>
<string name="screen_roomlist_filter_people_empty_state_title">"Du hast noch keine Direktnachrichten"</string>
<string name="screen_roomlist_filter_rooms">"Gruppen"</string>
@@ -45,7 +45,7 @@ Deaktiviere den entsprechenden Filter, um deine anderen Chats zu sehen"</string>
<string name="screen_roomlist_filter_unreads">"Ungelesen"</string>
<string name="screen_roomlist_filter_unreads_empty_state_title">"Glückwunsch!
Du hast keine ungelesenen Nachrichten!"</string>
<string name="screen_roomlist_knock_event_sent_description">"Beitrittsanfrage geschickt"</string>
<string name="screen_roomlist_knock_event_sent_description">"Beitrittsanfrage gestellt"</string>
<string name="screen_roomlist_main_space_title">"Chats"</string>
<string name="screen_roomlist_mark_as_read">"Als gelesen markieren"</string>
<string name="screen_roomlist_mark_as_unread">"Als ungelesen markieren"</string>
@@ -14,7 +14,7 @@
<string name="confirm_recovery_key_banner_title">"L\'archiviazione delle chiavi non è sincronizzata"</string>
<string name="full_screen_intent_banner_message">"Per non perdere mai una chiamata importante, modifica le impostazioni per consentire le notifiche a schermo intero quando il telefono è bloccato."</string>
<string name="full_screen_intent_banner_title">"Migliora la tua esperienza di chiamata"</string>
<string name="screen_home_tab_chats">"Tutte le conversazioni"</string>
<string name="screen_home_tab_chats">"Conversazioni"</string>
<string name="screen_home_tab_spaces">"Spazi"</string>
<string name="screen_invites_decline_chat_message">"Vuoi davvero rifiutare l\'invito ad entrare in %1$s?"</string>
<string name="screen_invites_decline_chat_title">"Rifiuta l\'invito"</string>
@@ -46,7 +46,7 @@ Per il momento, puoi deselezionare i filtri per vedere le altre conversazioni."<
<string name="screen_roomlist_filter_unreads_empty_state_title">"Congratulazioni!
Non hai messaggi non letti!"</string>
<string name="screen_roomlist_knock_event_sent_description">"Richiesta di accesso inviata"</string>
<string name="screen_roomlist_main_space_title">"Tutte le conversazioni"</string>
<string name="screen_roomlist_main_space_title">"Conversazioni"</string>
<string name="screen_roomlist_mark_as_read">"Segna come letto"</string>
<string name="screen_roomlist_mark_as_unread">"Segna come non letto"</string>
<string name="screen_roomlist_tombstoned_room_description">"Questa stanza è stata aggiornata"</string>
@@ -14,7 +14,7 @@
<string name="confirm_recovery_key_banner_title">"Magazyn kluczy nie jest zsynchronizowany"</string>
<string name="full_screen_intent_banner_message">"Upewnij się, że nie pominiesz żadnego połączenia. Zmień swoje ustawienia i zezwól na powiadomienia na blokadzie ekranu."</string>
<string name="full_screen_intent_banner_title">"Popraw jakość swoich rozmów"</string>
<string name="screen_home_tab_chats">"Wszystkie czaty"</string>
<string name="screen_home_tab_chats">"Czaty"</string>
<string name="screen_home_tab_spaces">"Przestrzenie"</string>
<string name="screen_invites_decline_chat_message">"Czy na pewno chcesz odrzucić zaproszenie dołączenia do %1$s?"</string>
<string name="screen_invites_decline_chat_title">"Odrzuć zaproszenie"</string>
@@ -46,7 +46,7 @@ Na razie możesz wyczyścić filtry, aby zobaczyć pozostałe czaty"</string>
<string name="screen_roomlist_filter_unreads_empty_state_title">"Gratulacje!
Nie masz żadnych nieprzeczytanych wiadomości!"</string>
<string name="screen_roomlist_knock_event_sent_description">"Wysłano prośbę o dołączenie"</string>
<string name="screen_roomlist_main_space_title">"Wszystkie czaty"</string>
<string name="screen_roomlist_main_space_title">"Czaty"</string>
<string name="screen_roomlist_mark_as_read">"Oznacz jako przeczytane"</string>
<string name="screen_roomlist_mark_as_unread">"Oznacz jako nieprzeczytane"</string>
<string name="screen_roomlist_tombstoned_room_description">"Ten pokój został ulepszony"</string>
@@ -14,7 +14,7 @@
<string name="confirm_recovery_key_banner_title">"Backup-ul nu este sincronizat"</string>
<string name="full_screen_intent_banner_message">"Pentru a vă asigura că nu pierdeți niciodată un apel important, vă rugăm să modificați setările pentru a permite notificări fullscreen atunci când telefonul este blocat."</string>
<string name="full_screen_intent_banner_title">"Îmbunătățiți-vă experiența in timpul unui apel"</string>
<string name="screen_home_tab_chats">"Toate conversatiile"</string>
<string name="screen_home_tab_chats">"Conversații"</string>
<string name="screen_home_tab_spaces">"Spații"</string>
<string name="screen_invites_decline_chat_message">"Sigur doriți să refuzați alăturarea la %1$s?"</string>
<string name="screen_invites_decline_chat_title">"Refuzați invitația"</string>
@@ -46,7 +46,7 @@ Deocamdată, puteți deselecta filtrele pentru a vedea celelalte chat-uri"</stri
<string name="screen_roomlist_filter_unreads_empty_state_title">"Felicitari!
Nu aveți mesaje necitite!"</string>
<string name="screen_roomlist_knock_event_sent_description">"Cererea de alăturare a fost trimisă"</string>
<string name="screen_roomlist_main_space_title">"Toate conversatiile"</string>
<string name="screen_roomlist_main_space_title">"Conversații"</string>
<string name="screen_roomlist_mark_as_read">"Marcați ca citită"</string>
<string name="screen_roomlist_mark_as_unread">"Marcați ca necitită"</string>
<string name="screen_roomlist_tombstoned_room_description">"Această cameră a fost modernizată."</string>
@@ -36,6 +36,8 @@
<string name="screen_roomlist_filter_invites_empty_state_title">"У вас немає запрошень, що очікують на розгляд."</string>
<string name="screen_roomlist_filter_low_priority">"Низький пріоритет"</string>
<string name="screen_roomlist_filter_low_priority_empty_state_title">"У вас ще немає неважливих бесід"</string>
<string name="screen_roomlist_filter_mention">"Згадка"</string>
<string name="screen_roomlist_filter_mention_empty_state_title">"У вас немає непрочитаних згадок"</string>
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Ви можете зняти фільтри, щоб побачити інші ваші бесіди"</string>
<string name="screen_roomlist_filter_mixed_empty_state_title">"Ви не маєте бесід для цієї категорії"</string>
<string name="screen_roomlist_filter_people">"Люди"</string>
@@ -36,6 +36,8 @@
<string name="screen_roomlist_filter_invites_empty_state_title">"没有待处理的邀请。"</string>
<string name="screen_roomlist_filter_low_priority">"低优先级"</string>
<string name="screen_roomlist_filter_low_priority_empty_state_title">"你暂无任何低优先级聊天"</string>
<string name="screen_roomlist_filter_mention">"提及"</string>
<string name="screen_roomlist_filter_mention_empty_state_title">"你没有任何未读提及"</string>
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"你可以取消选择筛选器以查看其它对话"</string>
<string name="screen_roomlist_filter_mixed_empty_state_title">"你暂无适用于此选项的聊天"</string>
<string name="screen_roomlist_filter_people">"人员"</string>
@@ -36,13 +36,15 @@ For now, you can deselect filters in order to see your other chats"</string>
<string name="screen_roomlist_filter_invites_empty_state_title">"You don\'t have any pending invites."</string>
<string name="screen_roomlist_filter_low_priority">"Low Priority"</string>
<string name="screen_roomlist_filter_low_priority_empty_state_title">"You don’t have any low priority chats yet"</string>
<string name="screen_roomlist_filter_mention">"Mention"</string>
<string name="screen_roomlist_filter_mention_empty_state_title">"You don’t have any unread mentions"</string>
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"You can deselect filters in order to see your other chats"</string>
<string name="screen_roomlist_filter_mixed_empty_state_title">"You don’t have chats for this selection"</string>
<string name="screen_roomlist_filter_people">"People"</string>
<string name="screen_roomlist_filter_people_empty_state_title">"You don’t have any DMs yet"</string>
<string name="screen_roomlist_filter_rooms">"Rooms"</string>
<string name="screen_roomlist_filter_rooms_empty_state_title">"You’re not in any room yet"</string>
<string name="screen_roomlist_filter_unreads">"Unreads"</string>
<string name="screen_roomlist_filter_unreads">"Unread"</string>
<string name="screen_roomlist_filter_unreads_empty_state_title">"Congrats!
You don’t have any unread messages!"</string>
<string name="screen_roomlist_knock_event_sent_description">"Request to join sent"</string>
@@ -8,10 +8,36 @@
package io.element.android.features.home.impl.datasource
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.dateformatter.api.DateFormatter
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter
import io.element.android.libraries.eventformatter.test.FakeRoomLatestEventFormatter
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.room.aRoomSummary
import org.junit.Test
class RoomListRoomSummaryFactoryTest {
@Test
fun `a room with two members does not prefix its preview with the sender`() {
val formatter = FakeRoomLatestEventFormatter()
val factory = aRoomListRoomSummaryFactory(roomLatestEventFormatter = formatter)
factory.create(aRoomSummary(info = aRoomInfo(isDm = false, activeMembersCount = 2)))
assertThat(formatter.lastIsDmRoom).isTrue()
}
@Test
fun `a room with more than two members prefixes its preview with the sender`() {
val formatter = FakeRoomLatestEventFormatter()
val factory = aRoomListRoomSummaryFactory(roomLatestEventFormatter = formatter)
factory.create(aRoomSummary(info = aRoomInfo(isDm = false, activeMembersCount = 3)))
assertThat(formatter.lastIsDmRoom).isFalse()
}
}
fun aRoomListRoomSummaryFactory(
dateFormatter: DateFormatter = FakeDateFormatter { _, _, _ -> "Today" },
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_decline_and_block_block_user_option_description">"Du wirst keine Nachrichten oder Chat-Einladungen von diesem Nutzer sehen."</string>
<string name="screen_decline_and_block_block_user_option_description">"Du wirst keine Nachrichten oder Chat-Einladungen von diesem Nutzer sehen"</string>
<string name="screen_decline_and_block_block_user_option_title">"Nutzer blockieren"</string>
<string name="screen_decline_and_block_report_user_option_description">"Melde diesen Chat deinem Konto-Anbieter."</string>
<string name="screen_decline_and_block_report_user_option_description">"Melde diesen Chat deinem Kontoanbieter."</string>
<string name="screen_decline_and_block_report_user_reason_placeholder">"Beschreibe den Grund für die Meldung…"</string>
<string name="screen_decline_and_block_title">"Ablehnen und blockieren"</string>
<string name="screen_invites_decline_chat_message">"Möchtest du die Einladung zum Betreten von %1$s wirklich ablehnen?"</string>
<string name="screen_invites_decline_chat_message">"Möchtest du die Einladung zu %1$s wirklich ablehnen?"</string>
<string name="screen_invites_decline_chat_title">"Einladung ablehnen"</string>
<string name="screen_invites_decline_direct_chat_message">"Bist du sicher, dass du diese Direktnachricht von %1$s ablehnen möchtest?"</string>
<string name="screen_invites_decline_direct_chat_title">"Einladung ablehnen"</string>
@@ -2,4 +2,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_invite_users_already_a_member">"Ήδη μέλος"</string>
<string name="screen_invite_users_already_invited">"Ήδη προσκεκλημένος"</string>
<string name="screen_invite_users_confirm_dialog_subtitle_multiple_users">"Προς το παρόν δεν έχετε καμία συνομιλία με αυτές τις επαφές. Επιβεβαιώστε την πρόσκλησή τους σε αυτήν την αίθουσα πριν συνεχίσετε."</string>
<string name="screen_invite_users_confirm_dialog_subtitle_one_user">"Προς το παρόν δεν έχετε καμία συνομιλία με αυτήν την επαφή. Επιβεβαιώστε την πρόσκληση σε αυτήν την αίθουσα συνομιλίας πριν συνεχίσετε."</string>
<string name="screen_invite_users_confirm_dialog_title_mutiple_users">"Πρόσκληση νέων επαφών σε αυτη την αίθουσα;"</string>
<string name="screen_invite_users_confirm_dialog_title_one_user">"Θέλετε να προσκαλέσετε νέα επαφή σε αυτήν την αίθουσα;"</string>
</resources>
@@ -22,12 +22,12 @@
<string name="screen_join_room_knock_message_characters_count">"%1$d von %2$d erlaubte Zeichen"</string>
<string name="screen_join_room_knock_message_description">"Nachricht (optional)"</string>
<string name="screen_join_room_knock_sent_description">"Sollte deine Anfrage akzeptiert werden, erhältst du eine Einladung, dem Chat beizutreten."</string>
<string name="screen_join_room_knock_sent_title">"Beitrittsanfrage geschickt"</string>
<string name="screen_join_room_knock_sent_title">"Beitrittsanfrage gestellt"</string>
<string name="screen_join_room_loading_alert_message">"Wir konnten die Chat Vorschau nicht anzeigen. Dies kann an Netzwerk- oder Serverproblemen liegen."</string>
<string name="screen_join_room_loading_alert_title">"Wir konnten diese Chat-Vorschau nicht anzeigen"</string>
<string name="screen_join_room_space_not_supported_description">"%1$s unterstützt noch keine Spaces. Du kannst auf Spaces im Web zugreifen."</string>
<string name="screen_join_room_space_not_supported_title">"Spaces werden noch nicht unterstützt"</string>
<string name="screen_join_room_subtitle_knock">"Klopfe an um einen Admin zu benachrichtigen. Nach der Freigabe kannst du dich an der Unterhaltung beteiligen."</string>
<string name="screen_join_room_subtitle_knock">"Wähle die Schaltfläche unten – eine Chat-Administration wird benachrichtigt. Nach der Freigabe kannst du dich an der Unterhaltung beteiligen."</string>
<string name="screen_join_room_subtitle_no_preview">"Du musst Mitglied in diesem Chat sein, um den Nachrichtenverlauf zu sehen."</string>
<string name="screen_join_room_title_knock">"Willst du diesem Chat beitreten?"</string>
<string name="screen_join_room_title_no_preview">"Vorschau nicht verfügbar"</string>
@@ -27,7 +27,7 @@
<string name="screen_join_room_loading_alert_title">"Nie udało nam się wyświetlić podglądu tego pokoju"</string>
<string name="screen_join_room_space_not_supported_description">"%1$s jeszcze nie obsługuje przestrzeni. Uzyskaj dostęp do przestrzeni w wersji web."</string>
<string name="screen_join_room_space_not_supported_title">"Przestrzenie nie są jeszcze obsługiwane"</string>
<string name="screen_join_room_subtitle_knock">"Kliknij przycisk poniżej, aby powiadomić administratora pokoju. Po zatwierdzeniu będziesz mógł dołączyć do rozmowy."</string>
<string name="screen_join_room_subtitle_knock">"Dotknij przycisku poniżej, aby powiadomić administratora czatu. Po zatwierdzeniu będziesz mógł dołączyć do rozmowy."</string>
<string name="screen_join_room_subtitle_no_preview">"Musisz być członkiem tego pokoju, aby wyświetlić historię wiadomości."</string>
<string name="screen_join_room_title_knock">"Chcesz dołączyć do tego pokoju?"</string>
<string name="screen_join_room_title_no_preview">"Podgląd nie jest dostępny"</string>
@@ -27,7 +27,7 @@
<string name="screen_join_room_loading_alert_title">"Ми не можемо показати попередній перегляд цієї кімнати"</string>
<string name="screen_join_room_space_not_supported_description">"%1$s ще не підтримує простори. Ви можете отримати доступ до них у вебверсії."</string>
<string name="screen_join_room_space_not_supported_title">"Простори поки що не підтримуються"</string>
<string name="screen_join_room_subtitle_knock">"Натисніть кнопку нижче, і адміністратор кімнати отримає сповіщення. Ви зможете приєднатися до розмови після схвалення."</string>
<string name="screen_join_room_subtitle_knock">"Торкніться кнопки нижче і адміністратор бесіди отримає сповіщення. Ви зможете приєднатися до розмови після схвалення."</string>
<string name="screen_join_room_subtitle_no_preview">"Ви мусите бути учасником цієї кімнати, щоб переглядати історію повідомлень."</string>
<string name="screen_join_room_title_knock">"Хочете приєднатися до цієї кімнати?"</string>
<string name="screen_join_room_title_no_preview">"Попередній перегляд недоступний"</string>
@@ -27,7 +27,7 @@
<string name="screen_join_room_loading_alert_title">"无法显示此房间预览"</string>
<string name="screen_join_room_space_not_supported_description">"%1$s 暂不支持空间。你可以通过 Web 客户端访问空间。"</string>
<string name="screen_join_room_space_not_supported_title">"空间尚未受到支持"</string>
<string name="screen_join_room_subtitle_knock">"点击以下按钮以通知房间管理员。获得批准后你将能加入对话。"</string>
<string name="screen_join_room_subtitle_knock">"点击以下按钮聊天管理员将收到通知。获得批准后即可加入对话。"</string>
<string name="screen_join_room_subtitle_no_preview">"只有房间成员才能查看消息历史。"</string>
<string name="screen_join_room_title_knock">"想加入此房间吗?"</string>
<string name="screen_join_room_title_no_preview">"预览不可用"</string>
@@ -27,7 +27,7 @@
<string name="screen_join_room_loading_alert_title">"We couldn’t display this room preview"</string>
<string name="screen_join_room_space_not_supported_description">"%1$s does not support spaces yet. You can access spaces on web."</string>
<string name="screen_join_room_space_not_supported_title">"Spaces are not supported yet"</string>
<string name="screen_join_room_subtitle_knock">"Click the button below and a room administrator will be notified. You’ll be able to join the conversation once approved."</string>
<string name="screen_join_room_subtitle_knock">"Tap the button below and a chat administrator will be notified. You\'ll be able to join the conversation once approved."</string>
<string name="screen_join_room_subtitle_no_preview">"You must be a member of this room to view the message history."</string>
<string name="screen_join_room_title_knock">"Want to join this room?"</string>
<string name="screen_join_room_title_no_preview">"Preview is not available"</string>
@@ -9,7 +9,7 @@
<string name="screen_knock_requests_list_accept_all_loading_title">"Alle Beitrittsanfragen werden angenommen"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Wir konnten diese Beitrittsanfrage nicht annehmen. Möchtest du es noch mal versuchen?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Die Beitrittsanfrage konnte nicht akzeptiert werden"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Beitrittsanfrage annehmen"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Beitrittsanfrage wird angenommen"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ja, ablehnen und sperren"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Bist du sicher, dass du %1$s ablehnen und sperren möchtest? Dieser Nutzer kann dann keinen erneuten Beitritt zu diesem Chat anfragen."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Ablehnen und Zugriff sperren"</string>
@@ -29,7 +29,7 @@
<item quantity="one">"%1$s +%2$d weiterer möchten diesem Chat beitreten"</item>
<item quantity="other">"%1$s +%2$d weitere möchten diesem Chat beitreten"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Alles ansehen"</string>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Alle anzeigen"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Akzeptieren"</string>
<string name="screen_room_single_knock_request_title">"%1$s möchte diesem Chat beitreten"</string>
<string name="screen_room_single_knock_request_view_button_title">"Ansicht"</string>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="leave_conversation_alert_subtitle">"Bist du sicher, dass du diese Unterhaltung verlassen willst? Diese Unterhaltung ist nicht öffentlich und du kannst ihr ohne Einladung nicht wieder beitreten."</string>
<string name="leave_room_alert_empty_subtitle">"Bist du sicher, dass du diesen Chat verlassen möchtest? Du bist die einzige Person hier. Wenn du gehst, kann in Zukunft niemand mehr eintreten, auch du nicht."</string>
<string name="leave_room_alert_empty_subtitle">"Bist du sicher, dass du diesen Chat verlassen möchtest? Du bist die einzige Person hier. Wenn du gehst, kann in Zukunft niemand mehr beitreten, auch du nicht."</string>
<string name="leave_room_alert_private_subtitle">"Bist du sicher, dass du diesen Chat verlassen möchtest? Dieser Chat ist nicht öffentlich und du kannst ihm ohne Einladung nicht erneut beitreten."</string>
<string name="leave_room_alert_select_new_owner_action">"Wähle Eigentümer"</string>
<string name="leave_room_alert_select_new_owner_subtitle">"Du bist der einzige Eigentümer dieses Chats. Du musst die Eigentumsrechte an jemand anderen übertragen, bevor du den Chat verlässt."</string>
@@ -5,6 +5,7 @@
<string name="screen_link_new_device_desktop_step3">"Scanne den QR-Code mit diesem Gerät"</string>
<string name="screen_link_new_device_desktop_submit">"Bereit zum Scannen"</string>
<string name="screen_link_new_device_desktop_title">"Öffne %1$s auf einem Desktop-Computer, um den QR-Code zu erhalten"</string>
<string name="screen_link_new_device_desktop_warning">"Scanne einen QR-Code nur, nachdem du die obigen Schritte auf deinem eigenen Gerät ausgeführt hast. Scanne niemals einen Code, den dir eine andere Person geschickt hat."</string>
<string name="screen_link_new_device_enter_number_error_numbers_do_not_match">"Die Zahlen stimmen nicht überein"</string>
<string name="screen_link_new_device_enter_number_notice">"Gib den 2-stelligen Code ein"</string>
<string name="screen_link_new_device_enter_number_subtitle">"Dadurch wird überprüft, ob die Verbindung zu deinem anderen Gerät sicher ist."</string>
@@ -19,19 +20,19 @@
<string name="screen_link_new_device_error_request_timeout_title">"Die Anmeldung wurde nicht rechtzeitig abgeschlossen"</string>
<string name="screen_link_new_device_mobile_step1">"Öffne %1$s auf dem anderen Gerät"</string>
<string name="screen_link_new_device_mobile_step2">"Wähle %1$s"</string>
<string name="screen_link_new_device_mobile_step2_action">"„Mit QR-Code anmelden”"</string>
<string name="screen_link_new_device_mobile_step3">"Scanne den hier gezeigten QR-Code mit dem anderen Gerät."</string>
<string name="screen_link_new_device_mobile_step2_action">"„Mit QR-Code anmelden“"</string>
<string name="screen_link_new_device_mobile_step3">"Scanne den hier gezeigten QR-Code mit dem anderen Gerät"</string>
<string name="screen_link_new_device_mobile_title">"Öffne %1$s auf dem anderen Gerät"</string>
<string name="screen_link_new_device_root_desktop_computer">"Desktop-Computer"</string>
<string name="screen_link_new_device_root_loading_qr_code">"QR-Code wird geladen…"</string>
<string name="screen_link_new_device_root_loading_qr_code">"QR-Code wird geladen …"</string>
<string name="screen_link_new_device_root_mobile_device">"Mobilgerät"</string>
<string name="screen_link_new_device_root_title">"Welchen Gerätetyp möchtest du verknüpfen?"</string>
<string name="screen_link_new_device_wrong_number_subtitle">"Versuch\' es bitte noch mal und stell sicher, dass du den zweistelligen Code richtig eingegeben hast. Wenn die Zahlen immer noch nicht übereinstimmen, wende dich an deinen Kontoanbieter."</string>
<string name="screen_link_new_device_wrong_number_subtitle">"Bitte versuche es erneut und stelle sicher, dass du den zweistelligen Code richtig eingegeben hast. Wenn die Zahlen weiterhin nicht übereinstimmen, wende dich an deinen Kontoanbieter."</string>
<string name="screen_link_new_device_wrong_number_title">"Die Zahlen stimmen nicht überein"</string>
<string name="screen_qr_code_login_connection_note_secure_state_description">"Es konnte keine sichere Verbindung zu dem neuen Gerät hergestellt werden."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Und jetzt?"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Versuche, dich erneut mit einem QR-Code anzumelden, falls dies ein Netzwerkproblem war."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Wenn das Problem bestehen bleibt, versuche es mit einem anderen WLAN-Netzwerk oder verwende deine mobilen Daten statt WLAN."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Versuche, dich erneut mit einem QR-Code anzumelden, falls dies ein Netzwerkproblem war"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Wenn das Problem bestehen bleibt, versuche es mit einem anderen WLAN-Netzwerk oder verwende deine mobilen Daten statt WLAN"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Wenn das nicht funktioniert, melde dich manuell an"</string>
<string name="screen_qr_code_login_connection_note_secure_state_title">"Die Verbindung ist nicht sicher"</string>
<string name="screen_qr_code_login_device_code_subtitle">"Du wirst aufgefordert, die beiden unten abgebildeten Ziffern einzugeben."</string>
@@ -41,7 +42,7 @@
<string name="screen_qr_code_login_error_declined_subtitle">"Die Anmeldung auf dem anderen Gerät wurde abgelehnt."</string>
<string name="screen_qr_code_login_error_declined_title">"Anmelden abgelehnt"</string>
<string name="screen_qr_code_login_error_device_already_signed_in_subtitle">"Du musst nichts weiter tun."</string>
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Dein anderes Gerät ist schon angemeldet."</string>
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Dein anderes Gerät ist schon angemeldet"</string>
<string name="screen_qr_code_login_error_expired_subtitle">"Die Anmeldung ist abgelaufen. Bitte versuche es erneut."</string>
<string name="screen_qr_code_login_error_expired_title">"Die Anmeldung wurde nicht rechtzeitig abgeschlossen"</string>
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Dein anderes Gerät unterstützt die Anmeldung bei %s mit einem QR-Code nicht.
@@ -5,6 +5,7 @@
<string name="screen_link_new_device_desktop_step3">"Scansiona il codice QR con questo dispositivo"</string>
<string name="screen_link_new_device_desktop_submit">"Pronto per la scansione"</string>
<string name="screen_link_new_device_desktop_title">"Apri %1$s su un computer desktop per ottenere il codice QR"</string>
<string name="screen_link_new_device_desktop_warning">"Scansiona un codice QR solo dopo aver seguito i passaggi sopra descritti sul tuo dispositivo. Non scansionare mai un codice inviato da un\'altra persona."</string>
<string name="screen_link_new_device_enter_number_error_numbers_do_not_match">"I numeri non corrispondono"</string>
<string name="screen_link_new_device_enter_number_notice">"Inserisci il codice a 2 cifre"</string>
<string name="screen_link_new_device_enter_number_subtitle">"Questo verificherà che la connessione con l\'altro dispositivo sia sicura."</string>
@@ -5,6 +5,7 @@
<string name="screen_link_new_device_desktop_step3">"この端末でQRコードを読み取る"</string>
<string name="screen_link_new_device_desktop_submit">"読み取る"</string>
<string name="screen_link_new_device_desktop_title">"%1$s をコンピュータで開き、QRコードを表示してください"</string>
<string name="screen_link_new_device_desktop_warning">"あなた自身の端末においてのみ、以上の手順にならってQRコードを読み取ってください。他者から送られたコードは絶対に読み取らないでください。"</string>
<string name="screen_link_new_device_enter_number_error_numbers_do_not_match">"数字が一致しません"</string>
<string name="screen_link_new_device_enter_number_notice">"2桁の数字を入力してください"</string>
<string name="screen_link_new_device_enter_number_subtitle">"他の端末との接続が安全であることを確認します。"</string>
@@ -2,4 +2,5 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_share_location_live_location_disclaimer_title">"Der Verlauf deines Live-Standorts wird im Chat gespeichert und bleibt für die Mitglieder sichtbar, auch nachdem du die Freigabe beendet hast."</string>
<string name="screen_share_location_live_location_duration_picker_title">"Wie lange soll der Live-Standort geteilt werden?"</string>
<string name="screen_share_location_live_location_missing_permissions">"Du hast keine Berechtigung, deinen Live-Standort in diesem Chat zu teilen"</string>
</resources>
@@ -7,20 +7,20 @@
<string name="screen_app_lock_forgot_pin">"PIN vergessen?"</string>
<string name="screen_app_lock_settings_change_pin">"PIN-Code ändern"</string>
<string name="screen_app_lock_settings_enable_biometric_unlock">"Biometrisches Entsperren zulassen"</string>
<string name="screen_app_lock_settings_remove_pin">"Pin entfernen"</string>
<string name="screen_app_lock_settings_remove_pin">"PIN entfernen"</string>
<string name="screen_app_lock_settings_remove_pin_alert_message">"Bist du sicher, dass du die PIN entfernen willst?"</string>
<string name="screen_app_lock_settings_remove_pin_alert_title">"PIN entfernen?"</string>
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"%1$s zulassen"</string>
<string name="screen_app_lock_setup_biometric_unlock_skip">"Ich möchte diese PIN verwenden."</string>
<string name="screen_app_lock_setup_biometric_unlock_skip">"Ich würde lieber eine PIN verwenden"</string>
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Spare dir etwas Zeit und benutze %1$s, um die App zu entsperren"</string>
<string name="screen_app_lock_setup_choose_pin">"PIN wählen"</string>
<string name="screen_app_lock_setup_confirm_pin">"PIN bestätigen"</string>
<string name="screen_app_lock_setup_pin_context">"Sperre %1$s um deine Chats zusätzlich abzusichern.
Wähle eine einprägsame PIN. Wenn du sie vergisst, wirst du aus der App abgemeldet."</string>
<string name="screen_app_lock_setup_pin_forbidden_dialog_content">"Aus Sicherheitsgründen kann dieser PIN-Code nicht verwendet werden."</string>
<string name="screen_app_lock_setup_pin_forbidden_dialog_title">"Bitte eine andere PIN verwenden."</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Bitte gib die gleiche PIN wie zuvor ein."</string>
<string name="screen_app_lock_setup_pin_forbidden_dialog_content">"Aus Sicherheitsgründen kann dieser PIN-Code nicht verwendet werden"</string>
<string name="screen_app_lock_setup_pin_forbidden_dialog_title">"Bitte eine andere PIN verwenden"</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Bitte gib die gleiche PIN wie zuvor ein"</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"Die PINs stimmen nicht überein"</string>
<string name="screen_app_lock_signout_alert_message">"Um fortzufahren, musst du dich erneut anmelden und eine neue PIN erstellen"</string>
<string name="screen_app_lock_signout_alert_title">"Dieses Gerät wurde entfernt"</string>
@@ -115,6 +115,9 @@ fun ConfirmAccountProviderView(
HeaderFooterPage(
modifier = modifier,
// Let the header and content scroll so the supporting text is not clipped by the footer when the
// keyboard is visible and vertical space is reduced.
isScrollable = true,
header = {
IconTitleSubtitleMolecule(
modifier = Modifier.padding(top = 60.dp),
@@ -5,9 +5,9 @@
<string name="screen_account_provider_form_notice">"Gib einen Suchbegriff oder eine Domainadresse ein."</string>
<string name="screen_account_provider_form_subtitle">"Suche nach einem Unternehmen, einer Community oder einem privaten Server."</string>
<string name="screen_account_provider_form_title">"Kontoanbieter finden"</string>
<string name="screen_account_provider_signin_subtitle">"Hier werden deine Gespräche gespeichert - so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würdest."</string>
<string name="screen_account_provider_signin_subtitle">"Hier werden deine Gespräche gespeichert – so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würdest."</string>
<string name="screen_account_provider_signin_title">"Du bist dabei, dich bei %s anzumelden"</string>
<string name="screen_account_provider_signup_subtitle">"Hier werden deine Gespräche gespeichert - so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würdest."</string>
<string name="screen_account_provider_signup_subtitle">"Hier werden deine Gespräche gespeichert – so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würdest."</string>
<string name="screen_account_provider_signup_title">"Du bist dabei, ein Konto bei %s zu erstellen"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org ist ein großer, kostenloser Server im öffentlichen Matrix-Netzwerk für eine sichere, dezentralisierte Kommunikation, der von der Matrix.org Foundation betrieben wird."</string>
<string name="screen_change_account_provider_other">"Sonstige"</string>
@@ -16,23 +16,31 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"Auf %1$s ist die Element Pro App erforderlich. Bitte lade diese aus dem Store."</string>
<string name="screen_change_server_error_element_pro_required_title">"Element Pro erforderlich"</string>
<string name="screen_change_server_error_invalid_homeserver">"Wir konnten diesen Homeserver nicht erreichen. Bitte überprüfe, ob du die Homeserver-URL korrekt eingegeben hast. Wenn die URL korrekt ist, wende dich an deinen Homeserver-Administrator, um weitere Hilfe zu erhalten."</string>
<string name="screen_change_server_error_invalid_well_known">"Der Server ist aufgrund eines Problems in der \".well-known\" Datei nicht verfügbar:
<string name="screen_change_server_error_invalid_homeserver">"Wir konnten diesen Kontoanbieter nicht erreichen. Bitte überprüfe, ob du die Homeserver-URL korrekt eingegeben hast. Wenn die URL korrekt ist, wende dich an deinen Kontoanbieter, um weitere Hilfe zu erhalten."</string>
<string name="screen_change_server_error_invalid_well_known">"Der Server ist aufgrund eines Problems in der Datei .well-known nicht verfügbar:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Der gewählte Kontoanbieter unterstützt Sliding-Sync nicht. Für die Verwendung von %1$s ist eine Aktualisierung des Servers erforderlich."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Der gewählte Kontoanbieter unterstützt Sliding-Sync nicht. Für die Verwendung von %1$s ist eine Aktualisierung erforderlich."</string>
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s darf keine Verbindung zu %2$s herstellen."</string>
<string name="screen_change_server_error_unauthorized_homeserver_content">"Die App wurde so konfiguriert, dass sie %1$s zulässt."</string>
<string name="screen_change_server_error_unauthorized_homeserver_title">"Kontoanbieter %1$s ist nicht zulässig."</string>
<string name="screen_change_server_form_header">"Homeserver-URL"</string>
<string name="screen_change_server_form_notice">"Gib eine Domain-Adresse ein."</string>
<string name="screen_change_server_navigation_title_login">"Anmelden"</string>
<string name="screen_change_server_navigation_title_register">"Konto erstellen"</string>
<string name="screen_change_server_subtitle">"Wie lautet die Adresse deines Servers?"</string>
<string name="screen_change_server_textfield_footer_login">"Wenn dein Konto von deinem Unternehmen oder einer Community bereitgestellt wird, ändere den Kontoanbieter (Beispiel: firmenname.com)."</string>
<string name="screen_change_server_textfield_footer_register">"Erstelle kostenlos ein Konto auf matrix.org. Wenn du ein Konto bei einem anderen Anbieter erstellen möchtest, passe die Eingabe entsprechend an (Beispiel: communityname.org)."</string>
<string name="screen_change_server_textfield_header">"Kontoanbieter"</string>
<string name="screen_change_server_textfield_placeholder">"example.com"</string>
<string name="screen_change_server_title">"Wähle deinen Server aus"</string>
<string name="screen_change_server_title_login">"Gib deinen Kontoanbieter ein"</string>
<string name="screen_change_server_title_register">"Wähle einen Kontoanbieter"</string>
<string name="screen_create_account_title">"Konto erstellen"</string>
<string name="screen_login_error_deactivated_account">"Dieses Konto wurde gelöscht."</string>
<string name="screen_login_error_invalid_credentials">"Falscher Nutzername und/oder Passwort"</string>
<string name="screen_login_error_invalid_user_id">"Dies ist keine gültige Nutzerkennung. Erwartetes Format: \'@nutzer:homeserver.org\'"</string>
<string name="screen_login_error_invalid_user_id">"Dies ist keine gültige Nutzerkennung. Erwartetes Format: ‚@nutzer:homeserver.org‘"</string>
<string name="screen_login_error_refresh_tokens">"Dieser Server ist so konfiguriert, dass er Refresh-Tokens verwendet. Diese werden für die passwortbasierte Anmeldung nicht unterstützt."</string>
<string name="screen_login_error_unsupported_authentication">"Der ausgewählte Homeserver unterstützt weder den Login per Passwort noch per OAuth. Bitte kontaktiere deinen Administrator oder wähle einen anderen Homeserver."</string>
<string name="screen_login_error_unsupported_authentication">"Der ausgewählte Kontoanbieter unterstützt weder die Anmeldung per Passwort noch per OAuth. Wende dich an deinen Kontoanbieter oder wähle einen anderen."</string>
<string name="screen_login_form_header">"Gib deine Daten ein"</string>
<string name="screen_login_subtitle">"Matrix ist ein offenes Netzwerk für eine sichere, dezentrale Kommunikation."</string>
<string name="screen_login_title">"Willkommen zurück!"</string>
@@ -40,9 +48,10 @@
<string name="screen_missing_key_backup_open_element_classic">"Element Classic öffnen"</string>
<string name="screen_missing_key_backup_step_1">"Öffne Element Classic auf deinem Gerät"</string>
<string name="screen_missing_key_backup_step_2_android">"Geh zu Einstellungen &gt; Sicherheit &amp; Datenschutz"</string>
<string name="screen_missing_key_backup_step_4">"Folge den Anweisungen, um den Schlüsselspeicher zu aktivieren."</string>
<string name="screen_missing_key_backup_step_3_android">"Wähle unter „Verwaltung der Verschlüsselungs-Schlüssel“ den Eintrag „Wiederherstellung verschlüsselter Nachrichten“"</string>
<string name="screen_missing_key_backup_step_4">"Folge den Anweisungen, um deinen Schlüsselspeicher zu aktivieren"</string>
<string name="screen_missing_key_backup_step_5">"Zurück zu %1$s"</string>
<string name="screen_missing_key_backup_title">"Aktiviere deinen Schlüsselspeicher, bevor du mit %1$s fortfährst."</string>
<string name="screen_missing_key_backup_title">"Aktiviere deinen Schlüsselspeicher, bevor du mit %1$s fortfährst"</string>
<string name="screen_onboarding_app_version">"Version %1$s"</string>
<string name="screen_onboarding_checking_account">"Konto wird geprüft"</string>
<string name="screen_onboarding_sign_in_manually">"Manuell anmelden"</string>
@@ -52,12 +61,12 @@
<string name="screen_onboarding_welcome_back">"Willkommen zurück"</string>
<string name="screen_onboarding_welcome_message">"Willkommen beim schnellsten %1$s aller Zeiten. Optimiert für Geschwindigkeit und Einfachheit."</string>
<string name="screen_onboarding_welcome_subtitle">"Willkommen zu %1$s. Aufgeladen, für Geschwindigkeit und Einfachheit."</string>
<string name="screen_onboarding_welcome_title">"Sei in Deinem Element"</string>
<string name="screen_qr_code_login_connecting_subtitle">"Sichere Verbindung aufbauen"</string>
<string name="screen_onboarding_welcome_title">"Sei in deinem Element"</string>
<string name="screen_qr_code_login_connecting_subtitle">"Sichere Verbindung wird aufgebaut"</string>
<string name="screen_qr_code_login_connection_note_secure_state_description">"Es konnte keine sichere Verbindung zu dem neuen Gerät hergestellt werden."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Und jetzt?"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Versuche, dich erneut mit einem QR-Code anzumelden, falls dies ein Netzwerkproblem war."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Wenn das Problem bestehen bleibt, versuche es mit einem anderen WLAN-Netzwerk oder verwende deine mobilen Daten statt WLAN."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Versuche, dich erneut mit einem QR-Code anzumelden, falls dies ein Netzwerkproblem war"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Wenn das Problem bestehen bleibt, versuche es mit einem anderen WLAN-Netzwerk oder verwende deine mobilen Daten statt WLAN"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Wenn das nicht funktioniert, melde dich manuell an"</string>
<string name="screen_qr_code_login_connection_note_secure_state_title">"Die Verbindung ist nicht sicher"</string>
<string name="screen_qr_code_login_device_code_subtitle">"Du wirst aufgefordert, die beiden unten abgebildeten Ziffern einzugeben."</string>
@@ -69,7 +78,7 @@
<string name="screen_qr_code_login_error_declined_subtitle">"Die Anmeldung auf dem anderen Gerät wurde abgelehnt."</string>
<string name="screen_qr_code_login_error_declined_title">"Anmelden abgelehnt"</string>
<string name="screen_qr_code_login_error_device_already_signed_in_subtitle">"Du musst nichts weiter tun."</string>
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Dein anderes Gerät ist schon angemeldet."</string>
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Dein anderes Gerät ist schon angemeldet"</string>
<string name="screen_qr_code_login_error_expired_subtitle">"Die Anmeldung ist abgelaufen. Bitte versuche es erneut."</string>
<string name="screen_qr_code_login_error_expired_title">"Die Anmeldung wurde nicht rechtzeitig abgeschlossen"</string>
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Dein anderes Gerät unterstützt die Anmeldung bei %s mit einem QR-Code nicht.
@@ -80,9 +89,9 @@ Versuche, dich manuell anzumelden, oder scanne den QR-Code mit einem anderen Ger
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s wird nicht unterstützt"</string>
<string name="screen_qr_code_login_initial_state_button_title">"Bereit zum Scannen"</string>
<string name="screen_qr_code_login_initial_state_item_1">"%1$s auf einem Desktop-Gerät öffnen"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Klick auf deinen Avatar"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Wähle dein Profilbild"</string>
<string name="screen_qr_code_login_initial_state_item_3">"Wähle %1$s"</string>
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Neues Gerät verknüpfen\""</string>
<string name="screen_qr_code_login_initial_state_item_3_action">"„Neues Gerät verknüpfen“"</string>
<string name="screen_qr_code_login_initial_state_item_4">"Scanne den QR-Code mit diesem Gerät"</string>
<string name="screen_qr_code_login_initial_state_subtitle">"Nur verfügbar falls dein Kontoanbieter dies unterstützt."</string>
<string name="screen_qr_code_login_initial_state_title">"Öffne %1$s auf einem anderen Gerät, um den QR-Code zu erhalten"</string>
@@ -96,12 +105,12 @@ Versuche, dich manuell anzumelden, oder scanne den QR-Code mit einem anderen Ger
<string name="screen_qr_code_login_start_over_button">"Neu beginnen"</string>
<string name="screen_qr_code_login_unknown_error_description">"Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es erneut."</string>
<string name="screen_qr_code_login_verify_code_loading">"Warten auf dein anderes Gerät"</string>
<string name="screen_qr_code_login_verify_code_subtitle">"Dein Konto-Provider kann nach dem folgenden Code fragen, um die Anmeldung zu bestätigen."</string>
<string name="screen_qr_code_login_verify_code_subtitle">"Dein Kontoanbieter fragt möglicherweise nach dem folgenden Code, um die Anmeldung zu bestätigen."</string>
<string name="screen_qr_code_login_verify_code_title">"Dein Verifizierungscode"</string>
<string name="screen_server_confirmation_change_server">"Kontoanbieter wechseln"</string>
<string name="screen_server_confirmation_message_login_element_dot_io">"Ein privater Server für die Mitarbeiter von Element."</string>
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix ist ein offenes Netzwerk für eine sichere, dezentrale Kommunikation."</string>
<string name="screen_server_confirmation_message_register">"Hier werden deine Gespräche gespeichert - so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würdest."</string>
<string name="screen_server_confirmation_message_register">"Hier werden deine Gespräche gespeichert – so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würdest."</string>
<string name="screen_server_confirmation_title_login">"Du bist dabei, dich bei %1$s anzumelden"</string>
<string name="screen_server_confirmation_title_picker_mode">"Kontoanbieter auswählen"</string>
<string name="screen_server_confirmation_title_register">"Du bist dabei, auf %1$s ein Konto zu erstellen"</string>
@@ -16,7 +16,7 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"Η εφαρμογή Element Pro απαιτείται στο %1$s. Παρακαλώ κατεβάστε την από το κατάστημα."</string>
<string name="screen_change_server_error_element_pro_required_title">"Απαιτείται το Element Pro"</string>
<string name="screen_change_server_error_invalid_homeserver">"Δεν μπορούσαμε να επικοινωνήσουμε με αυτόν τον οικιακό διακομιστή. Βεβαιώσου ότι έχεις εισαγάγει σωστά τη διεύθυνση URL του αρχικού διακομιστή. Εάν η διεύθυνση URL είναι σωστή, επικοινώνησε με τον διαχειριστή του κεντρικού διακομιστή για περαιτέρω βοήθεια."</string>
<string name="screen_change_server_error_invalid_homeserver">"Δεν ήταν δυνατή η επικοινωνία με αυτόν τον πάροχο λογαριασμού. Ελέγξτε ότι έχετε εισαγάγει σωστά τη διεύθυνση URL του παρόχου λογαριασμού. Εάν η διεύθυνση URL είναι σωστή, επικοινωνήστε με τον διαχειριστή του παρόχου λογαριασμού σας για περαιτέρω βοήθεια."</string>
<string name="screen_change_server_error_invalid_well_known">"Ο διακομιστής δεν είναι διαθέσιμος λόγω προβλήματος στο αρχείο .well-known:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Ο επιλεγμένος πάροχος λογαριασμού δεν υποστηρίζει sliding sync. Απαιτείται αναβάθμιση στο διακομιστή για χρήση του %1$s."</string>
@@ -32,7 +32,7 @@
<string name="screen_login_error_invalid_credentials">"Λανθασμένο όνομα χρήστη ή κωδικός πρόσβασης"</string>
<string name="screen_login_error_invalid_user_id">"Αυτό δεν είναι έγκυρο αναγνωριστικό χρήστη. Αναμενόμενη μορφή: \'@χρήστης:homeserver.org\'"</string>
<string name="screen_login_error_refresh_tokens">"Αυτός ο διακομιστής έχει ρυθμιστεί ώστε να χρησιμοποιεί διακριτικά ανανέωσης. Αυτά δεν υποστηρίζονται όταν χρησιμοποιείς σύνδεση μέσω κωδικού πρόσβασης."</string>
<string name="screen_login_error_unsupported_authentication">"Ο επιλεγμένος οικιακός διακομιστής δεν υποστηρίζει κωδικό πρόσβασης ή σύνδεση OAuth. Επικοινωνήστε με τον διαχειριστή σου ή επέλεξε άλλο οικιακό διακομιστή."</string>
<string name="screen_login_error_unsupported_authentication">"Ο επιλεγμένος πάροχος λογαριασμού δεν υποστηρίζει σύνδεση με κωδικό πρόσβασης ή OAuth. Παρακαλούμε επικοινωνήστε με τον πάροχο του λογαριασμού σας ή επιλέξτε διαφορετικό."</string>
<string name="screen_login_form_header">"Εισήγαγε τα στοιχεία σου"</string>
<string name="screen_login_subtitle">"Το Matrix είναι ένα ανοιχτό δίκτυο για ασφαλή, αποκεντρωμένη επικοινωνία."</string>
<string name="screen_login_title">"Καλωσόρισες ξανά!"</string>
@@ -89,7 +89,7 @@ Proovi käsitsi sisselogimist või skaneeri QR-koodi mõne muu seadmega."</strin
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s pole toetatud"</string>
<string name="screen_qr_code_login_initial_state_button_title">"Skaneerimiseks valmis"</string>
<string name="screen_qr_code_login_initial_state_item_1">"Ava %1$s töölauarakenduses"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Klõpsi oma tunnuspilti"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Klõpsa oma tunnuspilti"</string>
<string name="screen_qr_code_login_initial_state_item_3">"Vali %1$s"</string>
<string name="screen_qr_code_login_initial_state_item_3_action">"„Seo uus seade“"</string>
<string name="screen_qr_code_login_initial_state_item_4">"Skaneeri QR-koodi selle seadmega"</string>
@@ -16,10 +16,10 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"L’application Element Pro est requise sur %1$s. Veuillez la télécharger depuis le store."</string>
<string name="screen_change_server_error_element_pro_required_title">"Element Pro est requis"</string>
<string name="screen_change_server_error_invalid_homeserver">"Nous n’avons pas pu atteindre ce serveur d’accueil. Vérifiez que vous avez correctement saisi l’URL du serveur d’accueil. Si l’URL est correcte, contactez l’administrateur de votre serveur d’accueil pour obtenir de l’aide."</string>
<string name="screen_change_server_error_invalid_homeserver">"Impossible de se connecter à ce fournisseur de compte. Vérifiez que l’URL saisie est correcte. Si c’est le cas, contactez l’administrateur de votre fournisseur de compte pour obtenir de l’aide."</string>
<string name="screen_change_server_error_invalid_well_known">"Ce fournisseur de compte n’est pas disponible en raison d’un problème dans le fichier .well-known:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Le fournisseur de compte sélectionné ne prend pas en charge le sliding sync. Une mise à jour du serveur est nécessaire pour pouvoir utiliser %1$s."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Le fournisseur de compte sélectionné ne prend pas en charge le sliding sync. Le serveur doit être mis à jour pour pouvoir utiliser %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s n’est pas autorisé à se connecter à %2$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_content">"Cette application a été configurée pour autoriser: %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_title">"Le fournisseur de compte %1$s n’est pas autorisé."</string>
@@ -40,7 +40,7 @@
<string name="screen_login_error_invalid_credentials">"Nom d’utilisateur et/ou mot de passe incorrects"</string>
<string name="screen_login_error_invalid_user_id">"Il ne s’agit pas d’un identifiant utilisateur valide. Format attendu : « @user:homeserver.org »"</string>
<string name="screen_login_error_refresh_tokens">"Ce serveur est configuré pour utiliser des tokens d’actualisation. Ils ne sont pas pris en charge lors de l’utilisation d’une connexion basée sur un mot de passe."</string>
<string name="screen_login_error_unsupported_authentication">"Le serveur d’accueil sélectionné ne prend pas en charge le mot de passe ou la connexion OAuth. Contactez votre administrateur ou choisissez un autre serveur d’accueil."</string>
<string name="screen_login_error_unsupported_authentication">"Le fournisseur de compte sélectionné ne prend pas en charge la connexion avec mot de passe ni OAuth. Contactez votre administrateur ou choisissez un autre fournisseur de compte."</string>
<string name="screen_login_form_header">"Saisissez vos identifiants"</string>
<string name="screen_login_subtitle">"Matrix est un réseau ouvert pour une communication sécurisée et décentralisée."</string>
<string name="screen_login_title">"Content de vous revoir !"</string>
@@ -25,8 +25,16 @@
<string name="screen_change_server_error_unauthorized_homeserver_title">"Davatelj usluga računa %1$s nije dopušten."</string>
<string name="screen_change_server_form_header">"URL matičnog poslužitelja"</string>
<string name="screen_change_server_form_notice">"Unesite adresu domene."</string>
<string name="screen_change_server_navigation_title_login">"Prijava"</string>
<string name="screen_change_server_navigation_title_register">"Izradi račun"</string>
<string name="screen_change_server_subtitle">"Koja je adresa vašeg poslužitelja?"</string>
<string name="screen_change_server_textfield_footer_login">"Ako vam je račun omogućila vaša tvrtka ili zajednica, promijenite pružatelja računa (primjer: nazivtvrtke.com)."</string>
<string name="screen_change_server_textfield_footer_register">"Besplatno kreirajte račun na matrix.org. Ako želite kreirati račun kod drugog pružatelja usluga, promijenite ga u skladu s tim (primjer: communityname.org)."</string>
<string name="screen_change_server_textfield_header">"Pružatelj usluga računa"</string>
<string name="screen_change_server_textfield_placeholder">"primjer.com"</string>
<string name="screen_change_server_title">"Odaberite svoj poslužitelj"</string>
<string name="screen_change_server_title_login">"Unesite davatelja usluge računa"</string>
<string name="screen_change_server_title_register">"Odaberite pružatelja usluga računa"</string>
<string name="screen_create_account_title">"Izradi račun"</string>
<string name="screen_login_error_deactivated_account">"Ovaj je račun deaktiviran."</string>
<string name="screen_login_error_invalid_credentials">"Netočno korisničko ime i/ili zaporka"</string>
@@ -16,23 +16,31 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"L\'app Element Pro è necessaria su %1$s. Scaricala dallo store."</string>
<string name="screen_change_server_error_element_pro_required_title">"Element Pro è richiesto"</string>
<string name="screen_change_server_error_invalid_homeserver">"Non siamo riusciti a raggiungere questo homeserver. Verifica di aver inserito correttamente l\'URL. Se l\'URL è corretto, contatta l\'amministratore del homeserver per ulteriore assistenza."</string>
<string name="screen_change_server_error_invalid_homeserver">"Non è stato possibile contattare il gestorehomeserver di questo account. Verifica di aver inserito correttamente l\'URL dell\'homeserver. Se l\'URL è corretto, contatta il gestore del tuo account per ulteriore assistenza."</string>
<string name="screen_change_server_error_invalid_well_known">"Il server non è disponibile per un problema nel file well-known:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Il fornitore di account selezionato non supporta la Sliding sync. Per utilizzare %1$s è necessario un aggiornamento del server."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Il gestore dell\'account selezionato non supporta la sliding sync. È necessario un aggiornamento per utilizzare %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s non è autorizzato a connettersi a %2$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_content">"Questa app è stata configurata per consentire: %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_title">"Fornitore dell\'account %1$s non consentito."</string>
<string name="screen_change_server_form_header">"URL dell\'homeserver"</string>
<string name="screen_change_server_form_notice">"Inserisci un indirizzo di dominio."</string>
<string name="screen_change_server_navigation_title_login">"Accedi"</string>
<string name="screen_change_server_navigation_title_register">"Crea account"</string>
<string name="screen_change_server_subtitle">"Qual è l\'indirizzo del tuo server?"</string>
<string name="screen_change_server_textfield_footer_login">"Se il tuo account è fornito dalla tua azienda o da una comunità, cambia il gestore dell\'account (esempio: companyname.com)."</string>
<string name="screen_change_server_textfield_footer_register">"Crea un account gratuito su matrix.org. Se desideri creare un account con un provider diverso, modificalo di conseguenza (esempio: communityname.org)."</string>
<string name="screen_change_server_textfield_header">"Gestore dell\'account"</string>
<string name="screen_change_server_textfield_placeholder">"esempio.com"</string>
<string name="screen_change_server_title">"Seleziona il tuo server"</string>
<string name="screen_change_server_title_login">"Inserisci il tuo gestore dell\'account"</string>
<string name="screen_change_server_title_register">"Scegli un gestore dell\'account"</string>
<string name="screen_create_account_title">"Crea account"</string>
<string name="screen_login_error_deactivated_account">"Questo account è stato eliminato."</string>
<string name="screen_login_error_invalid_credentials">"Nome utente e/o password errati"</string>
<string name="screen_login_error_invalid_user_id">"Questo non è un identità utente valida. il formato atteso é: \'@user:homeserver.org\'"</string>
<string name="screen_login_error_refresh_tokens">"Questo server è configurato per usare i token di aggiornamento. Non sono supportati quando si usa l\'accesso basato su password."</string>
<string name="screen_login_error_unsupported_authentication">"L\'homeserver selezionato non supporta la password o l\'accesso OAuth. Contatta il tuo amministratore o scegli un altro homeserver."</string>
<string name="screen_login_error_unsupported_authentication">"Il gestore dell\'account selezionato non supporta l\'accesso tramite password o OAuth. Si prega di contattare il proprio gestore dell\'account o di sceglierne uno diverso."</string>
<string name="screen_login_form_header">"Inserisci i tuoi dati"</string>
<string name="screen_login_subtitle">"Matrix è una rete aperta per comunicazioni sicure e decentralizzate."</string>
<string name="screen_login_title">"Bentornato!"</string>
@@ -53,7 +53,7 @@
<string name="screen_onboarding_welcome_back">"다시 오신 것을 환영합니다"</string>
<string name="screen_onboarding_welcome_message">"%1$s 에 오신 것을 환영합니다. 속도와 단순성을 극대화한 가장 빠른 버전입니다."</string>
<string name="screen_onboarding_welcome_subtitle">"%1$s 에 오신 것을 환영합니다. 속도와 단순성을 위해 최적화된 앱입니다."</string>
<string name="screen_onboarding_welcome_title">"당신의 엘리먼트에 있어"</string>
<string name="screen_onboarding_welcome_title">"가장 나다운 공간, Element에서"</string>
<string name="screen_qr_code_login_connecting_subtitle">"안전한 연결 설정"</string>
<string name="screen_qr_code_login_connection_note_secure_state_description">"새 장치에 안전하게 연결할 수 없습니다. 기존 장치는 여전히 안전하므로 걱정할 필요가 없습니다."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"이제 어떻게 해야 할까?"</string>
@@ -16,10 +16,10 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"Wymagana jest aplikacja Element Pro na %1$s. Znajdziesz ją w sklepie z aplikacjami."</string>
<string name="screen_change_server_error_element_pro_required_title">"Wymagany jest Element Pro"</string>
<string name="screen_change_server_error_invalid_homeserver">"Nie mogliśmy połączyć się z tym serwerem domowym. Sprawdź, czy adres URL serwera został wprowadzony poprawnie. Jeśli adres URL jest poprawny, skontaktuj się z administratorem serwera w celu uzyskania dalszej pomocy."</string>
<string name="screen_change_server_error_invalid_homeserver">"Nie mogliśmy połączyć się z dostawcą konta. Sprawdź, czy adres URL serwera został wprowadzony poprawnie. Jeśli adres URL jest poprawny, skontaktuj się z administratorem dostawcy konta w celu uzyskania dalszej pomocy."</string>
<string name="screen_change_server_error_invalid_well_known">"Serwer nie jest dostępny z powodu problemu pliku .well-known:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Wybrany dostawca konta nie wspiera synchronizacji przesuwnej. Wymagana jest aktualizacja serwera %1$s."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Wybrany dostawca konta nie wspiera funkcji sliding sync. Wymagana jest aktualizacja serwera, aby korzystać z %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s nie posiada zezwolenia na dołączenie do %2$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_content">"Aplikacja została skonfigurowana tak, aby zezwalała na: %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_title">"Dostawca konta %1$s jest niedozwolony."</string>
@@ -40,7 +40,7 @@
<string name="screen_login_error_invalid_credentials">"Nieprawidłowa nazwa użytkownika i/lub hasło"</string>
<string name="screen_login_error_invalid_user_id">"To nie jest prawidłowy identyfikator użytkownika. Oczekiwany format: \'@user:homeserver.org\'"</string>
<string name="screen_login_error_refresh_tokens">"Ten serwer został skonfigurowany do korzystania z tokenów odświeżania. Nie są one obsługiwane, gdy korzystasz z hasła."</string>
<string name="screen_login_error_unsupported_authentication">"Wybrany serwer domowy nie obsługuje uwierzytelniania hasłem, ani OAuth. Skontaktuj się z jego administratorem lub wybierz inny serwer domowy."</string>
<string name="screen_login_error_unsupported_authentication">"Wybrany dostawca konta nie obsługuje uwierzytelniania hasłem, ani OAuth. Spróbuj się z nim skontaktować lub wybierz innego."</string>
<string name="screen_login_form_header">"Wprowadź swoje dane"</string>
<string name="screen_login_subtitle">"Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."</string>
<string name="screen_login_title">"Witaj ponownie!"</string>
@@ -16,10 +16,10 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"Необхідно встановити застосунок Element Pro на %1$s. Завантажте його з магазину."</string>
<string name="screen_change_server_error_element_pro_required_title">"Потрібен Element Pro"</string>
<string name="screen_change_server_error_invalid_homeserver">"Не вдалося під\'єднатися до цього домашнього сервера. Перевірте правильність введеної URL-адреси домашнього сервера. Якщо URL-адреса правильна, зверніться по додаткову допомогу до адміністратора домашнього сервера."</string>
<string name="screen_change_server_error_invalid_homeserver">"Нам не вдалося зв’язатися з цим постачальником облікового запису. Перевірте, чи правильно ви ввели URL-адресу постачальника облікового запису. Якщо URL-адреса правильна, зверніться до адміністратора постачальника вашого облікового запису по додаткову допомогу."</string>
<string name="screen_change_server_error_invalid_well_known">"Сервер недоступний через помилку у файлі well-known:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Вибраний постачальник облікових записів не підтримує sliding sync. Щоб користуватися %1$s необхідно оновити сервер."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Вибраний постачальник облікового запису не підтримує Sliding sync. Для використання функції %1$s потрібно оновити сервер."</string>
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s не дозволено під\'єднуватися до %2$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_content">"Цей застосунок налаштовано так, щоб дозволити: %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_title">"Постачальник облікового запису %1$s не дозволений."</string>
@@ -40,7 +40,7 @@
<string name="screen_login_error_invalid_credentials">"Неправильне ім\'я користувача та/або пароль"</string>
<string name="screen_login_error_invalid_user_id">"Це недійсний ідентифікатор користувача. Очікуваний формат: \'@user:homeserver.org\'"</string>
<string name="screen_login_error_refresh_tokens">"Цей сервер налаштований на використання оновлюваних токенів. Вони не підтримуються, якщо використовується вхід за допомогою основі пароля."</string>
<string name="screen_login_error_unsupported_authentication">"Обраний домашній сервер не підтримує вхід за допомогою пароля або OAuth. Зверніться до адміністратора або виберіть інший домашній сервер."</string>
<string name="screen_login_error_unsupported_authentication">"Вибраний постачальник облікового запису не підтримує пароль або вхід через OAuth. Зверніться до постачальника облікового запису або виберіть іншого."</string>
<string name="screen_login_form_header">"Введіть свої дані"</string>
<string name="screen_login_subtitle">"Matrix — це відкрита мережа для безпечної, децентралізованої комунікації."</string>
<string name="screen_login_title">"З поверненням!"</string>
@@ -89,7 +89,7 @@
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s не підтримується"</string>
<string name="screen_qr_code_login_initial_state_button_title">"Готовий до сканування"</string>
<string name="screen_qr_code_login_initial_state_item_1">"Відкрийте %1$s на комп\'ютері"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Натисніть на свою аватарку"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Натисніть на свій аватар"</string>
<string name="screen_qr_code_login_initial_state_item_3">"Виберіть %1$s"</string>
<string name="screen_qr_code_login_initial_state_item_3_action">"“Під\'єднати новий пристрій”"</string>
<string name="screen_qr_code_login_initial_state_item_4">"Зіскануйте QR-код цим пристроєм"</string>
@@ -25,8 +25,16 @@
<string name="screen_change_server_error_unauthorized_homeserver_title">"不允許帳號提供者 %1$s。"</string>
<string name="screen_change_server_form_header">"家伺服器 URL"</string>
<string name="screen_change_server_form_notice">"輸入網域位址。"</string>
<string name="screen_change_server_navigation_title_login">"登入"</string>
<string name="screen_change_server_navigation_title_register">"建立帳號"</string>
<string name="screen_change_server_subtitle">"您的伺服器地址?"</string>
<string name="screen_change_server_textfield_footer_login">"若您的帳號是由您的公司或社群提供的,請變更帳號提供者(範例:companyname.com)。"</string>
<string name="screen_change_server_textfield_footer_register">"在 matrix.org 上建立免費帳號。若您想要使用其他提供者建立帳號,請據此調整(範例:communityname.org)。"</string>
<string name="screen_change_server_textfield_header">"帳號提供者"</string>
<string name="screen_change_server_textfield_placeholder">"example.com"</string>
<string name="screen_change_server_title">"選擇您的伺服器"</string>
<string name="screen_change_server_title_login">"輸入您的帳號提供者"</string>
<string name="screen_change_server_title_register">"選擇帳號提供者"</string>
<string name="screen_create_account_title">"建立帳號"</string>
<string name="screen_login_error_deactivated_account">"此帳號已被刪除。"</string>
<string name="screen_login_error_invalid_credentials">"不正確的使用者名稱或密碼"</string>
@@ -16,7 +16,7 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"%1$s 要求 Element Pro。请从应用商店下载。"</string>
<string name="screen_change_server_error_element_pro_required_title">"需要 Element Pro"</string>
<string name="screen_change_server_error_invalid_homeserver">"我们无法访问此服务器。请检查输入的服务器 URL 是否正确。如果 URL 正确,请联系服务器管理员寻求进一步帮助。"</string>
<string name="screen_change_server_error_invalid_homeserver">"我们无法访问此账户提供者。请检查输入的账户提供者 URL 是否正确。如果 URL 正确,请联系账户提供者管理员寻求进一步帮助。"</string>
<string name="screen_change_server_error_invalid_well_known">"由于 .well-known 文件存在问题,服务器不可用:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"所选账户提供者不支持滑动同步。需要升级服务器才能使用 %1$s。"</string>
@@ -40,7 +40,7 @@
<string name="screen_login_error_invalid_credentials">"用户名与(或)密码不正确"</string>
<string name="screen_login_error_invalid_user_id">"这不是合法的用户 ID。预期格式:“@user:homeserver.org”。"</string>
<string name="screen_login_error_refresh_tokens">"此服务器使用刷新令牌。使用密码登录时不支持这些功能。"</string>
<string name="screen_login_error_unsupported_authentication">"该服务器不支持密码登录与 OAuth 登录。请联系服务器管理员或选择另一服务器。"</string>
<string name="screen_login_error_unsupported_authentication">"所选账户提供者不支持密码或 OAuth 登录。请联系账户提供者或选择另一账户提供者。"</string>
<string name="screen_login_form_header">"输入详细信息"</string>
<string name="screen_login_subtitle">"Matrix 是一个用于安全、去中心化通信的开放网络。"</string>
<string name="screen_login_title">"欢迎回来!"</string>
@@ -16,10 +16,10 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"The Element Pro app is required on %1$s. Please download it from the store."</string>
<string name="screen_change_server_error_element_pro_required_title">"Element Pro required"</string>
<string name="screen_change_server_error_invalid_homeserver">"We couldn\'t reach this homeserver. Please check that you have entered the homeserver URL correctly. If the URL is correct, contact your homeserver administrator for further help."</string>
<string name="screen_change_server_error_invalid_homeserver">"We couldn\'t reach this account provider. Please check that you have entered the account provider URL correctly. If the URL is correct, contact your account provider administrator for further help."</string>
<string name="screen_change_server_error_invalid_well_known">"Server isn\'t available due to an issue in the .well-known file:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"The selected account provider does not support sliding sync. An upgrade to the server is needed to use %1$s."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"The selected account provider does not support sliding sync. The server needs to be upgraded to use %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s is not allowed to connect to %2$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_content">"This app has been configured to allow: %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_title">"Account provider %1$s not allowed."</string>
@@ -40,7 +40,7 @@
<string name="screen_login_error_invalid_credentials">"Incorrect username and/or password"</string>
<string name="screen_login_error_invalid_user_id">"This is not a valid user identifier. Expected format: ‘@user:homeserver.org’"</string>
<string name="screen_login_error_refresh_tokens">"This server is configured to use refresh tokens. These aren\'t supported when using password based login."</string>
<string name="screen_login_error_unsupported_authentication">"The selected homeserver doesn\'t support password or OAuth login. Please contact your admin or choose another homeserver."</string>
<string name="screen_login_error_unsupported_authentication">"The selected account provider doesn\'t support password or OAuth login. Please contact your account provider or choose a different one."</string>
<string name="screen_login_form_header">"Enter your details"</string>
<string name="screen_login_subtitle">"Matrix is an open network for secure, decentralised communication."</string>
<string name="screen_login_title">"Welcome back!"</string>
@@ -51,6 +51,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
import io.element.android.features.messages.impl.timeline.model.event.captionOrNull
import io.element.android.features.messages.impl.timeline.model.event.htmlCaptionOrNull
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
import io.element.android.features.messages.impl.voicemessages.composer.DefaultVoiceMessageComposerPresenter
import io.element.android.features.roomcall.api.RoomCallState
@@ -396,7 +397,7 @@ class MessagesPresenter(
TimelineItemAction.Edit,
TimelineItemAction.EditPoll -> handleActionEdit(targetEvent, composerState, enableTextFormatting)
TimelineItemAction.AddCaption -> handleActionAddCaption(targetEvent, composerState)
TimelineItemAction.EditCaption -> handleActionEditCaption(targetEvent, composerState)
TimelineItemAction.EditCaption -> handleActionEditCaption(targetEvent, composerState, enableTextFormatting)
TimelineItemAction.RemoveCaption -> handleRemoveCaption(targetEvent)
TimelineItemAction.Reply -> handleActionReply(targetEvent, composerState, timelineProtectionState)
TimelineItemAction.ReplyInThread -> {
@@ -575,10 +576,15 @@ class MessagesPresenter(
private suspend fun handleActionEditCaption(
targetEvent: TimelineItem.Event,
composerState: MessageComposerState,
enableTextFormatting: Boolean,
) {
val composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = targetEvent.eventOrTransactionId,
content = targetEvent.content.captionOrNull().orEmpty(),
content = if (enableTextFormatting) {
targetEvent.content.htmlCaptionOrNull() ?: targetEvent.content.captionOrNull()
} else {
targetEvent.content.captionOrNull()
}.orEmpty(),
)
composerState.eventSink(
MessageComposerEvent.SetMode(composerMode)
@@ -115,9 +115,10 @@ class SuggestionsProcessor(
.filterUpTo(MAX_BATCH_ITEMS) { member ->
isJoinedMemberAndNotSelf(member) && memberMatchesQuery(member, query)
}
.sortedBy { member -> matchRank(member, query) }
.map(ResolvedSuggestion::Member)
if ("room".contains(query) && canSendRoomMention) {
if ("room".startsWith(query, ignoreCase = true) && canSendRoomMention) {
listOf(ResolvedSuggestion.AtRoom) + matchingMembers
} else {
matchingMembers
@@ -125,6 +126,18 @@ class SuggestionsProcessor(
}
}
/**
* Members whose display name starts with the query come first, then members whose user id
* localpart starts with it, then the remaining matches on any other part of the name or id.
*/
private fun matchRank(member: RoomMember, query: String): Int {
return when {
member.displayName?.startsWith(query, ignoreCase = true) == true -> 0
member.userId.extractedDisplayName.startsWith(query, ignoreCase = true) -> 1
else -> 2
}
}
companion object {
// We don't want to retrieve thousands of members
private const val MAX_BATCH_ITEMS = 100
@@ -52,9 +52,9 @@ sealed interface TimelineEvent {
*/
sealed interface TimelineItemPollEvent : TimelineItemEvent
data class SelectPollAnswer(
data class SendPollResponse(
val pollStartId: EventId,
val answerId: String
val answerIds: List<String>
) : TimelineItemPollEvent
data class EndPoll(
@@ -201,13 +201,15 @@ class TimelinePresenter(
newEventState.value = NewEventState.None
}
}
is TimelineEvent.SelectPollAnswer -> sessionCoroutineScope.launch {
is TimelineEvent.SendPollResponse -> sessionCoroutineScope.launch {
timelineController.invokeOnCurrentTimeline {
sendPollResponseAction.execute(
timeline = this,
pollStartId = event.pollStartId,
answerId = event.answerId
)
answerIds = event.answerIds,
).onFailure { error ->
Timber.tag(tag).e(error, "Failed to send poll response")
}
}
}
is TimelineEvent.EndPoll -> sessionCoroutineScope.launch {
@@ -49,7 +49,9 @@ import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.hideFromAccessibility
import androidx.compose.ui.semantics.isTraversalGroup
import androidx.compose.ui.semantics.semantics
@@ -217,13 +219,23 @@ fun TimelineItemEventRow(
inReplyToClick(inReplyToEventId)
}
Column(modifier = modifier.fillMaxWidth()) {
val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.canBeRepliedTo
val accessibilityActions = rememberTimelineItemAccessibilityActions(
canReply = canReply,
onLongClick = onLongClick,
onSwipeToReply = onSwipeToReply,
)
Column(
modifier = modifier
.fillMaxWidth()
.semantics { customActions = accessibilityActions }
) {
if (event.groupPosition.isNew()) {
Spacer(modifier = Modifier.height(16.dp))
} else {
Spacer(modifier = Modifier.height(2.dp))
}
val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.canBeRepliedTo
if (canReply) {
val state: SwipeableActionsState = rememberSwipeableActionsState()
val offset = state.offset.floatValue
@@ -319,6 +331,40 @@ fun TimelineItemEventRow(
}
}
/**
* Exposes the gestures of a timeline item — long press for the action list, swipe for a reply — as TalkBack actions, since neither gesture is reachable
* with a screen reader enabled.
*/
@Composable
private fun rememberTimelineItemAccessibilityActions(
canReply: Boolean,
onLongClick: () -> Unit,
onSwipeToReply: () -> Unit,
): List<CustomAccessibilityAction> {
val messageActionsLabel = stringResource(CommonStrings.common_message_actions)
val replyLabel = stringResource(CommonStrings.action_reply)
val latestOnLongClick by rememberUpdatedState(onLongClick)
val latestOnSwipeToReply by rememberUpdatedState(onSwipeToReply)
return remember(canReply, messageActionsLabel, replyLabel) {
buildList {
add(
CustomAccessibilityAction(messageActionsLabel) {
latestOnLongClick()
true
}
)
if (canReply) {
add(
CustomAccessibilityAction(replyLabel) {
latestOnSwipeToReply()
true
}
)
}
}
}
}
@Composable
private fun ThreadSummaryView(
threadSummary: ThreadSummary,
@@ -26,8 +26,8 @@ fun TimelineItemPollView(
eventSink: (TimelineEvent.TimelineItemPollEvent) -> Unit,
modifier: Modifier = Modifier,
) {
fun onSelectAnswer(pollStartId: EventId, answerId: String) {
eventSink(TimelineEvent.SelectPollAnswer(pollStartId, answerId))
fun onSendPollResponse(pollStartId: EventId, answerIds: List<String>) {
eventSink(TimelineEvent.SendPollResponse(pollStartId, answerIds))
}
fun onEndPoll(pollStartId: EventId) {
@@ -43,10 +43,12 @@ fun TimelineItemPollView(
question = content.question,
answerItems = content.answerItems.toImmutableList(),
pollKind = content.pollKind,
isMultipleSelection = content.maxSelections > 1u,
maxSelections = content.maxSelections,
isPollEnded = content.isEnded,
isPollEditable = content.isEditable,
isMine = content.isMine,
onSelectAnswer = ::onSelectAnswer,
onSendPollResponse = ::onSendPollResponse,
onEditPoll = ::onEditPoll,
onEndPoll = ::onEndPoll,
modifier = modifier,
@@ -15,7 +15,9 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
@@ -23,6 +25,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
import io.element.android.features.messages.impl.timeline.model.event.AN_EMOJI_ONLY_TEXT
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContentPreviewParam
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
@@ -47,8 +50,13 @@ fun TimelineItemTextView(
// The View <-> Compose interop is not working well with Compose UI tests (it loops indefinitely), so we skip it in the UI test mode.
if (LocalUiTestMode.current) return
val emojiOnly = content.formattedBody.toString() == content.body &&
content.body.replace(" ", "").containsOnlyEmojis()
val isInPreview = LocalInspectionMode.current
val emojiOnly = remember(content.body, content.formattedBody, isInPreview) {
content.formattedBody.toString() == content.body &&
content.body.replace(" ", "").let { body ->
if (isInPreview) body == AN_EMOJI_ONLY_TEXT else body.containsOnlyEmojis()
}
}
val textStyle = when {
emojiOnly -> ElementTheme.typography.fontHeadingXlRegular
else -> ElementTheme.typography.fontBodyLgRegular
@@ -108,6 +108,7 @@ class TimelineItemContentMessageFactory(
fileSize = messageType.info?.size ?: 0,
caption = messageType.caption?.trimEnd(),
formattedCaption = formattedCaption,
htmlCaption = messageType.formattedCaption?.body,
isEdited = content.isEdited,
mediaSource = messageType.source,
thumbnailSource = messageType.info?.thumbnailSource,
@@ -132,6 +133,7 @@ class TimelineItemContentMessageFactory(
fileSize = messageType.info?.size ?: 0,
caption = messageType.caption?.trimEnd(),
formattedCaption = formattedCaption,
htmlCaption = messageType.formattedCaption?.body,
isEdited = content.isEdited,
mediaSource = messageType.source,
thumbnailSource = messageType.info?.thumbnailSource,
@@ -174,6 +176,7 @@ class TimelineItemContentMessageFactory(
fileSize = messageType.info?.size ?: 0,
caption = messageType.caption?.trimEnd(),
formattedCaption = formattedCaption,
htmlCaption = messageType.formattedCaption?.body,
isEdited = content.isEdited,
thumbnailSource = messageType.info?.thumbnailSource,
mediaSource = messageType.source,
@@ -198,6 +201,7 @@ class TimelineItemContentMessageFactory(
fileSize = messageType.info?.size ?: 0,
caption = messageType.caption?.trimEnd(),
formattedCaption = formattedCaption,
htmlCaption = messageType.formattedCaption?.body,
isEdited = content.isEdited,
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
@@ -216,6 +220,7 @@ class TimelineItemContentMessageFactory(
fileSize = messageType.info?.size ?: 0,
caption = messageType.caption?.trimEnd(),
formattedCaption = formattedCaption,
htmlCaption = messageType.formattedCaption?.body,
isEdited = content.isEdited,
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
@@ -235,6 +240,7 @@ class TimelineItemContentMessageFactory(
fileSize = messageType.info?.size ?: 0,
caption = messageType.caption?.trimEnd(),
formattedCaption = formattedCaption,
htmlCaption = messageType.formattedCaption?.body,
isEdited = content.isEdited,
thumbnailSource = messageType.info?.thumbnailSource,
mediaSource = messageType.source,
@@ -345,6 +351,7 @@ class TimelineItemContentMessageFactory(
body = messageType.body,
caption = messageType.body.trimEnd().takeIf { it.isNotEmpty() },
formattedCaption = formattedCaption,
htmlCaption = messageType.formatted?.body,
isEdited = content.isEdited,
items = galleryItems.toImmutableList(),
)
@@ -402,6 +409,7 @@ class TimelineItemContentMessageFactory(
body = messageType.body,
caption = messageType.body.trimEnd().takeIf { it.isNotEmpty() },
formattedCaption = formattedCaption,
htmlCaption = messageType.formatted?.body,
isEdited = content.isEdited,
attachments = attachments.toImmutableList(),
)
@@ -33,6 +33,7 @@ class TimelineItemContentPollFactory(
question = pollContentState.question,
answerItems = pollContentState.answerItems,
pollKind = pollContentState.pollKind,
maxSelections = pollContentState.maxSelections,
isEnded = pollContentState.isPollEnded,
isEdited = content.isEdited
)
@@ -28,10 +28,12 @@ import io.element.android.libraries.dateformatter.api.DateFormatterMode
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
import io.element.android.libraries.matrix.ui.messages.reply.map
@@ -64,7 +66,7 @@ class TimelineItemEventFactory(
val currentSender = currentTimelineItem.event.sender
val groupPosition =
computeGroupPosition(currentTimelineItem, timelineItems, index)
val senderProfile = currentTimelineItem.event.senderProfile
val senderProfile = currentTimelineItem.event.senderProfile.orFallbackTo(roomMembers, currentSender)
val sentTime = dateFormatter.format(
timestamp = currentTimelineItem.event.timestamp,
mode = DateFormatterMode.TimeOnly,
@@ -138,11 +140,32 @@ class TimelineItemEventFactory(
roomMembers: List<RoomMember>,
renderReadReceipts: Boolean,
): TimelineItem.Event {
val senderProfile = receivedMatrixTimelineItem.event.senderProfile.orFallbackTo(roomMembers, timelineItem.senderId)
return timelineItem.copy(
senderProfile = senderProfile,
senderAvatar = timelineItem.senderAvatar.copy(
name = senderProfile.getDisambiguatedDisplayName(timelineItem.senderId),
url = senderProfile.getAvatarUrl(),
),
readReceiptState = receivedMatrixTimelineItem.computeReadReceiptState(roomMembers, renderReadReceipts)
)
}
/**
* The timeline only knows the profiles the SDK has resolved, so a sender it has nothing for is rendered as a raw
* user ID. The room member list usually knows that user, so use it rather than showing the ID.
*/
private fun ProfileDetails.orFallbackTo(roomMembers: List<RoomMember>, senderId: UserId): ProfileDetails {
if (this is ProfileDetails.Ready) return this
val member = roomMembers.find { it.userId == senderId } ?: return this
return ProfileDetails.Ready(
displayName = member.displayName,
displayNameAmbiguous = member.isNameAmbiguous,
avatarUrl = member.avatarUrl,
displayedStatus = member.displayedStatus,
)
}
private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions {
if (!config.computeReactions) {
return TimelineItemReactions(reactions = persistentListOf())
@@ -91,7 +91,7 @@ internal fun MatrixTimelineItem.Event.canBeDisplayedInBubbleBlock(): Boolean {
// Can be grouped
is FailedToParseMessageLikeContent,
is MessageContent,
RedactedContent,
is RedactedContent,
is StickerContent,
is PollContent,
is UnableToDecryptContent,
@@ -12,6 +12,7 @@ import androidx.annotation.VisibleForTesting
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.UniqueId
@@ -99,7 +100,8 @@ internal const val MIN_REDACTED_RUN_SIZE = 3
* Fold runs of [MIN_REDACTED_RUN_SIZE] or more consecutive deleted (redacted) messages into a single
* [TimelineItem.GroupedEvents], so a long stretch of "Message removed" tiles shows as one expandable
* "N removed messages" line, like element-web. Shorter runs, and anything that is not a redacted event
* (day dividers included), are left untouched.
* (day dividers included), are left untouched. A deleted message that still heads a thread is left
* untouched too, so that its thread summary stays reachable.
*
* This runs as the final step of [TimelineItemGrouper.group], after the state and membership events
* have been grouped. It is deliberately kept out of the normal grouping: a run needs a higher minimum
@@ -134,7 +136,7 @@ internal fun List<TimelineItem>.collapseRedactedRuns(groupIds: MutableMap<String
}
for (item in this) {
if (item is TimelineItem.Event && item.content is TimelineItemRedactedContent) {
if (item is TimelineItem.Event && item.content is TimelineItemRedactedContent && item.threadInfo !is TimelineItemThreadInfo.ThreadRoot) {
run.add(item)
} else {
flushRun()
@@ -14,6 +14,7 @@ data class TimelineItemAttachmentsContent(
val body: String,
val caption: String?,
val formattedCaption: CharSequence?,
val htmlCaption: String? = null,
override val isEdited: Boolean,
val attachments: ImmutableList<AttachmentItem>,
) : TimelineItemEventContent, TimelineItemEventMutableContent {
@@ -17,6 +17,7 @@ data class TimelineItemAudioContent(
override val fileSize: Long?,
override val caption: String?,
override val formattedCaption: CharSequence?,
override val htmlCaption: String? = null,
override val isEdited: Boolean,
val duration: Duration,
override val mediaSource: MediaSource,
@@ -33,6 +33,9 @@ sealed interface TimelineItemEventContentWithAttachment :
val fileSize: Long?
val caption: String?
val formattedCaption: CharSequence?
/** The caption as it was written, in HTML, so that editing it can start from the formatted text. */
val htmlCaption: String?
val mediaSource: MediaSource
val mimeType: String
val formattedFileSize: String
@@ -127,6 +130,13 @@ fun TimelineItemEventContent.formattedCaptionOrNull(): CharSequence? = when (thi
else -> null
}
fun TimelineItemEventContent.htmlCaptionOrNull(): String? = when (this) {
is TimelineItemEventContentWithAttachment -> htmlCaption
is TimelineItemGalleryContent -> htmlCaption
is TimelineItemAttachmentsContent -> htmlCaption
else -> null
}
fun TimelineItemEventContentWithAttachment.duration(): Duration? {
return when (this) {
is TimelineItemAudioContent -> duration
@@ -16,6 +16,7 @@ data class TimelineItemFileContent(
override val fileSize: Long?,
override val caption: String?,
override val formattedCaption: CharSequence?,
override val htmlCaption: String? = null,
override val isEdited: Boolean,
override val mediaSource: MediaSource,
val thumbnailSource: MediaSource?,
@@ -16,6 +16,7 @@ data class TimelineItemGalleryContent(
val body: String,
val caption: String?,
val formattedCaption: CharSequence?,
val htmlCaption: String? = null,
override val isEdited: Boolean,
val items: ImmutableList<GalleryItem>,
) : TimelineItemEventContent, TimelineItemEventMutableContent {
@@ -19,6 +19,7 @@ data class TimelineItemImageContent(
override val fileSize: Long?,
override val caption: String?,
override val formattedCaption: CharSequence?,
override val htmlCaption: String? = null,
override val isEdited: Boolean,
override val mediaSource: MediaSource,
val thumbnailSource: MediaSource?,
@@ -28,6 +28,7 @@ fun aTimelineItemImageContent(
blurhash: String? = A_BLUR_HASH,
filename: String = "A picture.jpg",
caption: String? = null,
htmlCaption: String? = null,
width: Int? = null,
height: Int? = 300,
) = TimelineItemImageContent(
@@ -35,6 +36,7 @@ fun aTimelineItemImageContent(
fileSize = 4 * 1024 * 1024L,
caption = caption,
formattedCaption = null,
htmlCaption = htmlCaption,
isEdited = false,
mediaSource = MediaSource(""),
thumbnailSource = null,
@@ -19,6 +19,7 @@ data class TimelineItemPollContent(
val question: String,
val answerItems: List<PollAnswerItem>,
val pollKind: PollKind,
val maxSelections: ULong,
val isEnded: Boolean,
override val isEdited: Boolean,
) : TimelineItemEventContent,
@@ -32,12 +32,14 @@ fun aTimelineItemPollContent(
isEditable: Boolean = false,
isEnded: Boolean = false,
isEdited: Boolean = false,
maxSelections: ULong = 1u,
): TimelineItemPollContent {
return TimelineItemPollContent(
eventId = EventId("\$anEventId"),
pollKind = PollKind.Disclosed,
question = question,
answerItems = answerItems,
maxSelections = maxSelections,
isMine = isMine,
isEditable = isEditable,
isEnded = isEnded,
@@ -15,6 +15,7 @@ data class TimelineItemStickerContent(
override val fileSize: Long?,
override val caption: String?,
override val formattedCaption: CharSequence?,
override val htmlCaption: String? = null,
override val isEdited: Boolean,
override val mediaSource: MediaSource,
val thumbnailSource: MediaSource?,
@@ -16,6 +16,7 @@ data class TimelineItemVideoContent(
override val fileSize: Long?,
override val caption: String?,
override val formattedCaption: CharSequence?,
override val htmlCaption: String? = null,
override val isEdited: Boolean,
val duration: Duration,
override val mediaSource: MediaSource,
@@ -19,6 +19,7 @@ data class TimelineItemVoiceContent(
override val fileSize: Long?,
override val caption: String?,
override val formattedCaption: CharSequence?,
override val htmlCaption: String? = null,
override val isEdited: Boolean,
val duration: Duration,
override val mediaSource: MediaSource,
@@ -8,25 +8,15 @@
package io.element.android.features.messages.impl.utils
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalInspectionMode
import com.sigpwned.emoji4j.core.Grapheme.Type.EMOJI
import com.sigpwned.emoji4j.core.Grapheme.Type.PICTOGRAPHIC
import com.sigpwned.emoji4j.core.GraphemeMatchResult
import com.sigpwned.emoji4j.core.GraphemeMatcher
import io.element.android.features.messages.impl.timeline.model.event.AN_EMOJI_ONLY_TEXT
/**
* Returns true if the string consists exclusively of "emoji or pictographic graphemes".
*/
@Composable
fun String.containsOnlyEmojis(): Boolean {
if (LocalInspectionMode.current) return this == AN_EMOJI_ONLY_TEXT
if (isEmpty()) return false
return containsOnlyEmojisInternal()
}
internal fun String.containsOnlyEmojisInternal(): Boolean {
internal fun String.containsOnlyEmojis(): Boolean {
val matcher = GraphemeMatcher(this)
var m: GraphemeMatchResult? = null
var contiguous = true
@@ -7,19 +7,37 @@
<string name="crypto_event_authenticity_unknown_device">"Verschlüsselt von einem unbekannten oder gelöschten Gerät."</string>
<string name="crypto_event_authenticity_unsigned_device">"Verschlüsselt durch ein Gerät, das nicht von seinem Besitzer verifiziert wurde."</string>
<string name="crypto_event_authenticity_unverified_identity">"Verschlüsselt durch einen nicht verifizierten Nutzer."</string>
<string name="error_sending_failure_invalid_mime_type">"Ungültiger MIME-Typ: %s"</string>
<string name="error_sending_failure_missing_media_content">"Fehlende Medieninhalte"</string>
<string name="error_sending_failure_other">"Fehlerdetails: %s"</string>
<string name="error_sending_failure_sending_from_unverified_device">"Senden von einem nicht verifizierten Gerät"</string>
<string name="error_sending_failure_unknown">"Unbekannter Fehler"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally">"Bild horizontal spiegeln"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Horizontal gespiegelt"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Original"</string>
<string name="screen_image_edition_a11y_flip_image_vertically">"Bild vertikal spiegeln"</string>
<string name="screen_image_edition_a11y_flip_image_vertically_state_flipped">"Vertikal gespiegelt"</string>
<string name="screen_image_edition_a11y_flip_image_vertically_state_original">"Original"</string>
<string name="screen_image_edition_a11y_rotate_to_the_left">"Drehe das Bild nach links"</string>
<plurals name="screen_image_edition_a11y_rotation_state">
<item quantity="one">"%1$d Grad"</item>
<item quantity="other">"%1$d Grad"</item>
</plurals>
<string name="screen_image_edition_title">"Foto bearbeiten"</string>
<string name="screen_media_upload_preview_caption_warning">"Bildunterschriften sind für Nutzer älterer Apps möglicherweise nicht sichtbar."</string>
<string name="screen_media_upload_preview_change_video_quality_prompt">"Tippe, um die Qualität des Video-Uploads zu ändern"</string>
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Die Datei konnte nicht hochgeladen werden."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Das Hochladen der Medien ist fehlgeschlagen. Bitte versuche es erneut."</string>
<string name="screen_media_upload_preview_error_too_large_message">"Die maximal zulässige Dateigröße beträgt %1$s."</string>
<string name="screen_media_upload_preview_error_too_large_title">"Die Datei ist zu groß zum Hochladen."</string>
<string name="screen_media_upload_preview_error_too_large_title">"Die Datei ist zu groß zum Hochladen"</string>
<string name="screen_media_upload_preview_item_count">"%1$d von %2$d"</string>
<string name="screen_media_upload_preview_optimize_image_quality_title">"Optimiere die Bildqualität"</string>
<string name="screen_media_upload_preview_processing">"Verarbeitung läuft …"</string>
<string name="screen_media_upload_preview_title">"Medien hinzufügen"</string>
<string name="screen_report_content_block_user">"Nutzer blockieren"</string>
<string name="screen_report_content_block_user_hint">"Prüfe, ob du alle aktuellen und zukünftigen Nachrichten dieses Nutzers ausblenden möchtest"</string>
<string name="screen_report_content_explanation">"Diese Nachricht wird dem Administrator deines Homeservers gemeldet. Dieser kann allerdings keine verschlüsselten Nachrichten lesen."</string>
<string name="screen_report_content_explanation">"Diese Nachricht wird an deinen Kontoanbieter gemeldet. Verschlüsselte Nachrichten kann dieser nicht lesen."</string>
<string name="screen_report_content_hint">"Grund für die Meldung dieses Inhalts"</string>
<string name="screen_room_attachment_source_camera">"Kamera"</string>
<string name="screen_room_attachment_source_camera_photo">"Foto aufnehmen"</string>
@@ -29,7 +47,7 @@
<string name="screen_room_attachment_source_location">"Standort teilen"</string>
<string name="screen_room_attachment_source_poll">"Umfrage"</string>
<string name="screen_room_attachment_text_formatting">"Textformatierung"</string>
<string name="screen_room_encrypted_history_banner">"Der Nachrichtenverlauf ist derzeit nicht verfügbar"</string>
<string name="screen_room_encrypted_history_banner">"Der Nachrichtenverlauf ist derzeit nicht verfügbar."</string>
<string name="screen_room_encrypted_history_banner_unverified">"Der Nachrichtenverlauf ist nicht verfügbar. Verifiziere dieses Gerät, um deinen Nachrichtenverlauf zu sehen."</string>
<string name="screen_room_invite_again_alert_message">"Möchtest du sie wieder einladen?"</string>
<string name="screen_room_invite_again_alert_title">"Du bist allein in diesem Chat"</string>
@@ -57,6 +75,10 @@
<string name="screen_room_timeline_reactions_show_more">"Mehr anzeigen"</string>
<string name="screen_room_timeline_reactions_show_reactions_summary">"Zusammenfassung der Reaktionen anzeigen"</string>
<string name="screen_room_timeline_read_marker_title">"Neu"</string>
<plurals name="screen_room_timeline_redacted_messages">
<item quantity="one">"%1$d entfernte Nachricht"</item>
<item quantity="other">"%1$d entfernte Nachrichten"</item>
</plurals>
<plurals name="screen_room_timeline_state_changes">
<item quantity="one">"%1$d Änderung im Chat"</item>
<item quantity="other">"%1$d Änderungen im Chat"</item>
@@ -67,7 +89,7 @@
<string name="screen_room_timeline_upgraded_room_message">"Dieser Chat ist eine Fortsetzung eines anderen Chats"</string>
<plurals name="screen_room_typing_many_members">
<item quantity="one">"%1$s, %2$s und %3$d weitere Person"</item>
<item quantity="other">"%1$s, %2$s und %3$d weitere Person"</item>
<item quantity="other">"%1$s, %2$s und %3$d weitere Personen"</item>
</plurals>
<plurals name="screen_room_typing_notification">
<item quantity="one">"%1$s schreibt…"</item>
@@ -19,7 +19,7 @@
<string name="screen_media_upload_preview_processing">"Επεξεργασία…"</string>
<string name="screen_report_content_block_user">"Αποκλεισμός χρήστη"</string>
<string name="screen_report_content_block_user_hint">"Επέλεξε εάν θες να αποκρύψεις όλα τα τρέχοντα και μελλοντικά μηνύματα από αυτόν τον χρήστη"</string>
<string name="screen_report_content_explanation">"Αυτό το μήνυμα θα αναφερθεί στον διαχειριστή του οικιακού διακομιστή σας. Δεν θα μπορεί να διαβάσει κρυπτογραφημένα μηνύματα."</string>
<string name="screen_report_content_explanation">"Αυτό το μήνυμα θα αναφερθεί στον πάροχο του λογαριασμού σας. Δεν θα μπορεί να διαβάσει κανένα κρυπτογραφημένο μήνυμα."</string>
<string name="screen_report_content_hint">"Λόγος αναφοράς αυτού του περιεχομένου"</string>
<string name="screen_room_attachment_source_camera">"Κάμερα"</string>
<string name="screen_room_attachment_source_camera_photo">"Τράβηξε φωτογραφία"</string>
@@ -7,6 +7,10 @@
<string name="crypto_event_authenticity_unknown_device">"Krüptitud tundmatu või kustutatud seadme poolt."</string>
<string name="crypto_event_authenticity_unsigned_device">"Krüptitud seadme poolt, mida tema omanik pole verifitseerinud."</string>
<string name="crypto_event_authenticity_unverified_identity">"Krüptitud verifitseerimata kasutaja poolt."</string>
<string name="error_sending_failure_invalid_mime_type">"Vigane MIME-tüüp: %s"</string>
<string name="error_sending_failure_other">"Vea üksikasjad: %s"</string>
<string name="error_sending_failure_sending_from_unverified_device">"Saadetakse verifitseerimata seadmest"</string>
<string name="error_sending_failure_unknown">"Tundmatu viga"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally">"Pööra pilti ümber rõhttelje"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Pilt on pööratud ümber rõhttelje"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Pildi algne variant"</string>
@@ -7,6 +7,11 @@
<string name="crypto_event_authenticity_unknown_device">"Chiffré par un appareil inconnu ou supprimé."</string>
<string name="crypto_event_authenticity_unsigned_device">"Chiffré par un appareil non vérifié par son propriétaire."</string>
<string name="crypto_event_authenticity_unverified_identity">"Chiffré par un utilisateur non vérifié."</string>
<string name="error_sending_failure_invalid_mime_type">"Type MIME invalide : %s"</string>
<string name="error_sending_failure_missing_media_content">"Contenu multimédia manquant"</string>
<string name="error_sending_failure_other">"Détails de l’erreur : %s"</string>
<string name="error_sending_failure_sending_from_unverified_device">"Envoi depuis un appareil non vérifié"</string>
<string name="error_sending_failure_unknown">"Erreur inconnue"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally">"Retourner l’image horizontalement"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Retournée horizontalement"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Originale"</string>
@@ -7,6 +7,11 @@
<string name="crypto_event_authenticity_unknown_device">"Šifrirano nepoznatim ili izbrisanim uređajem."</string>
<string name="crypto_event_authenticity_unsigned_device">"Šifrirano uređajem koji vlasnik nije potvrdio."</string>
<string name="crypto_event_authenticity_unverified_identity">"Šifrirao neprovjereni korisnik."</string>
<string name="error_sending_failure_invalid_mime_type">"Nevažeći MIME tip: %s"</string>
<string name="error_sending_failure_missing_media_content">"Nedostaje medijski sadržaj"</string>
<string name="error_sending_failure_other">"Detalji pogreške: %s"</string>
<string name="error_sending_failure_sending_from_unverified_device">"Slanje s neprovjerenog uređaja"</string>
<string name="error_sending_failure_unknown">"Nepoznata pogreška"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally">"Okreni sliku vodoravno"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Okrenuto vodoravno"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Izvornik"</string>
@@ -7,6 +7,17 @@
<string name="crypto_event_authenticity_unknown_device">"Dienkripsi oleh perangkat yang tidak dikenal atau dihapus."</string>
<string name="crypto_event_authenticity_unsigned_device">"Dienkripsi oleh perangkat yang tidak diverifikasi oleh pemiliknya."</string>
<string name="crypto_event_authenticity_unverified_identity">"Dienkripsi oleh pengguna yang tidak terverifikasi."</string>
<string name="error_sending_failure_invalid_mime_type">"Tipe MIME tidak valid: %s"</string>
<string name="error_sending_failure_missing_media_content">"Konten media tidak ditemukan"</string>
<string name="error_sending_failure_other">"Detail kesalahan: %s"</string>
<string name="error_sending_failure_sending_from_unverified_device">"Mengirim dari perangkat yang belum terverifikasi"</string>
<string name="error_sending_failure_unknown">"Kesalahan tidak diketahui"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally">"Balikkan gambar secara horizontal"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Dibalik secara horizontal"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Asli"</string>
<string name="screen_image_edition_a11y_flip_image_vertically">"Balikkan gambar secara vertikal"</string>
<string name="screen_image_edition_a11y_flip_image_vertically_state_flipped">"Dibalik secara vertikal"</string>
<string name="screen_image_edition_a11y_flip_image_vertically_state_original">"Asli"</string>
<string name="screen_image_edition_a11y_rotate_to_the_left">"Putar gambar ke kiri"</string>
<plurals name="screen_image_edition_a11y_rotation_state">
<item quantity="other">"%1$d derajat"</item>
@@ -7,6 +7,11 @@
<string name="crypto_event_authenticity_unknown_device">"Cifrato da un dispositivo sconosciuto o eliminato."</string>
<string name="crypto_event_authenticity_unsigned_device">"Cifrato da un dispositivo non verificato dal proprietario."</string>
<string name="crypto_event_authenticity_unverified_identity">"Cifrato da un utente non verificato."</string>
<string name="error_sending_failure_invalid_mime_type">"Tipo MIME non valido: %s"</string>
<string name="error_sending_failure_missing_media_content">"Contenuto multimediale mancante"</string>
<string name="error_sending_failure_other">"Dettagli dell\'errore: %s"</string>
<string name="error_sending_failure_sending_from_unverified_device">"Invio da dispositivo non verificato"</string>
<string name="error_sending_failure_unknown">"Errore sconosciuto"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally">"Capovolgi l\'immagine orizzontalmente"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Capovolto orizzontalmente"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Originale"</string>
@@ -32,7 +37,7 @@
<string name="screen_media_upload_preview_title">"Aggiungi contenuti multimediali"</string>
<string name="screen_report_content_block_user">"Blocca utente"</string>
<string name="screen_report_content_block_user_hint">"Seleziona se vuoi nascondere tutti i messaggi attuali e futuri di questo utente"</string>
<string name="screen_report_content_explanation">"Questo messaggio verrà segnalato all\'amministratore dell\'homeserver. Questi non sarà in grado di leggere i messaggi cifrati."</string>
<string name="screen_report_content_explanation">"Questo messaggio verrà segnalato al gestore del tuo account. Quest’ultimo non potrà leggere alcun messaggio cifrato."</string>
<string name="screen_report_content_hint">"Motivo della segnalazione di questo contenuto"</string>
<string name="screen_room_attachment_source_camera">"Fotocamera"</string>
<string name="screen_room_attachment_source_camera_photo">"Scatta foto"</string>
@@ -70,6 +75,10 @@
<string name="screen_room_timeline_reactions_show_more">"Mostra di più"</string>
<string name="screen_room_timeline_reactions_show_reactions_summary">"Mostra il riepilogo delle reazioni"</string>
<string name="screen_room_timeline_read_marker_title">"Nuovo"</string>
<plurals name="screen_room_timeline_redacted_messages">
<item quantity="one">"%1$d messaggio rimosso"</item>
<item quantity="other">"%1$d messaggi rimossi"</item>
</plurals>
<plurals name="screen_room_timeline_state_changes">
<item quantity="one">"%1$d modifica alla stanza"</item>
<item quantity="other">"%1$d modifiche alla stanza"</item>
@@ -67,6 +67,9 @@
<string name="screen_room_timeline_reactions_show_more">"さらに表示"</string>
<string name="screen_room_timeline_reactions_show_reactions_summary">"リアクションのまとめを表示"</string>
<string name="screen_room_timeline_read_marker_title">"新着"</string>
<plurals name="screen_room_timeline_redacted_messages">
<item quantity="other">"%1$d 件の削除されたメッセージ"</item>
</plurals>
<plurals name="screen_room_timeline_state_changes">
<item quantity="other">"%1$d 個のルーム更新点"</item>
</plurals>
@@ -7,6 +7,11 @@
<string name="crypto_event_authenticity_unknown_device">"Zaszyfrowana przez nieznane lub usunięte urządzenie."</string>
<string name="crypto_event_authenticity_unsigned_device">"Zaszyfrowana przez urządzenie niezweryfikowane przez jego właściciela."</string>
<string name="crypto_event_authenticity_unverified_identity">"Zaszyfrowana przez niezweryfikowanego użytkownika."</string>
<string name="error_sending_failure_invalid_mime_type">"Nieprawidłowy typ MIME: %s"</string>
<string name="error_sending_failure_missing_media_content">"Brak treści multimedialnych"</string>
<string name="error_sending_failure_other">"Szczegóły błędu: %s"</string>
<string name="error_sending_failure_sending_from_unverified_device">"Wysyłanie z niezweryfikowanego urządzenia"</string>
<string name="error_sending_failure_unknown">"Nieznany błąd"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally">"Odwróć obraz w poziomie"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Odwrócono w poziomie"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Oryginalny"</string>
@@ -33,7 +38,7 @@
<string name="screen_media_upload_preview_title">"Dodaj media"</string>
<string name="screen_report_content_block_user">"Zablokuj użytkownika"</string>
<string name="screen_report_content_block_user_hint">"Zaznacz, jeśli chcesz ukryć wszystkie bieżące i przyszłe wiadomości od tego użytkownika."</string>
<string name="screen_report_content_explanation">"Ta wiadomość zostanie zgłoszona do administratora Twojego serwera domowego. Nie będzie mógł on przeczytać żadnych zaszyfrowanych wiadomości."</string>
<string name="screen_report_content_explanation">"Wiadomość zostanie zgłoszona do Twojego dostawcy konta. Nie będzie mógł on przeczytać żadnych zaszyfrowanych wiadomości."</string>
<string name="screen_report_content_hint">"Powód zgłoszenia treści"</string>
<string name="screen_room_attachment_source_camera">"Kamera"</string>
<string name="screen_room_attachment_source_camera_photo">"Zrób zdjęcie"</string>
@@ -7,6 +7,11 @@
<string name="crypto_event_authenticity_unknown_device">"Зашифровано невідомим або видаленим пристроєм."</string>
<string name="crypto_event_authenticity_unsigned_device">"Зашифровано пристроєм, який не звірено його власником."</string>
<string name="crypto_event_authenticity_unverified_identity">"Зашифровано не звіреним користувачем."</string>
<string name="error_sending_failure_invalid_mime_type">"Недійсний тип MIME: %s"</string>
<string name="error_sending_failure_missing_media_content">"Відсутній медіавміст"</string>
<string name="error_sending_failure_other">"Подробиці помилки: %s"</string>
<string name="error_sending_failure_sending_from_unverified_device">"Надсилання з незвіреного пристрою"</string>
<string name="error_sending_failure_unknown">"Невідома помилка"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally">"Повернути зображення по горизонталі"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Перевернуто по горизонталі"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Оригінал"</string>
@@ -33,7 +38,7 @@
<string name="screen_media_upload_preview_title">"Додати медіа"</string>
<string name="screen_report_content_block_user">"Заблокувати користувача"</string>
<string name="screen_report_content_block_user_hint">"Перевірте, чи хочете ви приховати всі поточні та майбутні повідомлення від цього користувача"</string>
<string name="screen_report_content_explanation">"Це повідомлення буде надіслано адміністраторам вашого домашнього сервера. Вони не зможуть прочитати зашифровані повідомлення."</string>
<string name="screen_report_content_explanation">"Це повідомлення буде передано постачальнику вашого облікового запису. Він не зможе прочитати зашифрованих повідомлень."</string>
<string name="screen_report_content_hint">"Причина скарги на цей вміст"</string>
<string name="screen_room_attachment_source_camera">"Камера"</string>
<string name="screen_room_attachment_source_camera_photo">"Зробити фото"</string>
@@ -7,6 +7,11 @@
<string name="crypto_event_authenticity_unknown_device">"由未知或已删除的设备加密。"</string>
<string name="crypto_event_authenticity_unsigned_device">"由未经其所有者验证的设备加密。"</string>
<string name="crypto_event_authenticity_unverified_identity">"由未经验证的用户加密。"</string>
<string name="error_sending_failure_invalid_mime_type">"无效的 MIME 类型:%s"</string>
<string name="error_sending_failure_missing_media_content">"媒体内容丢失"</string>
<string name="error_sending_failure_other">"错误详细信息:%s"</string>
<string name="error_sending_failure_sending_from_unverified_device">"由未经验证的设备发送"</string>
<string name="error_sending_failure_unknown">"未知错误"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally">"水平翻转图像"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"水平翻转"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"原始"</string>
@@ -31,7 +36,7 @@
<string name="screen_media_upload_preview_title">"添加媒体"</string>
<string name="screen_report_content_block_user">"屏蔽用户"</string>
<string name="screen_report_content_block_user_hint">"请确认是否要隐藏该用户当前和未来的所有消息"</string>
<string name="screen_report_content_explanation">"此消息将举报给服务器管理员。他们无法读取任何加密消息。"</string>
<string name="screen_report_content_explanation">"此消息将举报给账户提供者。他们无法读取任何加密消息。"</string>
<string name="screen_report_content_hint">"举报此内容的理由"</string>
<string name="screen_room_attachment_source_camera">"相机"</string>
<string name="screen_room_attachment_source_camera_photo">"拍摄照片"</string>
@@ -37,7 +37,7 @@
<string name="screen_media_upload_preview_title">"Add media"</string>
<string name="screen_report_content_block_user">"Block user"</string>
<string name="screen_report_content_block_user_hint">"Check if you want to hide all current and future messages from this user"</string>
<string name="screen_report_content_explanation">"This message will be reported to your homeserver’s administrator. They will not be able to read any encrypted messages."</string>
<string name="screen_report_content_explanation">"This message will be reported to your account provider. They will not be able to read any encrypted messages."</string>
<string name="screen_report_content_hint">"Reason for reporting this content"</string>
<string name="screen_room_attachment_source_camera">"Camera"</string>
<string name="screen_room_attachment_source_camera_photo">"Take photo"</string>
@@ -1065,6 +1065,33 @@ class MessagesPresenterTest {
}
}
@Test
fun `present - handle action edit caption starts from the formatted caption`() = runTest {
val messageEvent = aMessageEvent(
content = aTimelineItemImageContent(
caption = "Hello world",
htmlCaption = "<b>Hello</b> world",
)
)
val composerRecorder = EventsRecorder<MessageComposerEvent>()
val presenter = createMessagesPresenter(
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder, showTextFormatting = true) },
)
presenter.testWithLifecycleOwner {
val initialState = awaitItem()
initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.EditCaption, messageEvent))
awaitItem()
composerRecorder.assertSingle(
MessageComposerEvent.SetMode(
composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
content = "<b>Hello</b> world",
)
)
)
}
}
@Test
fun `present - handle action add caption`() = runTest {
val composerRecorder = EventsRecorder<MessageComposerEvent>()
@@ -383,6 +383,48 @@ class SuggestionsProcessorTest {
)
}
@Test
fun `processing Mention suggestion returns the members matching the start of the query first`() = runTest {
val matchingOnAnyPart = aRoomMember(userId = UserId("@carol:server.org"), displayName = "Not Alice")
val matchingOnUserIdStart = aRoomMember(userId = UserId("@alicia:server.org"), displayName = null)
val matchingOnDisplayNameStart = aRoomMember(userId = UserId("@zoe:server.org"), displayName = "Alice")
val result = suggestionsProcessor.process(
suggestion = aMentionSuggestion("ali"),
roomMembersState = RoomMembersState.Ready(
persistentListOf(matchingOnAnyPart, matchingOnUserIdStart, matchingOnDisplayNameStart)
),
roomAliasSuggestions = emptyList(),
currentUserId = A_USER_ID_2,
canSendRoomMention = { false },
isInThread = false,
)
assertThat(result).isEqualTo(
listOf(
ResolvedSuggestion.Member(matchingOnDisplayNameStart),
ResolvedSuggestion.Member(matchingOnUserIdStart),
ResolvedSuggestion.Member(matchingOnAnyPart),
)
)
}
@Test
fun `processing Mention suggestion returns room ignoring the case of the query`() = runTest {
val aRoomMember = aRoomMember(userId = UserId("@alice:server.org"), displayName = "alice")
val result = suggestionsProcessor.process(
suggestion = aMentionSuggestion("RO"),
roomMembersState = RoomMembersState.Ready(persistentListOf(aRoomMember)),
roomAliasSuggestions = emptyList(),
currentUserId = A_USER_ID_2,
canSendRoomMention = { true },
isInThread = false,
)
assertThat(result).isEqualTo(
listOf(
ResolvedSuggestion.AtRoom,
)
)
}
@Test
fun `processing Mention suggestion with return matching display name but not room if not allowed`() = runTest {
val aRoomMember = aRoomMember(userId = UserId("@alice:server.org"), displayName = "ro")
@@ -51,7 +51,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSender
import io.element.android.libraries.matrix.api.timeline.item.event.Receipt
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
import io.element.android.libraries.matrix.test.AN_EVENT_ID
@@ -69,6 +68,7 @@ import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.aMessageContent
import io.element.android.libraries.matrix.test.timeline.aRedactedContent
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
import io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
@@ -880,7 +880,7 @@ class TimelinePresenterTest {
(0 until 3).map { index ->
MatrixTimelineItem.Event(
uniqueId = UniqueId("redacted_$index"),
event = anEventTimelineItem(eventId = EventId("\$R$index"), content = RedactedContent),
event = anEventTimelineItem(eventId = EventId("\$R$index"), content = aRedactedContent()),
)
}
),
@@ -951,14 +951,14 @@ class TimelinePresenterTest {
}
@Test
fun `present - PollAnswerSelected event`() = runTest {
fun `present - SendPollResponse event`() = runTest {
val sendPollResponseAction = FakeSendPollResponseAction()
val presenter = createTimelinePresenter(
sendPollResponseAction = sendPollResponseAction,
)
presenter.test {
val initialState = awaitFirstItem()
initialState.eventSink.invoke(TimelineEvent.SelectPollAnswer(AN_EVENT_ID, "anAnswerId"))
initialState.eventSink.invoke(TimelineEvent.SendPollResponse(AN_EVENT_ID, listOf("anAnswerId")))
}
delay(1)
sendPollResponseAction.verifyExecutionCount(1)
@@ -50,7 +50,7 @@ class TimelineItemPollViewTest : RobolectricTest() {
matcher = hasText(answer.text),
useUnmergedTree = true,
).performClick()
eventsRecorder.assertSingle(TimelineEvent.SelectPollAnswer(content.eventId!!, answer.id))
eventsRecorder.assertSingle(TimelineEvent.SendPollResponse(content.eventId!!, listOf(answer.id)))
}
@Test
@@ -12,12 +12,21 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactory
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
import io.element.android.libraries.matrix.api.timeline.item.ThreadSummary
import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
import io.element.android.libraries.matrix.test.A_THREAD_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.aRoomMember
import io.element.android.libraries.matrix.test.timeline.aMessageContent
import io.element.android.libraries.matrix.test.timeline.aRedactedContent
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -84,6 +93,42 @@ class TimelineItemsFactoryTest {
).inOrder()
}
@Test
fun `redacted message keeps its thread info`() = runTest {
val factory = aTimelineItemsFactory(
config = TimelineItemsFactoryConfig(
computeReadReceipts = false,
computeReactions = false,
)
)
val items = listOf(
MatrixTimelineItem.Event(
uniqueId = UniqueId("event-0"),
event = anEventTimelineItem(
sender = A_USER_ID,
timestamp = 0L,
content = aRedactedContent(
threadInfo = EventThreadInfo.ThreadResponse(A_THREAD_ID),
),
),
)
)
var threadInfo: TimelineItemThreadInfo? = null
factory.timelineItems.test {
factory.replaceWith(
timelineItems = items,
roomMembers = emptyList(),
renderReadReceipts = false,
)
threadInfo = awaitItem()
.filterIsInstance<TimelineItem.Event>()
.firstOrNull()
?.threadInfo
cancelAndIgnoreRemainingEvents()
}
assertThat(threadInfo).isEqualTo(TimelineItemThreadInfo.ThreadResponse(A_THREAD_ID))
}
@Test
fun `a key verification request is not rendered`() = runTest {
val factory = aTimelineItemsFactory(
@@ -128,6 +173,111 @@ class TimelineItemsFactoryTest {
}
}
@Test
fun `a deleted message keeps the summary of the thread it heads`() = runTest {
val factory = aTimelineItemsFactory(
config = TimelineItemsFactoryConfig(
computeReadReceipts = false,
computeReactions = false,
)
)
val items = listOf(
MatrixTimelineItem.Event(
uniqueId = UniqueId("event-0"),
event = anEventTimelineItem(
sender = A_USER_ID,
content = aRedactedContent(
threadInfo = EventThreadInfo.ThreadRoot(
summary = ThreadSummary(latestEvent = AsyncData.Uninitialized, numberOfReplies = 3),
),
),
),
),
)
factory.timelineItems.test {
factory.replaceWith(
timelineItems = items,
roomMembers = emptyList(),
renderReadReceipts = false,
)
val threadInfo = awaitItem()
.filterIsInstance<TimelineItem.Event>()
.single()
.threadInfo
assertThat(threadInfo).isInstanceOf(TimelineItemThreadInfo.ThreadRoot::class.java)
assertThat((threadInfo as TimelineItemThreadInfo.ThreadRoot).summary.numberOfReplies).isEqualTo(3)
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `a sender the timeline knows nothing about is named from the room member list`() = runTest {
val factory = aTimelineItemsFactory(
config = TimelineItemsFactoryConfig(
computeReadReceipts = false,
computeReactions = false,
)
)
val items = listOf(
MatrixTimelineItem.Event(
uniqueId = UniqueId("event-0"),
event = anEventTimelineItem(
sender = A_USER_ID,
senderProfile = ProfileDetails.Unavailable,
content = aMessageContent(body = "A regular message"),
),
),
)
factory.timelineItems.test {
factory.replaceWith(
timelineItems = items,
roomMembers = listOf(aRoomMember(userId = A_USER_ID, displayName = "Alice")),
renderReadReceipts = false,
)
val event = awaitItem().filterIsInstance<TimelineItem.Event>().single()
assertThat(event.senderProfile).isEqualTo(
ProfileDetails.Ready(
displayName = "Alice",
displayNameAmbiguous = false,
avatarUrl = null,
displayedStatus = null,
)
)
assertThat(event.senderAvatar.name).isEqualTo("Alice")
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `a sender the room member list knows nothing about keeps the profile the timeline gave`() = runTest {
val factory = aTimelineItemsFactory(
config = TimelineItemsFactoryConfig(
computeReadReceipts = false,
computeReactions = false,
)
)
val items = listOf(
MatrixTimelineItem.Event(
uniqueId = UniqueId("event-0"),
event = anEventTimelineItem(
sender = A_USER_ID,
senderProfile = ProfileDetails.Unavailable,
content = aMessageContent(body = "A regular message"),
),
),
)
factory.timelineItems.test {
factory.replaceWith(
timelineItems = items,
roomMembers = listOf(aRoomMember(userId = A_USER_ID_2, displayName = "Bob")),
renderReadReceipts = false,
)
val event = awaitItem().filterIsInstance<TimelineItem.Event>().single()
assertThat(event.senderProfile).isEqualTo(ProfileDetails.Unavailable)
cancelAndIgnoreRemainingEvents()
}
}
private suspend fun TestScope.groupPositionsOf(timestamps: List<Long>): List<TimelineItemGroupPosition> {
val factory = aTimelineItemsFactory(
config = TimelineItemsFactoryConfig(
@@ -229,6 +229,25 @@ class TimelineItemContentMessageFactoryTest : RobolectricTest() {
assertThat((result as TimelineItemTextContent).formattedBody).isEqualTo(SpannedString("body"))
}
@Test
fun `test create TextMessageType with a formatted body containing only an image falls back to the body`() = runTest {
val sut = createTimelineItemContentMessageFactory()
val result = sut.create(
content = createMessageContent(
type = TextMessageType(
body = "\uD83D\uDE1C",
formatted = FormattedBody(MessageFormat.HTML, "<img src=\"mxc://matrix.org/anImage\" alt=\"\uD83D\uDE1C\" />")
)
),
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
assertThat((result as TimelineItemTextContent).formattedBody).isEqualTo(SpannedString("\uD83D\uDE1C"))
assertThat(result.htmlDocument).isNull()
assertThat(result.plainText).isEqualTo("\uD83D\uDE1C")
}
@Test
fun `test create VideoMessageType`() = runTest {
val sut = createTimelineItemContentMessageFactory()
@@ -297,6 +316,7 @@ class TimelineItemContentMessageFactoryTest : RobolectricTest() {
fileSize = 555L,
caption = "body.mp4 caption",
formattedCaption = SpannedString("formatted"),
htmlCaption = "formatted",
isEdited = true,
duration = 1.minutes,
mediaSource = MediaSource(url = "url", json = null),
@@ -539,6 +559,7 @@ class TimelineItemContentMessageFactoryTest : RobolectricTest() {
fileSize = 888L,
caption = "body.jpg caption",
formattedCaption = SpannedString("formatted"),
htmlCaption = "formatted",
isEdited = true,
mediaSource = MediaSource(url = "url", json = null),
thumbnailSource = MediaSource("url_thumbnail"),
@@ -15,11 +15,14 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
import io.element.android.features.messages.impl.timeline.model.ReadReceiptData
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent
import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.matrix.api.timeline.item.ThreadSummary
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_USER_ID
@@ -190,6 +193,22 @@ class TimelineItemGrouperTest {
)
}
@Test
fun `a deleted message that still heads a thread is left out of the removed messages group`() {
val threadRoot = aGroupableItem.copy(
id = UniqueId("r1"),
content = TimelineItemRedactedContent,
threadInfo = TimelineItemThreadInfo.ThreadRoot(
summary = ThreadSummary(latestEvent = AsyncData.Uninitialized, numberOfReplies = 2),
latestEventText = null,
),
)
val r0 = aGroupableItem.copy(id = UniqueId("r0"), content = TimelineItemRedactedContent)
val r2 = aGroupableItem.copy(id = UniqueId("r2"), content = TimelineItemRedactedContent)
val result = sut.group(listOf(r0, threadRoot, r2))
assertThat(result).isEqualTo(listOf(r0, threadRoot, r2))
}
@Test
fun `a run of fewer than three redacted events is left untouched`() {
val r0 = aGroupableItem.copy(id = UniqueId("r0"), content = TimelineItemRedactedContent)
@@ -16,23 +16,23 @@ class EmojiTest {
@Test
fun validEmojis() {
// Simple single/multiple single-codepoint emojis per string
assertTrue("👍".containsOnlyEmojisInternal())
assertTrue("😀".containsOnlyEmojisInternal())
assertTrue("🙂🙁".containsOnlyEmojisInternal())
assertTrue("👁❤️🍝".containsOnlyEmojisInternal()) // 👁 is a pictographic
assertTrue("👨‍👩‍👦1️⃣🚀👳🏾‍♂️🪩".containsOnlyEmojisInternal())
assertTrue("🌍🌎🌏".containsOnlyEmojisInternal())
assertTrue("👍".containsOnlyEmojis())
assertTrue("😀".containsOnlyEmojis())
assertTrue("🙂🙁".containsOnlyEmojis())
assertTrue("👁❤️🍝".containsOnlyEmojis()) // 👁 is a pictographic
assertTrue("👨‍👩‍👦1️⃣🚀👳🏾‍♂️🪩".containsOnlyEmojis())
assertTrue("🌍🌎🌏".containsOnlyEmojis())
// Awkward multi-codepoint graphemes
assertTrue("🧑‍🧑‍🧒‍🧒".containsOnlyEmojisInternal())
assertTrue("🏴‍☠".containsOnlyEmojisInternal())
assertTrue("👩🏿‍🔧".containsOnlyEmojisInternal())
assertTrue("🧑‍🧑‍🧒‍🧒".containsOnlyEmojis())
assertTrue("🏴‍☠".containsOnlyEmojis())
assertTrue("👩🏿‍🔧".containsOnlyEmojis())
Assert.assertFalse("".containsOnlyEmojisInternal())
Assert.assertFalse(" ".containsOnlyEmojisInternal())
Assert.assertFalse("🙂 🙁".containsOnlyEmojisInternal())
Assert.assertFalse(" 🙂 🙁 ".containsOnlyEmojisInternal())
Assert.assertFalse("Hello".containsOnlyEmojisInternal())
Assert.assertFalse("Hello 👋".containsOnlyEmojisInternal())
Assert.assertFalse("".containsOnlyEmojis())
Assert.assertFalse(" ".containsOnlyEmojis())
Assert.assertFalse("🙂 🙁".containsOnlyEmojis())
Assert.assertFalse(" 🙂 🙁 ".containsOnlyEmojis())
Assert.assertFalse("Hello".containsOnlyEmojis())
Assert.assertFalse("Hello 👋".containsOnlyEmojis())
}
}
@@ -16,11 +16,11 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.core.FakeSendHandle
import io.element.android.libraries.matrix.test.timeline.aRedactedContent
import io.element.android.libraries.mediaplayer.api.MediaPlayer
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
import io.element.android.tests.testutils.testCoroutineDispatchers
@@ -88,7 +88,7 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf<MatrixTimelineItem>(
sender = A_USER_ID,
senderProfile = ProfileDetails.Unavailable,
timestamp = 9442,
content = RedactedContent,
content = aRedactedContent(),
origin = null,
timelineItemDebugInfoProvider = {
TimelineItemDebugInfo(
@@ -12,17 +12,17 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.Timeline
/**
* Sends the current user's answer to a poll, wrapping the timeline call so the analytics are captured in one place.
* Sends the current user's answer(s) to a poll, wrapping the timeline call so the analytics are captured in one place.
*/
interface SendPollResponseAction {
/**
* @param timeline the timeline the poll lives in.
* @param pollStartId the poll start event being answered.
* @param answerId the id of the chosen answer.
* @param answerIds the ids of the chosen answers.
*/
suspend fun execute(
timeline: Timeline,
pollStartId: EventId,
answerId: String
answerIds: List<String>,
): Result<Unit>
}
@@ -18,6 +18,7 @@ import kotlinx.collections.immutable.ImmutableList
* @property question the poll question.
* @property answerItems the list of answers.
* @property pollKind the kind of poll.
* @property maxSelections the maximum number of selections allowed.
* @property isPollEditable whether the poll is editable.
* @property isPollEnded whether the poll is ended.
* @property isMine whether the poll has been created by me.
@@ -27,7 +28,10 @@ data class PollContentState(
val question: String,
val answerItems: ImmutableList<PollAnswerItem>,
val pollKind: PollKind,
val maxSelections: ULong,
val isPollEditable: Boolean,
val isPollEnded: Boolean,
val isMine: Boolean,
)
) {
val isMultipleSelection = maxSelections > 1u
}
@@ -84,6 +84,7 @@ fun aPollContentState(
hasVotes: Boolean = true,
question: String = aPollQuestion(),
pollKind: PollKind = PollKind.Disclosed,
maxSelections: ULong = 1u,
answerItems: ImmutableList<PollAnswerItem> = aPollAnswerItemList(
isEnded = isEnded,
showVotes = showVotes,
@@ -94,6 +95,7 @@ fun aPollContentState(
question = question,
answerItems = answerItems,
pollKind = pollKind,
maxSelections = maxSelections,
isPollEditable = isMine && !isEnded && isPollEditable,
isPollEnded = isEnded,
isMine = isMine,
@@ -40,7 +40,7 @@ import kotlinx.collections.immutable.ImmutableList
@Composable
fun PollContentView(
state: PollContentState,
onSelectAnswer: (pollStartId: EventId, answerId: String) -> Unit,
onSendPollResponse: (pollStartId: EventId, answerIds: List<String>) -> Unit,
onEditPoll: (pollStartId: EventId) -> Unit,
onEndPoll: (pollStartId: EventId) -> Unit,
modifier: Modifier = Modifier,
@@ -50,11 +50,13 @@ fun PollContentView(
question = state.question,
answerItems = state.answerItems,
pollKind = state.pollKind,
isMultipleSelection = state.isMultipleSelection,
maxSelections = state.maxSelections,
isPollEditable = state.isPollEditable,
isPollEnded = state.isPollEnded,
isMine = state.isMine,
onEditPoll = onEditPoll,
onSelectAnswer = onSelectAnswer,
onSendPollResponse = onSendPollResponse,
onEndPoll = onEndPoll,
modifier = modifier,
)
@@ -66,10 +68,12 @@ fun PollContentView(
question: String,
answerItems: ImmutableList<PollAnswerItem>,
pollKind: PollKind,
isMultipleSelection: Boolean,
maxSelections: ULong,
isPollEditable: Boolean,
isPollEnded: Boolean,
isMine: Boolean,
onSelectAnswer: (pollStartId: EventId, answerId: String) -> Unit,
onSendPollResponse: (pollStartId: EventId, answerIds: List<String>) -> Unit,
onEditPoll: (pollStartId: EventId) -> Unit,
onEndPoll: (pollStartId: EventId) -> Unit,
modifier: Modifier = Modifier,
@@ -77,7 +81,23 @@ fun PollContentView(
val votesCount = remember(answerItems) { answerItems.sumOf { it.votesCount } }
fun onSelectAnswer(pollAnswer: PollAnswer) {
eventId?.let { onSelectAnswer(it, pollAnswer.id) }
eventId?.let {
if (isMultipleSelection) {
val newSelections = answerItems.map { item ->
if (item.answer.id == pollAnswer.id) {
item.copy(isSelected = !item.isSelected)
} else {
item
}
}.filter { it.isSelected }.map { it.answer.id }
// Only send the response if the selection count is within the limit
if (newSelections.size <= maxSelections.toInt()) {
onSendPollResponse(it, newSelections)
}
} else {
onSendPollResponse(it, listOf(pollAnswer.id))
}
}
}
fun onEditPoll() {
@@ -107,7 +127,11 @@ fun PollContentView(
) {
PollTitleView(title = question, isPollEnded = isPollEnded)
PollAnswers(answerItems = answerItems, onSelectAnswer = ::onSelectAnswer)
PollAnswers(
answerItems = answerItems,
isMultipleSelection = isMultipleSelection,
onSelectAnswer = ::onSelectAnswer,
)
if (isPollEnded || pollKind == PollKind.Disclosed) {
DisclosedPollBottomNotice(votesCount = votesCount)
@@ -130,8 +154,10 @@ fun PollContentView(
@Composable
private fun PollAnswers(
answerItems: ImmutableList<PollAnswerItem>,
isMultipleSelection: Boolean,
onSelectAnswer: (PollAnswer) -> Unit,
) {
val role = if (isMultipleSelection) Role.Checkbox else Role.RadioButton
Column(
modifier = Modifier.selectableGroup(),
verticalArrangement = Arrangement.spacedBy(16.dp),
@@ -144,7 +170,7 @@ private fun PollAnswers(
selected = it.isSelected,
enabled = it.isEnabled,
onClick = { onSelectAnswer(it.answer) },
role = Role.RadioButton,
role = role,
),
)
}
@@ -207,10 +233,12 @@ internal fun PollContentViewUndisclosedPreview() = ElementPreview {
question = "What type of food should we have at the party?",
answerItems = aPollAnswerItemList(showVotes = false),
pollKind = PollKind.Undisclosed,
isMultipleSelection = false,
maxSelections = 1u,
isPollEnded = false,
isPollEditable = false,
isMine = false,
onSelectAnswer = { _, _ -> },
onSendPollResponse = { _, _ -> },
onEditPoll = {},
onEndPoll = {},
)
@@ -224,10 +252,31 @@ internal fun PollContentViewDisclosedPreview() = ElementPreview {
question = "What type of food should we have at the party?",
answerItems = aPollAnswerItemList(),
pollKind = PollKind.Disclosed,
isMultipleSelection = false,
maxSelections = 1u,
isPollEnded = false,
isPollEditable = false,
isMine = false,
onSelectAnswer = { _, _ -> },
onSendPollResponse = { _, _ -> },
onEditPoll = {},
onEndPoll = {},
)
}
@PreviewsDayNight
@Composable
internal fun PollContentViewMultipleSelectionPreview() = ElementPreview {
PollContentView(
eventId = EventId("\$anEventId"),
question = "What type of food should we have at the party?",
answerItems = aPollAnswerItemList(),
pollKind = PollKind.Disclosed,
isMultipleSelection = true,
maxSelections = 3u,
isPollEnded = false,
isPollEditable = false,
isMine = false,
onSendPollResponse = { _, _ -> },
onEditPoll = {},
onEndPoll = {},
)
@@ -241,10 +290,12 @@ internal fun PollContentViewEndedPreview() = ElementPreview {
question = "What type of food should we have at the party?",
answerItems = aPollAnswerItemList(isEnded = true),
pollKind = PollKind.Disclosed,
isMultipleSelection = false,
maxSelections = 1u,
isPollEnded = true,
isPollEditable = false,
isMine = false,
onSelectAnswer = { _, _ -> },
onSendPollResponse = { _, _ -> },
onEditPoll = {},
onEndPoll = {},
)
@@ -258,10 +309,12 @@ internal fun PollContentViewCreatorEditablePreview() = ElementPreview {
question = "What type of food should we have at the party?",
answerItems = aPollAnswerItemList(hasVotes = false, isEnded = false),
pollKind = PollKind.Disclosed,
isMultipleSelection = false,
maxSelections = 1u,
isPollEnded = false,
isPollEditable = true,
isMine = true,
onSelectAnswer = { _, _ -> },
onSendPollResponse = { _, _ -> },
onEditPoll = {},
onEndPoll = {},
)
@@ -275,10 +328,12 @@ internal fun PollContentViewCreatorPreview() = ElementPreview {
question = "What type of food should we have at the party?",
answerItems = aPollAnswerItemList(isEnded = false),
pollKind = PollKind.Disclosed,
isMultipleSelection = false,
maxSelections = 1u,
isPollEnded = false,
isPollEditable = false,
isMine = true,
onSelectAnswer = { _, _ -> },
onSendPollResponse = { _, _ -> },
onEditPoll = {},
onEndPoll = {},
)
@@ -292,10 +347,12 @@ internal fun PollContentViewCreatorEndedPreview() = ElementPreview {
question = "What type of food should we have at the party?",
answerItems = aPollAnswerItemList(isEnded = true),
pollKind = PollKind.Disclosed,
isMultipleSelection = false,
maxSelections = 1u,
isPollEnded = true,
isPollEditable = false,
isMine = true,
onSelectAnswer = { _, _ -> },
onSendPollResponse = { _, _ -> },
onEditPoll = {},
onEndPoll = {},
)
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d τοις εκατό των συνολικών ψήφων"</item>
<item quantity="other">"%1$d τοις εκατό του συνόλου των ψήφων"</item>
<item quantity="one">"%1$d ποσοστό των συνολικών ψήφων"</item>
<item quantity="other">"%1$d ποσοστά επί του συνόλου των ψήφων"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Θα καταργήσει την προηγούμενη επιλογή"</string>
<string name="a11y_polls_winning_answer">"Αυτή είναι η νικητήρια απάντηση"</string>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="other">"総投票数うち%1$d%%"</item>
<item quantity="other">"総投票数うち%1$d% パーセント"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"前の項目を削除します"</string>
<string name="a11y_polls_winning_answer">"投票の勝者はこちらです"</string>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="other">"占总投票的 %1$d%"</item>
<item quantity="other">"占总投票的 %1$d%%"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"将移除之前的选择"</string>
<string name="a11y_polls_winning_answer">"此为胜出的答案"</string>
@@ -12,5 +12,5 @@ internal object PollConstants {
const val MIN_ANSWERS = 2
const val MAX_ANSWERS = 20
const val MAX_ANSWER_LENGTH = 240
const val MAX_SELECTIONS = 1
const val DEFAULT_MAX_SELECTIONS = 1
}
@@ -20,10 +20,10 @@ import io.element.android.services.analytics.api.AnalyticsService
class DefaultSendPollResponseAction(
private val analyticsService: AnalyticsService,
) : SendPollResponseAction {
override suspend fun execute(timeline: Timeline, pollStartId: EventId, answerId: String): Result<Unit> {
override suspend fun execute(timeline: Timeline, pollStartId: EventId, answerIds: List<String>): Result<Unit> {
return timeline.sendPollResponse(
pollStartId = pollStartId,
answers = listOf(answerId),
answers = answerIds,
).onSuccess {
analyticsService.capture(PollVote())
}

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