Merge branch 'develop' into feature/valere/message_shields

This commit is contained in:
Benoit Marty 2024-08-14 12:37:31 +02:00 committed by GitHub
commit 5d10b1fe85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
342 changed files with 5475 additions and 1377 deletions

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="error_no_compatible_app_found">"Bu amalni bajarish uchun mos ilova topilmadi."</string>
</resources>

View file

@ -29,7 +29,6 @@ android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
consumerProguardFiles("consumer-rules.pro")
}
}

View file

@ -179,6 +179,14 @@ val SemanticColors.badgeNegativeBackgroundColor
val SemanticColors.badgeNegativeContentColor
get() = if (isLight) LightColorTokens.colorRed1100 else DarkColorTokens.colorRed1100
@OptIn(CoreColorToken::class)
val SemanticColors.pinnedMessageBannerIndicator
get() = if (isLight) LightColorTokens.colorAlphaGray600 else DarkColorTokens.colorAlphaGray600
@OptIn(CoreColorToken::class)
val SemanticColors.pinnedMessageBannerBorder
get() = if (isLight) LightColorTokens.colorAlphaGray400 else DarkColorTokens.colorAlphaGray400
@PreviewsDayNight
@Composable
internal fun ColorAliasesPreview() = ElementPreview {

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.designsystem.utils
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
/**
* Returns whether the lazy list is currently scrolling up.
*/
@Composable
fun LazyListState.isScrollingUp(): Boolean {
var previousIndex by remember(this) { mutableIntStateOf(firstVisibleItemIndex) }
var previousScrollOffset by remember(this) { mutableIntStateOf(firstVisibleItemScrollOffset) }
return remember(this) {
derivedStateOf {
if (previousIndex != firstVisibleItemIndex) {
previousIndex > firstVisibleItemIndex
} else {
previousScrollOffset >= firstVisibleItemScrollOffset
}.also {
previousIndex = firstVisibleItemIndex
previousScrollOffset = firstVisibleItemScrollOffset
}
}
}.value
}

View file

@ -22,7 +22,7 @@ android {
buildTypes {
release {
isMinifyEnabled = true
isMinifyEnabled = false
consumerProguardFiles("consumer-proguard-rules.pro")
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.eventformatter.api
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
interface PinnedMessagesBannerFormatter {
fun format(event: EventTimelineItem): CharSequence
}

View file

@ -0,0 +1,121 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.eventformatter.impl
import androidx.annotation.StringRes
import androidx.compose.ui.text.AnnotatedString
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.eventformatter.api.PinnedMessagesBannerFormatter
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.MessageType
import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
import io.element.android.libraries.matrix.ui.messages.toPlainText
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider
import javax.inject.Inject
@ContributesBinding(SessionScope::class)
class DefaultPinnedMessagesBannerFormatter @Inject constructor(
private val sp: StringProvider,
private val permalinkParser: PermalinkParser,
) : PinnedMessagesBannerFormatter {
override fun format(event: EventTimelineItem): CharSequence {
return when (val content = event.content) {
is MessageContent -> processMessageContents(event, content)
is StickerContent -> {
content.body.prefixWith(CommonStrings.common_sticker)
}
is UnableToDecryptContent -> {
sp.getString(CommonStrings.common_waiting_for_decryption_key)
}
is PollContent -> {
content.question.prefixWith(CommonStrings.a11y_poll)
}
RedactedContent -> {
sp.getString(CommonStrings.common_message_removed)
}
else -> {
sp.getString(CommonStrings.common_unsupported_event)
}
}
}
private fun processMessageContents(
event: EventTimelineItem,
messageContent: MessageContent,
): CharSequence {
return when (val messageType: MessageType = messageContent.type) {
is EmoteMessageType -> {
val senderDisambiguatedDisplayName = event.senderProfile.getDisambiguatedDisplayName(event.sender)
"* $senderDisambiguatedDisplayName ${messageType.body}"
}
is TextMessageType -> {
messageType.toPlainText(permalinkParser)
}
is VideoMessageType -> {
messageType.body.prefixWith(CommonStrings.common_video)
}
is ImageMessageType -> {
messageType.body.prefixWith(CommonStrings.common_image)
}
is StickerMessageType -> {
messageType.body.prefixWith(CommonStrings.common_sticker)
}
is LocationMessageType -> {
messageType.body.prefixWith(CommonStrings.common_shared_location)
}
is FileMessageType -> {
messageType.body.prefixWith(CommonStrings.common_file)
}
is AudioMessageType -> {
messageType.body.prefixWith(CommonStrings.common_audio)
}
is VoiceMessageType -> {
messageType.body.prefixWith(CommonStrings.common_voice_message)
}
is OtherMessageType -> {
messageType.body
}
is NoticeMessageType -> {
messageType.body
}
}
}
private fun CharSequence.prefixWith(@StringRes res: Int): AnnotatedString {
val prefix = sp.getString(res)
return prefixWith(prefix)
}
}

View file

@ -16,11 +16,6 @@
package io.element.android.libraries.eventformatter.impl
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
@ -79,7 +74,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
RedactedContent -> {
val message = sp.getString(CommonStrings.common_message_removed)
if (!isDmRoom) {
prefix(message, senderDisambiguatedDisplayName)
message.prefixWith(senderDisambiguatedDisplayName)
} else {
message
}
@ -90,7 +85,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
is UnableToDecryptContent -> {
val message = sp.getString(CommonStrings.common_waiting_for_decryption_key)
if (!isDmRoom) {
prefix(message, senderDisambiguatedDisplayName)
message.prefixWith(senderDisambiguatedDisplayName)
} else {
message
}
@ -113,7 +108,6 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
}
is LegacyCallInviteContent -> sp.getString(CommonStrings.common_call_invite)
is CallNotifyContent -> sp.getString(CommonStrings.common_call_started)
else -> null
}?.take(MAX_SAFE_LENGTH)
}
@ -168,16 +162,6 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
): CharSequence = if (isDmRoom) {
message
} else {
prefix(message, senderDisambiguatedDisplayName)
}
private fun prefix(message: String, senderDisambiguatedDisplayName: String): AnnotatedString {
return buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(senderDisambiguatedDisplayName)
}
append(": ")
append(message)
}
message.prefixWith(senderDisambiguatedDisplayName)
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.eventformatter.impl
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
internal fun CharSequence.prefixWith(prefix: String): AnnotatedString {
return buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(prefix)
}
append(": ")
append(this@prefixWith)
}
}

View file

@ -80,6 +80,15 @@ class StateContentFormatter @Inject constructor(
else -> sp.getString(R.string.state_event_room_topic_removed, senderDisambiguatedDisplayName)
}
}
is OtherState.RoomPinnedEvents -> when (renderingMode) {
RenderingMode.RoomList -> {
Timber.v("Filtering timeline item for room state change: $content")
null
}
RenderingMode.Timeline -> {
formatRoomPinnedEvents(content, senderIsYou, senderDisambiguatedDisplayName)
}
}
is OtherState.Custom -> when (renderingMode) {
RenderingMode.RoomList -> {
Timber.v("Filtering timeline item for room state change: $content")
@ -161,15 +170,6 @@ class StateContentFormatter @Inject constructor(
"RoomJoinRules"
}
}
OtherState.RoomPinnedEvents -> when (renderingMode) {
RenderingMode.RoomList -> {
Timber.v("Filtering timeline item for room state change: $content")
null
}
RenderingMode.Timeline -> {
"RoomPinnedEvents"
}
}
is OtherState.RoomUserPowerLevels -> when (renderingMode) {
RenderingMode.RoomList -> {
Timber.v("Filtering timeline item for room state change: $content")
@ -217,4 +217,23 @@ class StateContentFormatter @Inject constructor(
}
}
}
private fun formatRoomPinnedEvents(
content: OtherState.RoomPinnedEvents,
senderIsYou: Boolean,
senderDisambiguatedDisplayName: String
) = when (content.change) {
OtherState.RoomPinnedEvents.Change.ADDED -> when {
senderIsYou -> sp.getString(R.string.state_event_room_pinned_events_pinned_by_you)
else -> sp.getString(R.string.state_event_room_pinned_events_pinned, senderDisambiguatedDisplayName)
}
OtherState.RoomPinnedEvents.Change.REMOVED -> when {
senderIsYou -> sp.getString(R.string.state_event_room_pinned_events_unpinned_by_you)
else -> sp.getString(R.string.state_event_room_pinned_events_unpinned, senderDisambiguatedDisplayName)
}
OtherState.RoomPinnedEvents.Change.CHANGED -> when {
senderIsYou -> sp.getString(R.string.state_event_room_pinned_events_changed_by_you)
else -> sp.getString(R.string.state_event_room_pinned_events_changed, senderDisambiguatedDisplayName)
}
}
}

View file

@ -45,6 +45,12 @@
<string name="state_event_room_name_removed_by_you">"Вы выдалілі назву пакоя"</string>
<string name="state_event_room_none">"%1$s не зрабіў(-ла) ніякіх змен"</string>
<string name="state_event_room_none_by_you">"Вы не зрабілі ніякіх змен"</string>
<string name="state_event_room_pinned_events_changed">"%1$s змяніў(-ла) замацаваныя паведамленні"</string>
<string name="state_event_room_pinned_events_changed_by_you">"Вы змянілі замацаваныя паведамленні"</string>
<string name="state_event_room_pinned_events_pinned">"%1$s замацаваў(-ла) паведамленне"</string>
<string name="state_event_room_pinned_events_pinned_by_you">"Вы замацавалі паведамленне"</string>
<string name="state_event_room_pinned_events_unpinned">"%1$s адмацаваў(-ла) паведамленне"</string>
<string name="state_event_room_pinned_events_unpinned_by_you">"Вы адмацавалі паведамленне"</string>
<string name="state_event_room_reject">"%1$s адхіліў(-ла) запрашэнне"</string>
<string name="state_event_room_reject_by_you">"Вы адхілілі запрашэнне"</string>
<string name="state_event_room_remove">"%1$s выдаліў(-ла) %2$s"</string>

View file

@ -45,6 +45,12 @@
<string name="state_event_room_name_removed_by_you">"Sina eemaldasid jututoa nime"</string>
<string name="state_event_room_none">"%1$s ei teinud ühtegi muudatust"</string>
<string name="state_event_room_none_by_you">"Sina ei teinud ühtegi muudatust"</string>
<string name="state_event_room_pinned_events_changed">"%1$s muutis esiletõstetud sõnumeid"</string>
<string name="state_event_room_pinned_events_changed_by_you">"Sina muutsid esiletõstetud sõnumeid"</string>
<string name="state_event_room_pinned_events_pinned">"%1$s tõstis sõnumi esile"</string>
<string name="state_event_room_pinned_events_pinned_by_you">"Sina tõstsid sõnumi esile"</string>
<string name="state_event_room_pinned_events_unpinned">"%1$s eemaldas esiletõstetud sõnumi"</string>
<string name="state_event_room_pinned_events_unpinned_by_you">"Sina eemaldasid esiletõstetud sõnumi"</string>
<string name="state_event_room_reject">"%1$s lükkas kutse tagasi"</string>
<string name="state_event_room_reject_by_you">"Sina lükkasid kutse tagasi"</string>
<string name="state_event_room_remove">"%1$s eemaldas jututoast kasutaja %2$s"</string>

View file

@ -45,6 +45,12 @@
<string name="state_event_room_name_removed_by_you">"Eltávolította a szoba nevét"</string>
<string name="state_event_room_none">"%1$s nem változtatott semmin"</string>
<string name="state_event_room_none_by_you">"Nem változtatott semmin"</string>
<string name="state_event_room_pinned_events_changed">"%1$s megváltoztatta a kitűzött üzeneteket"</string>
<string name="state_event_room_pinned_events_changed_by_you">"Megváltoztatta a kitűzött üzeneteket"</string>
<string name="state_event_room_pinned_events_pinned">"%1$s kitűzött egy üzenetet"</string>
<string name="state_event_room_pinned_events_pinned_by_you">"Kitűzött egy üzenetet"</string>
<string name="state_event_room_pinned_events_unpinned">"%1$s feloldotta egy üzenet kitűzését"</string>
<string name="state_event_room_pinned_events_unpinned_by_you">"Feloldotta egy üzenet kitűzését"</string>
<string name="state_event_room_reject">"%1$s elutasította a meghívást"</string>
<string name="state_event_room_reject_by_you">"Elutasította a meghívást"</string>
<string name="state_event_room_remove">"%1$s eltávolította: %2$s"</string>

View file

@ -3,12 +3,16 @@
<string name="state_event_avatar_changed_too">"(zdjęcie profilowe też zostało zmienione)"</string>
<string name="state_event_avatar_url_changed">"%1$s zmienił swoje zdjęcie profilowe"</string>
<string name="state_event_avatar_url_changed_by_you">"Zmieniłeś swoje zdjęcie profilowe"</string>
<string name="state_event_demoted_to_member">"%1$s został zdegradowany do członka"</string>
<string name="state_event_demoted_to_moderator">"%1$s został zdegradowany do moderatora"</string>
<string name="state_event_display_name_changed_from">"%1$s zmienił swoją wyświetlaną nazwę z %2$s na %3$s"</string>
<string name="state_event_display_name_changed_from_by_you">"Zmieniłeś swoją wyświetlaną nazwę z %1$s na %2$s"</string>
<string name="state_event_display_name_removed">"%1$s usunął swoją wyświetlaną nazwę (byo to %2$s)"</string>
<string name="state_event_display_name_removed_by_you">"Usunąłeś swoją wyświetlaną nazwę (było to %1$s)"</string>
<string name="state_event_display_name_set">"%1$s ustawił swoją wyświetlaną nazwę na %2$s"</string>
<string name="state_event_display_name_set_by_you">"Ustawiłeś swoją wyświetlaną nazwę na %1$s"</string>
<string name="state_event_promoted_to_administrator">"%1$s został awansowany na administratora"</string>
<string name="state_event_promoted_to_moderator">"%1$s został awansowany na moderatora"</string>
<string name="state_event_room_avatar_changed">"%1$s zmienił zdjęcie profilowe pokoju"</string>
<string name="state_event_room_avatar_changed_by_you">"Zmieniłeś zdjęcie profilowe pokoju"</string>
<string name="state_event_room_avatar_removed">"%1$s usunął zdjęcie profilowe pokoju"</string>
@ -42,7 +46,7 @@
<string name="state_event_room_none">"%1$s nie wprowadził żadnych zmian"</string>
<string name="state_event_room_none_by_you">"Nie wprowadzono żadnych zmian"</string>
<string name="state_event_room_reject">"%1$s odrzucił zaproszenie"</string>
<string name="state_event_room_reject_by_you">"Odrzuciłeś(aś) zaproszenie"</string>
<string name="state_event_room_reject_by_you">"Odrzuciłeś zaproszenie"</string>
<string name="state_event_room_remove">"%1$s usunął %2$s"</string>
<string name="state_event_room_remove_by_you">"Usunąłeś %1$s"</string>
<string name="state_event_room_third_party_invite">"%1$s wysłał zaproszenie do %2$s, aby dołączył do pokoju"</string>

View file

@ -3,12 +3,16 @@
<string name="state_event_avatar_changed_too">"(o avatar também foi alterado)"</string>
<string name="state_event_avatar_url_changed">"%1$s mudou seu avatar"</string>
<string name="state_event_avatar_url_changed_by_you">"Você mudou seu avatar"</string>
<string name="state_event_demoted_to_member">"%1$s foi rebaixado a membro"</string>
<string name="state_event_demoted_to_moderator">"%1$s foi rebaixado a moderador"</string>
<string name="state_event_display_name_changed_from">"%1$s mudou seu nome de exibição de %2$s para %3$s"</string>
<string name="state_event_display_name_changed_from_by_you">"Você alterou seu nome de exibição de %1$s para %2$s"</string>
<string name="state_event_display_name_removed">"%1$s removeu seu nome de exibição (era %2$s)"</string>
<string name="state_event_display_name_removed_by_you">"Você removeu seu nome de exibição (era %1$s)"</string>
<string name="state_event_display_name_set">"%1$s definiu seu nome de exibição como %2$s"</string>
<string name="state_event_display_name_set_by_you">"Você definiu seu nome de exibição como %1$s"</string>
<string name="state_event_promoted_to_administrator">"%1$s foi promovido a administrador"</string>
<string name="state_event_promoted_to_moderator">"%1$s foi promovido a moderador"</string>
<string name="state_event_room_avatar_changed">"%1$s mudou o avatar da sala"</string>
<string name="state_event_room_avatar_changed_by_you">"Você mudou o avatar da sala"</string>
<string name="state_event_room_avatar_removed">"%1$s removeu o avatar da sala"</string>
@ -39,6 +43,8 @@
<string name="state_event_room_name_changed_by_you">"Você mudou o nome da sala para: %1$s"</string>
<string name="state_event_room_name_removed">"%1$s removeu o nome da sala"</string>
<string name="state_event_room_name_removed_by_you">"Você removeu o nome da sala"</string>
<string name="state_event_room_none">"%1$s não fez alterações"</string>
<string name="state_event_room_none_by_you">"Você não fez nenhuma alteração"</string>
<string name="state_event_room_reject">"%1$s rejeitou o convite"</string>
<string name="state_event_room_reject_by_you">"Você rejeitou o convite"</string>
<string name="state_event_room_remove">"%1$s removido %2$s"</string>

View file

@ -45,6 +45,12 @@
<string name="state_event_room_name_removed_by_you">"Removeste o nome da sala"</string>
<string name="state_event_room_none">"%1$s não fiz nenhuma alteração"</string>
<string name="state_event_room_none_by_you">"Não fizeste nenhuma alteração"</string>
<string name="state_event_room_pinned_events_changed">"%1$s alterou as mensagens afixadas"</string>
<string name="state_event_room_pinned_events_changed_by_you">"Alteraste as mensagens afixadas"</string>
<string name="state_event_room_pinned_events_pinned">"%1$s afixou uma mensagem"</string>
<string name="state_event_room_pinned_events_pinned_by_you">"Afixaste uma mensagem"</string>
<string name="state_event_room_pinned_events_unpinned">"%1$s desafixou uma mensagem"</string>
<string name="state_event_room_pinned_events_unpinned_by_you">"Desafixaste uma mensagem"</string>
<string name="state_event_room_reject">"%1$s rejeitou o convite"</string>
<string name="state_event_room_reject_by_you">"Rejeitaste o convite"</string>
<string name="state_event_room_remove">"%1$s removeu %2$s"</string>

View file

@ -45,6 +45,12 @@
<string name="state_event_room_name_removed_by_you">"Вы удалили название комнаты"</string>
<string name="state_event_room_none">"%1$s ничего не изменил"</string>
<string name="state_event_room_none_by_you">"Вы не внесли никаких изменений"</string>
<string name="state_event_room_pinned_events_changed">"%1$s изменил закрепленные сообщения"</string>
<string name="state_event_room_pinned_events_changed_by_you">"Вы изменили закрепленные сообщения"</string>
<string name="state_event_room_pinned_events_pinned">"%1$s закрепил сообщение"</string>
<string name="state_event_room_pinned_events_pinned_by_you">"Вы закрепили сообщение"</string>
<string name="state_event_room_pinned_events_unpinned">"%1$s открепил сообщение"</string>
<string name="state_event_room_pinned_events_unpinned_by_you">"Вы открепили сообщение"</string>
<string name="state_event_room_reject">"%1$s отклонил приглашение"</string>
<string name="state_event_room_reject_by_you">"Вы отклонили приглашение"</string>
<string name="state_event_room_remove">"%1$s удалил %2$s"</string>

View file

@ -45,6 +45,12 @@
<string name="state_event_room_name_removed_by_you">"Odstránili ste názov miestnosti"</string>
<string name="state_event_room_none">"%1$s nevykonal/a žiadne zmeny"</string>
<string name="state_event_room_none_by_you">"Nevykonali ste žiadne zmeny"</string>
<string name="state_event_room_pinned_events_changed">"%1$s zmenil/a pripnuté správy"</string>
<string name="state_event_room_pinned_events_changed_by_you">"Zmenili ste pripnuté správy"</string>
<string name="state_event_room_pinned_events_pinned">"%1$s pripol/la správu"</string>
<string name="state_event_room_pinned_events_pinned_by_you">"Pripli ste správu"</string>
<string name="state_event_room_pinned_events_unpinned">"%1$s zrušil/a pripnutie správy"</string>
<string name="state_event_room_pinned_events_unpinned_by_you">"Zrušili ste pripnutie správy"</string>
<string name="state_event_room_reject">"%1$s odmietol/a pozvánku"</string>
<string name="state_event_room_reject_by_you">"Odmietli ste pozvánku"</string>
<string name="state_event_room_remove">"%1$s odstránil/a %2$s"</string>

View file

@ -45,6 +45,12 @@
<string name="state_event_room_name_removed_by_you">"Du tog bort rummets namn"</string>
<string name="state_event_room_none">"%1$s gjorde inga ändringar"</string>
<string name="state_event_room_none_by_you">"Du gjorde inga ändringar"</string>
<string name="state_event_room_pinned_events_changed">"%1$s ändrade de fästa meddelandena"</string>
<string name="state_event_room_pinned_events_changed_by_you">"Du ändrade de fästa meddelandena"</string>
<string name="state_event_room_pinned_events_pinned">"%1$s fäste ett meddelande"</string>
<string name="state_event_room_pinned_events_pinned_by_you">"Du har fäste ett meddelande"</string>
<string name="state_event_room_pinned_events_unpinned">"%1$s lossade ett meddelande"</string>
<string name="state_event_room_pinned_events_unpinned_by_you">"Du har lossade ett meddelande"</string>
<string name="state_event_room_reject">"%1$s avvisade inbjudan"</string>
<string name="state_event_room_reject_by_you">"Du avvisade inbjudan"</string>
<string name="state_event_room_remove">"%1$s tog bort %2$s"</string>

View file

