Merge branch 'develop' into feature/fga/waiting_ss_room

This commit is contained in:
ganfra 2023-07-07 11:34:45 +02:00
commit 10c2859fac
249 changed files with 3147 additions and 677 deletions

View file

@ -16,12 +16,35 @@
package io.element.android.libraries.architecture
import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.children.nodeOrNull
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.node.ParentNode
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
fun <NavTarget : Any> ParentNode<NavTarget>.childNode(navTarget: NavTarget): Node? {
val childMap = children.value
val key = childMap.keys.find { it.navTarget == navTarget }
return childMap[key]?.nodeOrNull
}
suspend inline fun <reified N : Node, NavTarget : Any> ParentNode<NavTarget>.waitForChildAttached(crossinline predicate: (NavTarget) -> Boolean): N =
suspendCancellableCoroutine { continuation ->
lifecycleScope.launch {
children.collect { childMap ->
val expectedChildNode = childMap.entries
.map { it.key.navTarget }
.lastOrNull(predicate)
?.let {
childNode(it) as? N
}
if (expectedChildNode != null && !continuation.isCompleted) {
continuation.resume(expectedChildNode)
}
}
}.invokeOnCompletion {
continuation.cancel()
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.designsystem.preview
import android.content.res.Configuration
import androidx.compose.ui.tooling.preview.Preview
/**
* Marker for a night mode preview.
*
* Previews with such marker will be rendered in night mode during screenshot testing.
*
* NB: Length of this constant is kept to a minimum to avoid screenshot file names being too long.
*/
const val NIGHT_MODE_NAME = "N"
/**
* Marker for a day mode preview.
*
* This marker is currently not used during screenshot testing, it mainly act as a counterpart to [NIGHT_MODE_NAME].
*
* NB: Length of this constant is kept to a minimum to avoid screenshot file names being too long.
*/
const val DAY_MODE_NAME = "D"
/**
* Generates 2 previews of the composable it is applied to: day and night mode.
*
* NB: Content should be wrapped into [ElementPreview] to apply proper theming.
*/
@Preview(name = DAY_MODE_NAME)
@Preview(name = NIGHT_MODE_NAME, uiMode = Configuration.UI_MODE_NIGHT_YES)
annotation class DayNightPreviews

View file

@ -17,6 +17,7 @@
package io.element.android.libraries.designsystem.preview
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -28,8 +29,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.theme.ElementTheme
@Composable
fun ElementPreviewLight(
@ -62,29 +63,35 @@ fun ElementThemedPreview(
vertical: Boolean = true,
content: @Composable () -> Unit,
) {
Box(modifier = Modifier
.background(Color.Gray)
.padding(4.dp)) {
Box(
modifier = Modifier
.background(Color.Gray)
.padding(4.dp)
) {
if (vertical) {
Column {
ElementPreviewLight(
ElementPreview(
darkTheme = false,
showBackground = showBackground,
content = content,
)
Spacer(modifier = Modifier.height(4.dp))
ElementPreviewDark(
ElementPreview(
darkTheme = true,
showBackground = showBackground,
content = content
)
}
} else {
Row {
ElementPreviewLight(
ElementPreview(
darkTheme = false,
showBackground = showBackground,
content = content,
)
Spacer(modifier = Modifier.width(4.dp))
ElementPreviewDark(
ElementPreview(
darkTheme = true,
showBackground = showBackground,
content = content
)
@ -95,18 +102,17 @@ fun ElementThemedPreview(
@Composable
@Suppress("ModifierMissing")
private fun ElementPreview(
darkTheme: Boolean,
showBackground: Boolean,
fun ElementPreview(
darkTheme: Boolean = isSystemInDarkTheme(),
showBackground: Boolean = true,
content: @Composable () -> Unit
) {
ElementTheme(darkTheme = darkTheme) {
if (showBackground) {
// If we have a proper contentColor applied we need a Surface instead of a Box
Surface { content() }
Surface(content = content)
} else {
content()
}
}
}

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="50dp"
android:height="54dp"
android:viewportWidth="50"
android:viewportHeight="54">
<path
android:pathData="M25,54L18.938,48L31.062,48L25,54Z"
android:fillColor="#EBEEF2"/>
<path
android:pathData="M25,25m-25,0a25,25 0,1 1,50 0a25,25 0,1 1,-50 0"
android:fillColor="#EBEEF2"/>
<group>
<clip-path
android:pathData="M13,13h24v24h-24z"/>
<path
android:pathData="M25,13C20.356,13 16.6,16.858 16.6,21.629C16.6,26.769 21.904,33.857 24.088,36.556C24.568,37.148 25.444,37.148 25.924,36.556C28.096,33.857 33.4,26.769 33.4,21.629C33.4,16.858 29.644,13 25,13ZM25,24.71C23.344,24.71 22,23.33 22,21.629C22,19.928 23.344,18.547 25,18.547C26.656,18.547 28,19.928 28,21.629C28,23.33 26.656,24.71 25,24.71Z"
android:fillColor="#101317"/>
</group>
</vector>

View file

@ -3,21 +3,17 @@
android:height="54dp"
android:viewportWidth="50"
android:viewportHeight="54">
<path
android:pathData="M25,54L18.938,48L31.062,48L25,54Z"
android:fillColor="#1B1D22"/>
<path
android:pathData="M25,25m-25,0a25,25 0,1 1,50 0a25,25 0,1 1,-50 0"
android:fillColor="#1B1D22"/>
<group>
<clip-path
android:pathData="M0,0h50v108h-50z"/>
android:pathData="M13,13h24v24h-24z"/>
<path
android:pathData="M25,54L18.94,48L31.06,48L25,54Z"
android:fillColor="#1B1D22"/>
<path
android:pathData="M25,25m-25,0a25,25 0,1 1,50 0a25,25 0,1 1,-50 0"
android:fillColor="#1B1D22"/>
<group>
<clip-path
android:pathData="M13,13h24v24h-24z"/>
<path
android:pathData="M25,13C20.36,13 16.6,16.86 16.6,21.63C16.6,26.77 21.9,33.86 24.09,36.56C24.57,37.15 25.44,37.15 25.92,36.56C28.1,33.86 33.4,26.77 33.4,21.63C33.4,16.86 29.64,13 25,13ZM25,24.71C23.34,24.71 22,23.33 22,21.63C22,19.93 23.34,18.55 25,18.55C26.66,18.55 28,19.93 28,21.63C28,23.33 26.66,24.71 25,24.71Z"
android:fillColor="#ffffff"/>
</group>
android:pathData="M25,13C20.356,13 16.6,16.858 16.6,21.629C16.6,26.769 21.904,33.857 24.088,36.556C24.568,37.148 25.444,37.148 25.924,36.556C28.096,33.857 33.4,26.769 33.4,21.629C33.4,16.858 29.644,13 25,13ZM25,24.71C23.344,24.71 22,23.33 22,21.629C22,19.928 23.344,18.547 25,18.547C26.656,18.547 28,19.928 28,21.629C28,23.33 26.656,24.71 25,24.71Z"
android:fillColor="#ffffff"/>
</group>
</vector>

View file

@ -162,7 +162,7 @@ class DefaultRoomLastMessageFormatterTests {
AudioMessageType(body, MediaSource("url"), null),
ImageMessageType(body, MediaSource("url"), null),
FileMessageType(body, MediaSource("url"), null),
LocationMessageType(body, "geo:1,2"),
LocationMessageType(body, "geo:1,2", null),
NoticeMessageType(body, null),
EmoteMessageType(body, null),
)

View file

@ -25,8 +25,8 @@ 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.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import java.io.Closeable
import java.io.File
@ -70,7 +70,7 @@ interface MatrixRoom : Closeable {
suspend fun sendMessage(message: String): Result<Unit>
suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit>
suspend fun editMessage(originalEventId: EventId?, transactionId: String?, message: String): Result<Unit>
suspend fun replyMessage(eventId: EventId, message: String): Result<Unit>
@ -122,6 +122,16 @@ interface MatrixRoom : Closeable {
* @param body A human readable textual representation of the location.
* @param geoUri A geo URI (RFC 5870) representing the location e.g. `geo:51.5008,0.1247;u=35`.
* Respectively: latitude, longitude, and (optional) uncertainty.
* @param description Optional description of the location to display to the user.
* @param zoomLevel Optional zoom level to display the map at.
* @param assetType Optional type of the location asset.
* Set to SENDER if sharing own location. Set to PIN if sharing any location.
*/
suspend fun sendLocation(body: String, geoUri: String): Result<Unit>
suspend fun sendLocation(
body: String,
geoUri: String,
description: String? = null,
zoomLevel: Int? = null,
assetType: AssetType? = null,
): Result<Unit>
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.room.location
enum class AssetType {
SENDER,
PIN
}

View file

@ -128,6 +128,7 @@ data class ImageMessageType(
data class LocationMessageType(
val body: String,
val geoUri: String,
val description: String?,
) : MessageType
data class AudioMessageType(

View file

@ -28,7 +28,7 @@ data class EventTimelineItem(
val isLocal: Boolean,
val isOwn: Boolean,
val isRemote: Boolean,
val localSendState: EventSendState?,
val localSendState: LocalEventSendState?,
val reactions: List<EventReaction>,
val sender: UserId,
val senderProfile: ProfileTimelineDetails,

View file

@ -18,15 +18,15 @@ package io.element.android.libraries.matrix.api.timeline.item.event
import io.element.android.libraries.matrix.api.core.EventId
sealed interface EventSendState {
object NotSentYet : EventSendState
object Canceled : EventSendState
sealed interface LocalEventSendState {
object NotSentYet : LocalEventSendState
object Canceled : LocalEventSendState
data class SendingFailed(
val error: String
) : EventSendState
) : LocalEventSendState
data class Sent(
val eventId: EventId
) : EventSendState
) : LocalEventSendState
}

View file

@ -32,13 +32,13 @@ class NotificationMapper {
NotificationData(
senderId = UserId(it.event.senderId()),
eventId = EventId(it.event.eventId()),
roomId = RoomId(it.roomId),
senderAvatarUrl = it.senderAvatarUrl,
senderDisplayName = it.senderDisplayName,
roomAvatarUrl = it.roomAvatarUrl,
roomDisplayName = it.roomDisplayName,
isDirect = it.isDirect,
isEncrypted = it.isEncrypted.orFalse(),
roomId = RoomId(it.roomInfo.id),
senderAvatarUrl = it.senderInfo.avatarUrl,
senderDisplayName = it.senderInfo.displayName,
roomAvatarUrl = it.roomInfo.avatarUrl,
roomDisplayName = it.roomInfo.displayName,
isDirect = it.roomInfo.isDirect,
isEncrypted = it.roomInfo.isEncrypted.orFalse(),
isNoisy = it.isNoisy,
event = it.event.use { event -> timelineEventMapper.map(event) }
)

View file

@ -24,7 +24,6 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.withTimeout
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListService
import org.matrix.rustcomponents.sdk.SlidingSync
import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineListener
import org.matrix.rustcomponents.sdk.genTransactionId

View file

@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.location.AssetType
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
@ -35,6 +36,7 @@ import io.element.android.libraries.matrix.api.room.roomMembers
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
import io.element.android.libraries.matrix.impl.room.location.toInner
import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import io.element.android.libraries.matrix.impl.timeline.timelineDiffFlow
@ -202,11 +204,16 @@ class RustMatrixRoom(
}
}
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> = withContext(coroutineDispatchers.io) {
val transactionId = genTransactionId()
// val content = messageEventContentFromMarkdown(message)
runCatching {
innerRoom.edit(/* TODO use content */ message, originalEventId.value, transactionId)
override suspend fun editMessage(originalEventId: EventId?, transactionId: String?, message: String): Result<Unit> = withContext(coroutineDispatchers.io) {
if (originalEventId != null) {
runCatching {
innerRoom.edit(/* TODO use content */ message, originalEventId.value, transactionId)
}
} else {
runCatching {
transactionId?.let { cancelSend(it) }
innerRoom.send(messageEventContentFromMarkdown(message), genTransactionId())
}
}
}
@ -363,13 +370,22 @@ class RustMatrixRoom(
}
}
//TODO expose inner parameters
override suspend fun sendLocation(
body: String,
geoUri: String
geoUri: String,
description: String?,
zoomLevel: Int?,
assetType: AssetType?,
): Result<Unit> = withContext(coroutineDispatchers.io) {
runCatching {
innerRoom.sendLocation(body, geoUri, null, null, null, genTransactionId())
innerRoom.sendLocation(
body = body,
geoUri = geoUri,
description = description,
zoomLevel = zoomLevel?.toUByte(),
assetType = assetType?.toInner(),
txnId = genTransactionId()
)
}
}
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.room.location
import io.element.android.libraries.matrix.api.room.location.AssetType
fun AssetType.toInner(): org.matrix.rustcomponents.sdk.AssetType = when (this) {
AssetType.SENDER -> org.matrix.rustcomponents.sdk.AssetType.SENDER
AssetType.PIN -> org.matrix.rustcomponents.sdk.AssetType.PIN
}

View file

@ -110,4 +110,8 @@ class RustMatrixTimeline(
innerRoom.sendReadReceipt(eventId = eventId.value)
}
}
fun getItemById(eventId: EventId): MatrixTimelineItem.Event? {
return _timelineItems.value.firstOrNull { (it as? MatrixTimelineItem.Event)?.eventId == eventId } as? MatrixTimelineItem.Event
}
}

View file

@ -55,7 +55,7 @@ class EventMessageMapper {
ImageMessageType(type.content.body, type.content.source.map(), type.content.info?.map())
}
is MessageType.Location -> {
LocationMessageType(type.content.body, type.content.geoUri)
LocationMessageType(type.content.body, type.content.geoUri, type.content.description)
}
is MessageType.Notice -> {
NoticeMessageType(type.content.body, type.content.formatted?.map())

View file

@ -20,7 +20,7 @@ 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.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
import org.matrix.rustcomponents.sdk.Reaction
@ -64,13 +64,13 @@ fun RustProfileDetails.map(): ProfileTimelineDetails {
}
}
fun RustEventSendState?.map(): EventSendState? {
fun RustEventSendState?.map(): LocalEventSendState? {
return when (this) {
null -> null
RustEventSendState.NotSentYet -> EventSendState.NotSentYet
is RustEventSendState.SendingFailed -> EventSendState.SendingFailed(error)
is RustEventSendState.Sent -> EventSendState.Sent(EventId(eventId))
RustEventSendState.Cancelled -> EventSendState.Canceled
RustEventSendState.NotSentYet -> LocalEventSendState.NotSentYet
is RustEventSendState.SendingFailed -> LocalEventSendState.SendingFailed(error)
is RustEventSendState.Sent -> LocalEventSendState.Sent(EventId(eventId))
RustEventSendState.Cancelled -> LocalEventSendState.Canceled
}
}

View file

@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.location.AssetType
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
@ -79,6 +80,7 @@ class FakeMatrixRoom(
private var reportContentResult = Result.success(Unit)
private var sendLocationResult = Result.success(Unit)
private var progressCallbackValues = emptyList<Pair<Long, Long>>()
val editMessageCalls = mutableListOf<String>()
var sendMediaCount = 0
private set
@ -172,11 +174,8 @@ class FakeMatrixRoom(
return cancelSendResult
}
var editMessageParameter: String? = null
private set
override suspend fun editMessage(originalEventId: EventId, message: String): Result<Unit> {
editMessageParameter = message
override suspend fun editMessage(originalEventId: EventId?, transactionId: String?, message: String): Result<Unit> {
editMessageCalls += message
return Result.success(Unit)
}
@ -285,7 +284,10 @@ class FakeMatrixRoom(
override suspend fun sendLocation(
body: String,
geoUri: String
geoUri: String,
description: String?,
zoomLevel: Int?,
assetType: AssetType?,
): Result<Unit> = simulateLongTask {
sendLocationCount++
return sendLocationResult

View file

@ -25,7 +25,7 @@ import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
@ -94,7 +94,7 @@ fun anEventTimelineItem(
isLocal: Boolean = false,
isOwn: Boolean = false,
isRemote: Boolean = false,
localSendState: EventSendState? = null,
localSendState: LocalEventSendState? = null,
reactions: List<EventReaction> = emptyList(),
sender: UserId = A_USER_ID,
senderProfile: ProfileTimelineDetails = aProfileTimelineDetails(),

2
libraries/rustsdk/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# Built application files
*.aar

View file

@ -25,11 +25,11 @@ sealed interface MessageComposerMode : Parcelable {
@Parcelize
data class Normal(val content: CharSequence?) : MessageComposerMode
sealed class Special(open val eventId: EventId, open val defaultContent: CharSequence) :
sealed class Special(open val eventId: EventId?, open val defaultContent: CharSequence) :
MessageComposerMode
@Parcelize
data class Edit(override val eventId: EventId, override val defaultContent: CharSequence) :
data class Edit(override val eventId: EventId?, override val defaultContent: CharSequence, val transactionId: String?) :
Special(eventId, defaultContent)
@Parcelize

View file

@ -473,7 +473,7 @@ private fun EditContentToPreview() {
TextComposer(
onSendMessage = {},
onComposerTextChange = {},
composerMode = MessageComposerMode.Edit(EventId("$1234"), "Some text"),
composerMode = MessageComposerMode.Edit(EventId("$1234"), "Some text", "1234"),
onResetComposerMode = {},
composerCanSendMessage = true,
composerText = "A message",

View file

@ -175,12 +175,6 @@
<string name="screen_share_open_osm_maps">"Otevřít v OpenStreetMap"</string>
<string name="screen_share_this_location_action">"Sdílet tuto polohu"</string>
<string name="screen_view_location_title">"Poloha"</string>
<string name="screen_waitlist_message">"Na %2$s je momentálně vysoká poptávka po %1$s. Vraťte se do aplikace za pár dní a zkuste to znovu.
Díky za trpělivost!"</string>
<string name="screen_waitlist_message_success">"Vítá vás %1$s"</string>
<string name="screen_waitlist_title">"Jste v pořadníku!"</string>
<string name="screen_waitlist_title_success">"Jdete do toho!"</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Práh detekce"</string>
<string name="settings_title_general">"Obecné"</string>

View file

@ -174,12 +174,6 @@
<string name="screen_share_open_osm_maps">"In OpenStreetMap öffnen"</string>
<string name="screen_share_this_location_action">"Diesen Ort teilen"</string>
<string name="screen_view_location_title">"Standort"</string>
<string name="screen_waitlist_message">"Im Moment besteht eine hohe Nachfrage nach %1$s auf %2$s. Besuche die App in ein paar Tagen wieder und versuche es erneut.
Vielen Dank für deine Geduld!"</string>
<string name="screen_waitlist_message_success">"Willkommen bei %1$s!"</string>
<string name="screen_waitlist_title">"Du hast es fast geschafft!"</string>
<string name="screen_waitlist_title_success">"Du bist dabei."</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Erkennungsschwelle"</string>
<string name="settings_title_general">"Allgemein"</string>

View file

@ -170,12 +170,6 @@
<string name="screen_share_my_location_action">"Distribuiți locația mea"</string>
<string name="screen_share_this_location_action">"Distribuiți această locație"</string>
<string name="screen_view_location_title">"Locație"</string>
<string name="screen_waitlist_message">"Există o cerere mare pentru %1$s pentru %2$s în acest moment. Reveniți la aplicație în câteva zile și încercați din nou.
Vă mulțumim pentru răbdare!"</string>
<string name="screen_waitlist_message_success">"Bun venit la %1$s"</string>
<string name="screen_waitlist_title">"Sunteți pe lista de așteptare"</string>
<string name="screen_waitlist_title_success">"Sunteți conectat!"</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Prag de detecție"</string>
<string name="settings_title_general">"General"</string>

View file

@ -175,12 +175,6 @@
<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_view_location_title">"Poloha"</string>
<string name="screen_waitlist_message">"Momentálne je veľký dopyt po %1$s na %2$s. Vráťte sa do aplikácie za pár dní a skúste to znova.
Ďakujeme za trpezlivosť!"</string>
<string name="screen_waitlist_message_success">"Vitajte v %1$s"</string>
<string name="screen_waitlist_title">"Ste na čakanej listine!"</string>
<string name="screen_waitlist_title_success">"Ste dnu!"</string>
<string name="settings_rageshake">"Zúrivé potrasenie"</string>
<string name="settings_rageshake_detection_threshold">"Prahová hodnota detekcie"</string>
<string name="settings_title_general">"Všeobecné"</string>

View file

@ -140,7 +140,10 @@
<string name="emoji_picker_category_places">"Travel &amp; Places"</string>
<string name="emoji_picker_category_symbols">"Symbols"</string>
<string name="error_failed_creating_the_permalink">"Failed creating the permalink"</string>
<string name="error_failed_loading_map">"Element could not load the map. Please try again later."</string>
<string name="error_failed_loading_messages">"Failed loading messages"</string>
<string name="error_failed_locating_user">"Element could not access your location. Please try again later."</string>
<string name="error_missing_location_auth">"Element does not have permission to access your location. You can enable access in Settings > Location"</string>
<string name="error_some_messages_have_not_been_sent">"Some messages have not been sent"</string>
<string name="error_unknown">"Sorry, an error occurred"</string>
<string name="invite_friends_rich_title">"🔐️ Join me on %1$s"</string>
@ -166,6 +169,11 @@
<string name="screen_media_upload_preview_error_failed_sending">"Failed uploading media, please try again."</string>
<string name="screen_migration_message">"This is a one time process, thanks for waiting."</string>
<string name="screen_migration_title">"Setting up your account."</string>
<string name="screen_notification_settings_enable_notifications">"Enable notifications on this device"</string>
<string name="screen_notification_settings_system_notifications_action_required">"To receive notifications, please change your %1$s."</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"system settings"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"System notifications turned off"</string>
<string name="screen_notification_settings_title">"Notifications"</string>
<string name="screen_report_content_block_user_hint">"Check if you want to hide all current and future messages from this user"</string>
<string name="screen_share_location_title">"Share location"</string>
<string name="screen_share_my_location_action">"Share my location"</string>
@ -180,6 +188,12 @@ Thanks for your patience!"</string>
<string name="screen_waitlist_message_success">"Welcome to %1$s!"</string>
<string name="screen_waitlist_title">"Youre almost there."</string>
<string name="screen_waitlist_title_success">"You\'re in."</string>
<string name="screen_welcome_bullet_1">"Calls, location sharing, search and more will be added later this year."</string>
<string name="screen_welcome_bullet_2">"Message history for encrypted rooms wont be available in this update."</string>
<string name="screen_welcome_bullet_3">"Wed love to hear from you, let us know what you think via the settings page."</string>
<string name="screen_welcome_button">"Let\'s go!"</string>
<string name="screen_welcome_subtitle">"Heres what you need to know:"</string>
<string name="screen_welcome_title">"Welcome to %1$s!"</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Detection threshold"</string>
<string name="settings_title_general">"General"</string>