Merge branch 'develop' into feature/bma/rageshakeConfigStep2
This commit is contained in:
commit
37b50e0139
350 changed files with 5589 additions and 1715 deletions
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.androidutils.media
|
||||
|
||||
import android.util.Size
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Helper class to calculate the resulting output size and optimal bitrate for video compression.
|
||||
*/
|
||||
class VideoCompressorHelper(
|
||||
/**
|
||||
* The maximum size (in pixels) for the output video.
|
||||
* The output will maintain the aspect ratio of the input video.
|
||||
*/
|
||||
val maxSize: Int,
|
||||
) {
|
||||
/**
|
||||
* Calculates the output size for video compression based on the input size and [maxSize].
|
||||
*/
|
||||
fun getOutputSize(inputSize: Size): Size {
|
||||
val resultMajor = min(inputSize.major(), maxSize)
|
||||
val aspectRatio = inputSize.major().toFloat() / inputSize.minor().toFloat()
|
||||
return Size(resultMajor, (resultMajor / aspectRatio).roundToInt())
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the optimal bitrate for video compression based on the input size and frame rate.
|
||||
*/
|
||||
fun calculateOptimalBitrate(inputSize: Size, frameRate: Int): Long {
|
||||
val outputSize = getOutputSize(inputSize)
|
||||
val pixelsPerFrame = outputSize.width * outputSize.height
|
||||
// Apparently, 0.1 bits per pixel is a sweet spot for video compression
|
||||
val bitsPerPixel = 0.1f
|
||||
return (pixelsPerFrame * bitsPerPixel * frameRate).toLong() / 1000
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Size.major(): Int = if (width > height) width else height
|
||||
internal fun Size.minor(): Int = if (width < height) width else height
|
||||
|
|
@ -184,4 +184,12 @@ enum class FeatureFlags(
|
|||
// False so it's displayed in the developer options screen
|
||||
isFinished = false,
|
||||
),
|
||||
SelectableMediaQuality(
|
||||
key = "feature.selectable_media_quality",
|
||||
title = "Select media quality per upload",
|
||||
description = "You can select the media quality for each attachment you upload.",
|
||||
defaultValue = { false },
|
||||
// False so it's displayed in the developer options screen
|
||||
isFinished = false,
|
||||
),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,6 +171,11 @@ interface MatrixClient {
|
|||
* Return true if Livekit Rtc is supported, i.e. if Element Call is available.
|
||||
*/
|
||||
suspend fun isLivekitRtcSupported(): Boolean
|
||||
|
||||
/**
|
||||
* Returns the maximum file upload size allowed by the Matrix server.
|
||||
*/
|
||||
suspend fun getMaxFileUploadSize(): Result<Long>
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -692,6 +692,10 @@ class RustMatrixClient(
|
|||
innerClient.isLivekitRtcSupported()
|
||||
}
|
||||
|
||||
override suspend fun getMaxFileUploadSize(): Result<Long> = withContext(sessionDispatcher) {
|
||||
runCatchingExceptions { innerClient.getMaxMediaUploadSize().toLong() }
|
||||
}
|
||||
|
||||
private suspend fun File.getCacheSize(
|
||||
includeCryptoDb: Boolean = false,
|
||||
): Long = withContext(sessionDispatcher) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.libraries.matrix.impl.notification
|
||||
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
|
@ -30,31 +31,33 @@ class NotificationMapper(
|
|||
eventId: EventId,
|
||||
roomId: RoomId,
|
||||
notificationItem: NotificationItem
|
||||
): NotificationData {
|
||||
return notificationItem.use { item ->
|
||||
val isDm = isDm(
|
||||
isDirect = item.roomInfo.isDirect,
|
||||
activeMembersCount = item.roomInfo.joinedMembersCount.toInt(),
|
||||
)
|
||||
NotificationData(
|
||||
sessionId = sessionId,
|
||||
eventId = eventId,
|
||||
// FIXME once the `NotificationItem` in the SDK returns the thread id
|
||||
threadId = null,
|
||||
roomId = roomId,
|
||||
senderAvatarUrl = item.senderInfo.avatarUrl,
|
||||
senderDisplayName = item.senderInfo.displayName,
|
||||
senderIsNameAmbiguous = item.senderInfo.isNameAmbiguous,
|
||||
roomAvatarUrl = item.roomInfo.avatarUrl ?: item.senderInfo.avatarUrl.takeIf { isDm },
|
||||
roomDisplayName = item.roomInfo.displayName,
|
||||
isDirect = item.roomInfo.isDirect,
|
||||
isDm = isDm,
|
||||
isEncrypted = item.roomInfo.isEncrypted.orFalse(),
|
||||
isNoisy = item.isNoisy.orFalse(),
|
||||
timestamp = item.timestamp() ?: clock.epochMillis(),
|
||||
content = item.event.use { notificationContentMapper.map(it) },
|
||||
hasMention = item.hasMention.orFalse(),
|
||||
)
|
||||
): Result<NotificationData> {
|
||||
return runCatchingExceptions {
|
||||
notificationItem.use { item ->
|
||||
val isDm = isDm(
|
||||
isDirect = item.roomInfo.isDirect,
|
||||
activeMembersCount = item.roomInfo.joinedMembersCount.toInt(),
|
||||
)
|
||||
NotificationData(
|
||||
sessionId = sessionId,
|
||||
eventId = eventId,
|
||||
// FIXME once the `NotificationItem` in the SDK returns the thread id
|
||||
threadId = null,
|
||||
roomId = roomId,
|
||||
senderAvatarUrl = item.senderInfo.avatarUrl,
|
||||
senderDisplayName = item.senderInfo.displayName,
|
||||
senderIsNameAmbiguous = item.senderInfo.isNameAmbiguous,
|
||||
roomAvatarUrl = item.roomInfo.avatarUrl ?: item.senderInfo.avatarUrl.takeIf { isDm },
|
||||
roomDisplayName = item.roomInfo.displayName,
|
||||
isDirect = item.roomInfo.isDirect,
|
||||
isDm = isDm,
|
||||
isEncrypted = item.roomInfo.isEncrypted.orFalse(),
|
||||
isNoisy = item.isNoisy.orFalse(),
|
||||
timestamp = item.timestamp() ?: clock.epochMillis(),
|
||||
content = item.event.use { notificationContentMapper.map(it) }.getOrThrow(),
|
||||
hasMention = item.hasMention.orFalse(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -62,11 +65,13 @@ class NotificationMapper(
|
|||
class NotificationContentMapper {
|
||||
private val timelineEventToNotificationContentMapper = TimelineEventToNotificationContentMapper()
|
||||
|
||||
fun map(notificationEvent: NotificationEvent): NotificationContent =
|
||||
fun map(notificationEvent: NotificationEvent): Result<NotificationContent> =
|
||||
when (notificationEvent) {
|
||||
is NotificationEvent.Timeline -> timelineEventToNotificationContentMapper.map(notificationEvent.event)
|
||||
is NotificationEvent.Invite -> NotificationContent.Invite(
|
||||
senderId = UserId(notificationEvent.sender),
|
||||
is NotificationEvent.Invite -> Result.success(
|
||||
NotificationContent.Invite(
|
||||
senderId = UserId(notificationEvent.sender),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,9 @@ class RustNotificationService(
|
|||
is BatchNotificationResult.Ok -> {
|
||||
when (val status = result.status) {
|
||||
is NotificationStatus.Event -> {
|
||||
put(eventId, Result.success(notificationMapper.map(sessionId, eventId, roomId, status.item)))
|
||||
val result = notificationMapper.map(sessionId, eventId, roomId, status.item)
|
||||
result.onFailure { Timber.e(it, "Could not map notification event $eventId") }
|
||||
put(eventId, result)
|
||||
}
|
||||
is NotificationStatus.EventNotFound -> {
|
||||
Timber.e("Could not retrieve event for notification with $eventId - event not found")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.notification
|
||||
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.notification.CallNotifyType
|
||||
|
|
@ -21,10 +22,12 @@ import org.matrix.rustcomponents.sdk.TimelineEventType
|
|||
import org.matrix.rustcomponents.sdk.use
|
||||
|
||||
class TimelineEventToNotificationContentMapper {
|
||||
fun map(timelineEvent: TimelineEvent): NotificationContent {
|
||||
return timelineEvent.use {
|
||||
timelineEvent.eventType().use { eventType ->
|
||||
eventType.toContent(senderId = UserId(timelineEvent.senderId()))
|
||||
fun map(timelineEvent: TimelineEvent): Result<NotificationContent> {
|
||||
return runCatchingExceptions {
|
||||
timelineEvent.use {
|
||||
timelineEvent.eventType().use { eventType ->
|
||||
eventType.toContent(senderId = UserId(timelineEvent.senderId()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import org.matrix.rustcomponents.sdk.NoPointer
|
|||
import org.matrix.rustcomponents.sdk.TimelineEvent
|
||||
import org.matrix.rustcomponents.sdk.TimelineEventType
|
||||
|
||||
class FakeFfiTimelineEvent(
|
||||
open class FakeFfiTimelineEvent(
|
||||
val timestamp: ULong = A_FAKE_TIMESTAMP.toULong(),
|
||||
val timelineEventType: TimelineEventType = aRustTimelineEventTypeMessageLike(),
|
||||
val senderId: String = A_USER_ID_2.value,
|
||||
|
|
|
|||
|
|
@ -12,8 +12,12 @@ import io.element.android.libraries.matrix.api.exception.NotificationResolverExc
|
|||
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
||||
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustBatchNotificationResult
|
||||
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustNotificationEventTimeline
|
||||
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustNotificationItem
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiNotificationClient
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiTimelineEvent
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
|
|
@ -26,6 +30,8 @@ import kotlinx.coroutines.test.TestScope
|
|||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.matrix.rustcomponents.sdk.NotificationClient
|
||||
import org.matrix.rustcomponents.sdk.NotificationStatus
|
||||
import org.matrix.rustcomponents.sdk.TimelineEventType
|
||||
|
||||
class RustNotificationServiceTest {
|
||||
@Test
|
||||
|
|
@ -49,6 +55,33 @@ class RustNotificationServiceTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test mapping invalid item only drops that item`() = runTest {
|
||||
val error = IllegalStateException("This event type is not supported")
|
||||
val faultyEvent = object : FakeFfiTimelineEvent() {
|
||||
override fun eventType(): TimelineEventType {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
val notificationClient = FakeFfiNotificationClient(
|
||||
notificationItemResult = mapOf(
|
||||
AN_EVENT_ID.value to aRustBatchNotificationResult(
|
||||
notificationStatus = NotificationStatus.Event(aRustNotificationItem(aRustNotificationEventTimeline(faultyEvent)))
|
||||
),
|
||||
AN_EVENT_ID_2.value to aRustBatchNotificationResult()
|
||||
),
|
||||
)
|
||||
val sut = createRustNotificationService(
|
||||
notificationClient = notificationClient,
|
||||
)
|
||||
val result = sut.getNotifications(mapOf(A_ROOM_ID to listOf(AN_EVENT_ID, AN_EVENT_ID_2))).getOrThrow()
|
||||
val exception = result[AN_EVENT_ID]!!.exceptionOrNull()
|
||||
assertThat(exception).isEqualTo(error)
|
||||
|
||||
val successfulResult = result[AN_EVENT_ID_2]
|
||||
assertThat(successfulResult?.isSuccess).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test unable to resolve event`() = runTest {
|
||||
val notificationClient = FakeFfiNotificationClient(
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ class FakeMatrixClient(
|
|||
private val canReportRoomLambda: () -> Boolean = { false },
|
||||
private val isLivekitRtcSupportedLambda: () -> Boolean = { false },
|
||||
override val ignoredUsersFlow: StateFlow<ImmutableList<UserId>> = MutableStateFlow(persistentListOf()),
|
||||
private val getMaxUploadSizeResult: () -> Result<Long> = { lambdaError() },
|
||||
) : MatrixClient {
|
||||
var setDisplayNameCalled: Boolean = false
|
||||
private set
|
||||
|
|
@ -343,4 +344,8 @@ class FakeMatrixClient(
|
|||
override suspend fun isLivekitRtcSupported(): Boolean {
|
||||
return isLivekitRtcSupportedLambda()
|
||||
}
|
||||
|
||||
override suspend fun getMaxFileUploadSize(): Result<Long> {
|
||||
return getMaxUploadSizeResult()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.mediaupload.api
|
||||
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Provides the maximum upload size allowed by the Matrix server.
|
||||
*/
|
||||
class MaxUploadSizeProvider @Inject constructor(
|
||||
private val matrixClient: MatrixClient,
|
||||
) {
|
||||
suspend fun getMaxUploadSize(): Result<Long> {
|
||||
return matrixClient.getMaxFileUploadSize()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.mediaupload.api
|
||||
|
||||
import io.element.android.libraries.androidutils.media.VideoCompressorHelper
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
|
||||
data class MediaOptimizationConfig(
|
||||
val compressImages: Boolean,
|
||||
val videoCompressionPreset: VideoCompressionPreset,
|
||||
)
|
||||
|
||||
fun VideoCompressionPreset.compressorHelper(): VideoCompressorHelper = when (this) {
|
||||
VideoCompressionPreset.STANDARD -> VideoCompressorHelper(1280)
|
||||
VideoCompressionPreset.HIGH -> VideoCompressorHelper(1920)
|
||||
VideoCompressionPreset.LOW -> VideoCompressorHelper(640)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.mediaupload.api
|
||||
|
||||
fun interface MediaOptimizationConfigProvider {
|
||||
suspend fun get(): MediaOptimizationConfig
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ interface MediaPreProcessor {
|
|||
uri: Uri,
|
||||
mimeType: String,
|
||||
deleteOriginal: Boolean,
|
||||
compressIfPossible: Boolean,
|
||||
mediaOptimizationConfig: MediaOptimizationConfig,
|
||||
): Result<MediaUploadInfo>
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -14,17 +14,15 @@ import io.element.android.libraries.matrix.api.core.ProgressCallback
|
|||
import io.element.android.libraries.matrix.api.media.MediaUploadHandler
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.first
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
class MediaSender @Inject constructor(
|
||||
private val preProcessor: MediaPreProcessor,
|
||||
private val room: JoinedRoom,
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider,
|
||||
) {
|
||||
private val ongoingUploadJobs = ConcurrentHashMap<Job.Key, MediaUploadHandler>()
|
||||
val hasOngoingMediaUploads get() = ongoingUploadJobs.isNotEmpty()
|
||||
|
|
@ -32,14 +30,14 @@ class MediaSender @Inject constructor(
|
|||
suspend fun preProcessMedia(
|
||||
uri: Uri,
|
||||
mimeType: String,
|
||||
mediaOptimizationConfig: MediaOptimizationConfig,
|
||||
): Result<MediaUploadInfo> {
|
||||
val compressIfPossible = sessionPreferencesStore.doesCompressMedia().first()
|
||||
return preProcessor
|
||||
.process(
|
||||
uri = uri,
|
||||
mimeType = mimeType,
|
||||
deleteOriginal = false,
|
||||
compressIfPossible = compressIfPossible,
|
||||
mediaOptimizationConfig = mediaOptimizationConfig,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -67,14 +65,14 @@ class MediaSender @Inject constructor(
|
|||
formattedCaption: String? = null,
|
||||
progressCallback: ProgressCallback? = null,
|
||||
inReplyToEventId: EventId? = null,
|
||||
mediaOptimizationConfig: MediaOptimizationConfig,
|
||||
): Result<Unit> {
|
||||
val compressIfPossible = sessionPreferencesStore.doesCompressMedia().first()
|
||||
return preProcessor
|
||||
.process(
|
||||
uri = uri,
|
||||
mimeType = mimeType,
|
||||
deleteOriginal = false,
|
||||
compressIfPossible = compressIfPossible,
|
||||
mediaOptimizationConfig = mediaOptimizationConfig,
|
||||
)
|
||||
.flatMapCatching { info ->
|
||||
room.liveTimeline.sendMedia(
|
||||
|
|
@ -100,7 +98,7 @@ class MediaSender @Inject constructor(
|
|||
uri = uri,
|
||||
mimeType = mimeType,
|
||||
deleteOriginal = true,
|
||||
compressIfPossible = false,
|
||||
mediaOptimizationConfig = mediaOptimizationConfigProvider.get(),
|
||||
)
|
||||
.flatMapCatching { info ->
|
||||
val audioInfo = (info as MediaUploadInfo.Audio).audioInfo
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
|
|||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -34,6 +33,11 @@ import java.io.File
|
|||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class MediaSenderTest {
|
||||
private val mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `given an attachment when sending it the preprocessor always runs`() = runTest {
|
||||
val preProcessor = FakeMediaPreProcessor()
|
||||
|
|
@ -57,7 +61,7 @@ class MediaSenderTest {
|
|||
)
|
||||
|
||||
val uri = Uri.parse("content://image.jpg")
|
||||
sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg)
|
||||
sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, mediaOptimizationConfig = mediaOptimizationConfig)
|
||||
|
||||
assertThat(preProcessor.processCallCount).isEqualTo(1)
|
||||
}
|
||||
|
|
@ -76,7 +80,7 @@ class MediaSenderTest {
|
|||
val sender = createMediaSender(room = room)
|
||||
|
||||
val uri = Uri.parse("content://image.jpg")
|
||||
sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg)
|
||||
sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, mediaOptimizationConfig = mediaOptimizationConfig)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -87,7 +91,7 @@ class MediaSenderTest {
|
|||
val sender = createMediaSender(preProcessor)
|
||||
|
||||
val uri = Uri.parse("content://image.jpg")
|
||||
val result = sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg)
|
||||
val result = sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, mediaOptimizationConfig = mediaOptimizationConfig)
|
||||
|
||||
assertThat(result.exceptionOrNull()).isNotNull()
|
||||
}
|
||||
|
|
@ -112,7 +116,7 @@ class MediaSenderTest {
|
|||
)
|
||||
|
||||
val uri = Uri.parse("content://image.jpg")
|
||||
val result = sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg)
|
||||
val result = sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, mediaOptimizationConfig = mediaOptimizationConfig)
|
||||
|
||||
assertThat(result.exceptionOrNull()).isNotNull()
|
||||
}
|
||||
|
|
@ -132,7 +136,7 @@ class MediaSenderTest {
|
|||
val sender = createMediaSender(room = room)
|
||||
val sendJob = launch {
|
||||
val uri = Uri.parse("content://image.jpg")
|
||||
sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg)
|
||||
sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, mediaOptimizationConfig = mediaOptimizationConfig)
|
||||
}
|
||||
// Wait until several internal tasks run and the file is being uploaded
|
||||
advanceTimeBy(3L)
|
||||
|
|
@ -154,10 +158,10 @@ class MediaSenderTest {
|
|||
private fun createMediaSender(
|
||||
preProcessor: MediaPreProcessor = FakeMediaPreProcessor(),
|
||||
room: JoinedRoom = FakeJoinedRoom(),
|
||||
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
mediaOptimizationConfigProvider: MediaOptimizationConfigProvider = MediaOptimizationConfigProvider { mediaOptimizationConfig },
|
||||
) = MediaSender(
|
||||
preProcessor = preProcessor,
|
||||
room = room,
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
mediaOptimizationConfigProvider = mediaOptimizationConfigProvider,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ import io.element.android.libraries.matrix.api.media.AudioInfo
|
|||
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig
|
||||
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
|
||||
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
|
@ -76,7 +78,7 @@ class AndroidMediaPreProcessor @Inject constructor(
|
|||
uri: Uri,
|
||||
mimeType: String,
|
||||
deleteOriginal: Boolean,
|
||||
compressIfPossible: Boolean,
|
||||
mediaOptimizationConfig: MediaOptimizationConfig,
|
||||
): Result<MediaUploadInfo> = withContext(coroutineDispatchers.computation) {
|
||||
runCatchingExceptions {
|
||||
val result = when {
|
||||
|
|
@ -85,10 +87,10 @@ class AndroidMediaPreProcessor @Inject constructor(
|
|||
processFile(uri, mimeType)
|
||||
}
|
||||
mimeType.isMimeTypeImage() -> {
|
||||
val shouldBeCompressed = compressIfPossible && mimeType !in notCompressibleImageTypes
|
||||
val shouldBeCompressed = mediaOptimizationConfig.compressImages && mimeType !in notCompressibleImageTypes
|
||||
processImage(uri, mimeType, shouldBeCompressed)
|
||||
}
|
||||
mimeType.isMimeTypeVideo() -> processVideo(uri, mimeType, compressIfPossible)
|
||||
mimeType.isMimeTypeVideo() -> processVideo(uri, mimeType, mediaOptimizationConfig.videoCompressionPreset)
|
||||
mimeType.isMimeTypeAudio() -> processAudio(uri, mimeType)
|
||||
else -> processFile(uri, mimeType)
|
||||
}
|
||||
|
|
@ -214,9 +216,9 @@ class AndroidMediaPreProcessor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun processVideo(uri: Uri, mimeType: String?, shouldBeCompressed: Boolean): MediaUploadInfo {
|
||||
private suspend fun processVideo(uri: Uri, mimeType: String?, videoCompressionPreset: VideoCompressionPreset): MediaUploadInfo {
|
||||
val resultFile = runCatchingExceptions {
|
||||
videoCompressor.compress(uri, shouldBeCompressed)
|
||||
videoCompressor.compress(uri, videoCompressionPreset)
|
||||
.onEach {
|
||||
if (it is VideoTranscodingEvent.Progress) {
|
||||
Timber.d("Video compression progress: ${it.value}%")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.mediaupload.impl
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig
|
||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import kotlinx.coroutines.flow.first
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultMediaOptimizationConfigProvider @Inject constructor(
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
) : MediaOptimizationConfigProvider {
|
||||
override suspend fun get(): MediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = sessionPreferencesStore.doesOptimizeImages().first(),
|
||||
videoCompressionPreset = sessionPreferencesStore.getVideoCompressionPreset().first(),
|
||||
)
|
||||
}
|
||||
|
|
@ -11,10 +11,10 @@ import android.content.Context
|
|||
import android.media.MediaCodecInfo
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.net.Uri
|
||||
import android.util.Size
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.common.util.Size
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.effect.Presentation
|
||||
import androidx.media3.transformer.Composition
|
||||
|
|
@ -31,6 +31,7 @@ import io.element.android.libraries.androidutils.file.createTmpFile
|
|||
import io.element.android.libraries.androidutils.file.safeDelete
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -47,12 +48,12 @@ class VideoCompressor @Inject constructor(
|
|||
@ApplicationContext private val context: Context,
|
||||
) {
|
||||
@OptIn(UnstableApi::class)
|
||||
fun compress(uri: Uri, shouldBeCompressed: Boolean): Flow<VideoTranscodingEvent> = callbackFlow {
|
||||
fun compress(uri: Uri, videoCompressionPreset: VideoCompressionPreset): Flow<VideoTranscodingEvent> = callbackFlow {
|
||||
val metadata = getVideoMetadata(uri)
|
||||
|
||||
val videoCompressorConfig = VideoCompressorConfigFactory.create(
|
||||
metadata = metadata,
|
||||
shouldBeCompressed = shouldBeCompressed
|
||||
preset = videoCompressionPreset,
|
||||
)
|
||||
|
||||
val tmpFile = context.createTmpFile(extension = "mp4")
|
||||
|
|
@ -60,7 +61,7 @@ class VideoCompressor @Inject constructor(
|
|||
val width = metadata?.width ?: Int.MAX_VALUE
|
||||
val height = metadata?.height ?: Int.MAX_VALUE
|
||||
|
||||
val videoResizeEffect = videoCompressorConfig.resizer?.let {
|
||||
val videoResizeEffect = videoCompressorConfig.videoCompressorHelper?.let {
|
||||
val outputSize = it.getOutputSize(Size(width, height))
|
||||
if (metadata?.rotation == 90 || metadata?.rotation == 270) {
|
||||
// If the video is rotated, we need to swap width and height
|
||||
|
|
|
|||
|
|
@ -7,62 +7,37 @@
|
|||
|
||||
package io.element.android.libraries.mediaupload.impl
|
||||
|
||||
import android.util.Size
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.util.Size
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.transformer.VideoEncoderSettings
|
||||
import io.element.android.libraries.androidutils.media.VideoCompressorHelper
|
||||
import io.element.android.libraries.mediaupload.api.compressorHelper
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
internal object VideoCompressorConfigFactory {
|
||||
// Major dimension of 720p
|
||||
private const val MAX_COMPRESSED_PIXEL_SIZE = 1280
|
||||
|
||||
// Major dimension of 1080p
|
||||
private const val MAX_PIXEL_SIZE = 1920
|
||||
|
||||
private const val DEFAULT_FRAME_RATE = 30
|
||||
|
||||
fun create(
|
||||
metadata: VideoFileMetadata?,
|
||||
shouldBeCompressed: Boolean,
|
||||
preset: VideoCompressionPreset,
|
||||
): VideoCompressorConfig {
|
||||
val width = metadata?.width?.takeIf { it >= 0 } ?: Int.MAX_VALUE
|
||||
val height = metadata?.height?.takeIf { it >= 0 } ?: Int.MAX_VALUE
|
||||
val originalBitrate = metadata?.bitrate?.takeIf { it >= 0 }
|
||||
val originalFrameRate = metadata?.frameRate?.takeIf { it >= 0 } ?: DEFAULT_FRAME_RATE
|
||||
|
||||
// We only create a resizer if needed
|
||||
val resizer = when {
|
||||
shouldBeCompressed && (width > MAX_COMPRESSED_PIXEL_SIZE || height > MAX_COMPRESSED_PIXEL_SIZE) -> VideoResizer(MAX_COMPRESSED_PIXEL_SIZE)
|
||||
width > MAX_PIXEL_SIZE || height > MAX_PIXEL_SIZE -> VideoResizer(MAX_PIXEL_SIZE)
|
||||
else -> null
|
||||
}
|
||||
val resizer = preset.compressorHelper()
|
||||
|
||||
// If we are resizing, we also want to reduce the frame rate to the default value (30fps)
|
||||
val newFrameRate = if (resizer is VideoResizer) {
|
||||
min(originalFrameRate, DEFAULT_FRAME_RATE)
|
||||
} else {
|
||||
originalFrameRate
|
||||
}
|
||||
val newFrameRate = min(originalFrameRate, DEFAULT_FRAME_RATE)
|
||||
|
||||
// If we need to resize the video, we also want to recalculate the bitrate
|
||||
val newBitrate = if (resizer is VideoResizer) {
|
||||
val maxSize = resizer.getOutputSize(Size(width, height))
|
||||
val pixelsPerFrame = maxSize.width * maxSize.height
|
||||
val frameRate = newFrameRate
|
||||
// Apparently, 0.1 bits per pixel is a sweet spot for video compression
|
||||
val bitsPerPixel = 0.1f
|
||||
|
||||
(pixelsPerFrame * bitsPerPixel * frameRate).toLong()
|
||||
} else {
|
||||
originalBitrate
|
||||
}
|
||||
val newBitrate = resizer.calculateOptimalBitrate(Size(width, height), newFrameRate)
|
||||
|
||||
return VideoCompressorConfig(
|
||||
resizer = resizer,
|
||||
newBitRate = newBitrate?.toInt() ?: VideoEncoderSettings.NO_VALUE,
|
||||
videoCompressorHelper = resizer,
|
||||
newBitRate = newBitrate.toInt(),
|
||||
newFrameRate = newFrameRate,
|
||||
)
|
||||
}
|
||||
|
|
@ -70,28 +45,7 @@ internal object VideoCompressorConfigFactory {
|
|||
|
||||
@OptIn(UnstableApi::class)
|
||||
internal data class VideoCompressorConfig(
|
||||
val resizer: VideoResizer?,
|
||||
val videoCompressorHelper: VideoCompressorHelper,
|
||||
val newBitRate: Int,
|
||||
val newFrameRate: Int,
|
||||
)
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
internal class VideoResizer(
|
||||
val maxSize: Int,
|
||||
) {
|
||||
fun getOutputSize(inputSize: Size): Size {
|
||||
val resultMajor = min(inputSize.major(), maxSize)
|
||||
val aspectRatio = inputSize.width.toFloat() / inputSize.height.toFloat()
|
||||
return if (inputSize.width > inputSize.height) {
|
||||
Size(resultMajor, (resultMajor / aspectRatio).roundToInt())
|
||||
} else {
|
||||
Size((resultMajor * aspectRatio).roundToInt(), resultMajor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
internal fun Size.major(): Int = if (width > height) width else height
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
internal fun Size.minor(): Int = if (width < height) width else height
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ import io.element.android.libraries.matrix.api.media.FileInfo
|
|||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
import io.element.android.libraries.matrix.api.media.ThumbnailInfo
|
||||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig
|
||||
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
|
||||
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import io.element.android.tests.testutils.fake.FakeTemporaryUriDeleter
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
|
|
@ -41,7 +43,7 @@ import kotlin.time.Duration
|
|||
class AndroidMediaPreProcessorTest {
|
||||
private suspend fun TestScope.process(
|
||||
asset: Asset,
|
||||
compressIfPossible: Boolean,
|
||||
mediaOptimizationConfig: MediaOptimizationConfig,
|
||||
sdkIntVersion: Int = Build.VERSION_CODES.P,
|
||||
deleteOriginal: Boolean = false,
|
||||
): MediaUploadInfo {
|
||||
|
|
@ -57,7 +59,7 @@ class AndroidMediaPreProcessorTest {
|
|||
uri = file.toUri(),
|
||||
mimeType = asset.mimeType,
|
||||
deleteOriginal = deleteOriginal,
|
||||
compressIfPossible = compressIfPossible,
|
||||
mediaOptimizationConfig = mediaOptimizationConfig,
|
||||
)
|
||||
val data = result.getOrThrow()
|
||||
assertThat(data.file.path).endsWith(asset.filename)
|
||||
|
|
@ -70,7 +72,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing png`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImagePng,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
assertThat(info.thumbnailFile).isNotNull()
|
||||
|
|
@ -91,7 +96,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing png api Q`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImagePng,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
sdkIntVersion = Build.VERSION_CODES.Q,
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
|
|
@ -114,7 +122,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing png no compression`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImagePng,
|
||||
compressIfPossible = false,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = false,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
assertThat(info.thumbnailFile).isNotNull()
|
||||
|
|
@ -136,7 +147,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing png and delete`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImagePng,
|
||||
compressIfPossible = false,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = false,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
deleteOriginal = true,
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
|
|
@ -161,7 +175,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing jpeg`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImageJpeg,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
assertThat(info.thumbnailFile).isNotNull()
|
||||
|
|
@ -182,7 +199,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing jpeg api Q`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImageJpeg,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
sdkIntVersion = Build.VERSION_CODES.Q,
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
|
|
@ -205,7 +225,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing jpeg no compression`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImageJpeg,
|
||||
compressIfPossible = false,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = false,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
assertThat(info.thumbnailFile).isNotNull()
|
||||
|
|
@ -227,7 +250,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing jpeg and delete`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetImageJpeg,
|
||||
compressIfPossible = false,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = false,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
deleteOriginal = true,
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
|
|
@ -252,7 +278,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing gif`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetAnimatedGif,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Image
|
||||
assertThat(info.thumbnailFile).isNotNull()
|
||||
|
|
@ -273,7 +302,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing file`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetText,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.AnyFile
|
||||
assertThat(info.fileInfo).isEqualTo(
|
||||
|
|
@ -291,7 +323,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing video`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetVideo,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Video
|
||||
assertThat(info.thumbnailFile).isNotNull()
|
||||
|
|
@ -315,7 +350,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing video no compression`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetVideo,
|
||||
compressIfPossible = false,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.HIGH,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Video
|
||||
// Computing thumbnailFile is failing with Robolectric
|
||||
|
|
@ -341,7 +379,10 @@ class AndroidMediaPreProcessorTest {
|
|||
fun `test processing audio`() = runTest {
|
||||
val mediaUploadInfo = process(
|
||||
asset = assetAudio,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
val info = mediaUploadInfo as MediaUploadInfo.Audio
|
||||
assertThat(info.audioInfo).isEqualTo(
|
||||
|
|
@ -363,7 +404,10 @@ class AndroidMediaPreProcessorTest {
|
|||
uri = file.toUri(),
|
||||
mimeType = MimeTypes.PlainText,
|
||||
deleteOriginal = false,
|
||||
compressIfPossible = true,
|
||||
mediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
),
|
||||
)
|
||||
assertThat(result.isFailure).isTrue()
|
||||
val failure = result.exceptionOrNull()
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package io.element.android.libraries.mediaupload.impl
|
|||
|
||||
import androidx.media3.transformer.VideoEncoderSettings
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
|
@ -20,16 +21,16 @@ class VideoCompressorConfigFactoryTest {
|
|||
fun `if we don't have metadata the video will be resized`() {
|
||||
// Given
|
||||
val metadata = null
|
||||
val shouldBeCompressed = false
|
||||
val preset = VideoCompressionPreset.STANDARD
|
||||
|
||||
// When
|
||||
val videoCompressorConfig = VideoCompressorConfigFactory.create(
|
||||
metadata = metadata,
|
||||
shouldBeCompressed = shouldBeCompressed
|
||||
preset = preset,
|
||||
)
|
||||
|
||||
// Then
|
||||
assertThat(videoCompressorConfig.resizer).isNotNull()
|
||||
assertThat(videoCompressorConfig.videoCompressorHelper).isNotNull()
|
||||
assertThat(videoCompressorConfig.newFrameRate).isEqualTo(30)
|
||||
assertThat(videoCompressorConfig.newBitRate).isNotEqualTo(VideoEncoderSettings.NO_VALUE)
|
||||
}
|
||||
|
|
@ -38,71 +39,71 @@ class VideoCompressorConfigFactoryTest {
|
|||
fun `if the video should be compressed and is larger than 720p it will be resized`() {
|
||||
// Given
|
||||
val metadata = VideoFileMetadata(width = 1920, height = 1080, bitrate = 1_000_000, frameRate = 50, rotation = 0)
|
||||
val shouldBeCompressed = true
|
||||
val preset = VideoCompressionPreset.STANDARD
|
||||
|
||||
// When
|
||||
val videoCompressorConfig = VideoCompressorConfigFactory.create(
|
||||
metadata = metadata,
|
||||
shouldBeCompressed = shouldBeCompressed
|
||||
preset = preset,
|
||||
)
|
||||
|
||||
// Then
|
||||
assertIsResized(videoCompressorConfig)
|
||||
assertIsResized(videoCompressorConfig, metadata.width)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if the video should be compressed and is smaller or equal to 720p it will not be resized`() {
|
||||
// Given
|
||||
val metadata = VideoFileMetadata(width = 1280, height = 720, bitrate = 1_000_000, frameRate = 50, rotation = 0)
|
||||
val shouldBeCompressed = true
|
||||
val preset = VideoCompressionPreset.STANDARD
|
||||
|
||||
// When
|
||||
val videoCompressorConfig = VideoCompressorConfigFactory.create(
|
||||
metadata = metadata,
|
||||
shouldBeCompressed = shouldBeCompressed
|
||||
preset = preset,
|
||||
)
|
||||
|
||||
// Then
|
||||
assertIsNotResized(videoCompressorConfig)
|
||||
assertIsNotResized(videoCompressorConfig, 1280)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if the video should not be compressed and is larger than 1080p it will be resized`() {
|
||||
// Given
|
||||
val metadata = VideoFileMetadata(width = 2560, height = 1440, bitrate = 1_000_000, frameRate = 50, rotation = 0)
|
||||
val shouldBeCompressed = false
|
||||
val preset = VideoCompressionPreset.HIGH
|
||||
|
||||
// When
|
||||
val videoCompressorConfig = VideoCompressorConfigFactory.create(
|
||||
metadata = metadata,
|
||||
shouldBeCompressed = shouldBeCompressed
|
||||
preset = preset,
|
||||
)
|
||||
|
||||
// Then
|
||||
assertIsResized(videoCompressorConfig)
|
||||
assertIsResized(videoCompressorConfig, metadata.width)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if the video should not be compressed and is smaller or equal than 1080p it will not be resized`() {
|
||||
// Given
|
||||
val metadata = VideoFileMetadata(width = 1920, height = 1080, bitrate = 1_000_000, frameRate = 50, rotation = 0)
|
||||
val shouldBeCompressed = false
|
||||
val preset = VideoCompressionPreset.HIGH
|
||||
|
||||
// When
|
||||
val videoCompressorConfig = VideoCompressorConfigFactory.create(
|
||||
metadata = metadata,
|
||||
shouldBeCompressed = shouldBeCompressed
|
||||
preset = preset,
|
||||
)
|
||||
|
||||
// Then
|
||||
assertIsNotResized(videoCompressorConfig)
|
||||
assertIsNotResized(videoCompressorConfig, 1920)
|
||||
}
|
||||
|
||||
private inline fun assertIsResized(videoCompressorConfig: VideoCompressorConfig) {
|
||||
assertThat(videoCompressorConfig.resizer).isNotNull()
|
||||
private inline fun assertIsResized(videoCompressorConfig: VideoCompressorConfig, referenceSize: Int) {
|
||||
assertThat(videoCompressorConfig.videoCompressorHelper.maxSize).isNotEqualTo(referenceSize)
|
||||
}
|
||||
|
||||
private inline fun assertIsNotResized(videoCompressorConfig: VideoCompressorConfig) {
|
||||
assertThat(videoCompressorConfig.resizer).isNull()
|
||||
private inline fun assertIsNotResized(videoCompressorConfig: VideoCompressorConfig, referenceSize: Int) {
|
||||
assertThat(videoCompressorConfig.videoCompressorHelper.maxSize).isEqualTo(referenceSize)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.mediaupload.test
|
||||
|
||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig
|
||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
|
||||
class FakeMediaOptimizationConfigProvider(
|
||||
val config: MediaOptimizationConfig = MediaOptimizationConfig(
|
||||
compressImages = true,
|
||||
videoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
)
|
||||
) : MediaOptimizationConfigProvider {
|
||||
override suspend fun get(): MediaOptimizationConfig = config
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.media.AudioInfo
|
|||
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig
|
||||
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
|
||||
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
|
|
@ -45,7 +46,7 @@ class FakeMediaPreProcessor(
|
|||
uri: Uri,
|
||||
mimeType: String,
|
||||
deleteOriginal: Boolean,
|
||||
compressIfPossible: Boolean
|
||||
mediaOptimizationConfig: MediaOptimizationConfig,
|
||||
): Result<MediaUploadInfo> = simulateLongTask {
|
||||
processLatch?.await()
|
||||
processCallCount++
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ data class MediaInfo(
|
|||
val filename: String,
|
||||
val caption: String?,
|
||||
val mimeType: String,
|
||||
val fileSize: Long?,
|
||||
val formattedFileSize: String,
|
||||
val fileExtension: String,
|
||||
val senderId: UserId?,
|
||||
|
|
@ -36,6 +37,7 @@ fun anImageMediaInfo(
|
|||
dateSentFull: String? = null,
|
||||
): MediaInfo = MediaInfo(
|
||||
filename = "an image file.jpg",
|
||||
fileSize = 4 * 1024 * 1024,
|
||||
caption = caption,
|
||||
mimeType = MimeTypes.Jpeg,
|
||||
formattedFileSize = "4MB",
|
||||
|
|
@ -57,6 +59,7 @@ fun aVideoMediaInfo(
|
|||
duration: String? = null,
|
||||
): MediaInfo = MediaInfo(
|
||||
filename = "a video file.mp4",
|
||||
fileSize = 14 * 1024 * 1024,
|
||||
caption = caption,
|
||||
mimeType = MimeTypes.Mp4,
|
||||
formattedFileSize = "14MB",
|
||||
|
|
@ -78,6 +81,7 @@ fun aPdfMediaInfo(
|
|||
dateSentFull: String? = null,
|
||||
): MediaInfo = MediaInfo(
|
||||
filename = filename,
|
||||
fileSize = 23 * 1024 * 1024,
|
||||
caption = caption,
|
||||
mimeType = MimeTypes.Pdf,
|
||||
formattedFileSize = "23MB",
|
||||
|
|
@ -98,6 +102,7 @@ fun anApkMediaInfo(
|
|||
dateSentFull: String? = null,
|
||||
): MediaInfo = MediaInfo(
|
||||
filename = "an apk file.apk",
|
||||
fileSize = 50 * 1024 * 1024,
|
||||
caption = null,
|
||||
mimeType = MimeTypes.Apk,
|
||||
formattedFileSize = "50MB",
|
||||
|
|
@ -121,6 +126,7 @@ fun anAudioMediaInfo(
|
|||
duration: String? = null,
|
||||
): MediaInfo = MediaInfo(
|
||||
filename = filename,
|
||||
fileSize = 7 * 1024 * 1024,
|
||||
caption = caption,
|
||||
mimeType = MimeTypes.Mp3,
|
||||
formattedFileSize = "7MB",
|
||||
|
|
@ -144,6 +150,7 @@ fun aVoiceMediaInfo(
|
|||
duration: String? = null,
|
||||
): MediaInfo = MediaInfo(
|
||||
filename = filename,
|
||||
fileSize = 3 * 1024 * 1024,
|
||||
caption = caption,
|
||||
mimeType = MimeTypes.Ogg,
|
||||
formattedFileSize = "3MB",
|
||||
|
|
@ -165,6 +172,7 @@ fun aTxtMediaInfo(
|
|||
dateSentFull: String? = null,
|
||||
): MediaInfo = MediaInfo(
|
||||
filename = filename,
|
||||
fileSize = 2 * 1024,
|
||||
caption = caption,
|
||||
mimeType = MimeTypes.PlainText,
|
||||
formattedFileSize = "2kB",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ class DefaultMediaViewerEntryPoint @Inject constructor() : MediaViewerEntryPoint
|
|||
eventId = null,
|
||||
mediaInfo = MediaInfo(
|
||||
filename = filename,
|
||||
fileSize = null,
|
||||
caption = null,
|
||||
mimeType = mimeType,
|
||||
formattedFileSize = "",
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ class EventItemFactory @Inject constructor(
|
|||
eventId = currentTimelineItem.eventId,
|
||||
mediaInfo = MediaInfo(
|
||||
filename = type.filename,
|
||||
fileSize = type.info?.size,
|
||||
caption = type.caption,
|
||||
mimeType = type.info?.mimetype.orEmpty(),
|
||||
formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(),
|
||||
|
|
@ -111,6 +112,7 @@ class EventItemFactory @Inject constructor(
|
|||
eventId = currentTimelineItem.eventId,
|
||||
mediaInfo = MediaInfo(
|
||||
filename = type.filename,
|
||||
fileSize = type.info?.size,
|
||||
caption = type.caption,
|
||||
mimeType = type.info?.mimetype.orEmpty(),
|
||||
formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(),
|
||||
|
|
@ -131,6 +133,7 @@ class EventItemFactory @Inject constructor(
|
|||
eventId = currentTimelineItem.eventId,
|
||||
mediaInfo = MediaInfo(
|
||||
filename = type.filename,
|
||||
fileSize = type.info?.size,
|
||||
caption = type.caption,
|
||||
mimeType = type.info?.mimetype.orEmpty(),
|
||||
formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(),
|
||||
|
|
@ -151,6 +154,7 @@ class EventItemFactory @Inject constructor(
|
|||
eventId = currentTimelineItem.eventId,
|
||||
mediaInfo = MediaInfo(
|
||||
filename = type.filename,
|
||||
fileSize = type.info?.size,
|
||||
caption = type.caption,
|
||||
mimeType = type.info?.mimetype.orEmpty(),
|
||||
formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(),
|
||||
|
|
@ -171,6 +175,7 @@ class EventItemFactory @Inject constructor(
|
|||
eventId = currentTimelineItem.eventId,
|
||||
mediaInfo = MediaInfo(
|
||||
filename = type.filename,
|
||||
fileSize = type.info?.size,
|
||||
caption = type.caption,
|
||||
mimeType = type.info?.mimetype.orEmpty(),
|
||||
formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(),
|
||||
|
|
@ -191,6 +196,7 @@ class EventItemFactory @Inject constructor(
|
|||
eventId = currentTimelineItem.eventId,
|
||||
mediaInfo = MediaInfo(
|
||||
filename = type.filename,
|
||||
fileSize = type.info?.size,
|
||||
caption = type.caption,
|
||||
mimeType = type.info?.mimetype.orEmpty(),
|
||||
formattedFileSize = type.info?.size?.let { fileSizeFormatter.format(it) }.orEmpty(),
|
||||
|
|
|
|||
|
|
@ -87,15 +87,17 @@ class AndroidLocalMediaFactory @Inject constructor(
|
|||
): LocalMedia {
|
||||
val resolvedMimeType = mimeType ?: context.getMimeType(uri) ?: MimeTypes.OctetStream
|
||||
val fileName = name ?: context.getFileName(uri) ?: ""
|
||||
val fileSize = formattedFileSize ?: fileSizeFormatter.format(context.getFileSize(uri))
|
||||
val fileSize = context.getFileSize(uri)
|
||||
val calculatedFormattedFileSize = formattedFileSize ?: fileSizeFormatter.format(fileSize)
|
||||
val fileExtension = fileExtensionExtractor.extractFromName(fileName)
|
||||
return LocalMedia(
|
||||
uri = uri,
|
||||
info = MediaInfo(
|
||||
mimeType = resolvedMimeType,
|
||||
filename = fileName,
|
||||
fileSize = fileSize,
|
||||
caption = caption,
|
||||
formattedFileSize = fileSize,
|
||||
formattedFileSize = calculatedFormattedFileSize,
|
||||
fileExtension = fileExtension,
|
||||
senderId = senderId,
|
||||
senderName = senderName,
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ class DefaultEventItemFactoryTest {
|
|||
mediaInfo = MediaInfo(
|
||||
mimeType = MimeTypes.Apk,
|
||||
filename = "filename.apk",
|
||||
fileSize = 123L,
|
||||
caption = "caption",
|
||||
formattedFileSize = "123 Bytes",
|
||||
fileExtension = "apk",
|
||||
|
|
@ -205,6 +206,7 @@ class DefaultEventItemFactoryTest {
|
|||
mediaInfo = MediaInfo(
|
||||
mimeType = MimeTypes.Jpeg,
|
||||
filename = "filename.jpg",
|
||||
fileSize = 123L,
|
||||
caption = "caption",
|
||||
formattedFileSize = "123 Bytes",
|
||||
fileExtension = "jpg",
|
||||
|
|
@ -252,6 +254,7 @@ class DefaultEventItemFactoryTest {
|
|||
mediaInfo = MediaInfo(
|
||||
mimeType = MimeTypes.Mp3,
|
||||
filename = "filename.mp3",
|
||||
fileSize = 123L,
|
||||
caption = "caption",
|
||||
formattedFileSize = "123 Bytes",
|
||||
fileExtension = "mp3",
|
||||
|
|
@ -303,6 +306,7 @@ class DefaultEventItemFactoryTest {
|
|||
mediaInfo = MediaInfo(
|
||||
mimeType = MimeTypes.Mp4,
|
||||
filename = "filename.mp4",
|
||||
fileSize = 123L,
|
||||
caption = "caption",
|
||||
formattedFileSize = "123 Bytes",
|
||||
fileExtension = "mp4",
|
||||
|
|
@ -354,6 +358,7 @@ class DefaultEventItemFactoryTest {
|
|||
mediaInfo = MediaInfo(
|
||||
mimeType = MimeTypes.Ogg,
|
||||
filename = "filename.ogg",
|
||||
fileSize = 123L,
|
||||
caption = "caption",
|
||||
formattedFileSize = "123 Bytes",
|
||||
fileExtension = "ogg",
|
||||
|
|
@ -404,6 +409,7 @@ class DefaultEventItemFactoryTest {
|
|||
mediaInfo = MediaInfo(
|
||||
mimeType = MimeTypes.Gif,
|
||||
filename = "filename.gif",
|
||||
fileSize = 123L,
|
||||
caption = "caption",
|
||||
formattedFileSize = "123 Bytes",
|
||||
fileExtension = "gif",
|
||||
|
|
|
|||
|
|
@ -232,6 +232,7 @@ class TimelineMediaGalleryDataSourceTest {
|
|||
eventId = AN_EVENT_ID,
|
||||
mediaInfo = MediaInfo(
|
||||
filename = "body.jpg",
|
||||
fileSize = 888L,
|
||||
caption = "body.jpg caption",
|
||||
mimeType = MimeTypes.Jpeg,
|
||||
formattedFileSize = "888 Bytes",
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ class AndroidLocalMediaFactoryTest {
|
|||
assertThat(result.info).isEqualTo(
|
||||
MediaInfo(
|
||||
filename = "an image file.jpg",
|
||||
// MediaFile does not provide file size in this test
|
||||
fileSize = 0L,
|
||||
caption = null,
|
||||
mimeType = MimeTypes.Jpeg,
|
||||
formattedFileSize = "4MB",
|
||||
|
|
|
|||
|
|
@ -29,10 +29,16 @@ class FakeLocalMediaFactory(
|
|||
return aLocalMedia(uri = localMediaUri, mediaInfo = mediaInfo)
|
||||
}
|
||||
|
||||
override fun createFromUri(uri: Uri, mimeType: String?, name: String?, formattedFileSize: String?): LocalMedia {
|
||||
override fun createFromUri(
|
||||
uri: Uri,
|
||||
mimeType: String?,
|
||||
name: String?,
|
||||
formattedFileSize: String?
|
||||
): LocalMedia {
|
||||
val safeName = name ?: fallbackName
|
||||
val mediaInfo = MediaInfo(
|
||||
filename = safeName,
|
||||
fileSize = null,
|
||||
caption = null,
|
||||
mimeType = mimeType ?: fallbackMimeType,
|
||||
formattedFileSize = formattedFileSize ?: fallbackFileSize,
|
||||
|
|
|
|||
|
|
@ -28,8 +28,11 @@ interface SessionPreferencesStore {
|
|||
suspend fun setSkipSessionVerification(skip: Boolean)
|
||||
fun isSessionVerificationSkipped(): Flow<Boolean>
|
||||
|
||||
suspend fun setCompressMedia(compress: Boolean)
|
||||
fun doesCompressMedia(): Flow<Boolean>
|
||||
suspend fun setOptimizeImages(compress: Boolean)
|
||||
fun doesOptimizeImages(): Flow<Boolean>
|
||||
|
||||
suspend fun setVideoCompressionPreset(preset: VideoCompressionPreset)
|
||||
fun getVideoCompressionPreset(): Flow<VideoCompressionPreset>
|
||||
|
||||
suspend fun clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.preferences.api.store
|
||||
|
||||
/**
|
||||
* Video compression presets to use when processing videos before uploading them.
|
||||
*/
|
||||
enum class VideoCompressionPreset {
|
||||
/** High quality compression, suitable for high-resolution videos. */
|
||||
HIGH,
|
||||
|
||||
/** Standard quality compression, suitable for most videos. */
|
||||
STANDARD,
|
||||
|
||||
/** Low quality compression, suitable for low-resolution videos or when bandwidth is a concern. */
|
||||
LOW
|
||||
}
|
||||
|
|
@ -12,12 +12,15 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
|||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import io.element.android.libraries.androidutils.file.safeDelete
|
||||
import io.element.android.libraries.androidutils.hash.hash
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
|
@ -41,7 +44,8 @@ class DefaultSessionPreferencesStore(
|
|||
private val sendTypingNotificationsKey = booleanPreferencesKey("sendTypingNotifications")
|
||||
private val renderTypingNotificationsKey = booleanPreferencesKey("renderTypingNotifications")
|
||||
private val skipSessionVerification = booleanPreferencesKey("skipSessionVerification")
|
||||
private val compressMedia = booleanPreferencesKey("compressMedia")
|
||||
private val compressImages = booleanPreferencesKey("compressMedia")
|
||||
private val compressMediaPreset = stringPreferencesKey("compressMediaPreset")
|
||||
|
||||
private val dataStoreFile = storeFile(context, sessionId)
|
||||
private val store = PreferenceDataStoreFactory.create(
|
||||
|
|
@ -82,8 +86,12 @@ class DefaultSessionPreferencesStore(
|
|||
override suspend fun setSkipSessionVerification(skip: Boolean) = update(skipSessionVerification, skip)
|
||||
override fun isSessionVerificationSkipped(): Flow<Boolean> = get(skipSessionVerification) { false }
|
||||
|
||||
override suspend fun setCompressMedia(compress: Boolean) = update(compressMedia, compress)
|
||||
override fun doesCompressMedia(): Flow<Boolean> = get(compressMedia) { true }
|
||||
override suspend fun setOptimizeImages(compress: Boolean) = update(compressImages, compress)
|
||||
override fun doesOptimizeImages(): Flow<Boolean> = get(compressImages) { true }
|
||||
|
||||
override suspend fun setVideoCompressionPreset(preset: VideoCompressionPreset) = update(compressMediaPreset, preset.name)
|
||||
override fun getVideoCompressionPreset(): Flow<VideoCompressionPreset> = get(compressMediaPreset) { VideoCompressionPreset.STANDARD.name }
|
||||
.map { tryOrNull { VideoCompressionPreset.valueOf(it) } ?: VideoCompressionPreset.STANDARD }
|
||||
|
||||
override suspend fun clear() {
|
||||
dataStoreFile.safeDelete()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.libraries.preferences.test
|
||||
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ class InMemorySessionPreferencesStore(
|
|||
isRenderTypingNotificationsEnabled: Boolean = true,
|
||||
isSessionVerificationSkipped: Boolean = false,
|
||||
doesCompressMedia: Boolean = true,
|
||||
videoCompressionPreset: VideoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
) : SessionPreferencesStore {
|
||||
private val isSharePresenceEnabled = MutableStateFlow(isSharePresenceEnabled)
|
||||
private val isSendPublicReadReceiptsEnabled = MutableStateFlow(isSendPublicReadReceiptsEnabled)
|
||||
|
|
@ -27,6 +29,7 @@ class InMemorySessionPreferencesStore(
|
|||
private val isRenderTypingNotificationsEnabled = MutableStateFlow(isRenderTypingNotificationsEnabled)
|
||||
private val isSessionVerificationSkipped = MutableStateFlow(isSessionVerificationSkipped)
|
||||
private val doesCompressMedia = MutableStateFlow(doesCompressMedia)
|
||||
private val videoCompressionPreset = MutableStateFlow(videoCompressionPreset)
|
||||
var clearCallCount = 0
|
||||
private set
|
||||
|
||||
|
|
@ -68,9 +71,17 @@ class InMemorySessionPreferencesStore(
|
|||
return isSessionVerificationSkipped
|
||||
}
|
||||
|
||||
override suspend fun setCompressMedia(compress: Boolean) = doesCompressMedia.emit(compress)
|
||||
override suspend fun setOptimizeImages(compress: Boolean) = doesCompressMedia.emit(compress)
|
||||
|
||||
override fun doesCompressMedia(): Flow<Boolean> = doesCompressMedia
|
||||
override fun doesOptimizeImages(): Flow<Boolean> = doesCompressMedia
|
||||
|
||||
override suspend fun setVideoCompressionPreset(preset: VideoCompressionPreset) {
|
||||
videoCompressionPreset.value = preset
|
||||
}
|
||||
|
||||
override fun getVideoCompressionPreset(): Flow<VideoCompressionPreset> {
|
||||
return videoCompressionPreset
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
clearCallCount++
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
<item quantity="one">"%d avisering"</item>
|
||||
<item quantity="other">"%d aviseringar"</item>
|
||||
</plurals>
|
||||
<string name="notification_fallback_content">"Du har nya meddelanden."</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>
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ fun CaptionWarningBottomSheet(
|
|||
style = BigIcon.Style.AlertSolid,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(CommonStrings.screen_media_upload_preview_caption_warning),
|
||||
text = stringResource(R.string.screen_media_upload_preview_caption_warning),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
textAlign = TextAlign.Center,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="rich_text_editor_a11y_add_attachment">"Přidat přílohu"</string>
|
||||
<string name="rich_text_editor_bullet_list">"Přepnout seznam s odrážkami"</string>
|
||||
<string name="rich_text_editor_close_formatting_options">"Zavřít možnosti formátování"</string>
|
||||
<string name="rich_text_editor_close_formatting_options">"Zrušit a zavřít formátování textu"</string>
|
||||
<string name="rich_text_editor_code_block">"Přepnout blok kódu"</string>
|
||||
<string name="rich_text_editor_composer_caption_placeholder">"Volitelný titulek…"</string>
|
||||
<string name="rich_text_editor_composer_encrypted_placeholder">"Šifrovaná zpráva…"</string>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="rich_text_editor_a11y_add_attachment">"Legg til vedlegg"</string>
|
||||
<string name="rich_text_editor_bullet_list">"Aktiver/deaktiver punktliste"</string>
|
||||
<string name="rich_text_editor_close_formatting_options">"Lukk formateringsvalg"</string>
|
||||
<string name="rich_text_editor_close_formatting_options">"Avbryt og lukk formateringsvalg"</string>
|
||||
<string name="rich_text_editor_code_block">"Aktiver kodeblokk"</string>
|
||||
<string name="rich_text_editor_composer_caption_placeholder">"Legg til en tekstbeskrivelse"</string>
|
||||
<string name="rich_text_editor_composer_encrypted_placeholder">"Kryptert melding…"</string>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="rich_text_editor_a11y_add_attachment">"Lägg till bilaga"</string>
|
||||
<string name="rich_text_editor_bullet_list">"Växla punktlista"</string>
|
||||
<string name="rich_text_editor_close_formatting_options">"Stäng formateringsalternativ"</string>
|
||||
<string name="rich_text_editor_close_formatting_options">"Avbryt och stäng textformatering"</string>
|
||||
<string name="rich_text_editor_code_block">"Växla kodblock"</string>
|
||||
<string name="rich_text_editor_composer_caption_placeholder">"Lägg till en bildtext"</string>
|
||||
<string name="rich_text_editor_composer_encrypted_placeholder">"Krypterat meddelande …"</string>
|
||||
|
|
@ -10,8 +10,12 @@
|
|||
<string name="rich_text_editor_composer_unencrypted_placeholder">"Okrypterat meddelande …"</string>
|
||||
<string name="rich_text_editor_create_link">"Skapa en länk"</string>
|
||||
<string name="rich_text_editor_edit_link">"Redigera länk"</string>
|
||||
<string name="rich_text_editor_format_action">"%1$s, läge: %2$s"</string>
|
||||
<string name="rich_text_editor_format_bold">"Använd fetstil"</string>
|
||||
<string name="rich_text_editor_format_italic">"Använd kursiv stil"</string>
|
||||
<string name="rich_text_editor_format_state_disabled">"inaktiverad"</string>
|
||||
<string name="rich_text_editor_format_state_off">"av"</string>
|
||||
<string name="rich_text_editor_format_state_on">"på"</string>
|
||||
<string name="rich_text_editor_format_strikethrough">"Använda genomstryken stil"</string>
|
||||
<string name="rich_text_editor_format_underline">"Använd understruken stil"</string>
|
||||
<string name="rich_text_editor_full_screen_toggle">"Växla helskärmsläge"</string>
|
||||
|
|
|
|||
|
|
@ -28,5 +28,6 @@
|
|||
<string name="rich_text_editor_remove_link">"Remove link"</string>
|
||||
<string name="rich_text_editor_unindent">"Unindent"</string>
|
||||
<string name="rich_text_editor_url_placeholder">"Link"</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Captions might not be visible to people using older apps."</string>
|
||||
<string name="screen_room_voice_message_tooltip">"Hold to record"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -209,6 +209,8 @@
|
|||
<item quantity="many">"%d галасоў"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Палітыка прыватнасці"</string>
|
||||
<string name="common_private_room">"Прыватны пакой"</string>
|
||||
<string name="common_public_room">"Публічны пакой"</string>
|
||||
<string name="common_reaction">"Рэакцыя"</string>
|
||||
<string name="common_reactions">"Рэакцыі"</string>
|
||||
<string name="common_recovery_key">"Ключ аднаўлення"</string>
|
||||
|
|
@ -295,8 +297,6 @@
|
|||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Паведаміць аб памылцы з дапамогай Rageshake"</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">
|
||||
|
|
|
|||
|
|
@ -175,6 +175,8 @@
|
|||
<item quantity="other">"%d гласа"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Политика за поверителност"</string>
|
||||
<string name="common_private_room">"Частна стая"</string>
|
||||
<string name="common_public_room">"Общодостъпна стая"</string>
|
||||
<string name="common_reaction">"Реакция"</string>
|
||||
<string name="common_reactions">"Реакции"</string>
|
||||
<string name="common_reason">"Причина"</string>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,19 @@
|
|||
<item quantity="few">"zadány %1$d číslice"</item>
|
||||
<item quantity="other">"zadáno %1$d číslic"</item>
|
||||
</plurals>
|
||||
<string name="a11y_edit_avatar">"Upravit avatar"</string>
|
||||
<string name="a11y_edit_room_address_hint">"Úplná adresa bude %1$s"</string>
|
||||
<string name="a11y_encryption_details">"Podrobnosti o šifrování"</string>
|
||||
<string name="a11y_hide_password">"Skrýt heslo"</string>
|
||||
<string name="a11y_join_call">"Připojit se k hovoru"</string>
|
||||
<string name="a11y_jump_to_bottom">"Přejít dolů"</string>
|
||||
<string name="a11y_move_the_map_to_my_location">"Přesunout mapu na mou polohu"</string>
|
||||
<string name="a11y_notifications_mentions_only">"Pouze zmínky"</string>
|
||||
<string name="a11y_notifications_muted">"Ztišeno"</string>
|
||||
<string name="a11y_notifications_new_mentions">"Nové zmínky"</string>
|
||||
<string name="a11y_notifications_new_messages">"Nové zprávy"</string>
|
||||
<string name="a11y_notifications_ongoing_call">"Probíhající hovor"</string>
|
||||
<string name="a11y_other_user_avatar">"Avatar jiného uživatele"</string>
|
||||
<string name="a11y_page_n">"Strana %1$d"</string>
|
||||
<string name="a11y_pause">"Pozastavit"</string>
|
||||
<string name="a11y_paused_voice_message">"Hlasová zpráva, délka: %1$s, aktuální pozice: %2$s"</string>
|
||||
|
|
@ -32,14 +40,20 @@
|
|||
<string name="a11y_read_receipts_tap_to_show_all">"Klepnutím zobrazíte vše"</string>
|
||||
<string name="a11y_remove_reaction">"Odstraňit reakci s %1$s"</string>
|
||||
<string name="a11y_remove_reaction_with">"Odstranit reakci pomocí %1$s"</string>
|
||||
<string name="a11y_room_avatar">"Avatar místnosti"</string>
|
||||
<string name="a11y_send_files">"Odeslat soubory"</string>
|
||||
<string name="a11y_show_password">"Zobrazit heslo"</string>
|
||||
<string name="a11y_start_call">"Zahájit hovor"</string>
|
||||
<string name="a11y_time_limited_action_required">"Vyžaduje se časově omezená akce"</string>
|
||||
<string name="a11y_tombstoned_room">"Místnost s náhrobkem"</string>
|
||||
<string name="a11y_user_avatar">"Avatar uživatele"</string>
|
||||
<string name="a11y_user_menu">"Uživatelské menu"</string>
|
||||
<string name="a11y_view_avatar">"Zobrazit avatar"</string>
|
||||
<string name="a11y_view_details">"Zobrazit podrobnosti"</string>
|
||||
<string name="a11y_voice_message">"Hlasová zpráva, délka: %1$s"</string>
|
||||
<string name="a11y_voice_message_record">"Nahrajte hlasovou zprávu."</string>
|
||||
<string name="a11y_voice_message_stop_recording">"Zastavit nahrávání"</string>
|
||||
<string name="a11y_your_avatar">"Váš avatar"</string>
|
||||
<string name="action_accept">"Přijmout"</string>
|
||||
<string name="action_add_caption">"Přidat titulek"</string>
|
||||
<string name="action_add_to_timeline">"Přidat na časovou osu"</string>
|
||||
|
|
@ -98,6 +112,7 @@
|
|||
<string name="action_no">"Ne"</string>
|
||||
<string name="action_not_now">"Teď ne"</string>
|
||||
<string name="action_ok">"OK"</string>
|
||||
<string name="action_open_context_menu">"Otevřít kontextovou nabídku"</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>
|
||||
|
|
@ -122,7 +137,9 @@
|
|||
<string name="action_save">"Uložit"</string>
|
||||
<string name="action_search">"Hledat"</string>
|
||||
<string name="action_send">"Odeslat"</string>
|
||||
<string name="action_send_edited_message">"Odeslat upravenou zprávu"</string>
|
||||
<string name="action_send_message">"Odeslat zprávu"</string>
|
||||
<string name="action_send_voice_message">"Odeslat hlasovou zprávu"</string>
|
||||
<string name="action_share">"Sdílet"</string>
|
||||
<string name="action_share_link">"Sdílet odkaz"</string>
|
||||
<string name="action_show">"Zobrazit"</string>
|
||||
|
|
@ -138,6 +155,7 @@
|
|||
<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">"Zobrazit"</string>
|
||||
<string name="action_view_in_timeline">"Zobrazit na časové ose"</string>
|
||||
<string name="action_view_source">"Zobrazit zdroj"</string>
|
||||
<string name="action_yes">"Ano"</string>
|
||||
|
|
@ -243,6 +261,8 @@ Důvod: %1$s."</string>
|
|||
<item quantity="other">"%d hlasů"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Zásady ochrany osobních údajů"</string>
|
||||
<string name="common_private_room">"Soukromá místnost"</string>
|
||||
<string name="common_public_room">"Veřejná místnost"</string>
|
||||
<string name="common_reaction">"Reakce"</string>
|
||||
<string name="common_reactions">"Reakce"</string>
|
||||
<string name="common_reason">"Důvod"</string>
|
||||
|
|
@ -336,6 +356,7 @@ Opravdu chcete pokračovat?"</string>
|
|||
<string name="error_failed_loading_messages">"Načítání zpráv se nezdařilo"</string>
|
||||
<string name="error_failed_locating_user">"%1$s nemá přístup k vaší poloze. Zkuste to prosím později."</string>
|
||||
<string name="error_failed_uploading_voice_message">"Nepodařilo se nahrát hlasovou zprávu."</string>
|
||||
<string name="error_invalid_invite">"Místnost již neexistuje nebo pozvánka již není platná."</string>
|
||||
<string name="error_message_not_found">"Zpráva nebyla nalezena"</string>
|
||||
<string name="error_missing_location_auth_android">"%1$s nemá oprávnění k přístupu k vaší poloze. Přístup můžete povolit v Nastavení."</string>
|
||||
<string name="error_missing_location_rationale_android">"%1$s nemá oprávnění k přístupu k vaší poloze. Povolit přístup níže."</string>
|
||||
|
|
@ -356,14 +377,12 @@ Opravdu chcete pokračovat?"</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>
|
||||
<string name="preference_rageshake">"Zatřeste zařízením pro nahlášení chyby"</string>
|
||||
<string name="screen_bug_report_a11y_screenshot">"Snímek obrazovky"</string>
|
||||
<string name="screen_create_poll_option_accessibility_label">"%1$s: %2$s"</string>
|
||||
<string name="screen_create_poll_options_section_title">"Možnosti"</string>
|
||||
<string name="screen_create_poll_remove_accessibility_label">"Odstranit %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Nastavení"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Výběr média se nezdařil, zkuste to prosím znovu."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Titulky nemusí být viditelné pro lidi, kteří používají starší aplikace."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Nahrání média se nezdařilo, zkuste to prosím znovu."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Nahrání média se nezdařilo, zkuste to prosím znovu."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Přidržte zprávu a vyberte „%1$s“, kterou chcete zahrnout sem."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Připněte důležité zprávy, aby je bylo možné snadno najít"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -273,6 +273,8 @@ Rheswm: %1$s."</string>
|
|||
<item quantity="other">"%d pleidlais"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Polisi preifatrwydd"</string>
|
||||
<string name="common_private_room">"Ystafell breifat"</string>
|
||||
<string name="common_public_room">"Ystafell gyhoeddus"</string>
|
||||
<string name="common_reaction">"Adwaith"</string>
|
||||
<string name="common_reactions">"Adweithiau"</string>
|
||||
<string name="common_reason">"Rheswm"</string>
|
||||
|
|
@ -389,9 +391,6 @@ Ydych chi\'n siŵr eich bod am barhau?"</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Tynnu %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Gosodiadau"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Wedi methu dewis cyfrwng, ceisiwch eto."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Efallai na fydd capsiynau yn weladwy i bobl sy\'n defnyddio apiau hŷn."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Wedi methu â phrosesu cyfryngau i\'w llwytho, ceisiwch eto."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Wedi methu llwytho cyfryngau, ceisiwch eto."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Pwyswch ar neges a dewis “%1$s” i\'w cynnwys yma."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Pinio negeseuon pwysig fel y mae modd eu darganfod yn hawdd"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -257,6 +257,8 @@
|
|||
</plurals>
|
||||
<string name="common_preparing">"Forbereder…"</string>
|
||||
<string name="common_privacy_policy">"Privatlivspolitik"</string>
|
||||
<string name="common_private_room">"Privat rum"</string>
|
||||
<string name="common_public_room">"Offentligt rum"</string>
|
||||
<string name="common_reaction">"Reaktion"</string>
|
||||
<string name="common_reactions">"Reaktioner"</string>
|
||||
<string name="common_reason">"Årsag"</string>
|
||||
|
|
@ -377,12 +379,6 @@ Er du sikker på, at du vil fortsætte?"</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Fjern %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Indstillinger"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Det lykkedes ikke at vælge medie. Prøv igen."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Billedtekster er muligvis ikke synlige for personer, der bruger ældre apps."</string>
|
||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Filen kunne ikke uploades."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Det lykkedes ikke at behandle medier til upload. Prøv venligst igen."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Upload af medier mislykkedes. Prøv igen."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_message">"Den maksimalt tilladte filstørrelse er %1$s ."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_title">"Filen er for stor til at kunne uploades."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Tryk på en besked og vælg \"%1$s\" for at inkludere den her."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Fastgør vigtige beskeder, så de let kan opdages"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -253,6 +253,8 @@ Grund: %1$s."</string>
|
|||
<item quantity="other">"%d Stimmen"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Datenschutzerklärung"</string>
|
||||
<string name="common_private_room">"Privater Chatroom"</string>
|
||||
<string name="common_public_room">"Öffentlicher Raum"</string>
|
||||
<string name="common_reaction">"Reaktion"</string>
|
||||
<string name="common_reactions">"Reaktionen"</string>
|
||||
<string name="common_reason">"Grund"</string>
|
||||
|
|
@ -373,9 +375,6 @@ Möchten Sie wirklich fortfahren?"</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Entferne %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Einstellungen"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Medienauswahl fehlgeschlagen, bitte versuche es erneut."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Bildunterschriften sind für Nutzer älterer Apps möglicherweise nicht sichtbar."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Das Hochladen der Medien ist fehlgeschlagen. Bitte versuche es erneut."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Drücke auf eine Nachricht und wähle “%1$s”, um sie hier einzufügen."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Fixiere wichtige Nachrichten, so dass sie leicht gefunden werden können"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -238,6 +238,8 @@
|
|||
<item quantity="other">"%d ψήφοι"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Πολιτική απορρήτου"</string>
|
||||
<string name="common_private_room">"Ιδιωτική αίθουσα"</string>
|
||||
<string name="common_public_room">"Δημόσια αίθουσα"</string>
|
||||
<string name="common_reaction">"Αντίδραση"</string>
|
||||
<string name="common_reactions">"Αντιδράσεις"</string>
|
||||
<string name="common_reason">"Αιτιολογία"</string>
|
||||
|
|
@ -358,9 +360,6 @@
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Αφαίρεση %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Ρυθμίσεις"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Αποτυχία επιλογής πολυμέσου, δοκίμασε ξανά."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Οι λεζάντες ενδέχεται να μην είναι ορατές σε άτομα που χρησιμοποιούν παλαιότερες εφαρμογές."</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">
|
||||
|
|
|
|||
|
|
@ -235,6 +235,8 @@ Motivo: %1$s."</string>
|
|||
<item quantity="other">"%d votos"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Política de privacidad"</string>
|
||||
<string name="common_private_room">"Sala privada"</string>
|
||||
<string name="common_public_room">"Sala pública"</string>
|
||||
<string name="common_reaction">"Reacción"</string>
|
||||
<string name="common_reactions">"Reacciones"</string>
|
||||
<string name="common_reason">"Motivo"</string>
|
||||
|
|
@ -344,9 +346,6 @@ Motivo: %1$s."</string>
|
|||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Agitar con fuerza para informar de un error"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Error al seleccionar archivos multimedia, por favor inténtalo de nuevo."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Es posible que las leyendas no sean visibles para las personas que usan aplicaciones más antiguas."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Error al procesar el contenido multimedia, por favor inténtalo de nuevo."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Error al subir el contenido multimedia, por favor inténtalo de nuevo."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Presiona sobre un mensaje y selecciona «%1$s» para incluirlo aquí."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Fija los mensajes importantes para que se puedan descubrir fácilmente"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -257,6 +257,10 @@ Põhjus: %1$s."</string>
|
|||
</plurals>
|
||||
<string name="common_preparing">"Ettevalmistamisel…"</string>
|
||||
<string name="common_privacy_policy">"Privaatsuspoliitika"</string>
|
||||
<string name="common_private_room">"Privaatne jututuba"</string>
|
||||
<string name="common_private_space">"Privaatne kogukond"</string>
|
||||
<string name="common_public_room">"Avalik jututuba"</string>
|
||||
<string name="common_public_space">"Avalik kogukond"</string>
|
||||
<string name="common_reaction">"Reaktsioon"</string>
|
||||
<string name="common_reactions">"Reaktsioonid"</string>
|
||||
<string name="common_reason">"Põhjus"</string>
|
||||
|
|
@ -274,6 +278,10 @@ Põhjus: %1$s."</string>
|
|||
<string name="common_room">"Jututuba"</string>
|
||||
<string name="common_room_name">"Jututoa nimi"</string>
|
||||
<string name="common_room_name_placeholder">"näiteks sinu projekti või seltsingu nimi"</string>
|
||||
<plurals name="common_rooms">
|
||||
<item quantity="one">"%1$d jututuba"</item>
|
||||
<item quantity="other">"%1$d jututuba"</item>
|
||||
</plurals>
|
||||
<string name="common_saved_changes">"Muudatused on salvestatud"</string>
|
||||
<string name="common_saving">"Salvestame"</string>
|
||||
<string name="common_screen_lock">"Ekraanilukk"</string>
|
||||
|
|
@ -293,6 +301,10 @@ Põhjus: %1$s."</string>
|
|||
<string name="common_signing_out">"Logime välja"</string>
|
||||
<string name="common_something_went_wrong">"Midagi läks valesti"</string>
|
||||
<string name="common_something_went_wrong_message">"Tekkis viga. Palun proovi uuesti."</string>
|
||||
<plurals name="common_spaces">
|
||||
<item quantity="one">"%1$d kogukond"</item>
|
||||
<item quantity="other">"%1$d kogukonda"</item>
|
||||
</plurals>
|
||||
<string name="common_starting_chat">"Alustame vestlust…"</string>
|
||||
<string name="common_sticker">"Kleeps"</string>
|
||||
<string name="common_success">"Õnnestus"</string>
|
||||
|
|
@ -378,12 +390,6 @@ Kas sa oled kindel, et soovid jätkata?"</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Kustuta: %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Seadistused"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Meediafaili valimine ei õnnestunud. Palun proovi uuesti."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Selgitused ja alapealkirjad ei pruugi olla nähtavad vanemate rakenduste kasutajatele."</string>
|
||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Faili üleslaadimine ei õnnestunud."</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_media_upload_preview_error_too_large_message">"Maksimaalne lubatud failisuurus on %1$s."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_title">"Fail on üleslaadimiseks liiga suur"</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">
|
||||
|
|
@ -418,6 +424,9 @@ Kas sa oled kindel, et soovid jätkata?"</string>
|
|||
<string name="screen_share_open_google_maps">"Ava Google Mapsis"</string>
|
||||
<string name="screen_share_open_osm_maps">"Ava OpenStreetMapis"</string>
|
||||
<string name="screen_share_this_location_action">"Jaga seda asukohta"</string>
|
||||
<string name="screen_space_list_description">"Sinu loodud kogukonnad ning need, millega oled liitunud."</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_title">"Kogukonnad"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Sõnum on saatmata, kuna kasutaja %1$s verifitseeritud identiteet on lähtestatud."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Sõnum on saatmata, kuna %1$s pole verifitseerinud kõiki oma seadmeid."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Kuna sa pole üks või enamgi oma seadet verifitseerinud, siis sinu sõnum on saatmata."</string>
|
||||
|
|
|
|||
|
|
@ -228,6 +228,8 @@ Arrazoia: %1$s."</string>
|
|||
<item quantity="other">"%d boto"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Pribatutasun-politika"</string>
|
||||
<string name="common_private_room">"Gela pribatua"</string>
|
||||
<string name="common_public_room">"Gela publikoa"</string>
|
||||
<string name="common_reaction">"Erreakzioa"</string>
|
||||
<string name="common_reactions">"Erreakzioak"</string>
|
||||
<string name="common_reason">"Arrazoia"</string>
|
||||
|
|
@ -323,7 +325,6 @@ Arrazoia: %1$s."</string>
|
|||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Astindu erroreen berri emateko"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Huts egin du multimedia aukeratzeak, saiatu berriro."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Huts egin du multimedia igotzeak, saiatu berriro."</string>
|
||||
<string name="screen_pinned_timeline_screen_title_empty">"Finkatutako mezuak"</string>
|
||||
<string name="screen_resolve_send_failure_unsigned_device_primary_button_title">"Bidali mezua hala ere"</string>
|
||||
<string name="screen_room_pinned_banner_indicator">"%2$s(e)tik %1$s"</string>
|
||||
|
|
|
|||
|
|
@ -231,6 +231,8 @@
|
|||
<string name="common_poll_undisclosed_text">"نتیجهها پس از پایان نظرسنجی نشان داده خواهند شد"</string>
|
||||
<string name="common_preparing">"آماده سازی…"</string>
|
||||
<string name="common_privacy_policy">"سیاست محرمانگی"</string>
|
||||
<string name="common_private_room">"اتاق خصوصی"</string>
|
||||
<string name="common_public_room">"اتاق عمومی"</string>
|
||||
<string name="common_reaction">"واکنش"</string>
|
||||
<string name="common_reactions">"واکنشها"</string>
|
||||
<string name="common_reason">"دلیل"</string>
|
||||
|
|
@ -332,8 +334,6 @@
|
|||
<string name="screen_create_poll_options_section_title">"گزینهها"</string>
|
||||
<string name="screen_create_poll_settings_section_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_screen_title_empty">"پیامهای سنجاق شده"</string>
|
||||
<string name="screen_reset_identity_confirmation_subtitle">"داردید برای بازنشانی هویتتان به حساب %1$s میروید. پس از آن به کاره برگردانده خواهید شد."</string>
|
||||
<string name="screen_resolve_send_failure_unsigned_device_primary_button_title">"فرستادن پیام به هر روی"</string>
|
||||
|
|
|
|||
|
|
@ -224,8 +224,8 @@ Syy: %1$s."</string>
|
|||
<item quantity="other">"%d muuta"</item>
|
||||
</plurals>
|
||||
<plurals name="common_member_count">
|
||||
<item quantity="one">"%1$d jäsen"</item>
|
||||
<item quantity="other">"%1$d jäsentä"</item>
|
||||
<item quantity="one">"%1$d Jäsen"</item>
|
||||
<item quantity="other">"%1$d Jäsentä"</item>
|
||||
</plurals>
|
||||
<string name="common_message">"Viesti"</string>
|
||||
<string name="common_message_actions">"Viestitoiminnot"</string>
|
||||
|
|
@ -257,6 +257,10 @@ Syy: %1$s."</string>
|
|||
</plurals>
|
||||
<string name="common_preparing">"Valmistellaan…"</string>
|
||||
<string name="common_privacy_policy">"Tietosuojakäytäntö"</string>
|
||||
<string name="common_private_room">"Yksityinen huone"</string>
|
||||
<string name="common_private_space">"Yksityinen tila"</string>
|
||||
<string name="common_public_room">"Julkinen huone"</string>
|
||||
<string name="common_public_space">"Julkinen tila"</string>
|
||||
<string name="common_reaction">"Reaktio"</string>
|
||||
<string name="common_reactions">"Reaktiot"</string>
|
||||
<string name="common_reason">"Syy"</string>
|
||||
|
|
@ -274,6 +278,10 @@ Syy: %1$s."</string>
|
|||
<string name="common_room">"Huone"</string>
|
||||
<string name="common_room_name">"Huoneen nimi"</string>
|
||||
<string name="common_room_name_placeholder">"esim. projektisi nimi"</string>
|
||||
<plurals name="common_rooms">
|
||||
<item quantity="one">"%1$d Huone"</item>
|
||||
<item quantity="other">"%1$d Huonetta"</item>
|
||||
</plurals>
|
||||
<string name="common_saved_changes">"Muutokset tallennettu"</string>
|
||||
<string name="common_saving">"Tallennetaan"</string>
|
||||
<string name="common_screen_lock">"Näyttölukko"</string>
|
||||
|
|
@ -293,6 +301,10 @@ Syy: %1$s."</string>
|
|||
<string name="common_signing_out">"Kirjaudutaan ulos"</string>
|
||||
<string name="common_something_went_wrong">"Jokin meni pieleen"</string>
|
||||
<string name="common_something_went_wrong_message">"Kohtasimme ongelman. Yritä uudelleen."</string>
|
||||
<plurals name="common_spaces">
|
||||
<item quantity="one">"%1$d Tila"</item>
|
||||
<item quantity="other">"%1$d Tilaa"</item>
|
||||
</plurals>
|
||||
<string name="common_starting_chat">"Aloitetaan keskustelua…"</string>
|
||||
<string name="common_sticker">"Tarra"</string>
|
||||
<string name="common_success">"Onnistui"</string>
|
||||
|
|
@ -378,12 +390,6 @@ Haluatko varmasti jatkaa?"</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Poista %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Asetukset"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Median valinta epäonnistui, yritä uudelleen."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Kuvatekstit eivät välttämättä näy ihmisille, jotka käyttävät vanhempia sovelluksia."</string>
|
||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Tiedostoa ei voitu lähettää."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Median käsittely epäonnistui, yritä uudelleen."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Median lähettäminen epäonnistui, yritä uudelleen."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_message">"Suurin sallittu tiedostokoko on %1$s."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_title">"Tiedosto on liian suuri lähetettäväksi"</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Paina viestiä ja valitse “%1$s” lisätäksesi sen tänne."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Kiinnitä tärkeät viestit, jotta ne löytyvät helposti."</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
@ -401,6 +407,7 @@ Haluatko varmasti jatkaa?"</string>
|
|||
<string name="screen_resolve_send_failure_unsigned_device_title">"Viestiäsi ei lähetetty, koska %1$s ei ole vahvistanut kaikkia laitteitaan."</string>
|
||||
<string name="screen_resolve_send_failure_you_unsigned_device_subtitle">"Yksi tai useampi laitteistasi on vahvistamaton. Voit lähettää viestin silti tai peruuttaa sen toistaiseksi ja yrittää uudelleen myöhemmin, kun olet vahvistanut kaikki laitteesi."</string>
|
||||
<string name="screen_resolve_send_failure_you_unsigned_device_title">"Viestiäsi ei lähetetty, koska et ole vahvistanut yhtä tai useampaa laitettasi."</string>
|
||||
<string name="screen_room_change_role_administrators_or_owners_title">"Muokkaa ylläpitäjiä tai omistajia"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Median käsittely epäonnistui, yritä uudelleen."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Käyttäjän tietojen hakeminen epäonnistui"</string>
|
||||
<string name="screen_room_event_pill">"Viesti huoneessa %1$s"</string>
|
||||
|
|
@ -418,6 +425,9 @@ Haluatko varmasti jatkaa?"</string>
|
|||
<string name="screen_share_open_google_maps">"Avaa Google Mapsissa"</string>
|
||||
<string name="screen_share_open_osm_maps">"Avaa OpenStreetMapissa"</string>
|
||||
<string name="screen_share_this_location_action">"Jaa tämä sijainti"</string>
|
||||
<string name="screen_space_list_description">"Luomasi tai liittymäsi tilat."</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_title">"Tilat"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Viestiä ei lähetetty, koska käyttäjän %1$s vahvistettu identiteetti nollattiin."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Viestiä ei lähetetty, koska %1$s ei ole vahvistanut kaikkia laitteitaan."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Viestiä ei lähetetty, koska et ole vahvistanut yhtä tai useampaa laitettasi."</string>
|
||||
|
|
|
|||
|
|
@ -257,6 +257,8 @@ Raison : %1$s."</string>
|
|||
</plurals>
|
||||
<string name="common_preparing">"Préparation…"</string>
|
||||
<string name="common_privacy_policy">"Politique de confidentialité"</string>
|
||||
<string name="common_private_room">"Salon privé"</string>
|
||||
<string name="common_public_room">"Salon public"</string>
|
||||
<string name="common_reaction">"Réaction"</string>
|
||||
<string name="common_reactions">"Réactions"</string>
|
||||
<string name="common_reason">"Raison"</string>
|
||||
|
|
@ -378,12 +380,6 @@ Raison : %1$s."</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Supprimer %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Paramètres"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Échec de la sélection du média, veuillez réessayer."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Les légendes peuvent ne pas être visibles pour les utilisateurs d’anciennes applications."</string>
|
||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Le fichier n’a pas pu être envoyé."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Échec du traitement des médias à télécharger, veuillez réessayer."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Échec du téléchargement du média, veuillez réessayer."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_message">"La taille maximale autorisée pour les fichiers est de %1$s."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_title">"Le fichier est trop volumineux pour être envoyé."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Cliquez (clic long) sur un message et choisissez « %1$s » pour qu‘il apparaisse ici."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Épinglez les messages importants pour leur donner plus de visibilité"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -255,7 +255,12 @@ Ok: %1$s."</string>
|
|||
<item quantity="one">"%d szavazat"</item>
|
||||
<item quantity="other">"%d szavazat"</item>
|
||||
</plurals>
|
||||
<string name="common_preparing">"Előkészítés…"</string>
|
||||
<string name="common_privacy_policy">"Adatvédelmi nyilatkozat"</string>
|
||||
<string name="common_private_room">"Privát szoba"</string>
|
||||
<string name="common_private_space">"Privát tér"</string>
|
||||
<string name="common_public_room">"Nyilvános szoba"</string>
|
||||
<string name="common_public_space">"Nyilvános tér"</string>
|
||||
<string name="common_reaction">"Reakció"</string>
|
||||
<string name="common_reactions">"Reakciók"</string>
|
||||
<string name="common_reason">"Ok"</string>
|
||||
|
|
@ -272,6 +277,10 @@ Ok: %1$s."</string>
|
|||
<string name="common_room">"Szoba"</string>
|
||||
<string name="common_room_name">"Szoba neve"</string>
|
||||
<string name="common_room_name_placeholder">"például a projektje neve"</string>
|
||||
<plurals name="common_rooms">
|
||||
<item quantity="one">"%1$d szoba"</item>
|
||||
<item quantity="other">"%1$d szoba"</item>
|
||||
</plurals>
|
||||
<string name="common_saved_changes">"Mentett módosítások"</string>
|
||||
<string name="common_saving">"Mentés"</string>
|
||||
<string name="common_screen_lock">"Képernyőzár"</string>
|
||||
|
|
@ -291,6 +300,10 @@ Ok: %1$s."</string>
|
|||
<string name="common_signing_out">"Kijelentkezés"</string>
|
||||
<string name="common_something_went_wrong">"Valamilyen hiba történt"</string>
|
||||
<string name="common_something_went_wrong_message">"Problémába ütköztünk. Próbálja újra."</string>
|
||||
<plurals name="common_spaces">
|
||||
<item quantity="one">"%1$d tér"</item>
|
||||
<item quantity="other">"%1$d tér"</item>
|
||||
</plurals>
|
||||
<string name="common_starting_chat">"Csevegés megkezdése…"</string>
|
||||
<string name="common_sticker">"Matrica"</string>
|
||||
<string name="common_success">"Sikeres"</string>
|
||||
|
|
@ -321,6 +334,12 @@ Ok: %1$s."</string>
|
|||
<string name="common_verify_identity">"Személyazonosság ellenőrzése"</string>
|
||||
<string name="common_verify_user">"Felhasználó ellenőrzése"</string>
|
||||
<string name="common_video">"Videó"</string>
|
||||
<string name="common_video_quality_high">"Magas minőség"</string>
|
||||
<string name="common_video_quality_high_description">"Legjobb minőség, de nagyobb fájlméret"</string>
|
||||
<string name="common_video_quality_low">"Alacsony minőség"</string>
|
||||
<string name="common_video_quality_low_description">"Leggyorsabb feltöltési sebesség és legkisebb fájlméret"</string>
|
||||
<string name="common_video_quality_standard">"Szokásos minőség"</string>
|
||||
<string name="common_video_quality_standard_description">"A minőség és a feltöltési sebesség egyensúlya."</string>
|
||||
<string name="common_voice_message">"Hangüzenet"</string>
|
||||
<string name="common_waiting">"Várakozás…"</string>
|
||||
<string name="common_waiting_for_decryption_key">"Várakozás a visszafejtési kulcsra"</string>
|
||||
|
|
@ -335,6 +354,10 @@ Ok: %1$s."</string>
|
|||
|
||||
Biztos, hogy folytatja?"</string>
|
||||
<string name="dialog_confirm_link_title">"Ellenőrizze újra ezt a hivatkozást"</string>
|
||||
<string name="dialog_default_video_quality_selector_subtitle">"Válaszd ki a feltöltött videók alapértelmezett minőségét."</string>
|
||||
<string name="dialog_default_video_quality_selector_title">"Feltöltött videó minősége"</string>
|
||||
<string name="dialog_file_too_large_to_upload_subtitle">"A legnagyobb megengedett fájlméret: %1$s"</string>
|
||||
<string name="dialog_file_too_large_to_upload_title">"A feltöltendő fájl túl nagy"</string>
|
||||
<string name="dialog_room_reported">"Szoba jelentve"</string>
|
||||
<string name="dialog_room_reported_and_left">"Jelentve, és a szoba elhagyva"</string>
|
||||
<string name="dialog_title_confirmation">"Megerősítés"</string>
|
||||
|
|
@ -343,6 +366,9 @@ Biztos, hogy folytatja?"</string>
|
|||
<string name="dialog_title_warning">"Figyelmeztetés"</string>
|
||||
<string name="dialog_unsaved_changes_description_android">"A módosítások nem lettek mentve. Biztos, hogy visszalép?"</string>
|
||||
<string name="dialog_unsaved_changes_title">"Menti a módosításokat?"</string>
|
||||
<string name="dialog_video_quality_selector_subtitle_file_size">"A legnagyobb megengedett fájlméret: %1$s"</string>
|
||||
<string name="dialog_video_quality_selector_subtitle_no_file_size">"Válassza ki a feltöltendő videó minőségét."</string>
|
||||
<string name="dialog_video_quality_selector_title">"Feltöltött videó minőségének kiválasztása"</string>
|
||||
<string name="error_account_creation_not_possible">"A Matrix-kiszolgálót frissíteni kell a Matrix Authentication Service és a fióklétrehozás támogatásához."</string>
|
||||
<string name="error_failed_creating_the_permalink">"Nem sikerült létrehozni az állandó hivatkozást"</string>
|
||||
<string name="error_failed_loading_map">"Az %1$s nem tudta betölteni a térképet. Próbálja meg újra később."</string>
|
||||
|
|
@ -376,12 +402,6 @@ Biztos, hogy folytatja?"</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Eltávolítás: %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Beállítások"</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_caption_warning">"Előfordulhat, hogy a feliratok nem láthatók a régebbi alkalmazásokat használók számára."</string>
|
||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"A fájl nem tölthető fel."</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_media_upload_preview_error_too_large_message">"A maximálisan megengedett fájlméret: %1$s ."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_title">"A fájl túl nagy a feltöltéshez"</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Nyomjon hosszan az üzenetre, és válassza a „%1$s” lehetőséget, hogy itt szerepeljen."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Tűzze ki a fontos üzeneteket, hogy könnyen felfedezhetők legyenek"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
@ -399,6 +419,7 @@ Biztos, hogy folytatja?"</string>
|
|||
<string name="screen_resolve_send_failure_unsigned_device_title">"Az üzenet nem lett elküldve, mert %1$s nem ellenőrizte az összes eszközét"</string>
|
||||
<string name="screen_resolve_send_failure_you_unsigned_device_subtitle">"Egy vagy több eszköze nincs ellenőrizve. Így is elküldheti az üzenetet, vagy egyelőre megszakíthatja, és később, az összes eszköz ellenőrzése után újrapróbálkozhat."</string>
|
||||
<string name="screen_resolve_send_failure_you_unsigned_device_title">"Az üzenet nem lett elküldve, mert egy vagy több eszközét nem ellenőrizte"</string>
|
||||
<string name="screen_room_change_role_administrators_or_owners_title">"Adminisztrátorok vagy tulajdonosok szerkesztése"</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_event_pill">"Üzenet a következőben: %1$s"</string>
|
||||
|
|
@ -416,6 +437,9 @@ Biztos, hogy folytatja?"</string>
|
|||
<string name="screen_share_open_google_maps">"Megnyitás a Google Mapsben"</string>
|
||||
<string name="screen_share_open_osm_maps">"Megnyitás az OpenStreetMapen"</string>
|
||||
<string name="screen_share_this_location_action">"E hely megosztása"</string>
|
||||
<string name="screen_space_list_description">"Létrehozott vagy olyan terek, melyekhez csatlakozott."</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_title">"Terek"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Az üzenet nem lett elküldve, mert %1$s ellenőrzött személyazonossága megváltozott."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Az üzenet nem lett elküldve, mert %1$s nem ellenőrizte az összes eszközét."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Az üzenet nem lett elküldve, mert egy vagy több eszközét nem ellenőrizte."</string>
|
||||
|
|
|
|||
|
|
@ -239,6 +239,8 @@ Alasan: %1$s."</string>
|
|||
<item quantity="other">"%d suara"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Kebijakan privasi"</string>
|
||||
<string name="common_private_room">"Ruangan pribadi"</string>
|
||||
<string name="common_public_room">"Ruangan publik"</string>
|
||||
<string name="common_reaction">"Reaksi"</string>
|
||||
<string name="common_reactions">"Reaksi"</string>
|
||||
<string name="common_reason">"Alasan"</string>
|
||||
|
|
@ -358,9 +360,6 @@ Apakah Anda yakin ingin melanjutkan?"</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Hapus %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Pengaturan"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Gagal memilih media, silakan coba lagi."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Keterangan mungkin tidak terlihat oleh orang yang menggunakan aplikasi lama."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Gagal memproses media untuk diunggah, silakan coba lagi."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Gagal mengunggah media, silakan coba lagi."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Tekan pesan dan pilih “%1$s” untuk disertakan di sini."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Sematkan pesan penting agar mudah ditemukan"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -207,8 +207,8 @@ Motivo:. %1$s"</string>
|
|||
<item quantity="other">"altri %d"</item>
|
||||
</plurals>
|
||||
<plurals name="common_member_count">
|
||||
<item quantity="one">"%1$d membro"</item>
|
||||
<item quantity="other">"%1$d membri"</item>
|
||||
<item quantity="one">"%1$d Membro"</item>
|
||||
<item quantity="other">"%1$d Membri"</item>
|
||||
</plurals>
|
||||
<string name="common_message">"Messaggio"</string>
|
||||
<string name="common_message_actions">"Azioni messaggio"</string>
|
||||
|
|
@ -239,6 +239,8 @@ Motivo:. %1$s"</string>
|
|||
<item quantity="other">"%d voti"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Informativa sulla privacy"</string>
|
||||
<string name="common_private_room">"Stanza privata"</string>
|
||||
<string name="common_public_room">"Stanza pubblica"</string>
|
||||
<string name="common_reaction">"Reazione"</string>
|
||||
<string name="common_reactions">"Reazioni"</string>
|
||||
<string name="common_reason">"Motivo"</string>
|
||||
|
|
@ -359,9 +361,6 @@ Sei sicuro di voler continuare?"</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Rimuovi %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Impostazioni"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Selezione del file multimediale fallita, riprova."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Le didascalie potrebbero non essere visibili agli utenti di app meno recenti."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Elaborazione del file multimediale da caricare fallita, riprova."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Caricamento del file multimediale fallito, riprova."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Premi su un messaggio e scegli “%1$s” per includerlo qui."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Fissa i messaggi importanti così che possano essere trovati facilmente"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@
|
|||
<item quantity="other">"%d ხმა"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"კონფიდენციალურობის პოლიტიკა"</string>
|
||||
<string name="common_private_room">"კერძო ოთახი"</string>
|
||||
<string name="common_reaction">"რეაქცია"</string>
|
||||
<string name="common_reactions">"რეაქციები"</string>
|
||||
<string name="common_recovery_key">
|
||||
|
|
@ -252,8 +253,6 @@
|
|||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"შეცდომის შესატყობინებლად ტელეფონის შენჯღრევა"</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_room_error_failed_processing_media">"მედიის ატვირთვა ვერ მოხერხდა. გთხოვთ, სცადოთ ხელახლა."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"მომხმარებლის მონაცემების მოძიება ვერ მოხერხდა"</string>
|
||||
<string name="screen_share_location_title">"მდებარეობის გაზიარება"</string>
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@
|
|||
<string name="common_password">"Slaptažodis"</string>
|
||||
<string name="common_people">"Žmonės"</string>
|
||||
<string name="common_permalink">"Nuolatinė nuoroda"</string>
|
||||
<string name="common_private_room">"Privatus kambarys"</string>
|
||||
<string name="common_reactions">"Reakcijos"</string>
|
||||
<string name="common_replying_to">"Atsakant %1$s"</string>
|
||||
<string name="common_report_a_bug">"Pranešti apie klaidą"</string>
|
||||
|
|
@ -141,8 +142,6 @@
|
|||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Papurtykite, kad praneštumėte apie klaidą"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Nepavyko pasirinkti laikmenos, pabandykite dar kartą."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Nepavyko apdoroti įkeliamos laikmenos, bandykite dar kartą."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Nepavyko įkelti laikmenos, pabandykite dar kartą."</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Nepavyko apdoroti įkeliamos laikmenos, bandykite dar kartą."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Nepavyko gauti naudotojo išsamios informacijos."</string>
|
||||
<string name="settings_version_number">"Versija: %1$s (%2$s)"</string>
|
||||
|
|
|
|||
|
|
@ -30,10 +30,13 @@
|
|||
<string name="a11y_read_receipts_tap_to_show_all">"Trykk for å vise alle"</string>
|
||||
<string name="a11y_remove_reaction">"Fjern reaksjonen med %1$s"</string>
|
||||
<string name="a11y_remove_reaction_with">"Fjern reaksjonen med %1$s"</string>
|
||||
<string name="a11y_room_avatar">"Romavatar"</string>
|
||||
<string name="a11y_send_files">"Sende filer"</string>
|
||||
<string name="a11y_show_password">"Vis passord"</string>
|
||||
<string name="a11y_start_call">"Start en samtale"</string>
|
||||
<string name="a11y_user_avatar">"Brukeravatar"</string>
|
||||
<string name="a11y_user_menu">"Brukermeny"</string>
|
||||
<string name="a11y_view_avatar">"Vis avatar"</string>
|
||||
<string name="a11y_view_details">"Vis detaljer"</string>
|
||||
<string name="a11y_voice_message">"Talemelding, lengde: %1$s"</string>
|
||||
<string name="a11y_voice_message_record">"Ta opp talemelding."</string>
|
||||
|
|
@ -136,6 +139,7 @@
|
|||
<string name="action_tap_for_options">"Trykk for alternativer"</string>
|
||||
<string name="action_try_again">"Prøv igjen"</string>
|
||||
<string name="action_unpin">"Løsne"</string>
|
||||
<string name="action_view">"Vis"</string>
|
||||
<string name="action_view_in_timeline">"Vis i tidslinjen"</string>
|
||||
<string name="action_view_source">"Vis kilde"</string>
|
||||
<string name="action_yes">"Ja"</string>
|
||||
|
|
@ -238,6 +242,8 @@
|
|||
<item quantity="other">"%d stemmer"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Retningslinjer for personvern"</string>
|
||||
<string name="common_private_room">"Privat rom"</string>
|
||||
<string name="common_public_room">"Offentlig rom"</string>
|
||||
<string name="common_reaction">"Reaksjon"</string>
|
||||
<string name="common_reactions">"Reaksjoner"</string>
|
||||
<string name="common_reason">"Årsak"</string>
|
||||
|
|
@ -356,9 +362,6 @@ Er du sikker på at du vil fortsette?"</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Fjern %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Innstillinger"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Kunne ikke velge medium, prøv igjen."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Teksting er kanskje ikke synlig for personer som bruker eldre apper."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Kunne ikke behandle medier for opplasting, vennligst prøv igjen."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Opplasting av medier mislyktes, vennligst prøv igjen."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Trykk på en melding og velg “%1$s” for å inkludere her."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Fest viktige meldinger slik at de lett kan ses"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -220,6 +220,8 @@ Reden: %1$s."</string>
|
|||
<item quantity="other">"%d stemmen"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Privacybeleid"</string>
|
||||
<string name="common_private_room">"Privé kamer"</string>
|
||||
<string name="common_public_room">"Openbare kamer"</string>
|
||||
<string name="common_reaction">"Reactie"</string>
|
||||
<string name="common_reactions">"Reacties"</string>
|
||||
<string name="common_recovery_key">"Herstelsleutel"</string>
|
||||
|
|
@ -309,8 +311,6 @@ Reden: %1$s."</string>
|
|||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Schudden om een bug te melden"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Het selecteren van media is mislukt. Probeer het opnieuw."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Het verwerken van media voor uploaden is mislukt. Probeer het opnieuw."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Het uploaden van media is mislukt. Probeer het opnieuw."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Druk op een bericht en kies „%1$s” om het hier toe te voegen."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Zet belangrijke berichten vast zodat ze gemakkelijk te vinden zijn"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -262,6 +262,8 @@ Powód: %1$s."</string>
|
|||
</plurals>
|
||||
<string name="common_preparing">"Przygotowuję…"</string>
|
||||
<string name="common_privacy_policy">"Polityka prywatności"</string>
|
||||
<string name="common_private_room">"Pokój prywatny"</string>
|
||||
<string name="common_public_room">"Pokój publiczny"</string>
|
||||
<string name="common_reaction">"Reakcja"</string>
|
||||
<string name="common_reactions">"Reakcje"</string>
|
||||
<string name="common_reason">"Powód"</string>
|
||||
|
|
@ -384,12 +386,6 @@ Czy na pewno chcesz kontynuować?"</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Usuń %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Ustawienia"</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_caption_warning">"Opis może być niedostępny dla osób korzystających ze starszej wersji aplikacji."</string>
|
||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Nie udało się przesłać pliku."</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>
|
||||
<string name="screen_media_upload_preview_error_too_large_message">"Maksymalny dozwolony rozmiar pliku to %1$s."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_title">"Plik jest za duży, aby go przesłać."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Naciśnij wiadomość i wybierz “%1$s”, aby dołączyć tutaj."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Przypinaj ważne wiadomości, aby można było je łatwo znaleźć"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -239,6 +239,8 @@ Motivo: %1$s."</string>
|
|||
<item quantity="other">"%d votos"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Política de Privacidade"</string>
|
||||
<string name="common_private_room">"Sala privada"</string>
|
||||
<string name="common_public_room">"Sala pública"</string>
|
||||
<string name="common_reaction">"Reação"</string>
|
||||
<string name="common_reactions">"Reações"</string>
|
||||
<string name="common_reason">"Motivo"</string>
|
||||
|
|
@ -359,9 +361,6 @@ Você tem certeza de que deseja continuar?"</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Remover %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Configurações"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Falha ao selecionar a mídia, tente novamente."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"As legendas podem não ser visíveis para pessoas que usam aplicativos mais antigos."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Falha ao processar mídia para upload. Tente novamente."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Falha ao enviar mídia. Tente novamente."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Pressione em uma mensagem e escolha \"%1$s\" para incluir aqui."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Fixe mensagens importantes para que elas possam ser facilmente descobertas"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -7,11 +7,19 @@
|
|||
<item quantity="one">"%1$d dígito inserido"</item>
|
||||
<item quantity="other">"%1$d dígitos inseridos"</item>
|
||||
</plurals>
|
||||
<string name="a11y_edit_avatar">"Editar avatar"</string>
|
||||
<string name="a11y_edit_room_address_hint">"O endereço completo será %1$s"</string>
|
||||
<string name="a11y_encryption_details">"Detalhes de cifragem"</string>
|
||||
<string name="a11y_hide_password">"Ocultar palavra-passe"</string>
|
||||
<string name="a11y_join_call">"Juntar-se à chamada"</string>
|
||||
<string name="a11y_jump_to_bottom">"Saltar para o fundo"</string>
|
||||
<string name="a11y_move_the_map_to_my_location">"Mover o mapa para a minha localização"</string>
|
||||
<string name="a11y_notifications_mentions_only">"Apenas menções"</string>
|
||||
<string name="a11y_notifications_muted">"Silenciado"</string>
|
||||
<string name="a11y_notifications_new_mentions">"Novas menções"</string>
|
||||
<string name="a11y_notifications_new_messages">"Novas mensagens"</string>
|
||||
<string name="a11y_notifications_ongoing_call">"Chamada em curso"</string>
|
||||
<string name="a11y_other_user_avatar">"O avatar do outro utilizador"</string>
|
||||
<string name="a11y_page_n">"Página %1$d"</string>
|
||||
<string name="a11y_pause">"Pausar"</string>
|
||||
<string name="a11y_paused_voice_message">"Mensagem de voz, duração: %1$s, posição atual: %2$s"</string>
|
||||
|
|
@ -30,15 +38,20 @@
|
|||
<string name="a11y_read_receipts_tap_to_show_all">"Toca para mostrar tudo"</string>
|
||||
<string name="a11y_remove_reaction">"Remover reação com %1$s"</string>
|
||||
<string name="a11y_remove_reaction_with">"Remover reação com %1$s"</string>
|
||||
<string name="a11y_room_avatar">"Ícone da sala"</string>
|
||||
<string name="a11y_send_files">"Enviar ficheiros"</string>
|
||||
<string name="a11y_show_password">"Mostrar palavra-passe"</string>
|
||||
<string name="a11y_start_call">"Iniciar chamada"</string>
|
||||
<string name="a11y_time_limited_action_required">"Necessária ação em tempo limitado"</string>
|
||||
<string name="a11y_tombstoned_room">"Sala antiga"</string>
|
||||
<string name="a11y_user_avatar">"Avatar do utilizador"</string>
|
||||
<string name="a11y_user_menu">"Menu de utilizador"</string>
|
||||
<string name="a11y_view_avatar">"Ver avatar"</string>
|
||||
<string name="a11y_view_details">"Ver detalhes"</string>
|
||||
<string name="a11y_voice_message">"Mensagem de voz, duração: %1$s"</string>
|
||||
<string name="a11y_voice_message_record">"Gravar mensagem de voz."</string>
|
||||
<string name="a11y_voice_message_stop_recording">"Parar gravação"</string>
|
||||
<string name="a11y_your_avatar">"O teu avatar"</string>
|
||||
<string name="action_accept">"Aceitar"</string>
|
||||
<string name="action_add_caption">"Adicionar legenda"</string>
|
||||
<string name="action_add_to_timeline">"Adicionar à cronologia"</string>
|
||||
|
|
@ -97,6 +110,7 @@
|
|||
<string name="action_no">"Não"</string>
|
||||
<string name="action_not_now">"Agora não"</string>
|
||||
<string name="action_ok">"OK"</string>
|
||||
<string name="action_open_context_menu">"Abrir menu de contexto"</string>
|
||||
<string name="action_open_settings">"Definições"</string>
|
||||
<string name="action_open_with">"Abrir com"</string>
|
||||
<string name="action_pin">"Afixar"</string>
|
||||
|
|
@ -121,7 +135,9 @@
|
|||
<string name="action_save">"Guardar"</string>
|
||||
<string name="action_search">"Pesquisar"</string>
|
||||
<string name="action_send">"Enviar"</string>
|
||||
<string name="action_send_edited_message">"Enviar mensagem editada"</string>
|
||||
<string name="action_send_message">"Enviar mensagem"</string>
|
||||
<string name="action_send_voice_message">"Enviar mensagem de voz"</string>
|
||||
<string name="action_share">"Partilhar"</string>
|
||||
<string name="action_share_link">"Partilhar ligação"</string>
|
||||
<string name="action_show">"Mostrar"</string>
|
||||
|
|
@ -137,6 +153,7 @@
|
|||
<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">"Ver"</string>
|
||||
<string name="action_view_in_timeline">"Ver na cronologia"</string>
|
||||
<string name="action_view_source">"Ver fonte"</string>
|
||||
<string name="action_yes">"Sim"</string>
|
||||
|
|
@ -238,7 +255,12 @@ Razão: %1$s."</string>
|
|||
<item quantity="one">"%d voto"</item>
|
||||
<item quantity="other">"%d votos"</item>
|
||||
</plurals>
|
||||
<string name="common_preparing">"A preparar…"</string>
|
||||
<string name="common_privacy_policy">"Política de privacidade"</string>
|
||||
<string name="common_private_room">"Sala privada"</string>
|
||||
<string name="common_private_space">"Espaço privado"</string>
|
||||
<string name="common_public_room">"Sala pública"</string>
|
||||
<string name="common_public_space">"Espaço público"</string>
|
||||
<string name="common_reaction">"Reação"</string>
|
||||
<string name="common_reactions">"Reações"</string>
|
||||
<string name="common_reason">"Motivo"</string>
|
||||
|
|
@ -256,6 +278,10 @@ Razão: %1$s."</string>
|
|||
<string name="common_room">"Sala"</string>
|
||||
<string name="common_room_name">"Nome da sala"</string>
|
||||
<string name="common_room_name_placeholder">"p.ex. o nome do teu projeto"</string>
|
||||
<plurals name="common_rooms">
|
||||
<item quantity="one">"%1$d sala"</item>
|
||||
<item quantity="other">"%1$d salas"</item>
|
||||
</plurals>
|
||||
<string name="common_saved_changes">"Alterações guardadas"</string>
|
||||
<string name="common_saving">"A guardar"</string>
|
||||
<string name="common_screen_lock">"Bloqueio do ecrã"</string>
|
||||
|
|
@ -275,6 +301,10 @@ Razão: %1$s."</string>
|
|||
<string name="common_signing_out">"A terminar sessão"</string>
|
||||
<string name="common_something_went_wrong">"Algo correu mal"</string>
|
||||
<string name="common_something_went_wrong_message">"Encontrámos um erro. Por favor, tenta novamente."</string>
|
||||
<plurals name="common_spaces">
|
||||
<item quantity="one">"%1$d espaço"</item>
|
||||
<item quantity="other">"%1$d espaços"</item>
|
||||
</plurals>
|
||||
<string name="common_starting_chat">"A iniciar conversa…"</string>
|
||||
<string name="common_sticker">"Autocolante"</string>
|
||||
<string name="common_success">"Sucesso"</string>
|
||||
|
|
@ -354,14 +384,12 @@ Tens a certeza de que queres continuar?"</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_bug_report_a11y_screenshot">"Captura de ecrã"</string>
|
||||
<string name="screen_create_poll_option_accessibility_label">"%1$s: %2$s"</string>
|
||||
<string name="screen_create_poll_options_section_title">"Opções"</string>
|
||||
<string name="screen_create_poll_remove_accessibility_label">"Remover %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Configurações"</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_caption_warning">"As legendas poderão não ser visíveis em versões mais antigas da aplicação."</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_pinned_timeline_empty_state_description">"Pressione uma mensagem e escolha \"%1$s\" para incluir aqui."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Fixa mensagens importantes para que possam ser facilmente descobertas"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
@ -396,6 +424,9 @@ Tens a certeza de que queres continuar?"</string>
|
|||
<string name="screen_share_open_google_maps">"Abrir no Google Maps"</string>
|
||||
<string name="screen_share_open_osm_maps">"Abrir no OpenStreetMap"</string>
|
||||
<string name="screen_share_this_location_action">"Partilhar este local"</string>
|
||||
<string name="screen_space_list_description">"Espaços que criaste ou nos quais entraste."</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_title">"Espaços"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Mensagem não enviada porque a identidade verificada de %1$s foi reposta."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Mensagem não enviada porque %1$s não verificou todos os dispositivos."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Mensagem não enviada porque não verificou um ou mais dos seus dispositivos."</string>
|
||||
|
|
|
|||
|
|
@ -208,6 +208,8 @@ Motiv:%1$s."</string>
|
|||
<item quantity="other">"%d voturi"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Politica de confidențialitate"</string>
|
||||
<string name="common_private_room">"Cameră privată"</string>
|
||||
<string name="common_public_room">"Cameră publică"</string>
|
||||
<string name="common_reaction">"Reacţie"</string>
|
||||
<string name="common_reactions">"Reacții"</string>
|
||||
<string name="common_recovery_key">"Cheie de recuperare"</string>
|
||||
|
|
@ -300,8 +302,6 @@ Motiv:%1$s."</string>
|
|||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Rageshake pentru a raporta erori"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Selectarea fișierelor media a eșuat, încercați din nou."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Procesarea datelor media a eșuat, vă rugăm să încercați din nou."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Încărcarea fișierelor media a eșuat, încercați din nou."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Apăsați pe un mesaj și alegeți \"%1$s\" pentru a-l include aici."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Fixați mesajele importante, astfel încât să poată fi descoperite cu ușurință"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -240,6 +240,8 @@
|
|||
<item quantity="many">"%d голосов"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Политика конфиденциальности"</string>
|
||||
<string name="common_private_room">"Частная комната"</string>
|
||||
<string name="common_public_room">"Общедоступная комната"</string>
|
||||
<string name="common_reaction">"Реакция"</string>
|
||||
<string name="common_reactions">"Реакции"</string>
|
||||
<string name="common_reason">"Причина"</string>
|
||||
|
|
@ -349,9 +351,6 @@
|
|||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Встряхните устройство, чтобы сообщить об ошибке"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Не удалось выбрать носитель, попробуйте еще раз."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Подпись может быть не видна пользователям старых приложений."</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">
|
||||
|
|
|
|||
|
|
@ -262,6 +262,10 @@ Dôvod: %1$s."</string>
|
|||
</plurals>
|
||||
<string name="common_preparing">"Pripravuje sa…"</string>
|
||||
<string name="common_privacy_policy">"Zásady ochrany osobných údajov"</string>
|
||||
<string name="common_private_room">"Súkromná miestnosť"</string>
|
||||
<string name="common_private_space">"Súkromný priestor"</string>
|
||||
<string name="common_public_room">"Verejná miestnosť"</string>
|
||||
<string name="common_public_space">"Verejný priestor"</string>
|
||||
<string name="common_reaction">"Reakcia"</string>
|
||||
<string name="common_reactions">"Reakcie"</string>
|
||||
<string name="common_reason">"Dôvod"</string>
|
||||
|
|
@ -280,6 +284,11 @@ Dôvod: %1$s."</string>
|
|||
<string name="common_room">"Miestnosť"</string>
|
||||
<string name="common_room_name">"Názov miestnosti"</string>
|
||||
<string name="common_room_name_placeholder">"napr. názov vášho projektu"</string>
|
||||
<plurals name="common_rooms">
|
||||
<item quantity="one">"%1$d miestnosť"</item>
|
||||
<item quantity="few">"%1$d miestnosti"</item>
|
||||
<item quantity="other">"%1$d miestností"</item>
|
||||
</plurals>
|
||||
<string name="common_saved_changes">"Uložené zmeny"</string>
|
||||
<string name="common_saving">"Ukladá sa"</string>
|
||||
<string name="common_screen_lock">"Zámok obrazovky"</string>
|
||||
|
|
@ -299,6 +308,11 @@ Dôvod: %1$s."</string>
|
|||
<string name="common_signing_out">"Odhlasovanie"</string>
|
||||
<string name="common_something_went_wrong">"Niečo sa pokazilo"</string>
|
||||
<string name="common_something_went_wrong_message">"Vyskytol sa problém. Skúste to prosím znova."</string>
|
||||
<plurals name="common_spaces">
|
||||
<item quantity="one">"%1$d priestor"</item>
|
||||
<item quantity="few">"%1$d priestory"</item>
|
||||
<item quantity="other">"%1$d priestorov"</item>
|
||||
</plurals>
|
||||
<string name="common_starting_chat">"Spustenie konverzácie…"</string>
|
||||
<string name="common_sticker">"Nálepka"</string>
|
||||
<string name="common_success">"Úspech"</string>
|
||||
|
|
@ -329,6 +343,12 @@ Dôvod: %1$s."</string>
|
|||
<string name="common_verify_identity">"Overiť totožnosť"</string>
|
||||
<string name="common_verify_user">"Overiť používateľa"</string>
|
||||
<string name="common_video">"Video"</string>
|
||||
<string name="common_video_quality_high">"Vysoká kvalita"</string>
|
||||
<string name="common_video_quality_high_description">"Najlepšia kvalita, ale väčšia veľkosť súboru"</string>
|
||||
<string name="common_video_quality_low">"Nízka kvalita"</string>
|
||||
<string name="common_video_quality_low_description">"Najrýchlejšia rýchlosť nahrávania a najmenšia veľkosť súboru"</string>
|
||||
<string name="common_video_quality_standard">"Štandardná kvalita"</string>
|
||||
<string name="common_video_quality_standard_description">"Vyvážená kvalita a rýchlosť nahrávania"</string>
|
||||
<string name="common_voice_message">"Hlasová správa"</string>
|
||||
<string name="common_waiting">"Čaká sa…"</string>
|
||||
<string name="common_waiting_for_decryption_key">"Čaká sa na dešifrovací kľúč"</string>
|
||||
|
|
@ -343,6 +363,10 @@ Dôvod: %1$s."</string>
|
|||
|
||||
Naozaj chcete pokračovať?"</string>
|
||||
<string name="dialog_confirm_link_title">"Dôkladne skontrolujte tento odkaz"</string>
|
||||
<string name="dialog_default_video_quality_selector_subtitle">"Vyberte predvolenú kvalitu nahrávaných videí."</string>
|
||||
<string name="dialog_default_video_quality_selector_title">"Kvalita nahrávania videa"</string>
|
||||
<string name="dialog_file_too_large_to_upload_subtitle">"Maximálna povolená veľkosť súboru je: %1$s"</string>
|
||||
<string name="dialog_file_too_large_to_upload_title">"Súbor je príliš veľký na nahratie"</string>
|
||||
<string name="dialog_room_reported">"Miestnosť nahlásená"</string>
|
||||
<string name="dialog_room_reported_and_left">"Nahlásili ste a opustili ste miestnosť"</string>
|
||||
<string name="dialog_title_confirmation">"Potvrdenie"</string>
|
||||
|
|
@ -351,6 +375,9 @@ Naozaj chcete pokračovať?"</string>
|
|||
<string name="dialog_title_warning">"Upozornenie"</string>
|
||||
<string name="dialog_unsaved_changes_description_android">"Vaše zmeny neboli uložené. Naozaj sa chcete vrátiť?"</string>
|
||||
<string name="dialog_unsaved_changes_title">"Uložiť zmeny?"</string>
|
||||
<string name="dialog_video_quality_selector_subtitle_file_size">"Maximálna povolená veľkosť súboru je: %1$s"</string>
|
||||
<string name="dialog_video_quality_selector_subtitle_no_file_size">"Vyberte kvalitu videa, ktoré chcete nahrať."</string>
|
||||
<string name="dialog_video_quality_selector_title">"Vyberte kvalitu nahrávania videa"</string>
|
||||
<string name="error_account_creation_not_possible">"Váš domovský server musí byť aktualizovaný tak, aby podporoval Matrix Authentication Service a vytvorenie účtu."</string>
|
||||
<string name="error_failed_creating_the_permalink">"Nepodarilo sa vytvoriť trvalý odkaz"</string>
|
||||
<string name="error_failed_loading_map">"%1$s nedokázal načítať mapu. Skúste to prosím neskôr."</string>
|
||||
|
|
@ -384,12 +411,6 @@ Naozaj chcete pokračovať?"</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Odstrániť %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Nastavenia"</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_caption_warning">"Titulky nemusia byť viditeľné pre ľudí používajúcich staršie aplikácie."</string>
|
||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Súbor sa nepodarilo nahrať."</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_media_upload_preview_error_too_large_message">"Maximálna povolená veľkosť súboru je %1$s."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_title">"Súbor je príliš veľký na nahratie"</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">
|
||||
|
|
@ -408,6 +429,7 @@ Naozaj chcete pokračovať?"</string>
|
|||
<string name="screen_resolve_send_failure_unsigned_device_title">"Vaša správa nebola odoslaná, pretože %1$s neoveril/a všetky zariadenia."</string>
|
||||
<string name="screen_resolve_send_failure_you_unsigned_device_subtitle">"Jedno alebo viac vašich zariadení nie je overených. Správu môžete odoslať aj tak, alebo môžete zatiaľ zrušiť a skúsiť to znova neskôr po overení všetkých svojich zariadení."</string>
|
||||
<string name="screen_resolve_send_failure_you_unsigned_device_title">"Vaša správa nebola odoslaná, pretože ste neoverili jedno alebo viac svojich zariadení"</string>
|
||||
<string name="screen_room_change_role_administrators_or_owners_title">"Upraviť správcov alebo vlastníkov"</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_event_pill">"Správa v %1$s"</string>
|
||||
|
|
@ -425,6 +447,9 @@ Naozaj chcete pokračovať?"</string>
|
|||
<string name="screen_share_open_google_maps">"Otvoriť v Mapách Google"</string>
|
||||
<string name="screen_share_open_osm_maps">"Otvoriť v OpenStreetMap"</string>
|
||||
<string name="screen_share_this_location_action">"Zdieľajte túto polohu"</string>
|
||||
<string name="screen_space_list_description">"Priestory, ktoré ste vytvorili alebo ku ktorým ste sa pripojili."</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_title">"Priestory"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Správa nebola odoslaná, pretože sa zmenila overená totožnosť používateľa %1$s."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Správa nebola odoslaná, pretože %1$s neoveril/a všetky zariadenia."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Správa nebola odoslaná, pretože ste neoverili jedno alebo viac svojich zariadení."</string>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="a11y_add_reaction">"Lägg till reaktion: %1$s"</string>
|
||||
<string name="a11y_avatar">"Avatar"</string>
|
||||
<string name="a11y_delete">"Radera"</string>
|
||||
<plurals name="a11y_digits_entered">
|
||||
<item quantity="one">"%1$d siffra angiven"</item>
|
||||
<item quantity="other">"%1$d siffror angivna"</item>
|
||||
</plurals>
|
||||
<string name="a11y_edit_avatar">"Redigera avatar"</string>
|
||||
<string name="a11y_hide_password">"Dölj lösenord"</string>
|
||||
<string name="a11y_join_call">"Anslut till samtal"</string>
|
||||
<string name="a11y_jump_to_bottom">"Hoppa till botten"</string>
|
||||
<string name="a11y_notifications_mentions_only">"Endast omnämningar"</string>
|
||||
<string name="a11y_notifications_muted">"Tystad"</string>
|
||||
<string name="a11y_notifications_new_mentions">"Nya omnämnanden"</string>
|
||||
<string name="a11y_notifications_new_messages">"Nya meddelanden"</string>
|
||||
<string name="a11y_notifications_ongoing_call">"Pågående samtal"</string>
|
||||
<string name="a11y_other_user_avatar">"Annan användares avatar"</string>
|
||||
<string name="a11y_page_n">"Sida %1$d"</string>
|
||||
<string name="a11y_pause">"Pausa"</string>
|
||||
<string name="a11y_paused_voice_message">"Röstmeddelande, varaktighet:%1$s, nuvarande position: %2$s"</string>
|
||||
|
|
@ -28,14 +34,19 @@
|
|||
<string name="a11y_read_receipts_single">"Läst av %1$s"</string>
|
||||
<string name="a11y_read_receipts_tap_to_show_all">"Tryck för att visa alla"</string>
|
||||
<string name="a11y_remove_reaction">"Ta bort reaktionen med %1$s"</string>
|
||||
<string name="a11y_remove_reaction_with">"Ta bort reaktion med %1$s"</string>
|
||||
<string name="a11y_room_avatar">"Rumsavatar"</string>
|
||||
<string name="a11y_send_files">"Skicka filer"</string>
|
||||
<string name="a11y_show_password">"Visa lösenord"</string>
|
||||
<string name="a11y_start_call">"Starta ett samtal"</string>
|
||||
<string name="a11y_user_avatar">"Användaravatar"</string>
|
||||
<string name="a11y_user_menu">"Användarmeny"</string>
|
||||
<string name="a11y_view_avatar">"Visa avatar"</string>
|
||||
<string name="a11y_view_details">"Visa detaljer"</string>
|
||||
<string name="a11y_voice_message">"Röstmeddelande, varaktighet: %1$s"</string>
|
||||
<string name="a11y_voice_message_record">"Spela in röstmeddelande."</string>
|
||||
<string name="a11y_voice_message_stop_recording">"Stoppa inspelning"</string>
|
||||
<string name="a11y_your_avatar">"Din avatar"</string>
|
||||
<string name="action_accept">"Godkänn"</string>
|
||||
<string name="action_add_caption">"Lägg till bildtext"</string>
|
||||
<string name="action_add_to_timeline">"Lägg till i tidslinjen"</string>
|
||||
|
|
@ -94,6 +105,7 @@
|
|||
<string name="action_no">"Nej"</string>
|
||||
<string name="action_not_now">"Inte nu"</string>
|
||||
<string name="action_ok">"OK"</string>
|
||||
<string name="action_open_context_menu">"Öppna kontextmenyn"</string>
|
||||
<string name="action_open_settings">"Inställningar"</string>
|
||||
<string name="action_open_with">"Öppna med"</string>
|
||||
<string name="action_pin">"Fäst"</string>
|
||||
|
|
@ -118,7 +130,9 @@
|
|||
<string name="action_save">"Spara"</string>
|
||||
<string name="action_search">"Sök"</string>
|
||||
<string name="action_send">"Skicka"</string>
|
||||
<string name="action_send_edited_message">"Skicka redigerat message"</string>
|
||||
<string name="action_send_message">"Skicka meddelande"</string>
|
||||
<string name="action_send_voice_message">"Skicka röstmeddelande"</string>
|
||||
<string name="action_share">"Dela"</string>
|
||||
<string name="action_share_link">"Dela länk"</string>
|
||||
<string name="action_show">"Visa"</string>
|
||||
|
|
@ -134,6 +148,7 @@
|
|||
<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">"Visa"</string>
|
||||
<string name="action_view_in_timeline">"Visa i tidslinjen"</string>
|
||||
<string name="action_view_source">"Visa källkod"</string>
|
||||
<string name="action_yes">"Ja"</string>
|
||||
|
|
@ -144,6 +159,7 @@
|
|||
<string name="common_acceptable_use_policy">"Policy för godtagbar användning"</string>
|
||||
<string name="common_adding_caption">"Lägga till bildtext"</string>
|
||||
<string name="common_advanced_settings">"Avancerade inställningar"</string>
|
||||
<string name="common_an_image">"en bild"</string>
|
||||
<string name="common_analytics">"Analysdata"</string>
|
||||
<string name="common_appearance">"Utseende"</string>
|
||||
<string name="common_audio">"Ljud"</string>
|
||||
|
|
@ -235,11 +251,17 @@ Anledning:%1$s."</string>
|
|||
<item quantity="other">"%d röster"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Integritetspolicy"</string>
|
||||
<string name="common_private_room">"Privat rum"</string>
|
||||
<string name="common_public_room">"Offentligt rum"</string>
|
||||
<string name="common_reaction">"Reaktion"</string>
|
||||
<string name="common_reactions">"Reaktioner"</string>
|
||||
<string name="common_reason">"Orsak"</string>
|
||||
<string name="common_recovery_key">"Återställningsnyckel"</string>
|
||||
<string name="common_refreshing">"Uppdaterar …"</string>
|
||||
<plurals name="common_replies">
|
||||
<item quantity="one">"%1$d svar"</item>
|
||||
<item quantity="other">"%1$d svar"</item>
|
||||
</plurals>
|
||||
<string name="common_replying_to">"Svarar till %1$s"</string>
|
||||
<string name="common_report_a_bug">"Rapportera en bugg"</string>
|
||||
<string name="common_report_a_problem">"Rapportera ett problem"</string>
|
||||
|
|
@ -259,6 +281,7 @@ Anledning:%1$s."</string>
|
|||
<string name="common_sending">"Skickar …"</string>
|
||||
<string name="common_sending_failed">"Misslyckades att skicka"</string>
|
||||
<string name="common_sent">"Skickat"</string>
|
||||
<string name="common_sentence_delimiter">". "</string>
|
||||
<string name="common_server_not_supported">"Servern stöds inte"</string>
|
||||
<string name="common_server_url">"Server-URL"</string>
|
||||
<string name="common_settings">"Inställningar"</string>
|
||||
|
|
@ -324,6 +347,7 @@ Anledning:%1$s."</string>
|
|||
<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_invalid_invite">"Rummet finns inte längre eller så är inbjudan inte längre giltig."</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>
|
||||
|
|
@ -333,6 +357,7 @@ Anledning:%1$s."</string>
|
|||
<string name="error_room_address_invalid_symbols">"Vissa tecken är inte tillåtna. Endast bokstäver, siffror och följande symboler stöds ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _"</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_mismatched_sender">"Avsändaren av händelsen matchar inte ägaren av den enhet som skickade den."</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_previously_verified">"Krypterat av en tidigare verifierad användare."</string>
|
||||
<string name="event_shield_reason_sent_in_clear">"Inte krypterad."</string>
|
||||
|
|
@ -343,10 +368,11 @@ Anledning:%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_create_poll_option_accessibility_label">"%1$s: %2$s"</string>
|
||||
<string name="screen_create_poll_options_section_title">"Alternativ"</string>
|
||||
<string name="screen_create_poll_remove_accessibility_label">"Ta bort %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Inställningar"</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_caption_warning">"Bildtexter kanske inte är synliga för personer som använder äldre appar."</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">
|
||||
|
|
@ -367,6 +393,9 @@ Anledning:%1$s."</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_event_pill">"Meddelande i %1$s"</string>
|
||||
<string name="screen_room_grouped_state_events_expand">"Expandera"</string>
|
||||
<string name="screen_room_grouped_state_events_reduce">"Reducera"</string>
|
||||
<string name="screen_room_permalink_same_room_android">"Visar redan det här rummet!"</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>
|
||||
|
|
|
|||
|
|
@ -220,6 +220,8 @@ Neden: %1$s."</string>
|
|||
<item quantity="other">"%d oy"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Gizlilik Politikası"</string>
|
||||
<string name="common_private_room">"Özel oda"</string>
|
||||
<string name="common_public_room">"Herkese açık oda"</string>
|
||||
<string name="common_reaction">"Tepki"</string>
|
||||
<string name="common_reactions">"Tepkiler"</string>
|
||||
<string name="common_recovery_key">"Kurtarma anahtarı"</string>
|
||||
|
|
@ -321,9 +323,6 @@ Neden: %1$s."</string>
|
|||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Hata bildirmek için Rageshake"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Medya seçilemedi, lütfen tekrar deneyin."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Açıklamalar, eski uygulamaları kullanan kişiler tarafından görülemeyebilir."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Medya yüklenemedi, lütfen tekrar deneyin."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Medya yüklenemedi, lütfen tekrar deneyin."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_description">"Bir mesaja basın ve buraya eklemek için “%1$s” yi seçin."</string>
|
||||
<string name="screen_pinned_timeline_empty_state_headline">"Önemli mesajları kolayca keşfedilebilmeleri için sabitleyin"</string>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
|
|
|
|||
|
|
@ -261,6 +261,8 @@
|
|||
</plurals>
|
||||
<string name="common_preparing">"Приготування…"</string>
|
||||
<string name="common_privacy_policy">"Політика конфіденційності"</string>
|
||||
<string name="common_private_room">"Приватна кімната (тільки за запрошенням)"</string>
|
||||
<string name="common_public_room">"Загальнодоступна кімната"</string>
|
||||
<string name="common_reaction">"Реакція"</string>
|
||||
<string name="common_reactions">"Реакції"</string>
|
||||
<string name="common_reason">"Причина"</string>
|
||||
|
|
@ -383,11 +385,6 @@
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Вилучити %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Налаштування"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Не вдалося вибрати медіафайл, спробуйте ще раз."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Користувачі старих застосунків можуть не бачити підписи."</string>
|
||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Файл не може бути вивантажено."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Не вдалося обробити медіафайл для завантаження, спробуйте ще раз."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Не вдалося завантажити медіафайл, спробуйте ще раз."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_title">"Файл завеликий для вивантаження"</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">
|
||||
|
|
|
|||
|
|
@ -190,6 +190,8 @@
|
|||
<item quantity="other">"%d آراء"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"سیاستِ نجیت"</string>
|
||||
<string name="common_private_room">"نجی کمرہ"</string>
|
||||
<string name="common_public_room">"عوامی کمرہ"</string>
|
||||
<string name="common_reaction">"ردعمل"</string>
|
||||
<string name="common_reactions">"ردود عمل"</string>
|
||||
<string name="common_recovery_key">"بازیابی کی کلید"</string>
|
||||
|
|
@ -272,8 +274,6 @@
|
|||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"خطاء کی اطلاع دینے کیلئے غصے سے ہلائیں"</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>
|
||||
<plurals name="screen_pinned_timeline_screen_title">
|
||||
<item quantity="one">"%1$d مثبوتہ پیغام"</item>
|
||||
<item quantity="other">"%1$d مثبوتہ پیغامات"</item>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
<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">"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>
|
||||
|
|
@ -102,6 +102,7 @@
|
|||
<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_gif">"GIF"</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>
|
||||
|
|
@ -133,6 +134,7 @@
|
|||
<item quantity="other">"%dovozlar"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"Maxfiylik siyosati"</string>
|
||||
<string name="common_private_room">"Shaxsiy xona (faqat taklif)"</string>
|
||||
<string name="common_reaction">"Reaktsiya"</string>
|
||||
<string name="common_reactions">"reaksiyalar"</string>
|
||||
<string name="common_recovery_key">"Qayta tiklash kaliti"</string>
|
||||
|
|
@ -189,8 +191,6 @@
|
|||
<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 bo‘lmadi, qayta urinib ko‘ring."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Media yuklanmadi, qayta urinib ko‘ring."</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Mediani yuklab bo‘lmadi, qayta urinib ko‘ring."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Foydalanuvchi tafsilotlarini olinmadi"</string>
|
||||
<string name="screen_share_location_title">"Joylashuvni ulashish"</string>
|
||||
|
|
|
|||
|
|
@ -230,6 +230,8 @@
|
|||
<item quantity="other">"%d 票"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"隱私權政策"</string>
|
||||
<string name="common_private_room">"私密聊天室"</string>
|
||||
<string name="common_public_room">"公開的聊天室"</string>
|
||||
<string name="common_reaction">"回應"</string>
|
||||
<string name="common_reactions">"回應"</string>
|
||||
<string name="common_reason">"理由"</string>
|
||||
|
|
@ -339,9 +341,6 @@
|
|||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"憤怒搖晃以回報臭蟲"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"選取媒體失敗,請再試一次。"</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"使用舊應用程式的使用者可能看不到標題。"</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">
|
||||
|
|
|
|||
|
|
@ -230,6 +230,8 @@
|
|||
<item quantity="other">"%d 票"</item>
|
||||
</plurals>
|
||||
<string name="common_privacy_policy">"隐私政策"</string>
|
||||
<string name="common_private_room">"私有聊天室"</string>
|
||||
<string name="common_public_room">"公共聊天室"</string>
|
||||
<string name="common_reaction">"回应"</string>
|
||||
<string name="common_reactions">"回应"</string>
|
||||
<string name="common_reason">"理由"</string>
|
||||
|
|
@ -342,9 +344,6 @@
|
|||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"摇一摇以报错"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"选择媒体失败,请重试。"</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"使用旧版应用程序的用户可能无法看到字幕。"</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">
|
||||
|
|
|
|||
|
|
@ -224,8 +224,8 @@ Reason: %1$s."</string>
|
|||
<item quantity="other">"%d others"</item>
|
||||
</plurals>
|
||||
<plurals name="common_member_count">
|
||||
<item quantity="one">"%1$d member"</item>
|
||||
<item quantity="other">"%1$d members"</item>
|
||||
<item quantity="one">"%1$d Member"</item>
|
||||
<item quantity="other">"%1$d Members"</item>
|
||||
</plurals>
|
||||
<string name="common_message">"Message"</string>
|
||||
<string name="common_message_actions">"Message actions"</string>
|
||||
|
|
@ -257,6 +257,10 @@ Reason: %1$s."</string>
|
|||
</plurals>
|
||||
<string name="common_preparing">"Preparing…"</string>
|
||||
<string name="common_privacy_policy">"Privacy policy"</string>
|
||||
<string name="common_private_room">"Private room"</string>
|
||||
<string name="common_private_space">"Private space"</string>
|
||||
<string name="common_public_room">"Public room"</string>
|
||||
<string name="common_public_space">"Public space"</string>
|
||||
<string name="common_reaction">"Reaction"</string>
|
||||
<string name="common_reactions">"Reactions"</string>
|
||||
<string name="common_reason">"Reason"</string>
|
||||
|
|
@ -274,6 +278,10 @@ Reason: %1$s."</string>
|
|||
<string name="common_room">"Room"</string>
|
||||
<string name="common_room_name">"Room name"</string>
|
||||
<string name="common_room_name_placeholder">"e.g. your project name"</string>
|
||||
<plurals name="common_rooms">
|
||||
<item quantity="one">"%1$d Room"</item>
|
||||
<item quantity="other">"%1$d Rooms"</item>
|
||||
</plurals>
|
||||
<string name="common_saved_changes">"Saved changes"</string>
|
||||
<string name="common_saving">"Saving"</string>
|
||||
<string name="common_screen_lock">"Screen lock"</string>
|
||||
|
|
@ -293,6 +301,10 @@ Reason: %1$s."</string>
|
|||
<string name="common_signing_out">"Signing out"</string>
|
||||
<string name="common_something_went_wrong">"Something went wrong"</string>
|
||||
<string name="common_something_went_wrong_message">"We encountered an issue. Please try again."</string>
|
||||
<plurals name="common_spaces">
|
||||
<item quantity="one">"%1$d Space"</item>
|
||||
<item quantity="other">"%1$d Spaces"</item>
|
||||
</plurals>
|
||||
<string name="common_starting_chat">"Starting chat…"</string>
|
||||
<string name="common_sticker">"Sticker"</string>
|
||||
<string name="common_success">"Success"</string>
|
||||
|
|
@ -323,6 +335,12 @@ Reason: %1$s."</string>
|
|||
<string name="common_verify_identity">"Verify identity"</string>
|
||||
<string name="common_verify_user">"Verify user"</string>
|
||||
<string name="common_video">"Video"</string>
|
||||
<string name="common_video_quality_high">"High quality"</string>
|
||||
<string name="common_video_quality_high_description">"Best quality but larger file size"</string>
|
||||
<string name="common_video_quality_low">"Low quality"</string>
|
||||
<string name="common_video_quality_low_description">"Fastest upload speed and smallest file size"</string>
|
||||
<string name="common_video_quality_standard">"Standard quality"</string>
|
||||
<string name="common_video_quality_standard_description">"Balance of quality and upload speed"</string>
|
||||
<string name="common_voice_message">"Voice message"</string>
|
||||
<string name="common_waiting">"Waiting…"</string>
|
||||
<string name="common_waiting_for_decryption_key">"Waiting for this message"</string>
|
||||
|
|
@ -337,6 +355,10 @@ Reason: %1$s."</string>
|
|||
|
||||
Are you sure you want to continue?"</string>
|
||||
<string name="dialog_confirm_link_title">"Double-check this link"</string>
|
||||
<string name="dialog_default_video_quality_selector_subtitle">"Select the default quality of videos you upload."</string>
|
||||
<string name="dialog_default_video_quality_selector_title">"Video upload quality"</string>
|
||||
<string name="dialog_file_too_large_to_upload_subtitle">"The max file size allowed is: %1$s"</string>
|
||||
<string name="dialog_file_too_large_to_upload_title">"The file size is too large to upload"</string>
|
||||
<string name="dialog_room_reported">"Room reported"</string>
|
||||
<string name="dialog_room_reported_and_left">"Reported and left room"</string>
|
||||
<string name="dialog_title_confirmation">"Confirmation"</string>
|
||||
|
|
@ -345,6 +367,9 @@ Are you sure you want to continue?"</string>
|
|||
<string name="dialog_title_warning">"Warning"</string>
|
||||
<string name="dialog_unsaved_changes_description_android">"Your changes have not been saved. Are you sure you want to go back?"</string>
|
||||
<string name="dialog_unsaved_changes_title">"Save changes?"</string>
|
||||
<string name="dialog_video_quality_selector_subtitle_file_size">"The max file size allowed is: %1$s"</string>
|
||||
<string name="dialog_video_quality_selector_subtitle_no_file_size">"Select the quality of the video you want to upload."</string>
|
||||
<string name="dialog_video_quality_selector_title">"Select video upload quality"</string>
|
||||
<string name="error_account_creation_not_possible">"Your homeserver needs to be upgraded to support Matrix Authentication Service and account creation."</string>
|
||||
<string name="error_failed_creating_the_permalink">"Failed creating the permalink"</string>
|
||||
<string name="error_failed_loading_map">"%1$s could not load the map. Please try again later."</string>
|
||||
|
|
@ -378,12 +403,6 @@ Are you sure you want to continue?"</string>
|
|||
<string name="screen_create_poll_remove_accessibility_label">"Remove %1$s"</string>
|
||||
<string name="screen_create_poll_settings_section_title">"Settings"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Failed selecting media, please try again."</string>
|
||||
<string name="screen_media_upload_preview_caption_warning">"Captions might not be visible to people using older apps."</string>
|
||||
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"The file could not be uploaded."</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_media_upload_preview_error_too_large_message">"The maximum file size allowed is %1$s."</string>
|
||||
<string name="screen_media_upload_preview_error_too_large_title">"The file is too large to upload"</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">
|
||||
|
|
@ -401,6 +420,7 @@ Are you sure you want to continue?"</string>
|
|||
<string name="screen_resolve_send_failure_unsigned_device_title">"Your message was not sent because %1$s has not verified all devices"</string>
|
||||
<string name="screen_resolve_send_failure_you_unsigned_device_subtitle">"One or more of your devices are unverified. You can send the message anyway, or you can cancel for now and try again later after you have verified all of your devices."</string>
|
||||
<string name="screen_resolve_send_failure_you_unsigned_device_title">"Your message was not sent because you have not verified one or more of your devices"</string>
|
||||
<string name="screen_room_change_role_administrators_or_owners_title">"Edit Admins or Owners"</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_event_pill">"Message in %1$s"</string>
|
||||
|
|
@ -418,6 +438,9 @@ Are you sure you want to continue?"</string>
|
|||
<string name="screen_share_open_google_maps">"Open in Google Maps"</string>
|
||||
<string name="screen_share_open_osm_maps">"Open in OpenStreetMap"</string>
|
||||
<string name="screen_share_this_location_action">"Share this location"</string>
|
||||
<string name="screen_space_list_description">"Spaces you have created or joined."</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_title">"Spaces"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Message not sent because %1$s’s verified identity was reset."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Message not sent because %1$s has not verified all devices."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_you_unsigned_device">"Message not sent because you have not verified one or more of your devices."</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ android {
|
|||
namespace = "io.element.android.libraries.ui.utils"
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.services.toolbox.impl)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.coroutines.test)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.ui.utils.formatter
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import io.element.android.libraries.androidutils.filesize.AndroidFileSizeFormatter
|
||||
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
|
||||
import io.element.android.libraries.ui.utils.version.LocalSdkIntVersionProvider
|
||||
|
||||
@Composable
|
||||
fun rememberFileSizeFormatter(): FileSizeFormatter {
|
||||
val context = LocalContext.current
|
||||
val sdkIntProvider = LocalSdkIntVersionProvider.current
|
||||
return remember {
|
||||
AndroidFileSizeFormatter(context, sdkIntProvider)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.ui.utils.version
|
||||
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||
import io.element.android.services.toolbox.impl.sdk.DefaultBuildVersionSdkIntProvider
|
||||
|
||||
val LocalSdkIntVersionProvider = staticCompositionLocalOf<BuildVersionSdkIntProvider> { DefaultBuildVersionSdkIntProvider() }
|
||||
Loading…
Add table
Add a link
Reference in a new issue