@ -30,7 +30,7 @@
<string name="state_event_room_join_by_you">"Ви приєдналися до кімнати"</string>
<string name="state_event_room_knock">"%1$s подав (-ла) запит на приєднання"</string>
<string name="state_event_room_knock_accepted">"%1$s дозволив (-ла) %2$s приєднатися"</string>
<string name="state_event_room_knock_accepted_by_you">"%1$s дозволив (-ла) Вам приєднатися"</string>
<string name="state_event_room_knock_accepted_by_you">"Ви дозволили %1$s приєднатися"</string>
<string name="state_event_room_knock_by_you">"Ви подали запит на приєднання"</string>
<string name="state_event_room_knock_denied">"%1$s відхилив (-ла) запит %2$s на приєднання"</string>
<string name="state_event_room_knock_denied_by_you">"Ви відхилили запит %1$s на приєднання"</string>
@ -45,6 +45,12 @@
<string name="state_event_room_name_removed_by_you">"Ви видалили назву кімнати"</string>
<string name="state_event_room_none">"%1$s не внесено жодних змін"</string>
<string name="state_event_room_none_by_you">"Ви не внесли жодних змін"</string>
<string name="state_event_room_pinned_events_changed">"%1$s змінив(-ла) закріплені повідомлення"</string>
<string name="state_event_room_pinned_events_changed_by_you">"Ви змінили закріплені повідомлення"</string>
<string name="state_event_room_pinned_events_pinned">"%1$s закріпив(-ла) повідомлення"</string>
<string name="state_event_room_pinned_events_pinned_by_you">"Ви закріпили повідомлення"</string>
<string name="state_event_room_pinned_events_unpinned">"%1$s відкріпив(-ла) повідомлення"</string>
<string name="state_event_room_pinned_events_unpinned_by_you">"Ви відкріпили повідомлення"</string>
<string name="state_event_room_reject">"%1$s відхилив (-ла) запрошення"</string>
<string name="state_event_room_reject_by_you">"Ви відхилили запрошення"</string>
<string name="state_event_room_remove">"%1$s вилучив (-ла) %2$s"</string>

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="state_event_avatar_changed_too">"(avatar ham o\'zgartirildi)"</string>
<string name="state_event_avatar_url_changed">"%1$s avatarini o\'zgartirdi"</string>
<string name="state_event_avatar_url_changed_by_you">"Siz avataringizni o\'zgartirdingiz"</string>
<string name="state_event_display_name_changed_from">"%1$s ko\'rsatiladigan nomini %2$sdan %3$sga o\'zgartirdi"</string>
<string name="state_event_display_name_changed_from_by_you">"Siz ko\'rsatiladigan nomingizni %1$s dan %2$s ga o\'zgartirdingiz"</string>
<string name="state_event_display_name_removed">"%1$s ko\'rinadigan nomini o\'chirib tashladi (avval %2$s bo\'lgan edi)"</string>
<string name="state_event_display_name_removed_by_you">"Siz ko\'rinadigan nomingizni o\'chirib tashladingiz (avval %1$s bo\'lgan edi)"</string>
<string name="state_event_display_name_set">"%1$s ularning ko\'rsatiladigan nomini o\'rnating %2$s"</string>
<string name="state_event_display_name_set_by_you">"Siz ko\'rsatiladigan nomingizni o\'rnating %1$s"</string>
<string name="state_event_room_avatar_changed">"%1$s xonani avatarini o\'zgartirdi"</string>
<string name="state_event_room_avatar_changed_by_you">"Siz xonani avatarini o\'zgartirdingiz"</string>
<string name="state_event_room_avatar_removed">"%1$s xonani avatarini o\'chirib tashladi"</string>
<string name="state_event_room_avatar_removed_by_you">"Siz xonani avatarini o\'chirib tashladingiz"</string>
<string name="state_event_room_ban">"%1$staqiqlangan%2$s"</string>
<string name="state_event_room_ban_by_you">"Siz taqiqlangansiz%1$s"</string>
<string name="state_event_room_created">"%1$sxonani yaratdi"</string>
<string name="state_event_room_created_by_you">"Siz xonani yaratdingiz"</string>
<string name="state_event_room_invite">"%1$staklif qilingan%2$s"</string>
<string name="state_event_room_invite_accepted">"%1$staklifni qabul qildi"</string>
<string name="state_event_room_invite_accepted_by_you">"Siz taklifni qabul qildingiz"</string>
<string name="state_event_room_invite_by_you">"Siz taklif qildingiz%1$s"</string>
<string name="state_event_room_invite_you">"%1$ssizni taklif qildi"</string>
<string name="state_event_room_join">"%1$sxonaga qo\'shildi"</string>
<string name="state_event_room_join_by_you">"Siz xonaga qo\'shildingiz"</string>
<string name="state_event_room_knock">"%1$s qo\'shilishni so\'radi"</string>
<string name="state_event_room_knock_accepted">"%1$s %2$sga qo\'shilishga ruxsat berdi"</string>
<string name="state_event_room_knock_accepted_by_you">"Siz %1$sga qo\'shilishaga ruxsat berdingiz"</string>
<string name="state_event_room_knock_by_you">"Siz qoʻshilishni soʻragansiz"</string>
<string name="state_event_room_knock_denied">"%1$s %2$sning qo\'shilish haqidagi iltimosini rad etdi"</string>
<string name="state_event_room_knock_denied_by_you">"Siz %1$sning qo\'shiliz iltimosini rad etdingiz"</string>
<string name="state_event_room_knock_denied_you">"%1$s sizni qo\'shilish iltimosingizni rad etdi"</string>
<string name="state_event_room_knock_retracted">"%1$ endi qo\'shilishdan manfaatdor emas"</string>
<string name="state_event_room_knock_retracted_by_you">"Siz qoʻshilish soʻrovingizni bekor qildingiz"</string>
<string name="state_event_room_leave">"%1$sxonani tark etdi"</string>
<string name="state_event_room_leave_by_you">"Siz xonani tark etdingiz"</string>
<string name="state_event_room_name_changed">"%1$s xonani nomini %2$s o\'zgartirdi"</string>
<string name="state_event_room_name_changed_by_you">"Siz xonani nomini %1$s ga o\'zgartirdingiz"</string>
<string name="state_event_room_name_removed">"%1$s xonani nomini o\'chirib tashladi"</string>
<string name="state_event_room_name_removed_by_you">"Siz xonani nomini o\'chirib tashladingiz"</string>
<string name="state_event_room_reject">"%1$staklifni rad etdi"</string>
<string name="state_event_room_reject_by_you">"Siz taklifni rad etdingiz"</string>
<string name="state_event_room_remove">"%1$o\'chirildi%2$s"</string>
<string name="state_event_room_remove_by_you">"siz o\'chirildingiz%1$s"</string>
<string name="state_event_room_third_party_invite">"%1$s taklifnoma yubordi %2$sga xonaga qo\'shilish uchun"</string>
<string name="state_event_room_third_party_invite_by_you">"Siz taklifnoma yubordingiz %1$sga xonaga qo\'shilishi uchun"</string>
<string name="state_event_room_third_party_revoked_invite">"%1$s taklifni %2$sga xonaga qo\'shilish uchun bekor qildi"</string>
<string name="state_event_room_third_party_revoked_invite_by_you">"Siz xonaga qo\'shilish taklifini $1$s ga bekor qildingiz"</string>
<string name="state_event_room_topic_changed">"%1$s mavzuni %2$s o\'zgartirdi"</string>
<string name="state_event_room_topic_changed_by_you">"Siz mavzuni %1$s ga o\'zgartirdingiz"</string>
<string name="state_event_room_topic_removed">"%1$s xonani mavzusini o\'chirib tashladi"</string>
<string name="state_event_room_topic_removed_by_you">"Siz xonani mavzusini o\'chirib tashladingiz"</string>
<string name="state_event_room_unban">"%1$staqiqlanmagan%2$s"</string>
<string name="state_event_room_unban_by_you">"Siz %1$s taqiqini bekor qildingiz"</string>
<string name="state_event_room_unknown_membership_change">"%1$s aʼzoligiga nomaʼlum oʻzgarishlar kiritdi"</string>
</resources>

View file

@ -45,6 +45,12 @@
<string name="state_event_room_name_removed_by_you">"You removed the room name"</string>
<string name="state_event_room_none">"%1$s made no changes"</string>
<string name="state_event_room_none_by_you">"You made no changes"</string>
<string name="state_event_room_pinned_events_changed">"%1$s changed the pinned messages"</string>
<string name="state_event_room_pinned_events_changed_by_you">"You changed the pinned messages"</string>
<string name="state_event_room_pinned_events_pinned">"%1$s pinned a message"</string>
<string name="state_event_room_pinned_events_pinned_by_you">"You pinned a message"</string>
<string name="state_event_room_pinned_events_unpinned">"%1$s unpinned a message"</string>
<string name="state_event_room_pinned_events_unpinned_by_you">"You unpinned a message"</string>
<string name="state_event_room_reject">"%1$s rejected the invitation"</string>
<string name="state_event_room_reject_by_you">"You rejected the invitation"</string>
<string name="state_event_room_remove">"%1$s removed %2$s"</string>

View file

@ -660,7 +660,7 @@ class DefaultRoomLastMessageFormatterTest {
OtherState.RoomGuestAccess,
OtherState.RoomHistoryVisibility,
OtherState.RoomJoinRules,
OtherState.RoomPinnedEvents,
OtherState.RoomPinnedEvents(OtherState.RoomPinnedEvents.Change.CHANGED),
OtherState.RoomUserPowerLevels(emptyMap()),
OtherState.RoomServerAcl,
OtherState.RoomTombstone,

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.eventformatter.test
import io.element.android.libraries.eventformatter.api.PinnedMessagesBannerFormatter
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
class FakePinnedMessagesBannerFormatter(
val formatLambda: (event: EventTimelineItem) -> CharSequence
) : PinnedMessagesBannerFormatter {
override fun format(event: EventTimelineItem): CharSequence {
return formatLambda(event)
}
}

View file

@ -120,4 +120,18 @@ enum class FeatureFlags(
defaultValue = { it.buildType != BuildType.RELEASE },
isFinished = false,
),
PinnedEvents(
key = "feature.pinnedEvents",
title = "Pinned Events",
description = "Allow user to pin events in a room",
defaultValue = { false },
isFinished = false,
),
SyncOnPush(
key = "feature.syncOnPush",
title = "Sync on push",
description = "Subscribe to room sync when a push is received",
defaultValue = { true },
isFinished = false,
),
}

View file

@ -106,6 +106,11 @@ interface MatrixRoom : Closeable {
*/
suspend fun timelineFocusedOnEvent(eventId: EventId): Result<Timeline>
/**
* Create a new timeline for the pinned events of the room.
*/
suspend fun pinnedEventsTimeline(): Result<Timeline>
fun destroy()
suspend fun subscribeToSync()
@ -180,6 +185,8 @@ interface MatrixRoom : Closeable {
suspend fun canUserTriggerRoomNotification(userId: UserId): Result<Boolean>
suspend fun canUserPinUnpin(userId: UserId): Result<Boolean>
suspend fun canUserJoinCall(userId: UserId): Result<Boolean> =
canUserSendState(userId, StateEventType.CALL_MEMBER)

View file

@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.api.room
import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
@ -52,4 +53,5 @@ data class MatrixRoomInfo(
val hasRoomCall: Boolean,
val activeRoomCallParticipants: ImmutableList<String>,
val heroes: ImmutableList<MatrixUser>,
val pinnedEventIds: ImmutableList<EventId>
)

View file

@ -65,3 +65,8 @@ suspend fun MatrixRoom.canRedactOwn(): Result<Boolean> = canUserRedactOwn(sessio
* Shortcut for calling [MatrixRoom.canRedactOther] with our own user.
*/
suspend fun MatrixRoom.canRedactOther(): Result<Boolean> = canUserRedactOther(sessionId)
/**
* Shortcut for calling [MatrixRoom.canUserPinUnpin] with our own user.
*/
suspend fun MatrixRoom.canPinUnpin(): Result<Boolean> = canUserPinUnpin(sessionId)

View file

@ -169,4 +169,22 @@ interface Timeline : AutoCloseable {
): Result<MediaUploadHandler>
suspend fun loadReplyDetails(eventId: EventId): InReplyTo
/**
* Adds a new pinned event by sending an updated `m.room.pinned_events`
* event containing the new event id.
*
* Returns `true` if we sent the request, `false` if the event was already
* pinned.
*/
suspend fun pinEvent(eventId: EventId): Result<Boolean>
/**
* Adds a new pinned event by sending an updated `m.room.pinned_events`
* event without the event id we want to remove.
*
* Returns `true` if we sent the request, `false` if the event wasn't
* pinned
*/
suspend fun unpinEvent(eventId: EventId): Result<Boolean>
}

View file

@ -26,6 +26,7 @@ data class EventTimelineItem(
val eventId: EventId?,
val transactionId: TransactionId?,
val isEditable: Boolean,
val canBeRepliedTo: Boolean,
val isLocal: Boolean,
val isOwn: Boolean,
val isRemote: Boolean,

View file

@ -32,7 +32,14 @@ sealed interface OtherState {
data object RoomHistoryVisibility : OtherState
data object RoomJoinRules : OtherState
data class RoomName(val name: String?) : OtherState
data object RoomPinnedEvents : OtherState
data class RoomPinnedEvents(val change: Change) : OtherState {
enum class Change {
ADDED,
REMOVED,
CHANGED
}
}
data class RoomUserPowerLevels(val users: Map<String, Long>) : OtherState
data object RoomServerAcl : OtherState
data class RoomThirdPartyInvite(val displayName: String?) : OtherState

View file

@ -37,12 +37,13 @@ dependencies {
debugImplementation(libs.matrix.sdk)
}
implementation(projects.appconfig)
implementation(projects.libraries.di)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.di)
implementation(projects.libraries.featureflag.api)
implementation(projects.libraries.network)
implementation(projects.libraries.preferences.api)
implementation(projects.services.analytics.api)
implementation(projects.services.toolbox.api)
implementation(projects.libraries.featureflag.api)
api(projects.libraries.matrix.api)
implementation(libs.dagger)
implementation(projects.libraries.core)

View file

@ -23,10 +23,12 @@ import io.element.android.libraries.matrix.impl.certificates.UserCertificatesPro
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
import io.element.android.libraries.matrix.impl.util.anonymizedTokens
import io.element.android.libraries.network.useragent.UserAgentProvider
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.sessionstorage.api.SessionData
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.ClientBuilder
import org.matrix.rustcomponents.sdk.Session
@ -46,9 +48,18 @@ class RustMatrixClientFactory @Inject constructor(
private val proxyProvider: ProxyProvider,
private val clock: SystemClock,
private val utdTracker: UtdTracker,
private val appPreferencesStore: AppPreferencesStore,
) {
suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) {
val client = getBaseClientBuilder(sessionData.sessionPath, sessionData.passphrase)
val client = getBaseClientBuilder(
sessionPath = sessionData.sessionPath,
passphrase = sessionData.passphrase,
slidingSync = if (appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first()) {
ClientBuilderSlidingSync.Simplified
} else {
ClientBuilderSlidingSync.Restored
},
)
.homeserverUrl(sessionData.homeserverUrl)
.username(sessionData.userId)
.use { it.build() }
@ -79,6 +90,7 @@ class RustMatrixClientFactory @Inject constructor(
sessionPath: String,
passphrase: String?,
slidingSyncProxy: String? = null,
slidingSync: ClientBuilderSlidingSync,
): ClientBuilder {
return ClientBuilder()
.sessionPath(sessionPath)
@ -88,6 +100,13 @@ class RustMatrixClientFactory @Inject constructor(
.addRootCertificates(userCertificatesProvider.provides())
.autoEnableBackups(true)
.autoEnableCrossSigning(true)
.run {
when (slidingSync) {
ClientBuilderSlidingSync.Restored -> this
ClientBuilderSlidingSync.Discovered -> requiresSlidingSync()
ClientBuilderSlidingSync.Simplified -> simplifiedSlidingSync(true)
}
}
.run {
// Workaround for non-nullable proxy parameter in the SDK, since each call to the ClientBuilder returns a new reference we need to keep
proxyProvider.provides()?.let { proxy(it) } ?: this
@ -95,6 +114,17 @@ class RustMatrixClientFactory @Inject constructor(
}
}
enum class ClientBuilderSlidingSync {
// The proxy will be supplied when restoring the Session.
Restored,
// A proxy must be discovered whilst building the session.
Discovered,
// Use Simplified Sliding Sync (discovery isn't a thing yet).
Simplified,
}
private fun SessionData.toSession() = Session(
accessToken = accessToken,
refreshToken = refreshToken,

View file

@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.auth.OidcDetails
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.impl.ClientBuilderSlidingSync
import io.element.android.libraries.matrix.impl.RustMatrixClientFactory
import io.element.android.libraries.matrix.impl.auth.qrlogin.QrErrorMapper
import io.element.android.libraries.matrix.impl.auth.qrlogin.SdkQrCodeLoginData
@ -59,7 +60,7 @@ import javax.inject.Inject
@ContributesBinding(AppScope::class)
@SingleIn(AppScope::class)
class RustMatrixAuthenticationService @Inject constructor(
baseDirectory: File,
private val baseDirectory: File,
private val coroutineDispatchers: CoroutineDispatchers,
private val sessionStore: SessionStore,
private val rustMatrixClientFactory: RustMatrixClientFactory,
@ -69,10 +70,19 @@ class RustMatrixAuthenticationService @Inject constructor(
// Passphrase which will be used for new sessions. Existing sessions will use the passphrase
// stored in the SessionData.
private val pendingPassphrase = getDatabasePassphrase()
private val sessionPath = File(baseDirectory, UUID.randomUUID().toString()).absolutePath
// Need to keep a copy of the current session path to eventually delete it.
// Ideally it would be possible to get the sessionPath from the Client to avoid doing this.
private var sessionPath: File? = null
private var currentClient: Client? = null
private var currentHomeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
private fun rotateSessionPath(): File {
sessionPath?.deleteRecursively()
return File(baseDirectory, UUID.randomUUID().toString())
.also { sessionPath = it }
}
override fun loggedInStateFlow(): Flow<LoggedInState> {
return sessionStore.isLoggedIn()
}
@ -116,8 +126,9 @@ class RustMatrixAuthenticationService @Inject constructor(
override suspend fun setHomeserver(homeserver: String): Result<Unit> =
withContext(coroutineDispatchers.io) {
val emptySessionPath = rotateSessionPath()
runCatching {
val client = getBaseClientBuilder()
val client = getBaseClientBuilder(emptySessionPath)
.serverNameOrHomeserverUrl(homeserver)
.build()
currentClient = client
@ -134,13 +145,14 @@ class RustMatrixAuthenticationService @Inject constructor(
withContext(coroutineDispatchers.io) {
runCatching {
val client = currentClient ?: error("You need to call `setHomeserver()` first")
val currentSessionPath = sessionPath ?: error("You need to call `setHomeserver()` first")
client.login(username, password, "Element X Android", null)
val sessionData = client.session()
.toSessionData(
isTokenValid = true,
loginType = LoginType.PASSWORD,
passphrase = pendingPassphrase,
sessionPath = sessionPath,
sessionPath = currentSessionPath.absolutePath,
)
clear()
sessionStore.storeData(sessionData)
@ -184,13 +196,14 @@ class RustMatrixAuthenticationService @Inject constructor(
return withContext(coroutineDispatchers.io) {
runCatching {
val client = currentClient ?: error("You need to call `setHomeserver()` first")
val currentSessionPath = sessionPath ?: error("You need to call `setHomeserver()` first")
val urlForOidcLogin = pendingOidcAuthorizationData ?: error("You need to call `getOidcUrl()` first")
client.loginWithOidcCallback(urlForOidcLogin, callbackUrl)
val sessionData = client.session().toSessionData(
isTokenValid = true,
loginType = LoginType.OIDC,
passphrase = pendingPassphrase,
sessionPath = sessionPath,
sessionPath = currentSessionPath.absolutePath,
)
clear()
pendingOidcAuthorizationData?.close()
@ -205,11 +218,13 @@ class RustMatrixAuthenticationService @Inject constructor(
override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) =
withContext(coroutineDispatchers.io) {
val emptySessionPath = rotateSessionPath()
runCatching {
val client = rustMatrixClientFactory.getBaseClientBuilder(
sessionPath = sessionPath,
sessionPath = emptySessionPath.absolutePath,
passphrase = pendingPassphrase,
slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL,
slidingSync = ClientBuilderSlidingSync.Discovered,
)
.buildWithQrCode(
qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData,
@ -227,7 +242,7 @@ class RustMatrixAuthenticationService @Inject constructor(
isTokenValid = true,
loginType = LoginType.QR,
passphrase = pendingPassphrase,
sessionPath = sessionPath,
sessionPath = emptySessionPath.absolutePath,
)
sessionStore.storeData(sessionData)
SessionId(sessionData.userId)
@ -244,15 +259,17 @@ class RustMatrixAuthenticationService @Inject constructor(
}
Timber.e(throwable, "Failed to login with QR code")
}
}
}
private fun getBaseClientBuilder() = rustMatrixClientFactory
private fun getBaseClientBuilder(
sessionPath: File,
) = rustMatrixClientFactory
.getBaseClientBuilder(
sessionPath = sessionPath,
sessionPath = sessionPath.absolutePath,
passphrase = pendingPassphrase,
slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL,
slidingSync = ClientBuilderSlidingSync.Discovered,
)
.requiresSlidingSync()
private fun clear() {
currentClient?.close()

View file

@ -16,6 +16,7 @@
package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
@ -58,7 +59,8 @@ class MatrixRoomInfoMapper {
userDefinedNotificationMode = it.userDefinedNotificationMode?.map(),
hasRoomCall = it.hasRoomCall,
activeRoomCallParticipants = it.activeRoomCallParticipants.toImmutableList(),
heroes = it.elementHeroes().toImmutableList()
heroes = it.elementHeroes().toImmutableList(),
pinnedEventIds = it.pinnedEventIds.map(::EventId).toImmutableList(),
)
}
}

View file

@ -51,23 +51,15 @@ class RoomSyncSubscriber(
includeHeroes = false,
)
suspend fun subscribe(roomId: RoomId) = mutex.withLock {
withContext(dispatchers.io) {
try {
subscribeToRoom(roomId)
} catch (exception: Exception) {
Timber.e("Failed to subscribe to room $roomId")
}
}
}
suspend fun batchSubscribe(roomIds: List<RoomId>) = mutex.withLock {
withContext(dispatchers.io) {
for (roomId in roomIds) {
suspend fun subscribe(roomId: RoomId) {
mutex.withLock {
withContext(dispatchers.io) {
try {
subscribeToRoom(roomId)
} catch (cancellationException: CancellationException) {
throw cancellationException
if (!isSubscribedTo(roomId)) {
Timber.d("Subscribing to room $roomId}")
roomListService.subscribeToRooms(listOf(roomId.value), settings)
}
subscribedRoomIds.add(roomId)
} catch (exception: Exception) {
Timber.e("Failed to subscribe to room $roomId")
}
@ -75,14 +67,21 @@ class RoomSyncSubscriber(
}
}
private fun subscribeToRoom(roomId: RoomId) {
if (!isSubscribedTo(roomId)) {
Timber.d("Subscribing to room $roomId}")
roomListService.room(roomId.value).use { roomListItem ->
roomListItem.subscribe(settings)
suspend fun batchSubscribe(roomIds: List<RoomId>) = mutex.withLock {
withContext(dispatchers.io) {
try {
val roomIdsToSubscribeTo = roomIds.filterNot { isSubscribedTo(it) }
if (roomIdsToSubscribeTo.isNotEmpty()) {
Timber.d("Subscribing to rooms: $roomIds")
roomListService.subscribeToRooms(roomIdsToSubscribeTo.map { it.value }, settings)
subscribedRoomIds.addAll(roomIds)
}
} catch (cancellationException: CancellationException) {
throw cancellationException
} catch (exception: Exception) {
Timber.e(exception, "Failed to subscribe to rooms: $roomIds")
}
}
subscribedRoomIds.add(roomId)
}
fun isSubscribedTo(roomId: RoomId): Boolean {

View file

@ -192,6 +192,21 @@ class RustMatrixRoom(
}
}
override suspend fun pinnedEventsTimeline(): Result<Timeline> {
return runCatching {
innerRoom.pinnedEventsTimeline(
internalIdPrefix = "pinned_events",
maxEventsToLoad = 100u,
).let { inner ->
createTimeline(inner, isLive = false)
}
}.onFailure {
if (it is CancellationException) {
throw it
}
}
}
override fun destroy() {
roomCoroutineScope.cancel()
liveTimeline.close()
@ -403,6 +418,12 @@ class RustMatrixRoom(
}
}
override suspend fun canUserPinUnpin(userId: UserId): Result<Boolean> {
return runCatching {
innerRoom.canUserPinUnpin(userId.value)
}
}
override suspend fun sendImage(
file: File,
thumbnailFile: File?,

View file

@ -525,6 +525,18 @@ class RustTimeline(
}
}
override suspend fun pinEvent(eventId: EventId): Result<Boolean> = withContext(dispatcher) {
runCatching {
inner.pinEvent(eventId = eventId.value)
}
}
override suspend fun unpinEvent(eventId: EventId): Result<Boolean> = withContext(dispatcher) {
runCatching {
inner.unpinEvent(eventId = eventId.value)
}
}
private suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> {
return runCatching {
inner.fetchDetailsForEvent(eventId.value)

View file

@ -47,6 +47,7 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap
eventId = it.eventId()?.let(::EventId),
transactionId = it.transactionId()?.let(::TransactionId),
isEditable = it.isEditable(),
canBeRepliedTo = it.canBeRepliedTo(),
isLocal = it.isLocal(),
isOwn = it.isOwn(),
isRemote = it.isRemote(),

View file

@ -40,6 +40,7 @@ import kotlinx.collections.immutable.toImmutableMap
import org.matrix.rustcomponents.sdk.TimelineItemContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
import org.matrix.rustcomponents.sdk.use
import uniffi.matrix_sdk_ui.RoomPinnedEventsChange
import org.matrix.rustcomponents.sdk.EncryptedMessage as RustEncryptedMessage
import org.matrix.rustcomponents.sdk.MembershipChange as RustMembershipChange
import org.matrix.rustcomponents.sdk.OtherState as RustOtherState
@ -176,7 +177,7 @@ private fun RustOtherState.map(): OtherState {
RustOtherState.RoomHistoryVisibility -> OtherState.RoomHistoryVisibility
RustOtherState.RoomJoinRules -> OtherState.RoomJoinRules
is RustOtherState.RoomName -> OtherState.RoomName(name)
RustOtherState.RoomPinnedEvents -> OtherState.RoomPinnedEvents
is RustOtherState.RoomPinnedEvents -> OtherState.RoomPinnedEvents(change.map())
is RustOtherState.RoomPowerLevels -> OtherState.RoomUserPowerLevels(users)
RustOtherState.RoomServerAcl -> OtherState.RoomServerAcl
is RustOtherState.RoomThirdPartyInvite -> OtherState.RoomThirdPartyInvite(displayName)
@ -187,6 +188,14 @@ private fun RustOtherState.map(): OtherState {
}
}
private fun RoomPinnedEventsChange.map(): OtherState.RoomPinnedEvents.Change {
return when (this) {
RoomPinnedEventsChange.ADDED -> OtherState.RoomPinnedEvents.Change.ADDED
RoomPinnedEventsChange.REMOVED -> OtherState.RoomPinnedEvents.Change.REMOVED
RoomPinnedEventsChange.CHANGED -> OtherState.RoomPinnedEvents.Change.CHANGED
}
}
private fun RustEncryptedMessage.map(): UnableToDecryptContent.Data {
return when (this) {
is RustEncryptedMessage.MegolmV1AesSha2 -> UnableToDecryptContent.Data.MegolmV1AesSha2(sessionId, cause.map())

View file

@ -189,6 +189,8 @@ class RoomSummaryListProcessorTest {
override fun syncIndicator(delayBeforeShowingInMs: UInt, delayBeforeHidingInMs: UInt, listener: RoomListServiceSyncIndicatorListener): TaskHandle {
return TaskHandle(Pointer.NULL)
}
override fun subscribeToRooms(roomIds: List<String>, settings: RoomSubscription?) = Unit
}
}
@ -221,6 +223,7 @@ private fun aRustRoomInfo(
numUnreadMessages: ULong = 0uL,
numUnreadNotifications: ULong = 0uL,
numUnreadMentions: ULong = 0uL,
pinnedEventIds: List<String> = listOf(),
) = RoomInfo(
id = id,
displayName = displayName,
@ -249,7 +252,8 @@ private fun aRustRoomInfo(
isMarkedUnread = isMarkedUnread,
numUnreadMessages = numUnreadMessages,
numUnreadNotifications = numUnreadNotifications,
numUnreadMentions = numUnreadMentions
numUnreadMentions = numUnreadMentions,
pinnedEventIds = pinnedEventIds,
)
class FakeRoomListItem(
@ -268,6 +272,4 @@ class FakeRoomListItem(
override suspend fun latestEvent(): EventTimelineItem? {
return latestEvent
}
override fun subscribe(settings: RoomSubscription?) = Unit
}

View file

@ -125,6 +125,7 @@ class FakeMatrixRoom(
private val getWidgetDriverResult: (MatrixWidgetSettings) -> Result<MatrixWidgetDriver> = { lambdaError() },
private val canUserTriggerRoomNotificationResult: (UserId) -> Result<Boolean> = { lambdaError() },
private val canUserJoinCallResult: (UserId) -> Result<Boolean> = { lambdaError() },
private val canUserPinUnpinResult: (UserId) -> Result<Boolean> = { lambdaError() },
private val setIsFavoriteResult: (Boolean) -> Result<Unit> = { lambdaError() },
private val powerLevelsResult: () -> Result<MatrixRoomPowerLevels> = { lambdaError() },
private val updatePowerLevelsResult: () -> Result<Unit> = { lambdaError() },
@ -134,10 +135,12 @@ class FakeMatrixRoom(
private val updateMembersResult: () -> Unit = { lambdaError() },
private val getMembersResult: (Int) -> Result<List<RoomMember>> = { lambdaError() },
private val timelineFocusedOnEventResult: (EventId) -> Result<Timeline> = { lambdaError() },
private val pinnedEventsTimelineResult: () -> Result<Timeline> = { lambdaError() },
private val setSendQueueEnabledLambda: (Boolean) -> Unit = { _: Boolean -> },
private val saveComposerDraftLambda: (ComposerDraft) -> Result<Unit> = { _: ComposerDraft -> Result.success(Unit) },
private val loadComposerDraftLambda: () -> Result<ComposerDraft?> = { Result.success<ComposerDraft?>(null) },
private val clearComposerDraftLambda: () -> Result<Unit> = { Result.success(Unit) },
private val subscribeToSyncLambda: () -> Unit = { lambdaError() },
) : MatrixRoom {
private val _roomInfoFlow: MutableSharedFlow<MatrixRoomInfo> = MutableSharedFlow(replay = 1)
override val roomInfoFlow: Flow<MatrixRoomInfo> = _roomInfoFlow
@ -180,7 +183,13 @@ class FakeMatrixRoom(
timelineFocusedOnEventResult(eventId)
}
override suspend fun subscribeToSync() = Unit
override suspend fun pinnedEventsTimeline(): Result<Timeline> = simulateLongTask {
pinnedEventsTimelineResult()
}
override suspend fun subscribeToSync() {
subscribeToSyncLambda()
}
override suspend fun powerLevels(): Result<MatrixRoomPowerLevels> {
return powerLevelsResult()
@ -289,6 +298,10 @@ class FakeMatrixRoom(
return canUserJoinCallResult(userId)
}
override suspend fun canUserPinUnpin(userId: UserId): Result<Boolean> {
return canUserPinUnpinResult(userId)
}
override suspend fun sendImage(
file: File,
thumbnailFile: File?,
@ -517,6 +530,7 @@ fun aRoomInfo(
userPowerLevels: ImmutableMap<UserId, Long> = persistentMapOf(),
activeRoomCallParticipants: List<String> = emptyList(),
heroes: List<MatrixUser> = emptyList(),
pinnedEventIds: List<EventId> = emptyList(),
) = MatrixRoomInfo(
id = id,
name = name,
@ -542,6 +556,7 @@ fun aRoomInfo(
userPowerLevels = userPowerLevels,
activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(),
heroes = heroes.toImmutableList(),
pinnedEventIds = pinnedEventIds.toImmutableList(),
)
fun defaultRoomPowerLevels() = MatrixRoomPowerLevels(

View file

@ -22,22 +22,16 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class FakeSyncService(
initialState: SyncState = SyncState.Idle
syncStateFlow: MutableStateFlow<SyncState> = MutableStateFlow(SyncState.Idle)
) : SyncService {
private val syncStateFlow = MutableStateFlow(initialState)
fun simulateError() {
syncStateFlow.value = SyncState.Error
}
var startSyncLambda: () -> Result<Unit> = { Result.success(Unit) }
override suspend fun startSync(): Result<Unit> {
syncStateFlow.value = SyncState.Running
return Result.success(Unit)
return startSyncLambda()
}
var stopSyncLambda: () -> Result<Unit> = { Result.success(Unit) }
override suspend fun stopSync(): Result<Unit> {
syncStateFlow.value = SyncState.Terminated
return Result.success(Unit)
return stopSyncLambda()
}
override val syncState: StateFlow<SyncState> = syncStateFlow

View file

@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.tests.testutils.lambda.lambdaError
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@ -371,6 +372,16 @@ class FakeTimeline(
override suspend fun loadReplyDetails(eventId: EventId) = loadReplyDetailsLambda(eventId)
var pinEventLambda: (eventId: EventId) -> Result<Boolean> = { lambdaError() }
override suspend fun pinEvent(eventId: EventId): Result<Boolean> {
return pinEventLambda(eventId)
}
var unpinEventLambda: (eventId: EventId) -> Result<Boolean> = { lambdaError() }
override suspend fun unpinEvent(eventId: EventId): Result<Boolean> {
return unpinEventLambda(eventId)
}
var closeCounter = 0
private set

View file

@ -47,6 +47,7 @@ fun anEventTimelineItem(
eventId: EventId = AN_EVENT_ID,
transactionId: TransactionId? = null,
isEditable: Boolean = false,
canBeRepliedTo: Boolean = false,
isLocal: Boolean = false,
isOwn: Boolean = false,
isRemote: Boolean = false,
@ -63,6 +64,7 @@ fun anEventTimelineItem(
eventId = eventId,
transactionId = transactionId,
isEditable = isEditable,
canBeRepliedTo = canBeRepliedTo,
isLocal = isLocal,
isOwn = isOwn,
isRemote = isRemote,

View file

@ -56,6 +56,13 @@ fun MatrixRoom.canCall(updateKey: Long): State<Boolean> {
}
}
@Composable
fun MatrixRoom.canPinUnpin(updateKey: Long): State<Boolean> {
return produceState(initialValue = false, key1 = updateKey) {
value = canUserPinUnpin(sessionId).getOrElse { false }
}
}
@Composable
fun MatrixRoom.isOwnUserAdmin(): Boolean {
val roomInfo by roomInfoFlow.collectAsState(initial = null)

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_invites_invited_you">"%1$s(%2$s ) sizni taklif qildi"</string>
</resources>

View file

@ -23,7 +23,6 @@ android {
buildTypes {
release {
isMinifyEnabled = true
consumerProguardFiles("consumer-rules.pro")
}
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="dialog_permission_camera">"Ilovaga kameradan foydalanishiga ruxsat berish uchun tizim sozlamalarida ruxsat bering."</string>
<string name="dialog_permission_generic">"Iltimos, tizim sozlamalarida ruxsat bering."</string>
<string name="dialog_permission_microphone">"Ilovaga mikrofondan foydalanishiga ruxsat berish uchun tizim sozlamalarida ruxsat bering."</string>
<string name="dialog_permission_notification">"Ilova bildirishnomalarni ko\'rsatishi uchun tizim sozlamalarida ruxsat bering."</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_check_permission_description">"Sprawdź, czy aplikacja może wyświetlać powiadomienia."</string>
<string name="troubleshoot_notifications_test_check_permission_title">"Sprawdź uprawnienia"</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_check_permission_description">"Перевірте, чи програма може показувати сповіщення."</string>
<string name="troubleshoot_notifications_test_check_permission_title">"Перевірте дозволи"</string>
</resources>

View file

@ -28,5 +28,8 @@ interface AppPreferencesStore {
suspend fun setTheme(theme: String)
fun getThemeFlow(): Flow<String?>
suspend fun setSimplifiedSlidingSyncEnabled(enabled: Boolean)
fun isSimplifiedSlidingSyncEnabledFlow(): Flow<Boolean>
suspend fun reset()
}

View file

@ -38,6 +38,7 @@ private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(na
private val developerModeKey = booleanPreferencesKey("developerMode")
private val customElementCallBaseUrlKey = stringPreferencesKey("elementCallBaseUrl")
private val themeKey = stringPreferencesKey("theme")
private val simplifiedSlidingSyncKey = booleanPreferencesKey("useSimplifiedSlidingSync")
@ContributesBinding(AppScope::class)
class DefaultAppPreferencesStore @Inject constructor(
@ -87,6 +88,18 @@ class DefaultAppPreferencesStore @Inject constructor(
}
}
override suspend fun setSimplifiedSlidingSyncEnabled(enabled: Boolean) {
store.edit { prefs ->
prefs[simplifiedSlidingSyncKey] = enabled
}
}
override fun isSimplifiedSlidingSyncEnabledFlow(): Flow<Boolean> {
return store.data.map { prefs ->
prefs[simplifiedSlidingSyncKey] ?: false
}
}
override suspend fun reset() {
store.edit { it.clear() }
}

View file

@ -24,10 +24,12 @@ class InMemoryAppPreferencesStore(
isDeveloperModeEnabled: Boolean = false,
customElementCallBaseUrl: String? = null,
theme: String? = null,
simplifiedSlidingSyncEnabled: Boolean = false
) : AppPreferencesStore {
private val isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled)
private val customElementCallBaseUrl = MutableStateFlow(customElementCallBaseUrl)
private val theme = MutableStateFlow(theme)
private val simplifiedSlidingSyncEnabled = MutableStateFlow(simplifiedSlidingSyncEnabled)
override suspend fun setDeveloperModeEnabled(enabled: Boolean) {
isDeveloperModeEnabled.value = enabled
@ -53,6 +55,14 @@ class InMemoryAppPreferencesStore(
return theme
}
override suspend fun setSimplifiedSlidingSyncEnabled(enabled: Boolean) {
simplifiedSlidingSyncEnabled.value = enabled
}
override fun isSimplifiedSlidingSyncEnabledFlow(): Flow<Boolean> {
return simplifiedSlidingSyncEnabled
}
override suspend fun reset() {
// No op
}

View file

@ -56,6 +56,7 @@ dependencies {
implementation(projects.libraries.uiStrings)
implementation(projects.libraries.troubleshoot.api)
implementation(projects.features.call.api)
implementation(projects.libraries.featureflag.api)
api(projects.libraries.pushproviders.api)
api(projects.libraries.pushstore.api)
api(projects.libraries.push.api)
@ -81,4 +82,6 @@ dependencies {
testImplementation(projects.services.appnavstate.test)
testImplementation(projects.services.toolbox.impl)
testImplementation(projects.services.toolbox.test)
testImplementation(projects.libraries.featureflag.test)
testImplementation(libs.kotlinx.collections.immutable)
}

View file

@ -32,9 +32,11 @@ interface OnNotifiableEventReceived {
class DefaultOnNotifiableEventReceived @Inject constructor(
private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager,
private val coroutineScope: CoroutineScope,
private val syncOnNotifiableEvent: SyncOnNotifiableEvent,
) : OnNotifiableEventReceived {
override fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) {
coroutineScope.launch {
launch { syncOnNotifiableEvent(notifiableEvent) }
defaultNotificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
}
}

View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.push.impl.push
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
import io.element.android.services.appnavstate.api.AppForegroundStateService
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
class SyncOnNotifiableEvent @Inject constructor(
private val matrixClientProvider: MatrixClientProvider,
private val featureFlagService: FeatureFlagService,
private val appForegroundStateService: AppForegroundStateService,
private val dispatchers: CoroutineDispatchers,
) {
private var syncCounter = AtomicInteger(0)
suspend operator fun invoke(notifiableEvent: NotifiableEvent) = withContext(dispatchers.io) {
if (!featureFlagService.isFeatureEnabled(FeatureFlags.SyncOnPush)) {
return@withContext
}
val client = matrixClientProvider.getOrRestore(notifiableEvent.sessionId).getOrNull() ?: return@withContext
client.getRoom(notifiableEvent.roomId)?.use { room ->
room.subscribeToSync()
// If the app is in foreground, sync is already running, so just add the subscription.
if (!appForegroundStateService.isInForeground.value) {
val syncService = client.syncService()
syncService.startSyncIfNeeded()
room.waitsUntilEventIsKnown(eventId = notifiableEvent.eventId, timeout = 10.seconds)
syncService.stopSyncIfNeeded()
}
}
}
private suspend fun MatrixRoom.waitsUntilEventIsKnown(eventId: EventId, timeout: Duration) {
withTimeoutOrNull(timeout) {
liveTimeline.timelineItems.first { timelineItems ->
timelineItems.any { timelineItem ->
when (timelineItem) {
is MatrixTimelineItem.Event -> timelineItem.eventId == eventId
else -> false
}
}
}
}
}
private suspend fun SyncService.startSyncIfNeeded() {
if (syncCounter.getAndIncrement() == 0) {
startSync()
}
}
private suspend fun SyncService.stopSyncIfNeeded() {
if (syncCounter.decrementAndGet() == 0 && !appForegroundStateService.isInForeground.value) {
stopSync()
}
}
}

View file

@ -34,6 +34,7 @@
<item quantity="many">"%d nowych wiadomości"</item>
</plurals>
<string name="notification_reaction_body">"Zareagował z %1$s"</string>
<string name="notification_room_action_mark_as_read">"Oznacz jako przeczytane"</string>
<string name="notification_room_action_quick_reply">"Szybka odpowiedź"</string>
<string name="notification_room_invite_body">"Zaprosił Cię do dołączenia do pokoju"</string>
<string name="notification_sender_me">"Ja"</string>
@ -56,4 +57,29 @@
<string name="push_distributor_background_sync_android">"Synchronizacja w tle"</string>
<string name="push_distributor_firebase_android">"Usługi Google"</string>
<string name="push_no_valid_google_play_services_apk_android">"Nie znaleziono usług Google Play. Powiadomienia mogą nie działać prawidłowo."</string>
<string name="troubleshoot_notifications_test_current_push_provider_description">"Uzyskaj nazwę bieżącego dostawcy."</string>
<string name="troubleshoot_notifications_test_current_push_provider_failure">"Nie wybrano dostawców push."</string>
<string name="troubleshoot_notifications_test_current_push_provider_success">"Bieżący dostawca push: %1$s."</string>
<string name="troubleshoot_notifications_test_current_push_provider_title">"Bieżący dostawca push"</string>
<string name="troubleshoot_notifications_test_detect_push_provider_description">"Upewnij się, że aplikacja ma co najmniej jednego dostawcę push."</string>
<string name="troubleshoot_notifications_test_detect_push_provider_failure">"Nie znaleziono dostawców push."</string>
<plurals name="troubleshoot_notifications_test_detect_push_provider_success">
<item quantity="one">"Znaleziono %1$d dostawcę push: %2$s"</item>
<item quantity="few">"Znaleziono %1$d dostawców push: %2$s"</item>
<item quantity="many">"Znaleziono %1$d dostawców push: %2$s"</item>
</plurals>
<string name="troubleshoot_notifications_test_detect_push_provider_title">"Wykryj dostawców powiadomień push"</string>
<string name="troubleshoot_notifications_test_display_notification_description">"Sprawdź, czy aplikacja może wyświetlać powiadomienie."</string>
<string name="troubleshoot_notifications_test_display_notification_failure">"Powiadomienie nie zostało kliknięte."</string>
<string name="troubleshoot_notifications_test_display_notification_permission_failure">"Nie można wyświetlić powiadomienia."</string>
<string name="troubleshoot_notifications_test_display_notification_success">"Powiadomienie zostało kliknięte!"</string>
<string name="troubleshoot_notifications_test_display_notification_title">"Wyświetl powiadomienie"</string>
<string name="troubleshoot_notifications_test_display_notification_waiting">"Kliknij powiadomienie, aby kontynuować test."</string>
<string name="troubleshoot_notifications_test_push_loop_back_description">"Upewnij się, że aplikacja otrzymuje powiadomienie push."</string>
<string name="troubleshoot_notifications_test_push_loop_back_failure_1">"Błąd: pusher odrzucił żądanie."</string>
<string name="troubleshoot_notifications_test_push_loop_back_failure_2">"Błąd: %1$s."</string>
<string name="troubleshoot_notifications_test_push_loop_back_failure_3">"Błąd, nie można przetestować push."</string>
<string name="troubleshoot_notifications_test_push_loop_back_failure_4">"Błąd, upłynął limit czasu powiadomienia push."</string>
<string name="troubleshoot_notifications_test_push_loop_back_success">"Pętla powrotna push zajęła %1$d ms."</string>
<string name="troubleshoot_notifications_test_push_loop_back_title">"Przetestuj pętlę Push back"</string>
</resources>

View file

@ -21,12 +21,14 @@
<item quantity="other">"%d convites"</item>
</plurals>
<string name="notification_invite_body">"Convidou você para conversar"</string>
<string name="notification_mentioned_you_body">"Mencionou você: %1$s"</string>
<string name="notification_new_messages">"Novas mensagens"</string>
<plurals name="notification_new_messages_for_room">
<item quantity="one">"%d nova mensagem"</item>
<item quantity="other">"%d novas mensagens"</item>
</plurals>
<string name="notification_reaction_body">"Reagiu com %1$s"</string>
<string name="notification_room_action_mark_as_read">"Marcar como lido"</string>
<string name="notification_room_action_quick_reply">"Resposta rápida"</string>
<string name="notification_room_invite_body">"Convidou você para entrar na sala"</string>
<string name="notification_sender_me">"Eu"</string>

View file

@ -3,6 +3,7 @@
<string name="notification_channel_call">"Samtal"</string>
<string name="notification_channel_listening_for_events">"Lyssnar efter händelser"</string>
<string name="notification_channel_noisy">"Högljudda aviseringar"</string>
<string name="notification_channel_ringing_calls">"Ringande samtal"</string>
<string name="notification_channel_silent">"Tysta aviseringar"</string>
<plurals name="notification_compat_summary_line_for_room">
<item quantity="one">"%1$s: %2$d meddelande"</item>
@ -12,6 +13,8 @@
<item quantity="one">"%d avisering"</item>
<item quantity="other">"%d aviseringar"</item>
</plurals>
<string name="notification_fallback_content">"notis"</string>
<string name="notification_incoming_call">"Inkommande samtal"</string>
<string name="notification_inline_reply_failed">"** Misslyckades att skicka - vänligen öppna rummet"</string>
<string name="notification_invitation_action_join">"Gå med"</string>
<string name="notification_invitation_action_reject">"Avvisa"</string>

View file

@ -3,6 +3,7 @@
<string name="notification_channel_call">"Виклик"</string>
<string name="notification_channel_listening_for_events">"Прослуховування подій"</string>
<string name="notification_channel_noisy">"Гучні сповіщення"</string>
<string name="notification_channel_ringing_calls">"Дзвінки"</string>
<string name="notification_channel_silent">"Тихі сповіщення"</string>
<plurals name="notification_compat_summary_line_for_room">
<item quantity="one">"%1$s: %2$d повідомлення"</item>
@ -15,6 +16,7 @@
<item quantity="many">"%d сповіщень"</item>
</plurals>
<string name="notification_fallback_content">"Сповіщення"</string>
<string name="notification_incoming_call">"Вхідний дзвінок"</string>
<string name="notification_inline_reply_failed">"** Не вдалося надіслати - будь ласка, відкрийте кімнату"</string>
<string name="notification_invitation_action_join">"Доєднатися"</string>
<string name="notification_invitation_action_reject">"Відхилити"</string>
@ -32,7 +34,7 @@
<item quantity="many">"%d нових повідомлень"</item>
</plurals>
<string name="notification_reaction_body">"Відреагував (-ла) з %1$s"</string>
<string name="notification_room_action_mark_as_read">"Позначити як прочитане"</string>
<string name="notification_room_action_mark_as_read">"Позначити прочитаним"</string>
<string name="notification_room_action_quick_reply">"Швидка відповідь"</string>
<string name="notification_room_invite_body">"Запросив (-ла) Вас приєднатися до кімнати"</string>
<string name="notification_sender_me">"Я"</string>
@ -55,5 +57,27 @@
<string name="push_distributor_background_sync_android">"Фонова синхронізація"</string>
<string name="push_distributor_firebase_android">"Сервіси Google"</string>
<string name="push_no_valid_google_play_services_apk_android">"Не знайдено дійсних сервісів Google Play. Сповіщення можуть не працювати належним чином."</string>
<string name="troubleshoot_notifications_test_current_push_provider_description">"Отримує назву поточного постачальника."</string>
<string name="troubleshoot_notifications_test_current_push_provider_failure">"Постачальників push-сповіщень не обрано."</string>
<string name="troubleshoot_notifications_test_current_push_provider_success">"Поточний постачальник: %1$s."</string>
<string name="troubleshoot_notifications_test_current_push_provider_title">"Поточний провайдер push"</string>
<string name="troubleshoot_notifications_test_detect_push_provider_description">"Переконайтеся, що програма має принаймні один push провайдер."</string>
<string name="troubleshoot_notifications_test_detect_push_provider_failure">"Не знайдено постачальників push-повідомлень."</string>
<plurals name="troubleshoot_notifications_test_detect_push_provider_success">
<item quantity="one">"Виявлено %1$d постачальника: %2$s"</item>
<item quantity="few">"Виявлено %1$d постачальників: %2$s"</item>
<item quantity="many">"Виявлено %1$d постачальників: %2$s"</item>
</plurals>
<string name="troubleshoot_notifications_test_detect_push_provider_title">"Виявлення push-провайдерів"</string>
<string name="troubleshoot_notifications_test_display_notification_description">"Перевірте, чи може програма відображати сповіщення."</string>
<string name="troubleshoot_notifications_test_display_notification_failure">"Ви не натиснули на сповіщення."</string>
<string name="troubleshoot_notifications_test_display_notification_permission_failure">"Не вдається відобразити сповіщення."</string>
<string name="troubleshoot_notifications_test_display_notification_success">"Ви натиснули на сповіщення!"</string>
<string name="troubleshoot_notifications_test_display_notification_title">"Відображення сповіщення"</string>
<string name="troubleshoot_notifications_test_display_notification_waiting">"Будь ласка, натисніть на сповіщення, щоб продовжити тест."</string>
<string name="troubleshoot_notifications_test_push_loop_back_description">"Переконується, що застосунок отримує push-сповіщення."</string>
<string name="troubleshoot_notifications_test_push_loop_back_failure_1">"Помилка: постачальник push-сповіщень відхилив запит."</string>
<string name="troubleshoot_notifications_test_push_loop_back_failure_2">"Помилка: %1$s."</string>
<string name="troubleshoot_notifications_test_push_loop_back_failure_3">"Помилка, неможливо перевірити push."</string>
<string name="troubleshoot_notifications_test_push_loop_back_title">"Перевірка зворотного надсилання"</string>
</resources>

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_call">"Qo\'ng\'iroq"</string>
<string name="notification_channel_listening_for_events">"Voqealarni tinglash"</string>
<string name="notification_channel_noisy">"Shovqinli bildirishnomalar"</string>
<string name="notification_channel_silent">"Ovozsiz bildirishnomalar"</string>
<plurals name="notification_compat_summary_line_for_room">
<item quantity="one">"%1$s:%2$d xabar"</item>
<item quantity="other">"%1$s:%2$d xabarlar"</item>
</plurals>
<plurals name="notification_compat_summary_title">
<item quantity="one">"%dbildirishnoma"</item>
<item quantity="other">"%dbildirishnomalar"</item>
</plurals>
<string name="notification_fallback_content">"Bildirishnoma"</string>
<string name="notification_inline_reply_failed">"** Yuborilmadi - iltimos, xonani oching"</string>
<string name="notification_invitation_action_join">"Qo\'shilish"</string>
<string name="notification_invitation_action_reject">"Rad etish"</string>
<plurals name="notification_invitations">
<item quantity="one">"%dtaklifnoma"</item>
<item quantity="other">"%dtaklifnomalar"</item>
</plurals>
<string name="notification_invite_body">"Sizni suhbatga taklif qildi"</string>
<string name="notification_new_messages">"Yangi xabarlar"</string>
<plurals name="notification_new_messages_for_room">
<item quantity="one">"%dyangi xabar"</item>
<item quantity="other">"%dyangi xabarlar"</item>
</plurals>
<string name="notification_reaction_body">"%1$sbilan munosabat bildiring"</string>
<string name="notification_room_action_quick_reply">"Tez javob"</string>
<string name="notification_room_invite_body">"Sizni xonaga kirishga taklif qildi"</string>
<string name="notification_sender_me">"Men"</string>
<string name="notification_test_push_notification_content">"Siz bildirishnomani ko\'ryapsiz! Meni bosing!"</string>
<string name="notification_ticker_text_dm">"%1$s:%2$s"</string>
<string name="notification_ticker_text_group">"%1$s:%2$s%3$s"</string>
<plurals name="notification_unread_notified_messages">
<item quantity="one">"%do\'qilmagan xabarnoma"</item>
<item quantity="other">"%do\'qilmagan xabarlar"</item>
</plurals>
<string name="notification_unread_notified_messages_and_invitation">"%1$sva%2$s"</string>
<string name="notification_unread_notified_messages_in_room">"%1$sichida%2$s"</string>
<string name="notification_unread_notified_messages_in_room_and_invitation">"%1$sichida%2$s va%3$s"</string>
<plurals name="notification_unread_notified_messages_in_room_rooms">
<item quantity="one">"%dxona"</item>
<item quantity="other">"%dxonalar"</item>
</plurals>
<string name="push_distributor_background_sync_android">"Orqa Fon sinxronizatsiyasi"</string>
<string name="push_distributor_firebase_android">"Google xizmatlari"</string>
<string name="push_no_valid_google_play_services_apk_android">"Yaroqli Google Play xizmatlari topilmadi. Bildirishnomalar to\'g\'ri ishlamasligi mumkin."</string>
</resources>

View file

@ -34,6 +34,7 @@
<string name="notification_room_action_quick_reply">"Quick reply"</string>
<string name="notification_room_invite_body">"Invited you to join the room"</string>
<string name="notification_sender_me">"Me"</string>
<string name="notification_sender_mention_reply">"%1$s mentioned or replied"</string>
<string name="notification_test_push_notification_content">"You are viewing the notification! Click me!"</string>
<string name="notification_ticker_text_dm">"%1$s: %2$s"</string>
<string name="notification_ticker_text_group">"%1$s: %2$s %3$s"</string>

View file

@ -0,0 +1,151 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.push.impl.push
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_UNIQUE_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.sync.FakeSyncService
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
class SyncOnNotifiableEventTest {
private val timelineItems = MutableStateFlow<List<MatrixTimelineItem>>(emptyList())
private val syncStateFlow = MutableStateFlow(SyncState.Idle)
private val startSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
private val stopSyncLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
private val subscribeToSyncLambda = lambdaRecorder<Unit> { }
private val liveTimeline = FakeTimeline(
timelineItems = timelineItems,
)
private val room = FakeMatrixRoom(
roomId = A_ROOM_ID,
liveTimeline = liveTimeline,
subscribeToSyncLambda = subscribeToSyncLambda
)
private val syncService = FakeSyncService(syncStateFlow).also {
it.startSyncLambda = startSyncLambda
it.stopSyncLambda = stopSyncLambda
}
private val client = FakeMatrixClient(
syncService = syncService,
).apply {
givenGetRoomResult(A_ROOM_ID, room)
}
private val notifiableEvent = aNotifiableMessageEvent()
@Test
fun `when feature flag is disabled, nothing happens`() = runTest {
val sut = createSyncOnNotifiableEvent(client = client, isSyncOnPushEnabled = false)
sut(notifiableEvent)
assert(startSyncLambda).isNeverCalled()
assert(stopSyncLambda).isNeverCalled()
assert(subscribeToSyncLambda).isNeverCalled()
}
@Test
fun `when feature flag is enabled and app is in foreground, sync is not started`() = runTest {
val sut = createSyncOnNotifiableEvent(client = client, isAppInForeground = true, isSyncOnPushEnabled = true)
sut(notifiableEvent)
assert(startSyncLambda).isNeverCalled()
assert(stopSyncLambda).isNeverCalled()
assert(subscribeToSyncLambda).isCalledOnce()
}
@Test
fun `when feature flag is enabled and app is in background, sync is started and stopped`() = runTest {
val sut = createSyncOnNotifiableEvent(client = client, isAppInForeground = false, isSyncOnPushEnabled = true)
timelineItems.emit(
listOf(MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem()))
)
syncStateFlow.emit(SyncState.Running)
sut(notifiableEvent)
assert(startSyncLambda).isCalledOnce()
assert(stopSyncLambda).isCalledOnce()
assert(subscribeToSyncLambda).isCalledOnce()
}
@Test
fun `when feature flag is enabled and app is in background, running multiple time only call once`() = runTest {
val sut = createSyncOnNotifiableEvent(client = client, isAppInForeground = false, isSyncOnPushEnabled = true)
coroutineScope {
launch { sut(notifiableEvent) }
launch { sut(notifiableEvent) }
launch {
delay(1)
timelineItems.emit(
listOf(MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem()))
)
}
}
assert(startSyncLambda).isCalledOnce()
assert(stopSyncLambda).isCalledOnce()
assert(subscribeToSyncLambda).isCalledExactly(2)
}
private fun TestScope.createSyncOnNotifiableEvent(
client: MatrixClient = FakeMatrixClient(),
isSyncOnPushEnabled: Boolean = true,
isAppInForeground: Boolean = true,
): SyncOnNotifiableEvent {
val featureFlagService = FakeFeatureFlagService(
initialState = mapOf(
FeatureFlags.SyncOnPush.key to isSyncOnPushEnabled
)
)
val appForegroundStateService = FakeAppForegroundStateService(
initialValue = isAppInForeground
)
val matrixClientProvider = FakeMatrixClientProvider { Result.success(client) }
return SyncOnNotifiableEvent(
matrixClientProvider = matrixClientProvider,
featureFlagService = featureFlagService,
appForegroundStateService = appForegroundStateService,
dispatchers = testCoroutineDispatchers(),
)
}
}

View file

@ -26,7 +26,6 @@ android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
consumerProguardFiles("consumer-proguard-rules.pro")
resValue(
type = "string",
@ -50,7 +49,6 @@ android {
)
}
register("nightly") {
isMinifyEnabled = true
consumerProguardFiles("consumer-proguard-rules.pro")
matchingFallbacks += listOf("release")
resValue(

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Upewnij się, że Firebase jest dostępny."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Baza Firebase jest niedostępna."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Baza Firebase jest dostępna."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Sprawdź Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Upewnij się, że token Firebase jest dostępny."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Token Firebase nie jest znany."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Token Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Sprawdź token Firebase"</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_firebase_availability_description">"Переконується, що Firebase доступний."</string>
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase недоступний."</string>
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase доступний."</string>
<string name="troubleshoot_notifications_test_firebase_availability_title">"Перевірка Firebase"</string>
<string name="troubleshoot_notifications_test_firebase_token_description">"Переконується, що токен Firebase доступний."</string>
<string name="troubleshoot_notifications_test_firebase_token_failure">"Токен Firebase невідомий."</string>
<string name="troubleshoot_notifications_test_firebase_token_success">"Токен Firebase: %1$s."</string>
<string name="troubleshoot_notifications_test_firebase_token_title">"Перевірка токена Firebase"</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_unified_push_description">"Upewnij się, że dystrybutorzy UnifiedPush są dostępni."</string>
<string name="troubleshoot_notifications_test_unified_push_failure">"Nie znaleziono dystrybutorów push."</string>
<plurals name="troubleshoot_notifications_test_unified_push_success">
<item quantity="one">"Znaleziono %1$d dystrybutora: %2$s."</item>
<item quantity="few">"Znaleziono %1$d dystrybutorów: %2$s."</item>
<item quantity="many">"Znaleziono %1$d dystrybutorów: %2$s."</item>
</plurals>
<string name="troubleshoot_notifications_test_unified_push_title">"Sprawdź UnifiedPush"</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_test_unified_push_description">"Переконується, що дистриб\'ютори UnifiedPush доступні."</string>
<string name="troubleshoot_notifications_test_unified_push_failure">"Дистриб\'юторів не знайдено."</string>
<plurals name="troubleshoot_notifications_test_unified_push_success">
<item quantity="one">"%1$d дистриб\'ютора знайдено: %2$s."</item>
<item quantity="few">"%1$d дистриб\'юторів знайдено: %2$s."</item>
<item quantity="many">"%1$d дистриб\'юторів знайдено: %2$s."</item>
</plurals>
<string name="troubleshoot_notifications_test_unified_push_title">"Перевірка UnifiedPush"</string>
</resources>

View file

@ -64,6 +64,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
@Suppress("MultipleEmitters") // False positive
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RoomSelectView(

View file

@ -7,14 +7,14 @@
<string name="rich_text_editor_composer_placeholder">"Wiadomość…"</string>
<string name="rich_text_editor_create_link">"Utwórz link"</string>
<string name="rich_text_editor_edit_link">"Edytuj link"</string>
<string name="rich_text_editor_format_bold">"Zastosuj pogrubiony format"</string>
<string name="rich_text_editor_format_italic">"Zastosuj format kursywy"</string>
<string name="rich_text_editor_format_strikethrough">"Zastosuj format przekreślenia"</string>
<string name="rich_text_editor_format_underline">"Zastosuj format podkreślenia"</string>
<string name="rich_text_editor_full_screen_toggle">"Przełącz tryb pełnoekranowy"</string>
<string name="rich_text_editor_format_bold">"Zastosuj pogrubienie"</string>
<string name="rich_text_editor_format_italic">"Zastosuj kursywę"</string>
<string name="rich_text_editor_format_strikethrough">"Zastosuj przekreślenie"</string>
<string name="rich_text_editor_format_underline">"Zastosuj podkreślenie"</string>
<string name="rich_text_editor_full_screen_toggle">"Przełącz pełny ekran"</string>
<string name="rich_text_editor_indent">"Wcięcie"</string>
<string name="rich_text_editor_inline_code">"Zastosuj format kodu wbudowanego"</string>
<string name="rich_text_editor_link">"Wstaw łącze"</string>
<string name="rich_text_editor_inline_code">"Zastosuj formatowanie kodu w wierszu"</string>
<string name="rich_text_editor_link">"Wstaw link"</string>
<string name="rich_text_editor_numbered_list">"Przełącz listę numerowaną"</string>
<string name="rich_text_editor_open_compose_options">"Otwórz opcje tworzenia"</string>
<string name="rich_text_editor_quote">"Przełącz cytat"</string>

View file

@ -12,12 +12,14 @@
<string name="rich_text_editor_format_strikethrough">"Aplicar formato tachado"</string>
<string name="rich_text_editor_format_underline">"Aplicar sublinhado"</string>
<string name="rich_text_editor_full_screen_toggle">"Alternar o modo de tela cheia"</string>
<string name="rich_text_editor_indent">"Identar"</string>
<string name="rich_text_editor_inline_code">"Aplicar formato de código embutido"</string>
<string name="rich_text_editor_link">"Definir link"</string>
<string name="rich_text_editor_numbered_list">"Alternar lista numerada"</string>
<string name="rich_text_editor_open_compose_options">"Abrir opções de composição"</string>
<string name="rich_text_editor_quote">"Alternar citação"</string>
<string name="rich_text_editor_remove_link">"Remover link"</string>
<string name="rich_text_editor_unindent">"Desidentar"</string>
<string name="rich_text_editor_url_placeholder">"Link"</string>
<string name="screen_room_voice_message_tooltip">"Segure para gravar"</string>
</resources>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="rich_text_editor_a11y_add_attachment">"Biriktirma qo\'shing"</string>
<string name="rich_text_editor_bullet_list">"Belgilar roʻyxatini almashtirish"</string>
<string name="rich_text_editor_close_formatting_options">"Formatlash parametrlarini yoping"</string>
<string name="rich_text_editor_code_block">"Kod blokini almashtirish"</string>
<string name="rich_text_editor_composer_placeholder">"Xabar…"</string>
<string name="rich_text_editor_create_link">"Havola yarating"</string>
<string name="rich_text_editor_edit_link">"Havolani tahrirlash"</string>
<string name="rich_text_editor_format_bold">"Qalin formatni qo\'llang"</string>
<string name="rich_text_editor_format_italic">"Kursiv formatini qo\'llang"</string>
<string name="rich_text_editor_format_strikethrough">"Chizilgan formatni qo\'llash"</string>
<string name="rich_text_editor_format_underline">"Pastki chiziq formatini qo\'llang"</string>
<string name="rich_text_editor_full_screen_toggle">"Toʻliq ekran rejimiga oʻtish"</string>
<string name="rich_text_editor_indent">"Paragraf"</string>
<string name="rich_text_editor_inline_code">"Koq formatini mos ravishda qo\'shing"</string>
<string name="rich_text_editor_link">"Havolani o\'rnatish"</string>
<string name="rich_text_editor_numbered_list">"Raqamlangan roʻyxatni almashtirish"</string>
<string name="rich_text_editor_open_compose_options">"Yozish parametrlarini oching"</string>
<string name="rich_text_editor_quote">"Iqtibosni almashtirish"</string>
<string name="rich_text_editor_remove_link">"Havolani olib tashlang"</string>
<string name="rich_text_editor_unindent">"Paragrafni bekor qilish"</string>
<string name="rich_text_editor_url_placeholder">"Havola"</string>
<string name="screen_room_voice_message_tooltip">"Yozib olish uchun bosib turing"</string>
</resources>

View file

@ -16,6 +16,7 @@
package io.element.android.libraries.troubleshoot.impl
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.progressSemantics
@ -68,7 +69,7 @@ fun TroubleshootNotificationsView(
}
@Composable
private fun TroubleshootTestView(
private fun ColumnScope.TroubleshootTestView(
testState: NotificationTroubleshootTestState,
onQuickFixClick: () -> Unit,
) {
@ -127,7 +128,7 @@ private fun TroubleshootTestView(
}
@Composable
private fun TroubleshootNotificationsContent(state: TroubleshootNotificationsState) {
private fun ColumnScope.TroubleshootNotificationsContent(state: TroubleshootNotificationsState) {
when (state.testSuiteState.mainState) {
AsyncAction.Loading,
AsyncAction.Confirming,
@ -197,7 +198,7 @@ private fun RunTestButton(state: TroubleshootNotificationsState) {
}
@Composable
private fun TestSuiteView(
private fun ColumnScope.TestSuiteView(
testSuiteState: TroubleshootTestSuiteState,
onQuickFixClick: (Int) -> Unit,
) {

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_screen_action">"Uruchom testy"</string>
<string name="troubleshoot_notifications_screen_action_again">"Uruchom testy ponownie"</string>
<string name="troubleshoot_notifications_screen_failure">"Niektóre testy się nie powiodły. Sprawdź szczegóły."</string>
<string name="troubleshoot_notifications_screen_notice">"Uruchom testy, aby wykryć potencjalne problemy z konfiguracją, jeśli powiadomienia nie działają prawidłowo."</string>
<string name="troubleshoot_notifications_screen_quick_fix_action">"Spróbuj naprawić"</string>
<string name="troubleshoot_notifications_screen_success">"Wszystkie testy przebiegły pomyślnie."</string>
<string name="troubleshoot_notifications_screen_title">"Powiadomienia rozwiązywania problemów"</string>
<string name="troubleshoot_notifications_screen_waiting">"Niektóre testy wymagają Twojej uwagi. Sprawdź szczegóły."</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="troubleshoot_notifications_screen_action">"Запустити тести"</string>
<string name="troubleshoot_notifications_screen_action_again">"Запустити тести знову"</string>
<string name="troubleshoot_notifications_screen_failure">"Деякі тести не пройшли. Будь ласка, перегляньте деталі."</string>
<string name="troubleshoot_notifications_screen_notice">"Запустіть тести, щоб виявити будь-яку проблему у вашій конфігурації, через яку сповіщення можуть не працювати належним чином."</string>
<string name="troubleshoot_notifications_screen_quick_fix_action">"Спробувати виправити"</string>
<string name="troubleshoot_notifications_screen_success">"Всі тести пройшли успішно."</string>
<string name="troubleshoot_notifications_screen_title">"Усунення неполадок сповіщень"</string>
<string name="troubleshoot_notifications_screen_waiting">"Деякі тести вимагають вашої уваги. Будь ласка, перегляньте деталі."</string>
</resources>

View file

@ -93,6 +93,7 @@
<string name="action_report_bug">"Паведаміць пра памылку"</string>
<string name="action_report_content">"Паскардзіцца на змест"</string>
<string name="action_reset">"Скінуць"</string>
<string name="action_reset_identity">"Скінуць ідэнтыфікацыйныя дадзеныя"</string>
<string name="action_retry">"Паўтарыць"</string>
<string name="action_retry_decryption">"Паўтарыць расшыфроўку"</string>
<string name="action_save">"Захаваць"</string>
@ -112,6 +113,7 @@
<string name="action_take_photo">"Зрабіць фота"</string>
<string name="action_tap_for_options">"Дакраніцеся, каб убачыць параметры"</string>
<string name="action_try_again">"Паўтарыць спробу"</string>
<string name="action_unpin">"Адмацаваць"</string>
<string name="action_view_source">"Прагляд зыходнага кода"</string>
<string name="action_yes">"Так"</string>
<string name="common_about">"Аб праграме"</string>
@ -262,6 +264,7 @@
<string name="error_some_messages_have_not_been_sent">"Некаторыя паведамленні не былі адпраўлены"</string>
<string name="error_unknown">"Выбачце, адбылася памылка"</string>
<string name="event_shield_reason_authenticity_not_guaranteed">"Сапраўднасць гэтага зашыфраванага паведамлення не можа быць гарантаваная на гэтай прыладзе."</string>
<string name="event_shield_reason_sent_in_clear">"Не зашыфраваны."</string>
<string name="event_shield_reason_unknown_device">"Зашыфравана невядомай ці выдаленай прыладай."</string>
<string name="event_shield_reason_unsigned_device">"Зашыфравана прыладай, не пацверджанай яе ўладальнікам."</string>
<string name="event_shield_reason_unverified_identity">"Зашыфравана неправераным карыстальнікам."</string>
@ -269,9 +272,30 @@
<string name="invite_friends_text">"Гэй, пагавары са мной у %1$s: %2$s"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<string name="preference_rageshake">"Паведаміць аб памылцы з дапамогай Rageshake"</string>
<string name="screen_encryption_reset_bullet_1">"Дадзеныя вашага ўліковага запісу, кантакты, налады і спіс чатаў будуць захаваны"</string>
<string name="screen_encryption_reset_bullet_2">"Вы страціце існуючую гісторыю паведамленняў"</string>
<string name="screen_encryption_reset_bullet_3">"Вам трэба будзе зноў запэўніць ўсе вашы існуючыя прылады і кантакты"</string>
<string name="screen_encryption_reset_footer">"Працягвайце, толькі калі вы ўпэўненыя, што страцілі ўсе астатнія прылады і ключ аднаўлення."</string>
<string name="screen_encryption_reset_subtitle">"Калі вы не ўвайшлі ў сістэму на іншых прыладах і страцілі ключ аднаўлення, вам неабходна скінуць ключы пацверджання, каб працягнуць выкарыстанне прыкладання."</string>
<string name="screen_encryption_reset_title">"Скіньце ключы пацверджання, калі вы не можаце пацвердзіць яго іншым спосабам"</string>
<string name="screen_media_picker_error_failed_selection">"Не ўдалося выбраць носьбіт, паўтарыце спробу."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Не атрымалася загрузіць медыяфайлы, паспрабуйце яшчэ раз."</string>
<string name="screen_pinned_timeline_empty_state_description">"Націсніце на паведамленне і абярыце «%1$s », каб уключыць сюды."</string>
<string name="screen_pinned_timeline_empty_state_headline">"Замацуеце важныя паведамленні, каб іх можна было лёгка знайсці"</string>
<plurals name="screen_pinned_timeline_screen_title">
<item quantity="one">"%1$d Замацаванае паведамленне"</item>
<item quantity="few">"%1$d Замацаваныя паведамленні"</item>
<item quantity="many">"%1$d Замацаваных паведамленняў"</item>
</plurals>
<string name="screen_pinned_timeline_screen_title_empty">"Замацаваныя паведамленні"</string>
<string name="screen_reset_encryption_confirmation_alert_action">"Так, скінуць зараз"</string>
<string name="screen_reset_encryption_confirmation_alert_subtitle">"Гэты працэс незваротны."</string>
<string name="screen_reset_encryption_confirmation_alert_title">"Вы ўпэўнены, што хочаце скінуць шыфраванне?"</string>
<string name="screen_reset_encryption_password_placeholder">"Увод…"</string>
<string name="screen_reset_encryption_password_subtitle">"Пацвердзіце, што вы хочаце скінуць шыфраванне"</string>
<string name="screen_reset_encryption_password_title">"Каб працягнуць, увядзіце пароль уліковага запісу"</string>
<string name="screen_room_details_pinned_events_row_title">"Замацаваныя паведамленні"</string>
<string name="screen_room_error_failed_processing_media">"Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Не ўдалося атрымаць інфармацыю пра карыстальніка"</string>
<string name="screen_room_member_details_block_alert_action">"Заблакіраваць"</string>
@ -281,6 +305,10 @@
<string name="screen_room_member_details_unblock_alert_action">"Разблакіраваць"</string>
<string name="screen_room_member_details_unblock_alert_description">"Вы зноў зможаце ўбачыць усе паведамленні."</string>
<string name="screen_room_member_details_unblock_user">"Разблакіраваць карыстальніка"</string>
<string name="screen_room_pinned_banner_indicator">"%1$s з %2$s"</string>
<string name="screen_room_pinned_banner_indicator_description">"%1$s Замацаваныя паведамленні"</string>
<string name="screen_room_pinned_banner_loading_description">"Загрузка паведамлення…"</string>
<string name="screen_room_pinned_banner_view_all_button_title">"Паглядзець усе"</string>
<string name="screen_room_title">"Чат"</string>
<string name="screen_share_location_title">"Падзяліцца месцазнаходжаннем"</string>
<string name="screen_share_my_location_action">"Падзяліцца маім месцазнаходжаннем"</string>

View file

@ -82,6 +82,7 @@
<string name="action_ok">"OK"</string>
<string name="action_open_settings">"Otevřít nastavení"</string>
<string name="action_open_with">"Otevřít v aplikaci"</string>
<string name="action_pin">"Pin"</string>
<string name="action_quick_reply">"Rychlá odpověď"</string>
<string name="action_quote">"Citovat"</string>
<string name="action_react">"Reagovat"</string>
@ -111,6 +112,7 @@
<string name="action_take_photo">"Vyfotit"</string>
<string name="action_tap_for_options">"Klepnutím zobrazíte možnosti"</string>
<string name="action_try_again">"Zkusit znovu"</string>
<string name="action_unpin">"Odepnout"</string>
<string name="action_view_source">"Zobrazit zdroj"</string>
<string name="action_yes">"Ano"</string>
<string name="common_about">"O aplikaci"</string>
@ -260,6 +262,10 @@ Důvod: %1$s."</string>
<string name="error_missing_microphone_voice_rationale_android">"%1$s nemá oprávnění k přístupu k mikrofonu. Povolte přístup k nahrávání hlasové zprávy."</string>
<string name="error_some_messages_have_not_been_sent">"Některé zprávy nebyly odeslány"</string>
<string name="error_unknown">"Omlouváme se, došlo k chybě"</string>
<string name="event_shield_reason_authenticity_not_guaranteed">"Autenticitu této zašifrované zprávy nelze na tomto zařízení zaručit."</string>
<string name="event_shield_reason_unknown_device">"Šifrováno neznámým nebo smazaným zařízením."</string>
<string name="event_shield_reason_unsigned_device">"Šifrováno zařízením, které nebylo ověřeno jeho vlastníkem."</string>
<string name="event_shield_reason_unverified_identity">"Šifrováno neověřeným uživatelem."</string>
<string name="invite_friends_rich_title">"🔐️ Připojte se ke mně na %1$s"</string>
<string name="invite_friends_text">"Ahoj, ozvi se mi na %1$s: %2$s"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
@ -276,6 +282,9 @@ Důvod: %1$s."</string>
<string name="screen_room_member_details_unblock_alert_action">"Odblokovat"</string>
<string name="screen_room_member_details_unblock_alert_description">"Znovu uvidíte všechny zprávy od nich."</string>
<string name="screen_room_member_details_unblock_user">"Odblokovat uživatele"</string>
<string name="screen_room_pinned_banner_indicator">"%1$s z %2$s"</string>
<string name="screen_room_pinned_banner_indicator_description">"%1$s Připnuté zprávy"</string>
<string name="screen_room_pinned_banner_view_all_button_title">"Zobrazit vše"</string>
<string name="screen_room_title">"Chat"</string>
<string name="screen_share_location_title">"Sdílet polohu"</string>
<string name="screen_share_my_location_action">"Sdílet moji polohu"</string>

View file

@ -110,6 +110,7 @@
<string name="action_take_photo">"Τράβηξε φωτογραφία"</string>
<string name="action_tap_for_options">"Πάτα για επιλογές"</string>
<string name="action_try_again">"Προσπάθησε ξανά"</string>
<string name="action_unpin">"Ξεκαρφίτσωμα"</string>
<string name="action_view_source">"Προβολή πηγής"</string>
<string name="action_yes">"Ναι"</string>
<string name="common_about">"Σχετικά"</string>

View file

@ -91,6 +91,7 @@
<string name="action_report_bug">"Teata veast"</string>
<string name="action_report_content">"Teata sisust haldurile"</string>
<string name="action_reset">"Lähtesta"</string>
<string name="action_reset_identity">"Lähtesta on identiteet"</string>
<string name="action_retry">"Proovi uuesti"</string>
<string name="action_retry_decryption">"Proovi dekrüptimist uuesti"</string>
<string name="action_save">"Salvesta"</string>
@ -110,6 +111,7 @@
<string name="action_take_photo">"Tee pilt"</string>
<string name="action_tap_for_options">"Valikuteks klõpsa"</string>
<string name="action_try_again">"Proovi uuesti"</string>
<string name="action_unpin">"Eemalda kinnitus"</string>
<string name="action_view_source">"Vaata lähtekoodi"</string>
<string name="action_yes">"Jah"</string>
<string name="common_about">"Rakenduse teave"</string>
@ -258,6 +260,7 @@ Põhjus: %1$s."</string>
<string name="error_some_messages_have_not_been_sent">"Mõned sõnumid on saatmata"</string>
<string name="error_unknown">"Vabandust, ilmnes viga"</string>
<string name="event_shield_reason_authenticity_not_guaranteed">"Selle krüptitud sõnumi tõepärasus pole selles seadmes tagatud."</string>
<string name="event_shield_reason_sent_in_clear">"Pole krüptitud."</string>
<string name="event_shield_reason_unknown_device">"Krüptitud tundmatu või kustutatud seadme poolt."</string>
<string name="event_shield_reason_unsigned_device">"Krüptitud seadme poolt, mida tema omanik pole verifitseerinud."</string>
<string name="event_shield_reason_unverified_identity">"Krüptitud verifitseerimata kasutaja poolt."</string>
@ -265,9 +268,29 @@ Põhjus: %1$s."</string>
<string name="invite_friends_text">"Hei, suhtle minuga %1$s võrgus: %2$s"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<string name="preference_rageshake">"Veast teatamiseks raputa nutiseadet ägedalt"</string>
<string name="screen_encryption_reset_bullet_1">"Sinu kasutajakonto andmed, kontaktid, eelistused ja vestluste loend säiluvad"</string>
<string name="screen_encryption_reset_bullet_2">"Sa kaotad seniste sõnumite ajaloo"</string>
<string name="screen_encryption_reset_bullet_3">"Sa pead kõik oma olemasolevad seadmed ja kontaktid uuesti verifitseerima"</string>
<string name="screen_encryption_reset_footer">"Lähtesta oma identiteet vaid siis, kui sul pole ligipääsu mitte ühelegi oma seadmele ja sa oled kaotanud oma taastevõtme."</string>
<string name="screen_encryption_reset_subtitle">"Kui sa soovid jätkata selle rakenduse kasutamist ja sa pole mitte üheski seadmes sisse logitud ning oled kaotanud oma taastevõtme, siis tõesti pead lähtestama oma identiteedi. "</string>
<string name="screen_encryption_reset_title">"Kui sa ühtegi muud võimalust ei leia, siis lähtesta oma identiteet."</string>
<string name="screen_media_picker_error_failed_selection">"Meediafaili valimine ei õnnestunud. Palun proovi uuesti."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Meediafaili töötlemine enne üleslaadimist ei õnnestunud. Palun proovi uuesti."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Meediafaili üleslaadimine ei õnnestunud. Palun proovi uuesti."</string>
<string name="screen_pinned_timeline_empty_state_description">"Siia lisamiseks vajuta sõnumil ja vali „%1$s“."</string>
<string name="screen_pinned_timeline_empty_state_headline">"Et olulisi sõnumeid oleks lihtsam leida, tõsta nad esile"</string>
<plurals name="screen_pinned_timeline_screen_title">
<item quantity="one">"%1$d esiletõstetud sõnum"</item>
<item quantity="other">"%1$d esiletõstetud sõnumit"</item>
</plurals>
<string name="screen_pinned_timeline_screen_title_empty">"Esiletõstetud sõnumid"</string>
<string name="screen_reset_encryption_confirmation_alert_action">"Jah, lähtesta nüüd"</string>
<string name="screen_reset_encryption_confirmation_alert_subtitle">"See tegevus on tagasipöördumatu."</string>
<string name="screen_reset_encryption_confirmation_alert_title">"Kas sa oled kindel, et soovid oma andmete krüptimist lähtestada?"</string>
<string name="screen_reset_encryption_password_placeholder">"Sisesta…"</string>
<string name="screen_reset_encryption_password_subtitle">"Palun kinnita, et soovid oma andmete krüptimist lähtestada."</string>
<string name="screen_reset_encryption_password_title">"Jätkamaks sisesta oma kasutajakonto salasõna"</string>
<string name="screen_room_details_pinned_events_row_title">"Esiletõstetud sõnumid"</string>
<string name="screen_room_error_failed_processing_media">"Meediafaili töötlemine enne üleslaadimist ei õnnestunud. Palun proovi uuesti."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Kasutaja andmete laadimine ei õnnestunud"</string>
<string name="screen_room_member_details_block_alert_action">"Blokeeri"</string>
@ -278,7 +301,8 @@ Põhjus: %1$s."</string>
<string name="screen_room_member_details_unblock_alert_description">"Nüüd näed sa jälle kõiki tema sõnumeid"</string>
<string name="screen_room_member_details_unblock_user">"Eemalda kasutajalt blokeering"</string>
<string name="screen_room_pinned_banner_indicator">"%1$s / %2$s"</string>
<string name="screen_room_pinned_banner_indicator_description">"%1$s kinnitatud sõnumit"</string>
<string name="screen_room_pinned_banner_indicator_description">"%1$s esiletõstetud sõnumit"</string>
<string name="screen_room_pinned_banner_loading_description">"Laadime sõnumit…"</string>
<string name="screen_room_pinned_banner_view_all_button_title">"Näita kõiki"</string>
<string name="screen_room_title">"Vestlus"</string>
<string name="screen_share_location_title">"Jaga asukohta"</string>

View file

@ -91,6 +91,7 @@
<string name="action_report_bug">"Hiba jelentése"</string>
<string name="action_report_content">"Tartalom jelentése"</string>
<string name="action_reset">"Visszaállítás"</string>
<string name="action_reset_identity">"Személyazonosság visszaállítása"</string>
<string name="action_retry">"Újra"</string>
<string name="action_retry_decryption">"Visszafejtés újbóli megpróbálása"</string>
<string name="action_save">"Mentés"</string>
@ -110,6 +111,7 @@
<string name="action_take_photo">"Fénykép készítése"</string>
<string name="action_tap_for_options">"Koppintson a beállításokért"</string>
<string name="action_try_again">"Próbálja újra"</string>
<string name="action_unpin">"Kitűzés feloldása"</string>
<string name="action_view_source">"Forrás megtekintése"</string>
<string name="action_yes">"Igen"</string>
<string name="common_about">"Névjegy"</string>
@ -258,6 +260,7 @@ Ok: %1$s."</string>
<string name="error_some_messages_have_not_been_sent">"Néhány üzenet nem került elküldésre"</string>
<string name="error_unknown">"Elnézést, hiba történt"</string>
<string name="event_shield_reason_authenticity_not_guaranteed">"A titkosított üzenetek valódiságát ezen az eszközön nem lehet garantálni."</string>
<string name="event_shield_reason_sent_in_clear">"Nincs titkosítva."</string>
<string name="event_shield_reason_unknown_device">"Ismeretlen vagy törölt eszköz által titkosítva."</string>
<string name="event_shield_reason_unsigned_device">"A tulajdonos által nem ellenőrzött eszköz által titkosítva."</string>
<string name="event_shield_reason_unverified_identity">"Nem ellenőrzött felhasználó által titkosítva."</string>
@ -265,9 +268,22 @@ Ok: %1$s."</string>
<string name="invite_friends_text">"Beszélgessünk itt: %1$s, %2$s"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<string name="preference_rageshake">"Az eszköz rázása a hibajelentéshez"</string>
<string name="screen_encryption_reset_bullet_1">"A fiókadatok, a kapcsolatok, a beállítások és a csevegéslista megmarad"</string>
<string name="screen_encryption_reset_bullet_2">"Elveszíti meglévő üzenetelőzményeit"</string>
<string name="screen_encryption_reset_bullet_3">"Újból ellenőriznie kell az összes meglévő eszközét és csevegőpartnerét"</string>
<string name="screen_encryption_reset_footer">"Csak akkor állítsa vissza a személyazonosságát, ha nem fér hozzá másik bejelentkezett eszközhöz, és elvesztette a helyreállítási kulcsot."</string>
<string name="screen_encryption_reset_subtitle">"Ha nincs bejelentkezve más eszközre, és elvesztette a helyreállítási kulcsot, akkor az alkalmazás használatának folytatásához vissza kell állítania személyazonosságát. "</string>
<string name="screen_encryption_reset_title">"Állítsa vissza a személyazonosságát, ha más módon nem tudja megerősíteni"</string>
<string name="screen_media_picker_error_failed_selection">"Nem sikerült kiválasztani a médiát, próbálja újra."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Nem sikerült a média feltöltése, próbálja újra."</string>
<string name="screen_reset_encryption_confirmation_alert_action">"Igen, visszaállítás most"</string>
<string name="screen_reset_encryption_confirmation_alert_subtitle">"Ez a folyamat visszafordíthatatlan."</string>
<string name="screen_reset_encryption_confirmation_alert_title">"Biztos, hogy visszaállítja a titkosítást?"</string>
<string name="screen_reset_encryption_password_placeholder">"Adja meg…"</string>
<string name="screen_reset_encryption_password_subtitle">"Erősítse meg, hogy vissza szeretné állítani a titkosítást."</string>
<string name="screen_reset_encryption_password_title">"A folytatáshoz adja meg fiókja jelszavát"</string>
<string name="screen_room_details_pinned_events_row_title">"Kitűzött üzenetek"</string>
<string name="screen_room_error_failed_processing_media">"Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Nem sikerült letölteni a felhasználói adatokat"</string>
<string name="screen_room_member_details_block_alert_action">"Letiltás"</string>
@ -277,6 +293,10 @@ Ok: %1$s."</string>
<string name="screen_room_member_details_unblock_alert_action">"Letiltás feloldása"</string>
<string name="screen_room_member_details_unblock_alert_description">"Újra láthatja az összes üzenetét."</string>
<string name="screen_room_member_details_unblock_user">"Felhasználó kitiltásának feloldása"</string>
<string name="screen_room_pinned_banner_indicator">"%1$s / %2$s"</string>
<string name="screen_room_pinned_banner_indicator_description">"%1$s kitűzött üzenet"</string>
<string name="screen_room_pinned_banner_loading_description">"Üzenet betöltése…"</string>
<string name="screen_room_pinned_banner_view_all_button_title">"Összes megtekintése"</string>
<string name="screen_room_title">"Csevegés"</string>
<string name="screen_share_location_title">"Hely megosztása"</string>
<string name="screen_share_my_location_action">"Saját hely megosztása"</string>

View file

@ -124,7 +124,7 @@
<string name="common_call_invite">"Rozmowa w trakcie (niewspierane)"</string>
<string name="common_call_started">"Rozpoczęto rozmowę"</string>
<string name="common_chat_backup">"Backup czatu"</string>
<string name="common_copyright">"Copyright"</string>
<string name="common_copyright">"Prawa autorskie"</string>
<string name="common_creating_room">"Tworzenie pokoju…"</string>
<string name="common_current_user_left_room">"Opuścił pokój"</string>
<string name="common_dark">"Ciemny"</string>
@ -260,10 +260,14 @@ Powód: %1$s."</string>
<string name="error_missing_microphone_voice_rationale_android">"%1$s nie ma uprawnień dostępu do Twojego mikrofonu. Włącz dostęp, aby nagrać wiadomość głosową."</string>
<string name="error_some_messages_have_not_been_sent">"Niektóre wiadomości nie zostały wysłane"</string>
<string name="error_unknown">"Przepraszamy, wystąpił błąd"</string>
<string name="event_shield_reason_authenticity_not_guaranteed">"Autentyczność tej wiadomości szyfrowanej nie jest gwarantowana na tym urządzeniu."</string>
<string name="event_shield_reason_unknown_device">"Zaszyfrowana przez nieznane lub usunięte urządzenie."</string>
<string name="event_shield_reason_unsigned_device">"Zaszyfrowana przez urządzenie niezweryfikowane przez jego właściciela."</string>
<string name="event_shield_reason_unverified_identity">"Zaszyfrowana przez niezweryfikowanego użytkownika."</string>
<string name="invite_friends_rich_title">"🔐️ Dołącz do mnie na %1$s"</string>
<string name="invite_friends_text">"Hej, porozmawiajmy na %1$s: %2$s"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<string name="preference_rageshake">"Gniewne wstrząsanie, aby zgłosić błąd"</string>
<string name="preference_rageshake">"Wstrząśnij gniewnie, aby zgłosić błąd"</string>
<string name="screen_media_picker_error_failed_selection">"Nie udało się wybrać multimediów. Spróbuj ponownie."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Przetwarzanie multimediów do przesłania nie powiodło się, spróbuj ponownie."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Przesyłanie multimediów nie powiodło się, spróbuj ponownie."</string>
@ -272,9 +276,11 @@ Powód: %1$s."</string>
<string name="screen_room_member_details_block_alert_action">"Zablokuj"</string>
<string name="screen_room_member_details_block_alert_description">"Zablokowani użytkownicy nie będą mogli wysyłać Ci wiadomości, a wszystkie ich wiadomości zostaną ukryte. Możesz odblokować ich w dowolnym momencie."</string>
<string name="screen_room_member_details_block_user">"Zablokuj użytkownika"</string>
<string name="screen_room_member_details_title">"Profil"</string>
<string name="screen_room_member_details_unblock_alert_action">"Odblokuj"</string>
<string name="screen_room_member_details_unblock_alert_description">"Będziesz mógł ponownie zobaczyć wszystkie wiadomości od tego użytkownika."</string>
<string name="screen_room_member_details_unblock_user">"Odblokuj użytkownika"</string>
<string name="screen_room_title">"Czat"</string>
<string name="screen_share_location_title">"Udostępnij lokalizację"</string>
<string name="screen_share_my_location_action">"Udostępnij moją lokalizację"</string>
<string name="screen_share_open_apple_maps">"Otwórz w Apple Maps"</string>

View file

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="a11y_delete">"Excluir"</string>
<plurals name="a11y_digits_entered">
<item quantity="one">"%1$d dígito inserido"</item>
<item quantity="other">"%1$d dígitos inseridos"</item>
</plurals>
<string name="a11y_hide_password">"Ocultar senha"</string>
<string name="a11y_jump_to_bottom">"Ir para o final"</string>
<string name="a11y_notifications_mentions_only">"Apenas menções"</string>
@ -14,6 +18,10 @@
<string name="a11y_react_with">"Reagir com %1$s"</string>
<string name="a11y_react_with_other_emojis">"Reaja com outros emojis"</string>
<string name="a11y_read_receipts_multiple">"Lido por %1$s e %2$s"</string>
<plurals name="a11y_read_receipts_multiple_with_others">
<item quantity="one">"Lido por %1$s e %2$d outro"</item>
<item quantity="other">"Lido por %1$s e %2$d outros"</item>
</plurals>
<string name="a11y_read_receipts_single">"Lido por %1$s"</string>
<string name="a11y_read_receipts_tap_to_show_all">"Toque para mostrar tudo"</string>
<string name="a11y_remove_reaction_with">"Remova a reação com %1$s"</string>
@ -186,7 +194,7 @@
<string name="common_room">"Sala"</string>
<string name="common_room_name">"Nome da sala"</string>
<string name="common_room_name_placeholder">"por exemplo, o nome do seu projeto"</string>
<string name="common_saved_changes">"Mudanças salvas"</string>
<string name="common_saved_changes">"Alterações salvas"</string>
<string name="common_saving">"Salvando"</string>
<string name="common_screen_lock">"Bloqueio de tela"</string>
<string name="common_search_for_someone">"Procurar alguém"</string>
@ -233,8 +241,8 @@
<string name="dialog_title_error">"Erro"</string>
<string name="dialog_title_success">"Sucesso"</string>
<string name="dialog_title_warning">"Aviso"</string>
<string name="dialog_unsaved_changes_description_android">"Suas mudanças não foram salvas. Tem certeza de que você quer voltar?"</string>
<string name="dialog_unsaved_changes_title">"Salvar mudanças?"</string>
<string name="dialog_unsaved_changes_description_android">"Suas alterações não foram salvas. Tem certeza de que você quer voltar?"</string>
<string name="dialog_unsaved_changes_title">"Salvar alterações?"</string>
<string name="error_failed_creating_the_permalink">"Falha ao criar o link permanente"</string>
<string name="error_failed_loading_map">"%1$s não conseguiu carregar o mapa. Por favor, tente novamente mais tarde."</string>
<string name="error_failed_loading_messages">"Falha ao carregar mensagens"</string>
@ -243,6 +251,7 @@
<string name="error_message_not_found">"Mensagem não encontrada"</string>
<string name="error_missing_location_auth_android">"%1$s não tem permissão para acessar sua localização. Você pode ativar o acesso nas Configurações."</string>
<string name="error_missing_location_rationale_android">"%1$s não tem permissão para acessar sua localização. Habilite o acesso abaixo."</string>
<string name="error_missing_microphone_voice_rationale_android">"%1$s não tem permissão para acessar seu microfone. Permita o acesso para gravar uma mensagem de voz."</string>
<string name="error_some_messages_have_not_been_sent">"Algumas mensagens não foram enviadas"</string>
<string name="error_unknown">"Desculpe, ocorreu um erro"</string>
<string name="event_shield_reason_unknown_device">"Criptografada por um dispositivo desconhecido ou apagado."</string>

View file

@ -80,6 +80,7 @@
<string name="action_ok">"OK"</string>
<string name="action_open_settings">"Configurações"</string>
<string name="action_open_with">"Abrir com"</string>
<string name="action_pin">"Afixar"</string>
<string name="action_quick_reply">"Resposta rápida"</string>
<string name="action_quote">"Citação"</string>
<string name="action_react">"Reagir"</string>
@ -90,6 +91,7 @@
<string name="action_report_bug">"Comunicar problema"</string>
<string name="action_report_content">"Denunciar conteúdo"</string>
<string name="action_reset">"Repor"</string>
<string name="action_reset_identity">"Repor identidade"</string>
<string name="action_retry">"Tentar novamente"</string>
<string name="action_retry_decryption">"Tentar decifragem novamente"</string>
<string name="action_save">"Guardar"</string>
@ -109,6 +111,7 @@
<string name="action_take_photo">"Tirar foto"</string>
<string name="action_tap_for_options">"Toca para ver as opções"</string>
<string name="action_try_again">"Tentar novamente"</string>
<string name="action_unpin">"Desafixar"</string>
<string name="action_view_source">"Ver fonte"</string>
<string name="action_yes">"Sim"</string>
<string name="common_about">"Sobre"</string>
@ -168,6 +171,7 @@ Razão: %1$s."</string>
<string name="common_no_results">"Sem resultados"</string>
<string name="common_no_room_name">"Sala sem nome"</string>
<string name="common_offline">"Desligado"</string>
<string name="common_open_source_licenses">"Licenças de código aberto"</string>
<string name="common_or">"ou"</string>
<string name="common_password">"Senha"</string>
<string name="common_people">"Pessoas"</string>
@ -255,13 +259,29 @@ Razão: %1$s."</string>
<string name="error_missing_microphone_voice_rationale_android">"A %1$s não tem permissão para aceder ao teu microfone. Permite o acesso para gravar uma mensagem de voz."</string>
<string name="error_some_messages_have_not_been_sent">"Algumas mensagens não foram enviadas"</string>
<string name="error_unknown">"Ocorreu um erro, desculpa"</string>
<string name="event_shield_reason_authenticity_not_guaranteed">"A autenticidade desta mensagem cifrada não pode ser garantida neste dispositivo."</string>
<string name="event_shield_reason_unknown_device">"Cifragem com origem num dispositivo eliminado ou desconhecido."</string>
<string name="event_shield_reason_unsigned_device">"Cifragem com origem num dispositivo não verificado pelo seu dono."</string>
<string name="event_shield_reason_unverified_identity">"Cifragem com origem num utilizador não verificado."</string>
<string name="invite_friends_rich_title">"🔐️ Junta-te a mim na %1$s"</string>
<string name="invite_friends_text">"Alô! Fala comigo na %1$s: %2$s"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<string name="preference_rageshake">"Agita o dispositivo em fúria para comunicar um problema"</string>
<string name="screen_encryption_reset_bullet_1">"Os detalhes da tua conta, contactos, preferências e lista de conversas serão mantidos."</string>
<string name="screen_encryption_reset_bullet_2">"Perderás o acesso ao teu histórico de mensagens existente"</string>
<string name="screen_encryption_reset_bullet_3">"Necessitarás de verificar todos os teus dispositivos e contactos novamente."</string>
<string name="screen_encryption_reset_footer">"Repõe a tua identidade apenas se não tiveres acesso a mais nenhum dispositivo com sessão iniciada e se tiveres perdido a tua chave de recuperação."</string>
<string name="screen_encryption_reset_subtitle">"Se não tiveres sessão iniciada em nenhum outro dispositivo e perdeste o acesso à tua chave de recuperação, precisarás de repor a tua identidade para continuares a usar a aplicação. "</string>
<string name="screen_encryption_reset_title">"Repõe a tua identidade caso não consigas confirmar de outra forma"</string>
<string name="screen_media_picker_error_failed_selection">"Falha ao selecionar multimédia, por favor tente novamente."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Falha ao processar multimédia para carregamento, por favor tente novamente."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Falhar ao carregar multimédia, por favor tente novamente."</string>
<string name="screen_reset_encryption_confirmation_alert_action">"Sim, repor agora"</string>
<string name="screen_reset_encryption_confirmation_alert_subtitle">"Este processo é irreversível."</string>
<string name="screen_reset_encryption_confirmation_alert_title">"Tens a certeza que pretendes repor a tua cifra?"</string>
<string name="screen_reset_encryption_password_placeholder">"Inserir…"</string>
<string name="screen_reset_encryption_password_subtitle">"Confirma que pretendes realmente repor a tua cifra."</string>
<string name="screen_reset_encryption_password_title">"Insere a tua palavra-passe para continuares"</string>
<string name="screen_room_error_failed_processing_media">"Falha ao processar multimédia para carregamento, por favor tente novamente."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Não foi possível obter os detalhes de utilizador."</string>
<string name="screen_room_member_details_block_alert_action">"Bloquear"</string>
@ -271,6 +291,9 @@ Razão: %1$s."</string>
<string name="screen_room_member_details_unblock_alert_action">"Desbloquear"</string>
<string name="screen_room_member_details_unblock_alert_description">"Poderás voltar a ver todas as suas mensagens."</string>
<string name="screen_room_member_details_unblock_user">"Desbloquear utilizador"</string>
<string name="screen_room_pinned_banner_indicator">"%1$s de %2$s"</string>
<string name="screen_room_pinned_banner_indicator_description">"%1$s mensagens afixadas"</string>
<string name="screen_room_pinned_banner_view_all_button_title">"Ver todas"</string>
<string name="screen_room_title">"Conversa"</string>
<string name="screen_share_location_title">"Partilhar localização"</string>
<string name="screen_share_my_location_action">"Partilhar a minha localização"</string>

View file

@ -82,6 +82,7 @@
<string name="action_ok">"Ок"</string>
<string name="action_open_settings">"Открыть настройки"</string>
<string name="action_open_with">"Открыть с помощью"</string>
<string name="action_pin">"Закрепить"</string>
<string name="action_quick_reply">"Быстрый ответ"</string>
<string name="action_quote">"Цитата"</string>
<string name="action_react">"Реакция"</string>
@ -92,6 +93,7 @@
<string name="action_report_bug">"Сообщить об ошибке"</string>
<string name="action_report_content">"Пожаловаться на содержание"</string>
<string name="action_reset">"Сбросить"</string>
<string name="action_reset_identity">"Сбросить идентификацию"</string>
<string name="action_retry">"Повторить"</string>
<string name="action_retry_decryption">"Повторите расшифровку"</string>
<string name="action_save">"Сохранить"</string>
@ -111,6 +113,7 @@
<string name="action_take_photo">"Сделать фото"</string>
<string name="action_tap_for_options">"Нажмите для просмотра вариантов"</string>
<string name="action_try_again">"Повторить попытку"</string>
<string name="action_unpin">"Открепить"</string>
<string name="action_view_source">"Показать источник"</string>
<string name="action_yes">"Да"</string>
<string name="common_about">"О приложении"</string>
@ -171,6 +174,7 @@
<string name="common_no_results">"Ничего не найдено"</string>
<string name="common_no_room_name">"Нету названия комнаты"</string>
<string name="common_offline">"Не в сети"</string>
<string name="common_open_source_licenses">"Лицензии с открытым исходным кодом"</string>
<string name="common_or">"или"</string>
<string name="common_password">"Пароль"</string>
<string name="common_people">"Люди"</string>
@ -261,13 +265,39 @@
<string name="error_missing_microphone_voice_rationale_android">"%1$s не имеет разрешения на доступ к вашему микрофону. Разрешите доступ к записи голосового сообщения."</string>
<string name="error_some_messages_have_not_been_sent">"Некоторые сообщения не были отправлены"</string>
<string name="error_unknown">"Извините, произошла ошибка"</string>
<string name="event_shield_reason_authenticity_not_guaranteed">"Подлинность этого зашифрованного сообщения не может быть гарантирована на этом устройстве."</string>
<string name="event_shield_reason_sent_in_clear">"Не зашифровано."</string>
<string name="event_shield_reason_unknown_device">"Зашифровано неизвестным или удаленным устройством."</string>
<string name="event_shield_reason_unsigned_device">"Зашифровано устройством, не проверенным его владельцем."</string>
<string name="event_shield_reason_unverified_identity">"Зашифровано непроверенным пользователем."</string>
<string name="invite_friends_rich_title">"🔐️ Присоединяйтесь ко мне в %1$s"</string>
<string name="invite_friends_text">"Привет, поговори со мной по %1$s: %2$s"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<string name="preference_rageshake">"Встряхните устройство, чтобы сообщить об ошибке"</string>
<string name="screen_encryption_reset_bullet_1">"Данные вашей учетной записи, контакты, настройки и список чатов будут сохранены"</string>
<string name="screen_encryption_reset_bullet_2">"Вы потеряете существующую историю сообщений"</string>
<string name="screen_encryption_reset_bullet_3">"Вам нужно будет заново подтвердить все существующие устройства и контакты."</string>
<string name="screen_encryption_reset_footer">"Сбрасывайте данные только в том случае, если у вас нет доступа к другому устройству, на котором выполнен вход, и вы потеряли ключ восстановления."</string>
<string name="screen_encryption_reset_subtitle">"Если вы не вошли в систему на других устройствах и потеряли ключ восстановления, вам необходимо сбросить учетные данные, чтобы продолжить использование приложения. "</string>
<string name="screen_encryption_reset_title">"Сбросьте ключи подтверждения, если вы не можете подтвердить свою личность другим способом."</string>
<string name="screen_media_picker_error_failed_selection">"Не удалось выбрать носитель, попробуйте еще раз."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Не удалось обработать медиафайл для загрузки, попробуйте еще раз."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Не удалось загрузить медиафайлы, попробуйте еще раз."</string>
<string name="screen_pinned_timeline_empty_state_description">"Нажмите на сообщение и выберите “%1$s”, чтобы добавить его сюда."</string>
<string name="screen_pinned_timeline_empty_state_headline">"Закрепите важные сообщения, чтобы их можно было легко найти"</string>
<plurals name="screen_pinned_timeline_screen_title">
<item quantity="one">"%1$d Закрепленное сообщение"</item>
<item quantity="few">"%1$d Закрепленных сообщений"</item>
<item quantity="many">"%1$d Закрепленных сообщений"</item>
</plurals>
<string name="screen_pinned_timeline_screen_title_empty">"Закрепленные сообщения"</string>
<string name="screen_reset_encryption_confirmation_alert_action">"Да, сбросить сейчас"</string>
<string name="screen_reset_encryption_confirmation_alert_subtitle">"Этот процесс необратим."</string>
<string name="screen_reset_encryption_confirmation_alert_title">"Вы действительно хотите сбросить шифрование?"</string>
<string name="screen_reset_encryption_password_placeholder">"Ввод…"</string>
<string name="screen_reset_encryption_password_subtitle">"Подтвердите, что вы хотите сбросить шифрование."</string>
<string name="screen_reset_encryption_password_title">"Введите пароль своей учетной записи, чтобы продолжить"</string>
<string name="screen_room_details_pinned_events_row_title">"Закрепленные сообщения"</string>
<string name="screen_room_error_failed_processing_media">"Не удалось обработать медиафайл для загрузки, попробуйте еще раз."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Не удалось получить данные о пользователе"</string>
<string name="screen_room_member_details_block_alert_action">"Заблокировать"</string>
@ -277,6 +307,10 @@
<string name="screen_room_member_details_unblock_alert_action">"Разблокировать"</string>
<string name="screen_room_member_details_unblock_alert_description">"Вы снова сможете увидеть все сообщения."</string>
<string name="screen_room_member_details_unblock_user">"Разблокировать пользователя"</string>
<string name="screen_room_pinned_banner_indicator">"%1$s из %2$s"</string>
<string name="screen_room_pinned_banner_indicator_description">"%1$s Закрепленные сообщения"</string>
<string name="screen_room_pinned_banner_loading_description">"Загрузка сообщения…"</string>
<string name="screen_room_pinned_banner_view_all_button_title">"Посмотреть все"</string>
<string name="screen_room_title">"Чат"</string>
<string name="screen_share_location_title">"Поделиться местоположением"</string>
<string name="screen_share_my_location_action">"Поделиться моим местоположением"</string>

View file

@ -93,6 +93,7 @@
<string name="action_report_bug">"Nahlásiť chybu"</string>
<string name="action_report_content">"Nahlásiť obsah"</string>
<string name="action_reset">"Obnoviť"</string>
<string name="action_reset_identity">"Obnoviť identitu"</string>
<string name="action_retry">"Skúsiť znova"</string>
<string name="action_retry_decryption">"Opakovať dešifrovanie"</string>
<string name="action_save">"Uložiť"</string>
@ -112,6 +113,7 @@
<string name="action_take_photo">"Urobiť fotku"</string>
<string name="action_tap_for_options">"Klepnutím získate možnosti"</string>
<string name="action_try_again">"Skúste to znova"</string>
<string name="action_unpin">"Odopnúť"</string>
<string name="action_view_source">"Zobraziť zdroj"</string>
<string name="action_yes">"Áno"</string>
<string name="common_about">"O aplikácii"</string>
@ -262,6 +264,7 @@ Dôvod: %1$s."</string>
<string name="error_some_messages_have_not_been_sent">"Niektoré správy neboli odoslané"</string>
<string name="error_unknown">"Prepáčte, vyskytla sa chyba"</string>
<string name="event_shield_reason_authenticity_not_guaranteed">"Pravosť tejto šifrovanej správy nie je možné zaručiť na tomto zariadení."</string>
<string name="event_shield_reason_sent_in_clear">"Nie je šifrované."</string>
<string name="event_shield_reason_unknown_device">"Zašifrované neznámym alebo odstráneným zariadením."</string>
<string name="event_shield_reason_unsigned_device">"Šifrované zariadením, ktoré nie je overené jeho majiteľom."</string>
<string name="event_shield_reason_unverified_identity">"Šifrované neovereným používateľom."</string>
@ -269,9 +272,30 @@ Dôvod: %1$s."</string>
<string name="invite_friends_text">"Ahoj, porozprávajte sa so mnou na %1$s: %2$s"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<string name="preference_rageshake">"Zúrivo potriasť pre nahlásenie chyby"</string>
<string name="screen_encryption_reset_bullet_1">"Údaje o vašom účte, kontakty, predvoľby a zoznam konverzácií budú zachované"</string>
<string name="screen_encryption_reset_bullet_2">"Stratíte svoju existujúcu históriu správ"</string>
<string name="screen_encryption_reset_bullet_3">"Budete musieť znova overiť všetky existujúce zariadenia a kontakty"</string>
<string name="screen_encryption_reset_footer">"Obnovte svoju totožnosť iba vtedy, ak nemáte prístup k inému prihlásenému zariadeniu a stratili ste kľúč na obnovenie."</string>
<string name="screen_encryption_reset_subtitle">"Ak nie ste prihlásení do žiadneho iného zariadenia a stratili ste kľúč na obnovenie, budete musieť znovu obnoviť svoju identitu, aby ste mohli pokračovať v používaní aplikácie. "</string>
<string name="screen_encryption_reset_title">"Znovu nastavte svoju totožnosť v prípade, že ju nemôžete potvrdiť iným spôsobom"</string>
<string name="screen_media_picker_error_failed_selection">"Nepodarilo sa vybrať médium, skúste to prosím znova."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Nepodarilo sa nahrať médiá, skúste to prosím znova."</string>
<string name="screen_pinned_timeline_empty_state_description">"Stlačte správu a vyberte možnosť „%1$s“, ktorú chcete zahrnúť sem."</string>
<string name="screen_pinned_timeline_empty_state_headline">"Pripnite dôležité správy, aby sa dali ľahko nájsť"</string>
<plurals name="screen_pinned_timeline_screen_title">
<item quantity="one">"%1$d pripnutá správa"</item>
<item quantity="few">"%1$d pripnuté správy"</item>
<item quantity="other">"%1$d pripnutých správ"</item>
</plurals>
<string name="screen_pinned_timeline_screen_title_empty">"Pripnuté správy"</string>
<string name="screen_reset_encryption_confirmation_alert_action">"Áno, znovu nastaviť teraz"</string>
<string name="screen_reset_encryption_confirmation_alert_subtitle">"Tento proces je nezvratný."</string>
<string name="screen_reset_encryption_confirmation_alert_title">"Naozaj chcete obnoviť svoje šifrovanie?"</string>
<string name="screen_reset_encryption_password_placeholder">"Zadajte…"</string>
<string name="screen_reset_encryption_password_subtitle">"Potvrďte, že chcete obnoviť svoje šifrovanie."</string>
<string name="screen_reset_encryption_password_title">"Ak chcete pokračovať, zadajte heslo účtu"</string>
<string name="screen_room_details_pinned_events_row_title">"Pripnuté správy"</string>
<string name="screen_room_error_failed_processing_media">"Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Nepodarilo sa získať údaje o používateľovi"</string>
<string name="screen_room_member_details_block_alert_action">"Zablokovať"</string>
@ -283,6 +307,7 @@ Dôvod: %1$s."</string>
<string name="screen_room_member_details_unblock_user">"Odblokovať používateľa"</string>
<string name="screen_room_pinned_banner_indicator">"%1$s z %2$s"</string>
<string name="screen_room_pinned_banner_indicator_description">"%1$s Pripnutých správ"</string>
<string name="screen_room_pinned_banner_loading_description">"Načítava sa správa…"</string>
<string name="screen_room_pinned_banner_view_all_button_title">"Zobraziť všetko"</string>
<string name="screen_room_title">"Konverzácia"</string>
<string name="screen_share_location_title">"Zdieľať polohu"</string>

View file

@ -34,6 +34,7 @@
<string name="action_accept">"Godkänn"</string>
<string name="action_add_to_timeline">"Lägg till i tidslinjen"</string>
<string name="action_back">"Tillbaka"</string>
<string name="action_call">"Ring"</string>
<string name="action_cancel">"Avbryt"</string>
<string name="action_choose_photo">"Välj bild"</string>
<string name="action_clear">"Rensa"</string>
@ -72,21 +73,25 @@
<string name="action_load_more">"Ladda mer"</string>
<string name="action_manage_account">"Hantera konto"</string>
<string name="action_manage_devices">"Hantera enheter"</string>
<string name="action_message">"Meddela"</string>
<string name="action_next">"Nästa"</string>
<string name="action_no">"Nej"</string>
<string name="action_not_now">"Inte nu"</string>
<string name="action_ok">"OK"</string>
<string name="action_open_settings">"Inställningar"</string>
<string name="action_open_with">"Öppna med"</string>
<string name="action_pin">"Fäst"</string>
<string name="action_quick_reply">"Snabbsvar"</string>
<string name="action_quote">"Citera"</string>
<string name="action_react">"Reagera"</string>
<string name="action_reject">"Avvisa"</string>
<string name="action_remove">"Ta bort"</string>
<string name="action_reply">"Svara"</string>
<string name="action_reply_in_thread">"Svara i tråd"</string>
<string name="action_report_bug">"Rapportera bugg"</string>
<string name="action_report_content">"Rapportera innehåll"</string>
<string name="action_reset">"Återställ"</string>
<string name="action_reset_identity">"Återställ identitet"</string>
<string name="action_retry">"Försök igen"</string>
<string name="action_retry_decryption">"Försök att avkryptera igen"</string>
<string name="action_save">"Spara"</string>
@ -106,6 +111,7 @@
<string name="action_take_photo">"Ta ett foto"</string>
<string name="action_tap_for_options">"Tryck för alternativ"</string>
<string name="action_try_again">"Försök igen"</string>
<string name="action_unpin">"Frigör"</string>
<string name="action_view_source">"Visa källkod"</string>
<string name="action_yes">"Ja"</string>
<string name="common_about">"Om"</string>
@ -117,6 +123,7 @@
<string name="common_blocked_users">"Blockerade användare"</string>
<string name="common_bubbles">"Bubblor"</string>
<string name="common_call_invite">"Samtal pågår (stöds inte)"</string>
<string name="common_call_started">"Samtal startat"</string>
<string name="common_chat_backup">"Chattsäkerhetskopia"</string>
<string name="common_copyright">"Upphovsrätt"</string>
<string name="common_creating_room">"Skapar rum …"</string>
@ -125,12 +132,16 @@
<string name="common_decryption_error">"Avkrypteringsfel"</string>
<string name="common_developer_options">"Utvecklaralternativ"</string>
<string name="common_direct_chat">"Direktchatt"</string>
<string name="common_do_not_show_this_again">"Visa inte detta igen"</string>
<string name="common_edited_suffix">"(redigerad)"</string>
<string name="common_editing">"Redigerar"</string>
<string name="common_emote">"* %1$s %2$s"</string>
<string name="common_encryption_enabled">"Kryptering aktiverad"</string>
<string name="common_enter_your_pin">"Ange din PIN-kod"</string>
<string name="common_error">"Fel"</string>
<string name="common_error_registering_pusher_android">"Ett fel inträffade, du kanske inte får aviseringar för nya meddelanden. Felsök aviseringar från inställningarna.
Anledning:%1$s."</string>
<string name="common_everyone">"Alla"</string>
<string name="common_failed">"Misslyckades"</string>
<string name="common_favourite">"Favorit"</string>
@ -158,12 +169,15 @@
<string name="common_modern">"Modernt"</string>
<string name="common_mute">"Tysta"</string>
<string name="common_no_results">"Inga resultat"</string>
<string name="common_no_room_name">"Inget rumsnamn"</string>
<string name="common_offline">"Frånkopplad"</string>
<string name="common_open_source_licenses">"Licenser för öppen källkod"</string>
<string name="common_or">"eller"</string>
<string name="common_password">"Lösenord"</string>
<string name="common_people">"Personer"</string>
<string name="common_permalink">"Permalänk"</string>
<string name="common_permission">"Behörighet"</string>
<string name="common_please_wait">"Vänligen vänta …"</string>
<string name="common_poll_end_confirmation">"Är du säker på att du vill avsluta den här omröstningen?"</string>
<string name="common_poll_summary">"Omröstning: %1$s"</string>
<string name="common_poll_total_votes">"Totalt antal röster: %1$s"</string>
@ -192,6 +206,7 @@
<string name="common_search_results">"Sökresultat"</string>
<string name="common_security">"Säkerhet"</string>
<string name="common_seen_by">"Sett av"</string>
<string name="common_send_to">"Skicka till"</string>
<string name="common_sending">"Skickar …"</string>
<string name="common_sending_failed">"Misslyckades att skicka"</string>
<string name="common_sent">"Skickat"</string>
@ -200,6 +215,7 @@
<string name="common_settings">"Inställningar"</string>
<string name="common_shared_location">"Delade plats"</string>
<string name="common_signing_out">"Loggar ut"</string>
<string name="common_something_went_wrong">"Något gick fel"</string>
<string name="common_starting_chat">"Startar chatt …"</string>
<string name="common_sticker">"Dekal"</string>
<string name="common_success">"Lyckades"</string>
@ -212,6 +228,7 @@
<string name="common_topic">"Ämne"</string>
<string name="common_topic_placeholder">"Vad handlar det här rummet om?"</string>
<string name="common_unable_to_decrypt">"Kan inte avkryptera"</string>
<string name="common_unable_to_decrypt_no_access">"Du har inte tillgång till det här meddelandet"</string>
<string name="common_unable_to_invite_message">"Inbjudan kunde inte skickas till en eller flera användare."</string>
<string name="common_unable_to_invite_title">"Kunde inte skicka inbjudningar"</string>
<string name="common_unlock">"Lås upp"</string>
@ -236,18 +253,44 @@
<string name="error_failed_loading_messages">"Misslyckades att ladda meddelanden"</string>
<string name="error_failed_locating_user">"%1$s kunde inte komma åt din plats. Vänligen försök igen senare."</string>
<string name="error_failed_uploading_voice_message">"Misslyckades med att ladda upp ditt röstmeddelande."</string>
<string name="error_message_not_found">"Meddelandet hittades inte"</string>
<string name="error_missing_location_auth_android">"%1$s är inte behörig att komma åt din plats. Du kan aktivera åtkomst i Inställningar."</string>
<string name="error_missing_location_rationale_android">"%1$s är inte behörig att komma åt din plats. Aktivera åtkomst nedan."</string>
<string name="error_missing_microphone_voice_rationale_android">"%1$s är inte behörig att komma åt din mikrofon. Aktivera åtkomst för att spela in ett röstmeddelande."</string>
<string name="error_some_messages_have_not_been_sent">"Vissa meddelanden har inte skickats"</string>
<string name="error_unknown">"Tyvärr, ett fel uppstod"</string>
<string name="event_shield_reason_authenticity_not_guaranteed">"Detta krypterade meddelandes äkthet kan inte garanteras på den här enheten."</string>
<string name="event_shield_reason_sent_in_clear">"Inte krypterad."</string>
<string name="event_shield_reason_unknown_device">"Krypterad av en okänd eller raderad enhet."</string>
<string name="event_shield_reason_unsigned_device">"Krypterad av en enhet som inte verifierats av ägaren."</string>
<string name="event_shield_reason_unverified_identity">"Krypterad av en overifierad användare."</string>
<string name="invite_friends_rich_title">"🔐️ Häng med mig på %1$s"</string>
<string name="invite_friends_text">"Hallå, prata med mig på %1$s: %2$s"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<string name="preference_rageshake">"Raseriskaka för att rapportera bugg"</string>
<string name="screen_encryption_reset_bullet_1">"Dina kontouppgifter, kontakter, inställningar och chattlistor kommer bevaras"</string>
<string name="screen_encryption_reset_bullet_2">"Du kommer att förlora din befintliga meddelandehistorik"</string>
<string name="screen_encryption_reset_bullet_3">"Du måste verifiera alla dina befintliga enheter och kontakter igen"</string>
<string name="screen_encryption_reset_footer">"Återställ bara din identitet om du inte har tillgång till en annan inloggad enhet och du har tappat bort din återställningsnyckel."</string>
<string name="screen_encryption_reset_subtitle">"Om du inte är inloggad på någon annan enhet och du har tappat bort din återställningsnyckel måste du återställa din identitet för att fortsätta använda appen. "</string>
<string name="screen_encryption_reset_title">"Återställ din identitet ifall du inte kan bekräfta på annat sätt"</string>
<string name="screen_media_picker_error_failed_selection">"Misslyckades att välja media, vänligen pröva igen."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Misslyckades att bearbeta media för uppladdning, vänligen pröva igen."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Misslyckades att ladda upp media, vänligen pröva igen."</string>
<string name="screen_pinned_timeline_empty_state_description">"Tryck på ett meddelande och välj ”%1$s” för att inkludera det här."</string>
<string name="screen_pinned_timeline_empty_state_headline">"Fäst viktiga meddelanden så att de lätt kan upptäckas"</string>
<plurals name="screen_pinned_timeline_screen_title">
<item quantity="one">"%1$d Fäst meddelande"</item>
<item quantity="other">"%1$d Fästa meddelanden"</item>
</plurals>
<string name="screen_pinned_timeline_screen_title_empty">"Fästa meddelanden"</string>
<string name="screen_reset_encryption_confirmation_alert_action">"Ja, återställ nu"</string>
<string name="screen_reset_encryption_confirmation_alert_subtitle">"Denna process är irreversibel."</string>
<string name="screen_reset_encryption_confirmation_alert_title">"Är du säker på att du vill återställa din kryptering?"</string>
<string name="screen_reset_encryption_password_placeholder">"Ange …"</string>
<string name="screen_reset_encryption_password_subtitle">"Bekräfta att du vill återställa din kryptering."</string>
<string name="screen_reset_encryption_password_title">"Ange ditt kontolösenord för att fortsätta"</string>
<string name="screen_room_details_pinned_events_row_title">"Fästa meddelanden"</string>
<string name="screen_room_error_failed_processing_media">"Misslyckades att bearbeta media för uppladdning, vänligen pröva igen."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Kunde inte hämta användarinformation"</string>
<string name="screen_room_member_details_block_alert_action">"Blockera"</string>
@ -257,6 +300,11 @@
<string name="screen_room_member_details_unblock_alert_action">"Avblockera"</string>
<string name="screen_room_member_details_unblock_alert_description">"Du kommer att kunna se alla meddelanden från dem igen."</string>
<string name="screen_room_member_details_unblock_user">"Avblockera användare"</string>
<string name="screen_room_pinned_banner_indicator">"%1$s av %2$s"</string>
<string name="screen_room_pinned_banner_indicator_description">"%1$s Fästa meddelanden"</string>
<string name="screen_room_pinned_banner_loading_description">"Laddar meddelande …"</string>
<string name="screen_room_pinned_banner_view_all_button_title">"Visa alla"</string>
<string name="screen_room_title">"Chatt"</string>
<string name="screen_share_location_title">"Dela plats"</string>
<string name="screen_share_my_location_action">"Dela min plats"</string>
<string name="screen_share_open_apple_maps">"Öppna i Apple Maps"</string>

View file

@ -36,6 +36,7 @@
<string name="action_accept">"Прийняти"</string>
<string name="action_add_to_timeline">"Додати до стрічки"</string>
<string name="action_back">"Назад"</string>
<string name="action_call">"Зателефонувати"</string>
<string name="action_cancel">"Скасувати"</string>
<string name="action_choose_photo">"Вибрати фото"</string>
<string name="action_clear">"Очистити"</string>
@ -74,21 +75,25 @@
<string name="action_load_more">"Завантажити ще"</string>
<string name="action_manage_account">"Керування обліковим записом"</string>
<string name="action_manage_devices">"Керування пристроями"</string>
<string name="action_message">"Написати"</string>
<string name="action_next">"Далі"</string>
<string name="action_no">"Ні"</string>
<string name="action_not_now">"Не зараз"</string>
<string name="action_ok">"Гаразд"</string>
<string name="action_open_settings">"Налаштування"</string>
<string name="action_open_with">"Відкрити за допомогою"</string>
<string name="action_pin">"Закріпити"</string>
<string name="action_quick_reply">"Швидка відповідь"</string>
<string name="action_quote">"Цитувати"</string>
<string name="action_react">"Реакція"</string>
<string name="action_reject">"Відхилити"</string>
<string name="action_remove">"Вилучити"</string>
<string name="action_reply">"Відповісти"</string>
<string name="action_reply_in_thread">"Відповісти в гілці"</string>
<string name="action_report_bug">"Повідомити про помилку"</string>
<string name="action_report_content">"Повідомити про вміст"</string>
<string name="action_reset">"Скинути"</string>
<string name="action_reset_identity">"Скинути ідентичність"</string>
<string name="action_retry">"Спробувати ще раз"</string>
<string name="action_retry_decryption">"Повторити спробу розшифрування"</string>
<string name="action_save">"Зберегти"</string>
@ -108,6 +113,7 @@
<string name="action_take_photo">"Зробити фото"</string>
<string name="action_tap_for_options">"Натисніть, щоб переглянути параметри"</string>
<string name="action_try_again">"Спробуйте ще раз"</string>
<string name="action_unpin">"Відкріпити"</string>
<string name="action_view_source">"Переглянути джерело"</string>
<string name="action_yes">"Так"</string>
<string name="common_about">"Відомості"</string>
@ -119,6 +125,7 @@
<string name="common_blocked_users">"Заблоковані користувачі"</string>
<string name="common_bubbles">"Бульбашки"</string>
<string name="common_call_invite">"Триває виклик (не підтримується)"</string>
<string name="common_call_started">"Дзвінок розпочато"</string>
<string name="common_chat_backup">"Резервне копіювання чату"</string>
<string name="common_copyright">"Авторське право"</string>
<string name="common_creating_room">"Створення кімнати…"</string>
@ -127,12 +134,16 @@
<string name="common_decryption_error">"Помилка розшифровки"</string>
<string name="common_developer_options">"Налаштування розробника"</string>
<string name="common_direct_chat">"Особистий чат"</string>
<string name="common_do_not_show_this_again">"Не показувати це знову"</string>
<string name="common_edited_suffix">"(відредаговано)"</string>
<string name="common_editing">"Редагування"</string>
<string name="common_emote">"* %1$s %2$s"</string>
<string name="common_encryption_enabled">"Шифрування ввімкнено"</string>
<string name="common_enter_your_pin">"Введіть свій PIN-код"</string>
<string name="common_error">"Помилка"</string>
<string name="common_error_registering_pusher_android">"Сталася помилка, ви можете не отримувати сповіщення про нові повідомлення. Усуньте неполадки зі сповіщеннями в налаштуваннях.
Причина: %1$s."</string>
<string name="common_everyone">"Усі"</string>
<string name="common_failed">"Невдало"</string>
<string name="common_favourite">"Улюблений"</string>
@ -161,12 +172,15 @@
<string name="common_modern">"Модерн"</string>
<string name="common_mute">"Вимкнути звук"</string>
<string name="common_no_results">"Немає результатів"</string>
<string name="common_no_room_name">"Немає назви кімнати"</string>
<string name="common_offline">"Не в мережі"</string>
<string name="common_open_source_licenses">"Ліцензії відкритого коду"</string>
<string name="common_or">"або"</string>
<string name="common_password">"Пароль"</string>
<string name="common_people">"Люди"</string>
<string name="common_permalink">"Постійне посилання"</string>
<string name="common_permission">"Дозвіл"</string>
<string name="common_please_wait">"Будь ласка, зачекайте…"</string>
<string name="common_poll_end_confirmation">"Ви впевнені, що хочете закінчити це опитування?"</string>
<string name="common_poll_summary">"Опитування: %1$s"</string>
<string name="common_poll_total_votes">"Всього голосів: %1$s"</string>
@ -196,6 +210,7 @@
<string name="common_search_results">"Результати пошуку"</string>
<string name="common_security">"Безпека"</string>
<string name="common_seen_by">"Побачили"</string>
<string name="common_send_to">"Надіслати до"</string>
<string name="common_sending">"Надсилання…"</string>
<string name="common_sending_failed">"Не вдалося відправити"</string>
<string name="common_sent">"Надіслано"</string>
@ -204,6 +219,7 @@
<string name="common_settings">"Налаштування"</string>
<string name="common_shared_location">"Поширене розташування"</string>
<string name="common_signing_out">"Вихід"</string>
<string name="common_something_went_wrong">"Щось пішло не так"</string>
<string name="common_starting_chat">"Початок чату…"</string>
<string name="common_sticker">"Наліпка"</string>
<string name="common_success">"Успіх"</string>
@ -216,6 +232,7 @@
<string name="common_topic">"Тема"</string>
<string name="common_topic_placeholder">"Про що ця кімната?"</string>
<string name="common_unable_to_decrypt">"Неможливо розшифрувати"</string>
<string name="common_unable_to_decrypt_no_access">"Ви не маєте доступу до цього повідомлення"</string>
<string name="common_unable_to_invite_message">"Не вдалося надіслати запрошення одному чи кільком користувачам."</string>
<string name="common_unable_to_invite_title">"Не вдалося надіслати запрошення"</string>
<string name="common_unlock">"Розблокувати"</string>
@ -240,26 +257,48 @@
<string name="error_failed_loading_messages">"Не вдалося завантажити повідомлення"</string>
<string name="error_failed_locating_user">"%1$s не вдалося отримати доступ до вашого місцезнаходження. Будь ласка, спробуйте ще раз пізніше."</string>
<string name="error_failed_uploading_voice_message">"Не вдалося завантажити голосове повідомлення."</string>
<string name="error_message_not_found">"Повідомлення не знайдено"</string>
<string name="error_missing_location_auth_android">"%1$s не має дозволу на доступ до вашого місцезнаходження. Увімкнути доступ можна в Налаштуваннях."</string>
<string name="error_missing_location_rationale_android">"%1$s не має дозволу на доступ до вашого місцезнаходження. Увімкніть доступ нижче."</string>
<string name="error_missing_microphone_voice_rationale_android">"%1$s не має доступу до вашого мікрофона. Надайте доступ, щоб записати голосове повідомлення."</string>
<string name="error_some_messages_have_not_been_sent">"Деякі повідомлення не були надіслані"</string>
<string name="error_unknown">"Вибачте, сталася помилка"</string>
<string name="event_shield_reason_authenticity_not_guaranteed">"Автентичність цього зашифрованого повідомлення не може бути гарантована на цьому пристрої."</string>
<string name="event_shield_reason_unknown_device">"Зашифрований невідомим або видаленим пристроєм."</string>
<string name="event_shield_reason_unsigned_device">"Зашифровано пристроєм, який не підтверджено його власником."</string>
<string name="event_shield_reason_unverified_identity">"Зашифровано неперевіреним користувачем."</string>
<string name="invite_friends_rich_title">"🔐️ Приєднуйтеся до мене в %1$s"</string>
<string name="invite_friends_text">"Привіт, пишіть мені за адресою %1$s: %2$s"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<string name="preference_rageshake">"Повідомити про ваду за допомогою Rageshake"</string>
<string name="screen_encryption_reset_bullet_1">"Дані вашого облікового запису, контакти, налаштування й чати будуть збережені"</string>
<string name="screen_encryption_reset_bullet_2">"Ви втратите свою наявну історію повідомлень"</string>
<string name="screen_encryption_reset_bullet_3">"Вам доведеться підтвердити всі наявні пристрої та контакти знову"</string>
<string name="screen_encryption_reset_footer">"Скидайте ідентичність тільки якщо ви не маєте доступу до інших пристроїв в обліковому записі та втратили свій ключ відновлення."</string>
<string name="screen_encryption_reset_subtitle">"Якщо ви не увійшли на інших пристроях та втратили свій ключ відновлення, то вам доведеться скинути свою ідентичність, щоб продовжити використовувати застосунок. "</string>
<string name="screen_encryption_reset_title">"Скиньте свою ідентичність, якщо не можете підтвердити іншим способом"</string>
<string name="screen_media_picker_error_failed_selection">"Не вдалося вибрати медіафайл, спробуйте ще раз."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Не вдалося обробити медіафайл для завантаження, спробуйте ще раз."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Не вдалося завантажити медіафайл, спробуйте ще раз."</string>
<string name="screen_reset_encryption_confirmation_alert_action">"Так, скинути зараз"</string>
<string name="screen_reset_encryption_confirmation_alert_subtitle">"Цей процес незворотний."</string>
<string name="screen_reset_encryption_confirmation_alert_title">"Ви впевнені, що хочете скинути шифрування?"</string>
<string name="screen_reset_encryption_password_placeholder">"Ввести…"</string>
<string name="screen_reset_encryption_password_subtitle">"Підтвердьте, що ви хочете скинути шифрування."</string>
<string name="screen_reset_encryption_password_title">"Введіть пароль облікового запису, щоб продовжити"</string>
<string name="screen_room_error_failed_processing_media">"Не вдалося обробити медіафайл для завантаження, спробуйте ще раз."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Не вдалося отримати дані користувача"</string>
<string name="screen_room_member_details_block_alert_action">"Заблокувати"</string>
<string name="screen_room_member_details_block_alert_description">"Заблоковані користувачі не зможуть надсилати Вам повідомлення, і всі їхні повідомлення будуть приховані. Ви можете розблокувати їх у будь-який час."</string>
<string name="screen_room_member_details_block_user">"Заблокувати користувача"</string>
<string name="screen_room_member_details_title">"Профіль"</string>
<string name="screen_room_member_details_unblock_alert_action">"Розблокувати"</string>
<string name="screen_room_member_details_unblock_alert_description">"Ви знову зможете бачити всі повідомлення від них."</string>
<string name="screen_room_member_details_unblock_user">"Розблокувати користувача"</string>
<string name="screen_room_pinned_banner_indicator">"%1$s із %2$s"</string>
<string name="screen_room_pinned_banner_indicator_description">"%1$s Закріплених повідомлень"</string>
<string name="screen_room_pinned_banner_view_all_button_title">"Переглянути всі"</string>
<string name="screen_room_title">"Чат"</string>
<string name="screen_share_location_title">"Поділитися розташуванням"</string>
<string name="screen_share_my_location_action">"Поділитися моїм розташуванням"</string>
<string name="screen_share_open_apple_maps">"Відкрити в Apple Maps"</string>

View file

@ -0,0 +1,210 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="a11y_delete">"Oʻchirish"</string>
<string name="a11y_hide_password">"Parolni yashirish"</string>
<string name="a11y_notifications_mentions_only">"Faqat eslatmalar"</string>
<string name="a11y_notifications_muted">"Ovozsiz"</string>
<string name="a11y_pause">"Pauza"</string>
<string name="a11y_play">"O\'ynang"</string>
<string name="a11y_poll">"So\'ro\'vnoma"</string>
<string name="a11y_poll_end">"Sorovnoma yakunlandi"</string>
<string name="a11y_send_files">"Fayllarni yuborish"</string>
<string name="a11y_show_password">"Parolni ko\'rsatish"</string>
<string name="a11y_user_menu">"Foydalanuvchi menyusi"</string>
<string name="a11y_voice_message_record">"Ovoz yozishni amalga oshiring"</string>
<string name="action_accept">"Qabul qiling"</string>
<string name="action_add_to_timeline">"Vaqt jadvaliga qo\'shing"</string>
<string name="action_back">"Orqaga"</string>
<string name="action_cancel">"Bekor qilish"</string>
<string name="action_choose_photo">"Fotosuratni tanlang"</string>
<string name="action_clear">"Tozalash"</string>
<string name="action_close">"Yopish"</string>
<string name="action_complete_verification">"To\'liq tekshirish"</string>
<string name="action_confirm">"Tasdiqlash"</string>
<string name="action_continue">"Davom etish"</string>
<string name="action_copy">"nusxa"</string>
<string name="action_copy_link">"Havolani nusxalash"</string>
<string name="action_copy_link_to_message">"Havolani xabaraga nusxalash"</string>
<string name="action_create">"Yaratmoq"</string>
<string name="action_create_a_room">"Xonani yaratish"</string>
<string name="action_decline">"Rad etish"</string>
<string name="action_disable">"Oʻchirish"</string>
<string name="action_done">"Bajarildi"</string>
<string name="action_edit">"Tahrirlash"</string>
<string name="action_edit_poll">"Sorovnomani tahrirlash"</string>
<string name="action_enable">"Yoqish"</string>
<string name="action_end_poll">"Sorovnomani tugatish"</string>
<string name="action_forgot_password">"Parolni unutdingizmi?"</string>
<string name="action_forward">"Oldinga"</string>
<string name="action_invite">"Taklif qilish"</string>
<string name="action_invite_friends">"Odamlarni taklif qiling"</string>
<string name="action_invite_friends_to_app">"Odamlarni taklif qilish%1$s"</string>
<string name="action_invite_people_to_app">"Odamlarni taklif qiling%1$s"</string>
<string name="action_invites_list">"Takliflar"</string>
<string name="action_join">"Qo\'shilish"</string>
<string name="action_learn_more">"Batafsil malumot"</string>
<string name="action_leave">"Tark etish "</string>
<string name="action_leave_room">"Xonani tark etish "</string>
<string name="action_manage_account">"Hisobni boshqarish"</string>
<string name="action_manage_devices">"Qurilmalarni boshqarish"</string>
<string name="action_next">"Keyingisi"</string>
<string name="action_no">"Yo\'q"</string>
<string name="action_not_now">"Hozir emas"</string>
<string name="action_ok">"Ok"</string>
<string name="action_open_settings">"Sozlamalar"</string>
<string name="action_open_with">"Bilan oching"</string>
<string name="action_quick_reply">"Tez javob"</string>
<string name="action_quote">"Iqtibos"</string>
<string name="action_react">"Reaksiya qilish"</string>
<string name="action_remove">"Ochirish"</string>
<string name="action_reply">"Javob bering"</string>
<string name="action_reply_in_thread">"Mavzuda javob bering"</string>
<string name="action_report_bug">"Xato haqida xabar berish"</string>
<string name="action_report_content">"Tarkib haqida xabar berish"</string>
<string name="action_retry">"Qayta urinish"</string>
<string name="action_retry_decryption">"Shifrni ochishni qayta urinish"</string>
<string name="action_save">"Saqlash"</string>
<string name="action_search">"Qidirmoq"</string>
<string name="action_send">"Yuborish"</string>
<string name="action_send_message">"Xabar yuborish"</string>
<string name="action_share">"Ulashish"</string>
<string name="action_share_link">"Havolani ulashing"</string>
<string name="action_sign_in_again">"Qaytadan kiring"</string>
<string name="action_signout">"Tizimdan chiqish"</string>
<string name="action_signout_anyway">"Baribir tizimdan chiqing"</string>
<string name="action_skip">"Oʻtkazib yuborish"</string>
<string name="action_start">"Boshlash"</string>
<string name="action_start_chat">"Suhbatni boshlash"</string>
<string name="action_start_verification">"Tasdiqlashni boshlang"</string>
<string name="action_static_map_load">"Xaritani yuklash uchun bosing"</string>
<string name="action_take_photo">"Rasmga olmoq"</string>
<string name="action_view_source">"Manbani korish "</string>
<string name="action_yes">"Ha"</string>
<string name="common_about">"Haqida"</string>
<string name="common_acceptable_use_policy">"Qabul qilinadigan foydalanish siyosati"</string>
<string name="common_advanced_settings">"Kengaytirilgan sozlamalar"</string>
<string name="common_analytics">"Analitika"</string>
<string name="common_audio">"Audio"</string>
<string name="common_bubbles">"Pufakchalar"</string>
<string name="common_chat_backup">"Chatning zaxira nusxasi"</string>
<string name="common_copyright">"Mualliflik huquqi"</string>
<string name="common_creating_room">"Xona yaratilmoqda…"</string>
<string name="common_current_user_left_room">"Xonani tark etdi"</string>
<string name="common_decryption_error">"Shifrni ochish xatosi"</string>
<string name="common_developer_options">"Dasturchi variantlari"</string>
<string name="common_edited_suffix">"(tahrirlangan)"</string>
<string name="common_editing">"Tahrirlash"</string>
<string name="common_emote">"*%1$s%2$s"</string>
<string name="common_encryption_enabled">"Shifrlash yoqilgan"</string>
<string name="common_error">"Xato"</string>
<string name="common_everyone">"Har kim"</string>
<string name="common_file">"Fayl"</string>
<string name="common_file_saved_on_disk_android">"Fayl “Yuklashlar”ga saqlandi"</string>
<string name="common_forward_message">"Xabarni yo\'naltirish"</string>
<string name="common_image">"Surat"</string>
<string name="common_in_reply_to">"%1$sga Javob bering"</string>
<string name="common_install_apk_android">"APK-ni o\'rnating"</string>
<string name="common_invite_unknown_profile">"Ushbu Matrix identifikatori topilmadi, shuning uchun taklif qabul qilinmasligi mumkin."</string>
<string name="common_leaving_room">"Xonadan chiqish"</string>
<string name="common_link_copied_to_clipboard">"Havola vaqtinchalik xotiraga nusxalandi"</string>
<string name="common_loading">"Yuklanmoqda…"</string>
<plurals name="common_member_count">
<item quantity="one">"%1$d a\'zo"</item>
<item quantity="other">"%1$d ishtirokchilar"</item>
</plurals>
<string name="common_message">"Xabar"</string>
<string name="common_message_layout">"Xabar tartibi"</string>
<string name="common_message_removed">"Xabar ochirib tashlandi"</string>
<string name="common_modern">"Zamonaviy"</string>
<string name="common_mute">"Ovozsiz qilish"</string>
<string name="common_no_results">"Natijalar yoʻq"</string>
<string name="common_offline">"Oflayn"</string>
<string name="common_password">"Parol"</string>
<string name="common_people">"Odamlar"</string>
<string name="common_permalink">"Doimiy havola"</string>
<string name="common_permission">"Ruxsat"</string>
<string name="common_poll_end_confirmation">"Haqiqatan ham bu soʻrovnomani tugatmoqchimisiz?"</string>
<string name="common_poll_summary">"Sorov:%1$s"</string>
<string name="common_poll_total_votes">"Jami ovozlar:%1$s"</string>
<string name="common_poll_undisclosed_text">"Natijalar soʻrovnoma tugagandan soʻng koʻrsatiladi"</string>
<plurals name="common_poll_votes_count">
<item quantity="one">"%dovoz berish"</item>
<item quantity="other">"%dovozlar"</item>
</plurals>
<string name="common_privacy_policy">"Maxfiylik siyosati"</string>
<string name="common_reaction">"Reaktsiya"</string>
<string name="common_reactions">"reaksiyalar"</string>
<string name="common_recovery_key">"Qayta tiklash kaliti"</string>
<string name="common_refreshing">"Yangilanmoqda…"</string>
<string name="common_replying_to">"%1$sga Javob berilmoqda"</string>
<string name="common_report_a_bug">"Xato haqida xabar bering"</string>
<string name="common_report_submitted">"Hisobot topshirildi"</string>
<string name="common_rich_text_editor">"Boy matn muharriri"</string>
<string name="common_room_name">"Xona nomi"</string>
<string name="common_room_name_placeholder">"masalan, loyihangiz nomi"</string>
<string name="common_search_for_someone">"Kimnidir qidiring"</string>
<string name="common_search_results">"Qidiruv natijalari"</string>
<string name="common_security">"Xavfsizlik"</string>
<string name="common_sending">"Yuborilmoqda…"</string>
<string name="common_server_not_supported">"Server qo\'llab-quvvatlanmaydi"</string>
<string name="common_server_url">"Server URL manzili"</string>
<string name="common_settings">"Sozlamalar"</string>
<string name="common_shared_location">"Joylashuvi ulashildi"</string>
<string name="common_starting_chat">"Chat boshlanmoqda…"</string>
<string name="common_sticker">"Stiker"</string>
<string name="common_success">"Muvaffaqiyat"</string>
<string name="common_suggestions">"Tavsiyalar"</string>
<string name="common_syncing">"Sinxronlash"</string>
<string name="common_text">"Matn"</string>
<string name="common_third_party_notices">"Uchinchi tomon bildirishnomalari"</string>
<string name="common_thread">"Ip"</string>
<string name="common_topic">"Mavzu"</string>
<string name="common_topic_placeholder">"Bu xona nima haqida?"</string>
<string name="common_unable_to_decrypt">"Shifrni ochish imkonsiz"</string>
<string name="common_unable_to_invite_message">"Takliflarni bir yoki bir nechta foydalanuvchiga yuborib bolmadi."</string>
<string name="common_unable_to_invite_title">"Taklif(lar)ni yuborib bolmadi"</string>
<string name="common_unmute">"Ovozni yoqish"</string>
<string name="common_unsupported_event">"Qo\'llab-quvvatlanmagan hodisa"</string>
<string name="common_username">"Foydalanuvchi nomi"</string>
<string name="common_verification_cancelled">"Tasdiqlash bekor qilindi"</string>
<string name="common_verification_complete">"Tasdiqlash yakunlandi"</string>
<string name="common_video">"Video"</string>
<string name="common_voice_message">"Ovozli xabar"</string>
<string name="common_waiting">"Kutilmoqda…"</string>
<string name="dialog_title_confirmation">"Tasdiqlash"</string>
<string name="dialog_title_error">"Xato"</string>
<string name="dialog_title_success">"Muvaffaqiyat"</string>
<string name="dialog_title_warning">"Ogohlantirish"</string>
<string name="error_failed_creating_the_permalink">"Doimiy havola yaratilmadi"</string>
<string name="error_failed_loading_map">"%1$sxaritani yuklay olmadi. Iltimos keyinroq qayta urinib ko\'ring."</string>
<string name="error_failed_loading_messages">"Xabarlar yuklanmadi"</string>
<string name="error_failed_locating_user">"%1$sjoylashuvingizga kira olmadi. Iltimos keyinroq qayta urinib ko\'ring."</string>
<string name="error_missing_location_auth_android">"%1$sjoylashuvingizga kirishga ruxsati yo\'q. Sozlamalar orqali kirishni yoqishingiz mumkin."</string>
<string name="error_missing_location_rationale_android">"%1$sjoylashuvingizga kirishga ruxsati yo\'q. Quyida kirishni yoqing."</string>
<string name="error_some_messages_have_not_been_sent">"Bazi xabarlar yuborilmagan"</string>
<string name="error_unknown">"Kechirasiz, xatolik yuz berdi"</string>
<string name="invite_friends_rich_title">"🔐️ Menga qo\'shiling%1$s"</string>
<string name="invite_friends_text">"Hey, men bilan gaplash%1$s :%2$s"</string>
<string name="login_initial_device_name_android">"%1$sAndroid"</string>
<string name="preference_rageshake">"Xato haqida xabar berish uchun G\'azablanish"</string>
<string name="screen_media_picker_error_failed_selection">"Media tanlash jarayonida xatolik yuz berdi, qayta urinib ko\'ring"</string>
<string name="screen_media_upload_preview_error_failed_processing">"Mediani yuklab bolmadi, qayta urinib koring."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Media yuklanmadi, qayta urinib koring."</string>
<string name="screen_room_error_failed_processing_media">"Mediani yuklab bolmadi, qayta urinib koring."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Foydalanuvchi tafsilotlarini olinmadi"</string>
<string name="screen_room_member_details_block_alert_action">"Bloklash"</string>
<string name="screen_room_member_details_block_alert_description">"Bloklangan foydalanuvchilar sizga xabar yubora olmaydi va ularning barcha xabarlari yashiriladi. Ularni istalgan vaqtda blokdan chiqarishingiz mumkin."</string>
<string name="screen_room_member_details_block_user">"Foydalanuvchini bloklash"</string>
<string name="screen_room_member_details_unblock_alert_action">"Blokdan chiqarish"</string>
<string name="screen_room_member_details_unblock_alert_description">"Ulardan kelgan barcha xabarlarni yana koʻrishingiz mumkin boʻladi."</string>
<string name="screen_room_member_details_unblock_user">"Foydalanuvchini blokdan chiqarish"</string>
<string name="screen_share_location_title">"Joylashuvni ulashish"</string>
<string name="screen_share_my_location_action">"Joylashuvimni ulashing"</string>
<string name="screen_share_open_apple_maps">"Apple Mapsda oching"</string>
<string name="screen_share_open_google_maps">"Google Mapsda oching"</string>
<string name="screen_share_open_osm_maps">"OpenStreetMapda oching"</string>
<string name="screen_share_this_location_action">"Bu joylashuvni ulashing"</string>
<string name="screen_view_location_title">"Joylashuv"</string>
<string name="settings_version_number">"Versiya:%1$s (%2$s )"</string>
<string name="test_language_identifier">"en"</string>
</resources>

View file

@ -91,6 +91,7 @@
<string name="action_report_bug">"Report bug"</string>
<string name="action_report_content">"Report content"</string>
<string name="action_reset">"Reset"</string>
<string name="action_reset_identity">"Reset identity"</string>
<string name="action_retry">"Retry"</string>
<string name="action_retry_decryption">"Retry decryption"</string>
<string name="action_save">"Save"</string>
@ -110,6 +111,7 @@
<string name="action_take_photo">"Take photo"</string>
<string name="action_tap_for_options">"Tap for options"</string>
<string name="action_try_again">"Try again"</string>
<string name="action_unpin">"Unpin"</string>
<string name="action_view_source">"View source"</string>
<string name="action_yes">"Yes"</string>
<string name="common_about">"About"</string>
@ -258,6 +260,7 @@ Reason: %1$s."</string>
<string name="error_some_messages_have_not_been_sent">"Some messages have not been sent"</string>
<string name="error_unknown">"Sorry, an error occurred"</string>
<string name="event_shield_reason_authenticity_not_guaranteed">"The authenticity of this encrypted message can\'t be guaranteed on this device."</string>
<string name="event_shield_reason_sent_in_clear">"Not encrypted."</string>
<string name="event_shield_reason_unknown_device">"Encrypted by an unknown or deleted device."</string>
<string name="event_shield_reason_unsigned_device">"Encrypted by a device not verified by its owner."</string>
<string name="event_shield_reason_unverified_identity">"Encrypted by an unverified user."</string>
@ -265,9 +268,29 @@ Reason: %1$s."</string>
<string name="invite_friends_text">"Hey, talk to me on %1$s: %2$s"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<string name="preference_rageshake">"Rageshake to report bug"</string>
<string name="screen_encryption_reset_bullet_1">"Your account details, contacts, preferences, and chat list will be kept"</string>
<string name="screen_encryption_reset_bullet_2">"You will lose your existing message history"</string>
<string name="screen_encryption_reset_bullet_3">"You will need to verify all your existing devices and contacts again"</string>
<string name="screen_encryption_reset_footer">"Only reset your identity if you dont have access to another signed-in device and youve lost your recovery key."</string>
<string name="screen_encryption_reset_subtitle">"If youre not signed in to any other devices and youve lost your recovery key, then youll need to reset your identity to continue using the app. "</string>
<string name="screen_encryption_reset_title">"Reset your identity in case you cant confirm another way"</string>
<string name="screen_media_picker_error_failed_selection">"Failed selecting media, please try again."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Failed processing media to upload, please try again."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Failed uploading media, please try again."</string>
<string name="screen_pinned_timeline_empty_state_description">"Press on a message and choose “%1$s” to include here."</string>
<string name="screen_pinned_timeline_empty_state_headline">"Pin important messages so that they can be easily discovered"</string>
<plurals name="screen_pinned_timeline_screen_title">
<item quantity="one">"%1$d Pinned message"</item>
<item quantity="other">"%1$d Pinned messages"</item>
</plurals>
<string name="screen_pinned_timeline_screen_title_empty">"Pinned messages"</string>
<string name="screen_reset_encryption_confirmation_alert_action">"Yes, reset now"</string>
<string name="screen_reset_encryption_confirmation_alert_subtitle">"This process is irreversible."</string>
<string name="screen_reset_encryption_confirmation_alert_title">"Are you sure you want to reset your encryption?"</string>
<string name="screen_reset_encryption_password_placeholder">"Enter…"</string>
<string name="screen_reset_encryption_password_subtitle">"Confirm that you want to reset your encryption."</string>
<string name="screen_reset_encryption_password_title">"Enter your account password to continue"</string>
<string name="screen_room_details_pinned_events_row_title">"Pinned messages"</string>
<string name="screen_room_error_failed_processing_media">"Failed processing media to upload, please try again."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Could not retrieve user details"</string>
<string name="screen_room_member_details_block_alert_action">"Block"</string>
@ -279,6 +302,7 @@ Reason: %1$s."</string>
<string name="screen_room_member_details_unblock_user">"Unblock user"</string>
<string name="screen_room_pinned_banner_indicator">"%1$s of %2$s"</string>
<string name="screen_room_pinned_banner_indicator_description">"%1$s Pinned messages"</string>
<string name="screen_room_pinned_banner_loading_description">"Loading message…"</string>
<string name="screen_room_pinned_banner_view_all_button_title">"View All"</string>
<string name="screen_room_title">"Chat"</string>
<string name="screen_share_location_title">"Share location"</string>