Merge branch 'develop' into dla/feature/room_list_decoration
This commit is contained in:
commit
bc29a31986
138 changed files with 2169 additions and 336 deletions
|
|
@ -25,6 +25,7 @@ import java.util.Locale
|
|||
import java.util.UUID
|
||||
|
||||
fun File.safeDelete() {
|
||||
if (exists().not()) return
|
||||
tryOrNull(
|
||||
onError = {
|
||||
Timber.e(it, "Error, unable to delete file $path")
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@
|
|||
package io.element.android.libraries.androidutils.ui
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.core.content.getSystemService
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
fun View.hideKeyboard() {
|
||||
val imm = context?.getSystemService<InputMethodManager>()
|
||||
|
|
@ -41,3 +44,24 @@ fun View.setHorizontalPadding(padding: Int) {
|
|||
paddingBottom
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun View.awaitWindowFocus() = suspendCancellableCoroutine { continuation ->
|
||||
if (hasWindowFocus()) {
|
||||
continuation.resume(Unit)
|
||||
} else {
|
||||
val listener = object : ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
if (hasFocus) {
|
||||
viewTreeObserver.removeOnWindowFocusChangeListener(this)
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewTreeObserver.addOnWindowFocusChangeListener(listener)
|
||||
|
||||
continuation.invokeOnCancellation {
|
||||
viewTreeObserver.removeOnWindowFocusChangeListener(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="error_no_compatible_app_found">"Für diese Aktion wurde keine kompatible App gefunden."</string>
|
||||
</resources>
|
||||
|
|
@ -24,10 +24,8 @@ package io.element.android.libraries.core.log.logger
|
|||
*/
|
||||
open class LoggerTag(name: String, parentTag: LoggerTag? = null) {
|
||||
|
||||
object SYNC : LoggerTag("SYNC")
|
||||
object VOIP : LoggerTag("VOIP")
|
||||
object CRYPTO : LoggerTag("CRYPTO")
|
||||
object RENDEZVOUS : LoggerTag("RZ")
|
||||
object PushLoggerTag : LoggerTag("Push")
|
||||
object NotificationLoggerTag : LoggerTag("Notification", PushLoggerTag)
|
||||
|
||||
val value: String = if (parentTag == null) {
|
||||
name
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
|
||||
@Composable
|
||||
fun LabelledOutlinedTextField(
|
||||
label: String,
|
||||
value: String,
|
||||
modifier: Modifier = Modifier,
|
||||
placeholder: String? = null,
|
||||
singleLine: Boolean = false,
|
||||
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
onValueChange: (String) -> Unit = {},
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
text = label
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = value,
|
||||
placeholder = placeholder?.let { { Text(placeholder) } },
|
||||
onValueChange = onValueChange,
|
||||
singleLine = singleLine,
|
||||
maxLines = maxLines,
|
||||
keyboardOptions = keyboardOptions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun LabelledOutlinedTextFieldPreview() = ElementPreview {
|
||||
Column {
|
||||
LabelledOutlinedTextField(
|
||||
label = "Room name",
|
||||
value = "",
|
||||
placeholder = "e.g. Product Sprint",
|
||||
)
|
||||
LabelledOutlinedTextField(
|
||||
label = "Room name",
|
||||
value = "a room name",
|
||||
placeholder = "e.g. Product Sprint",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,5 +43,7 @@ enum class AvatarSize(val dp: Dp) {
|
|||
RoomInviteItem(52.dp),
|
||||
InviteSender(16.dp),
|
||||
|
||||
EditRoomDetails(70.dp),
|
||||
|
||||
NotificationsOptIn(32.dp),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,57 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="state_event_avatar_changed_too">"(Avatar wurde auch geändert)"</string>
|
||||
<string name="state_event_avatar_url_changed">"%1$s hat den Avatar geändert"</string>
|
||||
<string name="state_event_avatar_url_changed_by_you">"Sie haben Ihren Avatar geändert"</string>
|
||||
<string name="state_event_display_name_changed_from">"%1$s hat den Anzeigenamen von %2$s auf %3$s geändert"</string>
|
||||
<string name="state_event_display_name_changed_from_by_you">"Sie haben Ihren Anzeigenamen von %1$s auf %2$s geändert"</string>
|
||||
<string name="state_event_display_name_removed">"%1$s hat den Anzeigenamen entfernt (war %2$s)"</string>
|
||||
<string name="state_event_display_name_removed_by_you">"Sie haben Ihren Anzeigenamen entfernt (war %1$s)"</string>
|
||||
<string name="state_event_display_name_set">"%1$s setzen ihren Anzeigenamen auf %2$s"</string>
|
||||
<string name="state_event_display_name_set_by_you">"Sie haben Ihren Anzeigenamen zu %1$s geändert"</string>
|
||||
<string name="state_event_room_avatar_changed">"%1$s hat den Raum-Avatar geändert"</string>
|
||||
<string name="state_event_room_avatar_changed_by_you">"Sie haben den Raum-Avatar geändert"</string>
|
||||
<string name="state_event_room_avatar_removed">"%1$s hat den Raum-Avatar entfernt"</string>
|
||||
<string name="state_event_room_avatar_removed_by_you">"Sie haben den Raum-Avatar entfernt"</string>
|
||||
<string name="state_event_room_ban">"%1$s hat %2$s gesperrt"</string>
|
||||
<string name="state_event_room_ban_by_you">"Sie haben %1$s gesperrt"</string>
|
||||
<string name="state_event_room_created">"%1$s hat den Raum erstellt"</string>
|
||||
<string name="state_event_room_created_by_you">"Du hast den Raum erstellt"</string>
|
||||
<string name="state_event_room_created_by_you">"Sie haben den Raum erstellt"</string>
|
||||
<string name="state_event_room_invite">"%1$s hat %2$s eingeladen"</string>
|
||||
<string name="state_event_room_invite_accepted">"%1$s hat die Einladung angenommen"</string>
|
||||
<string name="state_event_room_invite_accepted_by_you">"Sie haben die Einladung angenommen"</string>
|
||||
<string name="state_event_room_invite_by_you">"Sie haben %1$s eingeladen"</string>
|
||||
<string name="state_event_room_invite_you">"%1$s hat dich eingeladen"</string>
|
||||
<string name="state_event_room_join">"%1$s hat den Raum betreten"</string>
|
||||
<string name="state_event_room_join_by_you">"Sie haben den Raum betreten"</string>
|
||||
<string name="state_event_room_knock">"%1$s hat angefragt beizutreten"</string>
|
||||
<string name="state_event_room_knock_accepted">"%1$s hat %2$s den Beitritt erlaubt"</string>
|
||||
<string name="state_event_room_knock_accepted_by_you">"%1$s hat Ihnen den Betritt erlaubt"</string>
|
||||
<string name="state_event_room_knock_by_you">"Sie haben angefragt beizutreten"</string>
|
||||
<string name="state_event_room_knock_denied">"%1$s hat die Beitrittsanfrage von %2$s abgelehnt"</string>
|
||||
<string name="state_event_room_knock_denied_by_you">"Sie haben die Beitrittsanfrage von %1$s abgelehnt"</string>
|
||||
<string name="state_event_room_knock_denied_you">"%1$s hat Ihre Beitrittsanfrage abgelehnt"</string>
|
||||
<string name="state_event_room_knock_retracted">"%1$s ist nicht mehr an einem Beitritt interessiert"</string>
|
||||
<string name="state_event_room_knock_retracted_by_you">"Sie haben Ihre Beitrittsanfrage zurückgezogen"</string>
|
||||
<string name="state_event_room_leave">"%1$s hat den Raum verlassen"</string>
|
||||
<string name="state_event_room_leave_by_you">"Sie haben den Raum verlassen"</string>
|
||||
<string name="state_event_room_name_changed">"%1$s hat den Raumnamen geändert in: %2$s"</string>
|
||||
<string name="state_event_room_name_changed_by_you">"Sie haben den Raumnamen geändert in: %1$s"</string>
|
||||
<string name="state_event_room_name_removed">"%1$s hat den Raumnamen entfernt"</string>
|
||||
<string name="state_event_room_name_removed_by_you">"Sie haben den Raumnamen entfernt"</string>
|
||||
<string name="state_event_room_reject">"%1$s hat die Einladung abgelehnt"</string>
|
||||
<string name="state_event_room_reject_by_you">"Sie haben die Einladung abgelehnt"</string>
|
||||
<string name="state_event_room_remove">"%1$s hat %2$s entfernt"</string>
|
||||
<string name="state_event_room_remove_by_you">"Sie haben %1$s entfernt"</string>
|
||||
<string name="state_event_room_third_party_invite">"%1$s hat eine Einladung an %2$s gesendet, dem Raum beizutreten"</string>
|
||||
<string name="state_event_room_third_party_invite_by_you">"Sie haben eine Einladung an %1$s gesendet, dem Raum beizutreten"</string>
|
||||
<string name="state_event_room_third_party_revoked_invite">"%1$s hat die Einladung an %2$s zum Betreten des Raums zurückgezogen"</string>
|
||||
<string name="state_event_room_third_party_revoked_invite_by_you">"Sie haben die Einladung an %1$s zum Betreten des Raums zurückgezogen"</string>
|
||||
<string name="state_event_room_topic_changed">"%1$s hat das Thema geändert in: %2$s"</string>
|
||||
<string name="state_event_room_topic_changed_by_you">"Sie haben das Thema geändert in: %1$s"</string>
|
||||
<string name="state_event_room_topic_removed">"%1$s hat das Raumthema entfernt"</string>
|
||||
<string name="state_event_room_topic_removed_by_you">"Sie haben das Raumthema entfernt"</string>
|
||||
<string name="state_event_room_unban">"%1$s hat die Sperre für %2$s aufgehoben"</string>
|
||||
<string name="state_event_room_unban_by_you">"Sie haben die Sperre für %1$s aufgehoben"</string>
|
||||
<string name="state_event_room_unknown_membership_change">"%1$s hat eine unbekannte Raumänderung vorgenommen"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ interface MatrixClient : Closeable {
|
|||
suspend fun createDM(userId: UserId): Result<RoomId>
|
||||
suspend fun getProfile(userId: UserId): Result<MatrixUser>
|
||||
suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults>
|
||||
suspend fun setDisplayName(displayName: String): Result<Unit>
|
||||
suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit>
|
||||
suspend fun removeAvatar(): Result<Unit>
|
||||
fun syncService(): SyncService
|
||||
fun sessionVerificationService(): SessionVerificationService
|
||||
fun pushersService(): PushersService
|
||||
|
|
|
|||
|
|
@ -276,6 +276,23 @@ class RustMatrixClient constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun setDisplayName(displayName: String): Result<Unit> =
|
||||
withContext(sessionDispatcher) {
|
||||
runCatching { client.setDisplayName(displayName) }
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
override suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit> =
|
||||
withContext(sessionDispatcher) {
|
||||
runCatching { client.uploadAvatar(mimeType, data.toUByteArray().toList()) }
|
||||
}
|
||||
|
||||
override suspend fun removeAvatar(): Result<Unit> =
|
||||
withContext(sessionDispatcher) {
|
||||
runCatching { client.removeAvatar() }
|
||||
}
|
||||
|
||||
|
||||
override fun syncService(): SyncService = rustSyncService
|
||||
|
||||
override fun sessionVerificationService(): SessionVerificationService = verificationService
|
||||
|
|
|
|||
|
|
@ -111,6 +111,9 @@ class RoomSummaryListProcessor(
|
|||
RoomListEntriesUpdate.Clear -> {
|
||||
clear()
|
||||
}
|
||||
is RoomListEntriesUpdate.Truncate -> {
|
||||
subList(update.length.toInt(), size).clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +122,7 @@ class RoomSummaryListProcessor(
|
|||
RoomListEntry.Empty -> buildEmptyRoomSummary()
|
||||
is RoomListEntry.Filled -> buildAndCacheRoomSummaryForIdentifier(entry.roomId)
|
||||
is RoomListEntry.Invalidated -> {
|
||||
roomSummariesByIdentifier[entry.roomId] ?: buildEmptyRoomSummary()
|
||||
roomSummariesByIdentifier[entry.roomId] ?: buildAndCacheRoomSummaryForIdentifier(entry.roomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ internal class MatrixTimelineDiffProcessor(
|
|||
TimelineChange.CLEAR -> {
|
||||
clear()
|
||||
}
|
||||
TimelineChange.TRUNCATE -> {
|
||||
// Not supported
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ internal class RustTracingTree(private val retrieveFromStackTrace: Boolean) : Ti
|
|||
line = location.line,
|
||||
level = logLevel,
|
||||
target = Target.ELEMENT.filter,
|
||||
message = message,
|
||||
message = if (tag != null) "[$tag] $message" else message,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,13 @@ class FakeMatrixClient(
|
|||
private val accountManagementUrlString: Result<String?> = Result.success(null),
|
||||
) : MatrixClient {
|
||||
|
||||
var setDisplayNameCalled: Boolean = false
|
||||
private set
|
||||
var uploadAvatarCalled: Boolean = false
|
||||
private set
|
||||
var removeAvatarCalled: Boolean = false
|
||||
private set
|
||||
|
||||
private var ignoreUserResult: Result<Unit> = Result.success(Unit)
|
||||
private var unignoreUserResult: Result<Unit> = Result.success(Unit)
|
||||
private var createRoomResult: Result<RoomId> = Result.success(A_ROOM_ID)
|
||||
|
|
@ -69,6 +76,9 @@ class FakeMatrixClient(
|
|||
private val searchUserResults = mutableMapOf<String, Result<MatrixSearchUserResults>>()
|
||||
private val getProfileResults = mutableMapOf<UserId, Result<MatrixUser>>()
|
||||
private var uploadMediaResult: Result<String> = Result.success(AN_AVATAR_URL)
|
||||
private var setDisplayNameResult: Result<Unit> = Result.success(Unit)
|
||||
private var uploadAvatarResult: Result<Unit> = Result.success(Unit)
|
||||
private var removeAvatarResult: Result<Unit> = Result.success(Unit)
|
||||
|
||||
override suspend fun getRoom(roomId: RoomId): MatrixRoom? {
|
||||
return getRoomResults[roomId]
|
||||
|
|
@ -133,6 +143,7 @@ class FakeMatrixClient(
|
|||
override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?> {
|
||||
return accountManagementUrlString
|
||||
}
|
||||
|
||||
override suspend fun uploadMedia(
|
||||
mimeType: String,
|
||||
data: ByteArray,
|
||||
|
|
@ -141,6 +152,21 @@ class FakeMatrixClient(
|
|||
return uploadMediaResult
|
||||
}
|
||||
|
||||
override suspend fun setDisplayName(displayName: String): Result<Unit> = simulateLongTask {
|
||||
setDisplayNameCalled = true
|
||||
return setDisplayNameResult
|
||||
}
|
||||
|
||||
override suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit> = simulateLongTask {
|
||||
uploadAvatarCalled = true
|
||||
return uploadAvatarResult
|
||||
}
|
||||
|
||||
override suspend fun removeAvatar(): Result<Unit> = simulateLongTask {
|
||||
removeAvatarCalled = true
|
||||
return removeAvatarResult
|
||||
}
|
||||
|
||||
override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService
|
||||
|
||||
override fun pushersService(): PushersService = pushersService
|
||||
|
|
@ -197,4 +223,16 @@ class FakeMatrixClient(
|
|||
fun givenUploadMediaResult(result: Result<String>) {
|
||||
uploadMediaResult = result
|
||||
}
|
||||
|
||||
fun givenSetDisplayNameResult(result: Result<Unit>) {
|
||||
setDisplayNameResult = result
|
||||
}
|
||||
|
||||
fun givenUploadAvatarResult(result: Result<Unit>) {
|
||||
uploadAvatarResult = result
|
||||
}
|
||||
|
||||
fun givenRemoveAvatarResult(result: Result<Unit>) {
|
||||
removeAvatarResult = result
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* 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.ui.components
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.AddAPhoto
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
|
||||
@Composable
|
||||
fun EditableAvatarView(
|
||||
userId: String?,
|
||||
displayName: String?,
|
||||
avatarUrl: Uri?,
|
||||
avatarSize: AvatarSize,
|
||||
onAvatarClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(avatarSize.dp)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = onAvatarClicked,
|
||||
indication = rememberRipple(bounded = false),
|
||||
)
|
||||
) {
|
||||
when (avatarUrl?.scheme) {
|
||||
null, "mxc" -> {
|
||||
userId?.let {
|
||||
Avatar(
|
||||
avatarData = AvatarData(it, displayName, avatarUrl?.toString(), size = avatarSize),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
UnsavedAvatar(
|
||||
avatarUri = avatarUrl,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.primary)
|
||||
.size(24.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(16.dp),
|
||||
imageVector = Icons.Outlined.AddAPhoto,
|
||||
contentDescription = "",
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,9 +28,14 @@ open class MatrixUserProvider : PreviewParameterProvider<MatrixUser> {
|
|||
)
|
||||
}
|
||||
|
||||
fun aMatrixUser(id: String = "@id_of_alice:server.org", displayName: String = "Alice") = MatrixUser(
|
||||
fun aMatrixUser(
|
||||
id: String = "@id_of_alice:server.org",
|
||||
displayName: String = "Alice",
|
||||
avatarUrl: String? = null,
|
||||
) = MatrixUser(
|
||||
userId = UserId(id),
|
||||
displayName = displayName,
|
||||
avatarUrl = avatarUrl,
|
||||
)
|
||||
|
||||
fun aMatrixUserList() = listOf(
|
||||
|
|
|
|||
|
|
@ -21,5 +21,5 @@ import io.element.android.libraries.matrix.api.core.SessionId
|
|||
|
||||
interface NotificationDrawerManager {
|
||||
fun clearMembershipNotificationForSession(sessionId: SessionId)
|
||||
fun clearMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId)
|
||||
fun clearMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId, doRender: Boolean)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import io.element.android.libraries.matrix.api.core.EventId
|
|||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||
import io.element.android.libraries.push.impl.config.PushConfig
|
||||
import io.element.android.libraries.push.impl.log.pushLoggerTag
|
||||
import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest
|
||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
|
|
@ -35,7 +34,7 @@ import javax.inject.Inject
|
|||
|
||||
internal const val DEFAULT_PUSHER_FILE_TAG = "mobile"
|
||||
|
||||
private val loggerTag = LoggerTag("PushersManager", pushLoggerTag)
|
||||
private val loggerTag = LoggerTag("PushersManager", LoggerTag.PushLoggerTag)
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class PushersManager @Inject constructor(
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* 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.push.impl.log
|
||||
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
|
||||
internal val pushLoggerTag = LoggerTag("Push")
|
||||
internal val notificationLoggerTag = LoggerTag("Notification", pushLoggerTag)
|
||||
|
|
@ -20,6 +20,7 @@ import io.element.android.libraries.androidutils.throttler.FirstThrottler
|
|||
import io.element.android.libraries.core.cache.CircularCache
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
|
|
@ -41,6 +42,8 @@ import kotlinx.coroutines.withContext
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("DefaultNotificationDrawerManager", LoggerTag.NotificationLoggerTag)
|
||||
|
||||
/**
|
||||
* The NotificationDrawerManager receives notification events as they arrived (from event stream or fcm) and
|
||||
* organise them in order to display them in the notification drawer.
|
||||
|
|
@ -89,7 +92,11 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
is NavigationState.Space -> {}
|
||||
is NavigationState.Room -> {
|
||||
// Cleanup notification for current room
|
||||
clearMessagesForRoom(navigationState.parentSpace.parentSession.sessionId, navigationState.roomId)
|
||||
clearMessagesForRoom(
|
||||
sessionId = navigationState.parentSpace.parentSession.sessionId,
|
||||
roomId = navigationState.roomId,
|
||||
doRender = true,
|
||||
)
|
||||
}
|
||||
is NavigationState.Thread -> {
|
||||
onEnteringThread(
|
||||
|
|
@ -112,13 +119,13 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
|
||||
private fun NotificationEventQueue.onNotifiableEventReceived(notifiableEvent: NotifiableEvent) {
|
||||
if (buildMeta.lowPrivacyLoggingEnabled) {
|
||||
Timber.d("onNotifiableEventReceived(): $notifiableEvent")
|
||||
Timber.tag(loggerTag.value).d("onNotifiableEventReceived(): $notifiableEvent")
|
||||
} else {
|
||||
Timber.d("onNotifiableEventReceived(): is push: ${notifiableEvent.canBeReplaced}")
|
||||
Timber.tag(loggerTag.value).d("onNotifiableEventReceived(): is push: ${notifiableEvent.canBeReplaced}")
|
||||
}
|
||||
|
||||
if (filteredEventDetector.shouldBeIgnored(notifiableEvent)) {
|
||||
Timber.d("onNotifiableEventReceived(): ignore the event")
|
||||
Timber.tag(loggerTag.value).d("onNotifiableEventReceived(): ignore the event")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -132,7 +139,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
* Events might be grouped and there might not be one notification per event!
|
||||
*/
|
||||
fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) {
|
||||
updateEvents {
|
||||
updateEvents(doRender = true) {
|
||||
it.onNotifiableEventReceived(notifiableEvent)
|
||||
}
|
||||
}
|
||||
|
|
@ -140,8 +147,8 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
/**
|
||||
* Clear all known events and refresh the notification drawer.
|
||||
*/
|
||||
fun clearAllMessagesEvents(sessionId: SessionId) {
|
||||
updateEvents {
|
||||
fun clearAllMessagesEvents(sessionId: SessionId, doRender: Boolean) {
|
||||
updateEvents(doRender = doRender) {
|
||||
it.clearMessagesForSession(sessionId)
|
||||
}
|
||||
}
|
||||
|
|
@ -150,7 +157,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
* Clear all notifications related to the session and refresh the notification drawer.
|
||||
*/
|
||||
fun clearAllEvents(sessionId: SessionId) {
|
||||
updateEvents {
|
||||
updateEvents(doRender = true) {
|
||||
it.clearAllForSession(sessionId)
|
||||
}
|
||||
}
|
||||
|
|
@ -160,14 +167,14 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
* Used to ignore events related to that room (no need to display notification) and clean any existing notification on this room.
|
||||
* Can also be called when a notification for this room is dismissed by the user.
|
||||
*/
|
||||
fun clearMessagesForRoom(sessionId: SessionId, roomId: RoomId) {
|
||||
updateEvents {
|
||||
fun clearMessagesForRoom(sessionId: SessionId, roomId: RoomId, doRender: Boolean) {
|
||||
updateEvents(doRender = doRender) {
|
||||
it.clearMessagesForRoom(sessionId, roomId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearMembershipNotificationForSession(sessionId: SessionId) {
|
||||
updateEvents {
|
||||
updateEvents(doRender = true) {
|
||||
it.clearMembershipNotificationForSession(sessionId)
|
||||
}
|
||||
}
|
||||
|
|
@ -175,8 +182,12 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
/**
|
||||
* Clear invitation notification for the provided room.
|
||||
*/
|
||||
override fun clearMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId) {
|
||||
updateEvents {
|
||||
override fun clearMembershipNotificationForRoom(
|
||||
sessionId: SessionId,
|
||||
roomId: RoomId,
|
||||
doRender: Boolean,
|
||||
) {
|
||||
updateEvents(doRender = doRender) {
|
||||
it.clearMembershipNotificationForRoom(sessionId, roomId)
|
||||
}
|
||||
}
|
||||
|
|
@ -184,8 +195,8 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
/**
|
||||
* Clear the notifications for a single event.
|
||||
*/
|
||||
fun clearEvent(eventId: EventId) {
|
||||
updateEvents {
|
||||
fun clearEvent(eventId: EventId, doRender: Boolean) {
|
||||
updateEvents(doRender = doRender) {
|
||||
it.clearEvent(eventId)
|
||||
}
|
||||
}
|
||||
|
|
@ -195,14 +206,14 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
* Used to ignore events related to that thread (no need to display notification) and clean any existing notification on this room.
|
||||
*/
|
||||
private fun onEnteringThread(sessionId: SessionId, roomId: RoomId, threadId: ThreadId) {
|
||||
updateEvents {
|
||||
updateEvents(doRender = true) {
|
||||
it.clearMessagesForThread(sessionId, roomId, threadId)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO EAx Must be per account
|
||||
fun notificationStyleChanged() {
|
||||
updateEvents {
|
||||
updateEvents(doRender = true) {
|
||||
val newSettings = true // pushDataStore.useCompleteNotificationFormat()
|
||||
if (newSettings != useCompleteNotificationFormat) {
|
||||
// Settings has changed, remove all current notifications
|
||||
|
|
@ -212,41 +223,46 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateEvents(action: DefaultNotificationDrawerManager.(NotificationEventQueue) -> Unit) {
|
||||
notificationState.updateQueuedEvents(this) { queuedEvents, _ ->
|
||||
private fun updateEvents(
|
||||
doRender: Boolean,
|
||||
action: (NotificationEventQueue) -> Unit,
|
||||
) {
|
||||
notificationState.updateQueuedEvents { queuedEvents, _ ->
|
||||
action(queuedEvents)
|
||||
}
|
||||
coroutineScope.refreshNotificationDrawer()
|
||||
coroutineScope.refreshNotificationDrawer(doRender)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.refreshNotificationDrawer() = launch {
|
||||
private fun CoroutineScope.refreshNotificationDrawer(doRender: Boolean) = launch {
|
||||
// Implement last throttler
|
||||
val canHandle = firstThrottler.canHandle()
|
||||
Timber.v("refreshNotificationDrawer(), delay: ${canHandle.waitMillis()} ms")
|
||||
Timber.tag(loggerTag.value).v("refreshNotificationDrawer($doRender), delay: ${canHandle.waitMillis()} ms")
|
||||
withContext(dispatchers.io) {
|
||||
delay(canHandle.waitMillis())
|
||||
try {
|
||||
refreshNotificationDrawerBg()
|
||||
refreshNotificationDrawerBg(doRender)
|
||||
} catch (throwable: Throwable) {
|
||||
// It can happen if for instance session has been destroyed. It's a bit ugly to try catch like this, but it's safer
|
||||
Timber.w(throwable, "refreshNotificationDrawerBg failure")
|
||||
Timber.tag(loggerTag.value).w(throwable, "refreshNotificationDrawerBg failure")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshNotificationDrawerBg() {
|
||||
Timber.v("refreshNotificationDrawerBg()")
|
||||
val eventsToRender = notificationState.updateQueuedEvents(this) { queuedEvents, renderedEvents ->
|
||||
private suspend fun refreshNotificationDrawerBg(doRender: Boolean) {
|
||||
Timber.tag(loggerTag.value).v("refreshNotificationDrawerBg($doRender)")
|
||||
val eventsToRender = notificationState.updateQueuedEvents { queuedEvents, renderedEvents ->
|
||||
notifiableEventProcessor.process(queuedEvents.rawEvents(), renderedEvents).also {
|
||||
queuedEvents.clearAndAdd(it.onlyKeptEvents())
|
||||
}
|
||||
}
|
||||
|
||||
if (notificationState.hasAlreadyRendered(eventsToRender)) {
|
||||
Timber.d("Skipping notification update due to event list not changing")
|
||||
Timber.tag(loggerTag.value).d("Skipping notification update due to event list not changing")
|
||||
} else {
|
||||
notificationState.clearAndAddRenderedEvents(eventsToRender)
|
||||
renderEvents(eventsToRender)
|
||||
if (doRender) {
|
||||
renderEvents(eventsToRender)
|
||||
}
|
||||
persistEvents()
|
||||
}
|
||||
}
|
||||
|
|
@ -265,7 +281,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
|
||||
eventsForSessions.forEach { (sessionId, notifiableEvents) ->
|
||||
val currentUser = tryOrNull(
|
||||
onError = { Timber.e(it, "Unable to retrieve info for user ${sessionId.value}") },
|
||||
onError = { Timber.tag(loggerTag.value).e(it, "Unable to retrieve info for user ${sessionId.value}") },
|
||||
operation = {
|
||||
val client = matrixClientProvider.getOrRestore(sessionId).getOrThrow()
|
||||
// myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
|
||||
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||
|
|
@ -29,6 +30,8 @@ import javax.inject.Inject
|
|||
|
||||
private typealias ProcessedEvents = List<ProcessedEvent<NotifiableEvent>>
|
||||
|
||||
private val loggerTag = LoggerTag("NotifiableEventProcessor", LoggerTag.NotificationLoggerTag)
|
||||
|
||||
class NotifiableEventProcessor @Inject constructor(
|
||||
private val outdatedDetector: OutdatedEventDetector,
|
||||
private val appNavigationStateService: AppNavigationStateService,
|
||||
|
|
@ -45,10 +48,10 @@ class NotifiableEventProcessor @Inject constructor(
|
|||
is NotifiableMessageEvent -> when {
|
||||
it.shouldIgnoreEventInRoom(appState) -> {
|
||||
ProcessedEvent.Type.REMOVE
|
||||
.also { Timber.d("notification message removed due to currently viewing the same room or thread") }
|
||||
.also { Timber.tag(loggerTag.value).d("notification message removed due to currently viewing the same room or thread") }
|
||||
}
|
||||
outdatedDetector.isMessageOutdated(it) -> ProcessedEvent.Type.REMOVE
|
||||
.also { Timber.d("notification message removed due to being read") }
|
||||
.also { Timber.tag(loggerTag.value).d("notification message removed due to being read") }
|
||||
else -> ProcessedEvent.Type.KEEP
|
||||
}
|
||||
is SimpleNotifiableEvent -> when (it.type) {
|
||||
|
|
@ -58,7 +61,7 @@ class NotifiableEventProcessor @Inject constructor(
|
|||
is FallbackNotifiableEvent -> when {
|
||||
it.shouldIgnoreEventInRoom(appState) -> {
|
||||
ProcessedEvent.Type.REMOVE
|
||||
.also { Timber.d("notification fallback removed due to currently viewing the same room or thread") }
|
||||
.also { Timber.tag(loggerTag.value).d("notification fallback removed due to currently viewing the same room or thread") }
|
||||
}
|
||||
else -> ProcessedEvent.Type.KEEP
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageTy
|
|||
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
|
||||
import io.element.android.libraries.push.impl.R
|
||||
import io.element.android.libraries.push.impl.log.pushLoggerTag
|
||||
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
|
|
@ -47,7 +46,7 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("NotifiableEventResolver", pushLoggerTag)
|
||||
private val loggerTag = LoggerTag("NotifiableEventResolver", LoggerTag.NotificationLoggerTag)
|
||||
|
||||
/**
|
||||
* The notifiable event resolver is able to create a NotifiableEvent (view model for notifications) from an sdk Event.
|
||||
|
|
|
|||
|
|
@ -24,11 +24,10 @@ import io.element.android.libraries.core.log.logger.LoggerTag
|
|||
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
|
||||
import io.element.android.libraries.push.impl.log.notificationLoggerTag
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("NotificationBroadcastReceiver", notificationLoggerTag)
|
||||
private val loggerTag = LoggerTag("NotificationBroadcastReceiver", LoggerTag.NotificationLoggerTag)
|
||||
|
||||
/**
|
||||
* Receives actions broadcast by notification (on click, on dismiss, inline replies, etc.).
|
||||
|
|
@ -41,34 +40,34 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
|||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent == null || context == null) return
|
||||
context.bindings<NotificationBroadcastReceiverBindings>().inject(this)
|
||||
Timber.tag(loggerTag.value).v("NotificationBroadcastReceiver received : $intent")
|
||||
val sessionId = intent.extras?.getString(KEY_SESSION_ID)?.let(::SessionId) ?: return
|
||||
val roomId = intent.getStringExtra(KEY_ROOM_ID)?.let(::RoomId)
|
||||
val eventId = intent.getStringExtra(KEY_EVENT_ID)?.let(::EventId)
|
||||
Timber.tag(loggerTag.value).d("onReceive: ${intent.action} ${intent.data} for: ${roomId?.value}/${eventId?.value}")
|
||||
when (intent.action) {
|
||||
actionIds.smartReply ->
|
||||
handleSmartReply(intent, context)
|
||||
actionIds.dismissRoom -> if (roomId != null) {
|
||||
defaultNotificationDrawerManager.clearMessagesForRoom(sessionId, roomId)
|
||||
defaultNotificationDrawerManager.clearMessagesForRoom(sessionId, roomId, doRender = false)
|
||||
}
|
||||
actionIds.dismissSummary ->
|
||||
defaultNotificationDrawerManager.clearAllMessagesEvents(sessionId)
|
||||
defaultNotificationDrawerManager.clearAllMessagesEvents(sessionId, doRender = false)
|
||||
actionIds.dismissInvite -> if (roomId != null) {
|
||||
defaultNotificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId)
|
||||
defaultNotificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId, doRender = false)
|
||||
}
|
||||
actionIds.dismissEvent -> if (eventId != null) {
|
||||
defaultNotificationDrawerManager.clearEvent(eventId)
|
||||
defaultNotificationDrawerManager.clearEvent(eventId, doRender = false)
|
||||
}
|
||||
actionIds.markRoomRead -> if (roomId != null) {
|
||||
defaultNotificationDrawerManager.clearMessagesForRoom(sessionId, roomId)
|
||||
defaultNotificationDrawerManager.clearMessagesForRoom(sessionId, roomId, doRender = true)
|
||||
handleMarkAsRead(sessionId, roomId)
|
||||
}
|
||||
actionIds.join -> if (roomId != null) {
|
||||
defaultNotificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId)
|
||||
defaultNotificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId, doRender = true)
|
||||
handleJoinRoom(sessionId, roomId)
|
||||
}
|
||||
actionIds.reject -> if (roomId != null) {
|
||||
defaultNotificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId)
|
||||
defaultNotificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId, doRender = true)
|
||||
handleRejectRoom(sessionId, roomId)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import io.element.android.libraries.androidutils.file.safeDelete
|
|||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.push.impl.log.notificationLoggerTag
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
|
@ -33,7 +32,7 @@ import javax.inject.Inject
|
|||
private const val ROOMS_NOTIFICATIONS_FILE_NAME_LEGACY = "im.vector.notifications.cache"
|
||||
private const val FILE_NAME = "notifications.bin"
|
||||
|
||||
private val loggerTag = LoggerTag("NotificationEventPersistence", notificationLoggerTag)
|
||||
private val loggerTag = LoggerTag("NotificationEventPersistence", LoggerTag.NotificationLoggerTag)
|
||||
|
||||
class NotificationEventPersistence @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
|
||||
|
|
@ -26,6 +27,8 @@ import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiab
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("NotificationRenderer", LoggerTag.NotificationLoggerTag)
|
||||
|
||||
class NotificationRenderer @Inject constructor(
|
||||
private val notificationIdProvider: NotificationIdProvider,
|
||||
private val notificationDisplayer: NotificationDisplayer,
|
||||
|
|
@ -54,7 +57,7 @@ class NotificationRenderer @Inject constructor(
|
|||
|
||||
// Remove summary first to avoid briefly displaying it after dismissing the last notification
|
||||
if (summaryNotification == SummaryNotification.Removed) {
|
||||
Timber.d("Removing summary notification")
|
||||
Timber.tag(loggerTag.value).d("Removing summary notification")
|
||||
notificationDisplayer.cancelNotificationMessage(
|
||||
tag = null,
|
||||
id = notificationIdProvider.getSummaryNotificationId(currentUser.userId)
|
||||
|
|
@ -64,14 +67,14 @@ class NotificationRenderer @Inject constructor(
|
|||
roomNotifications.forEach { wrapper ->
|
||||
when (wrapper) {
|
||||
is RoomNotification.Removed -> {
|
||||
Timber.d("Removing room messages notification ${wrapper.roomId}")
|
||||
Timber.tag(loggerTag.value).d("Removing room messages notification ${wrapper.roomId}")
|
||||
notificationDisplayer.cancelNotificationMessage(
|
||||
tag = wrapper.roomId.value,
|
||||
id = notificationIdProvider.getRoomMessagesNotificationId(currentUser.userId)
|
||||
)
|
||||
}
|
||||
is RoomNotification.Message -> if (useCompleteNotificationFormat) {
|
||||
Timber.d("Updating room messages notification ${wrapper.meta.roomId}")
|
||||
Timber.tag(loggerTag.value).d("Updating room messages notification ${wrapper.meta.roomId}")
|
||||
notificationDisplayer.showNotificationMessage(
|
||||
tag = wrapper.meta.roomId.value,
|
||||
id = notificationIdProvider.getRoomMessagesNotificationId(currentUser.userId),
|
||||
|
|
@ -84,14 +87,14 @@ class NotificationRenderer @Inject constructor(
|
|||
invitationNotifications.forEach { wrapper ->
|
||||
when (wrapper) {
|
||||
is OneShotNotification.Removed -> {
|
||||
Timber.d("Removing invitation notification ${wrapper.key}")
|
||||
Timber.tag(loggerTag.value).d("Removing invitation notification ${wrapper.key}")
|
||||
notificationDisplayer.cancelNotificationMessage(
|
||||
tag = wrapper.key,
|
||||
id = notificationIdProvider.getRoomInvitationNotificationId(currentUser.userId)
|
||||
)
|
||||
}
|
||||
is OneShotNotification.Append -> if (useCompleteNotificationFormat) {
|
||||
Timber.d("Updating invitation notification ${wrapper.meta.key}")
|
||||
Timber.tag(loggerTag.value).d("Updating invitation notification ${wrapper.meta.key}")
|
||||
notificationDisplayer.showNotificationMessage(
|
||||
tag = wrapper.meta.key,
|
||||
id = notificationIdProvider.getRoomInvitationNotificationId(currentUser.userId),
|
||||
|
|
@ -104,14 +107,14 @@ class NotificationRenderer @Inject constructor(
|
|||
simpleNotifications.forEach { wrapper ->
|
||||
when (wrapper) {
|
||||
is OneShotNotification.Removed -> {
|
||||
Timber.d("Removing simple notification ${wrapper.key}")
|
||||
Timber.tag(loggerTag.value).d("Removing simple notification ${wrapper.key}")
|
||||
notificationDisplayer.cancelNotificationMessage(
|
||||
tag = wrapper.key,
|
||||
id = notificationIdProvider.getRoomEventNotificationId(currentUser.userId)
|
||||
)
|
||||
}
|
||||
is OneShotNotification.Append -> if (useCompleteNotificationFormat) {
|
||||
Timber.d("Updating simple notification ${wrapper.meta.key}")
|
||||
Timber.tag(loggerTag.value).d("Updating simple notification ${wrapper.meta.key}")
|
||||
notificationDisplayer.showNotificationMessage(
|
||||
tag = wrapper.meta.key,
|
||||
id = notificationIdProvider.getRoomEventNotificationId(currentUser.userId),
|
||||
|
|
@ -124,14 +127,14 @@ class NotificationRenderer @Inject constructor(
|
|||
fallbackNotifications.forEach { wrapper ->
|
||||
when (wrapper) {
|
||||
is OneShotNotification.Removed -> {
|
||||
Timber.d("Removing fallback notification ${wrapper.key}")
|
||||
Timber.tag(loggerTag.value).d("Removing fallback notification ${wrapper.key}")
|
||||
notificationDisplayer.cancelNotificationMessage(
|
||||
tag = wrapper.key,
|
||||
id = notificationIdProvider.getFallbackNotificationId(currentUser.userId)
|
||||
)
|
||||
}
|
||||
is OneShotNotification.Append -> if (useCompleteNotificationFormat) {
|
||||
Timber.d("Updating fallback notification ${wrapper.meta.key}")
|
||||
Timber.tag(loggerTag.value).d("Updating fallback notification ${wrapper.meta.key}")
|
||||
notificationDisplayer.showNotificationMessage(
|
||||
tag = wrapper.meta.key,
|
||||
id = notificationIdProvider.getFallbackNotificationId(currentUser.userId),
|
||||
|
|
@ -143,7 +146,7 @@ class NotificationRenderer @Inject constructor(
|
|||
|
||||
// Update summary last to avoid briefly displaying it before other notifications
|
||||
if (summaryNotification is SummaryNotification.Update) {
|
||||
Timber.d("Updating summary notification")
|
||||
Timber.tag(loggerTag.value).d("Updating summary notification")
|
||||
notificationDisplayer.showNotificationMessage(
|
||||
tag = null,
|
||||
id = notificationIdProvider.getSummaryNotificationId(currentUser.userId),
|
||||
|
|
|
|||
|
|
@ -39,11 +39,10 @@ class NotificationState(
|
|||
) {
|
||||
|
||||
fun <T> updateQueuedEvents(
|
||||
drawerManager: DefaultNotificationDrawerManager,
|
||||
action: DefaultNotificationDrawerManager.(NotificationEventQueue, List<ProcessedEvent<NotifiableEvent>>) -> T
|
||||
action: (NotificationEventQueue, List<ProcessedEvent<NotifiableEvent>>) -> T
|
||||
): T {
|
||||
return synchronized(queuedEvents) {
|
||||
action(drawerManager, queuedEvents, renderedEvents)
|
||||
action(queuedEvents, renderedEvents)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import io.element.android.libraries.core.meta.BuildMeta
|
|||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.push.impl.PushersManager
|
||||
import io.element.android.libraries.push.impl.log.pushLoggerTag
|
||||
import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager
|
||||
import io.element.android.libraries.push.impl.notifications.NotifiableEventResolver
|
||||
import io.element.android.libraries.push.impl.store.DefaultPushDataStore
|
||||
|
|
@ -40,7 +39,7 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("PushHandler", pushLoggerTag)
|
||||
private val loggerTag = LoggerTag("PushHandler", LoggerTag.PushLoggerTag)
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPushHandler @Inject constructor(
|
||||
|
|
@ -67,7 +66,7 @@ class DefaultPushHandler @Inject constructor(
|
|||
* @param pushData the data received in the push.
|
||||
*/
|
||||
override suspend fun handle(pushData: PushData) {
|
||||
Timber.tag(loggerTag.value).d("## handling pushData")
|
||||
Timber.tag(loggerTag.value).d("## handling pushData: ${pushData.roomId}/${pushData.eventId}")
|
||||
|
||||
if (buildMeta.lowPrivacyLoggingEnabled) {
|
||||
Timber.tag(loggerTag.value).d("## pushData: $pushData")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,52 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="notification_channel_call">"Anruf"</string>
|
||||
<string name="notification_channel_listening_for_events">"Auf Ereignisse achten"</string>
|
||||
<string name="notification_channel_noisy">"Laute Benachrichtigungen"</string>
|
||||
<string name="notification_channel_silent">"Stumme Benachrichtigungen"</string>
|
||||
<string name="notification_inline_reply_failed">"** Fehler beim Senden - bitte Raum öffnen"</string>
|
||||
<string name="notification_invitation_action_join">"Beitreten"</string>
|
||||
<string name="notification_invitation_action_reject">"Ablehnen"</string>
|
||||
<string name="notification_invite_body">"Sie wurden zu einem Chat eingeladen"</string>
|
||||
<string name="notification_new_messages">"Neue Nachrichten"</string>
|
||||
<string name="notification_reaction_body">"Reagiert mit %1$s"</string>
|
||||
<string name="notification_room_action_mark_as_read">"Als gelesen markieren"</string>
|
||||
<string name="notification_room_invite_body">"Sie wurden eingeladen, den Raum zu betreten"</string>
|
||||
<string name="notification_sender_me">"Ich"</string>
|
||||
<string name="notification_test_push_notification_content">"Sie sehen sich die Benachrichtigung an! Klicken Sie hier!"</string>
|
||||
<string name="notification_ticker_text_dm">"%1$s: %2$s"</string>
|
||||
<string name="notification_ticker_text_group">"%1$s: %2$s %3$s"</string>
|
||||
<string name="notification_unread_notified_messages_and_invitation">"%1$s und %2$s"</string>
|
||||
<string name="notification_unread_notified_messages_in_room">"%1$s in %2$s"</string>
|
||||
<string name="notification_unread_notified_messages_in_room_and_invitation">"%1$s in %2$s und %3$s"</string>
|
||||
<plurals name="notification_compat_summary_line_for_room">
|
||||
<item quantity="one">"%1$s: %2$d Nachricht"</item>
|
||||
<item quantity="other">"%1$s: %2$d Nachrichten"</item>
|
||||
</plurals>
|
||||
<plurals name="notification_compat_summary_title">
|
||||
<item quantity="one">"%d Mitteilung"</item>
|
||||
<item quantity="other">"%d Mitteilungen"</item>
|
||||
</plurals>
|
||||
<plurals name="notification_invitations">
|
||||
<item quantity="one">"%d Einladung"</item>
|
||||
<item quantity="other">"%d Einladungen"</item>
|
||||
</plurals>
|
||||
<plurals name="notification_new_messages_for_room">
|
||||
<item quantity="one">"%d neue Nachricht"</item>
|
||||
<item quantity="other">"%d neue Nachrichten"</item>
|
||||
</plurals>
|
||||
<plurals name="notification_unread_notified_messages">
|
||||
<item quantity="one">"%d ungelesene gemeldete Nachricht"</item>
|
||||
<item quantity="other">"%d ungelesene gemeldete Nachrichten"</item>
|
||||
</plurals>
|
||||
<plurals name="notification_unread_notified_messages_in_room_rooms">
|
||||
<item quantity="one">"%d Raum"</item>
|
||||
<item quantity="other">"%d Räume"</item>
|
||||
</plurals>
|
||||
<string name="push_choose_distributor_dialog_title_android">"Wählen Sie aus, wie Sie Benachrichtigungen erhalten möchten"</string>
|
||||
<string name="push_distributor_background_sync_android">"Hintergrundsynchronisation"</string>
|
||||
<string name="push_distributor_firebase_android">"Google-Dienste"</string>
|
||||
<string name="push_no_valid_google_play_services_apk_android">"Keine gültigen Google Play-Dienste gefunden. Benachrichtigungen funktionieren möglicherweise nicht richtig."</string>
|
||||
<string name="notification_fallback_content">"Benachrichtigung"</string>
|
||||
<string name="notification_room_action_quick_reply">"Schnelle Antwort"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class FakeNotificationDrawerManager : NotificationDrawerManager {
|
|||
clearMemberShipNotificationForSessionCallsCount.merge(sessionId.value, 1) { oldValue, value -> oldValue + value }
|
||||
}
|
||||
|
||||
override fun clearMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId) {
|
||||
override fun clearMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId, doRender: Boolean) {
|
||||
val key = getMembershipNotificationKey(sessionId, roomId)
|
||||
clearMemberShipNotificationForRoomCallsCount.merge(key, 1) { oldValue, value -> oldValue + value }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import io.element.android.libraries.sessionstorage.api.toUserList
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("FirebaseNewTokenHandler")
|
||||
private val loggerTag = LoggerTag("FirebaseNewTokenHandler", LoggerTag.PushLoggerTag)
|
||||
|
||||
/**
|
||||
* Handle new token receive from Firebase. Will update all the sessions which are using Firebase as a push provider.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("FirebasePushProvider")
|
||||
private val loggerTag = LoggerTag("FirebasePushProvider", LoggerTag.PushLoggerTag)
|
||||
|
||||
@ContributesMultibinding(AppScope::class)
|
||||
class FirebasePushProvider @Inject constructor(
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("Firebase")
|
||||
private val loggerTag = LoggerTag("VectorFirebaseMessagingService", LoggerTag.PushLoggerTag)
|
||||
|
||||
class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||
@Inject lateinit var firebaseNewTokenHandler: FirebaseNewTokenHandler
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("UnifiedPushNewGatewayHandler")
|
||||
private val loggerTag = LoggerTag("UnifiedPushNewGatewayHandler", LoggerTag.PushLoggerTag)
|
||||
|
||||
/**
|
||||
* Handle new endpoint received from UnifiedPush. Will update all the sessions which are using UnifiedPush as a push provider.
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import org.unifiedpush.android.connector.MessagingReceiver
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("VectorUnifiedPushMessagingReceiver")
|
||||
private val loggerTag = LoggerTag("VectorUnifiedPushMessagingReceiver", LoggerTag.PushLoggerTag)
|
||||
|
||||
class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
|
||||
@Inject lateinit var pushParser: UnifiedPushParser
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.textcomposer
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import io.element.android.libraries.androidutils.ui.awaitWindowFocus
|
||||
import io.element.android.libraries.androidutils.ui.showKeyboard
|
||||
|
||||
/**
|
||||
* Shows the soft keyboard when a given key changes to meet the required condition.
|
||||
*
|
||||
* Uses [showKeyboard] to show the keyboard for compatibility with [AndroidView].
|
||||
*
|
||||
* @param T
|
||||
* @param key The key to watch for changes.
|
||||
* @param onRequestFocus A callback to request focus to the view that will receive the keyboard input.
|
||||
* @param predicate The predicate that [key] must meet before showing the keyboard.
|
||||
*/
|
||||
@Composable
|
||||
internal fun <T> SoftKeyboardEffect(
|
||||
key: T,
|
||||
onRequestFocus: () -> Unit,
|
||||
predicate: (T) -> Boolean,
|
||||
) {
|
||||
val view = LocalView.current
|
||||
LaunchedEffect(key) {
|
||||
if (predicate(key)) {
|
||||
// Await window focus in case returning from a dialog
|
||||
view.awaitWindowFocus()
|
||||
|
||||
// Show the keyboard, temporarily using the root view for focus
|
||||
view.showKeyboard(andRequestFocus = true)
|
||||
|
||||
// Refocus to the correct view
|
||||
onRequestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,6 @@ import androidx.compose.material.icons.filled.Close
|
|||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -52,7 +51,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
|
|
@ -84,7 +82,6 @@ import io.element.android.wysiwyg.compose.RichTextEditor
|
|||
import io.element.android.wysiwyg.compose.RichTextEditorDefaults
|
||||
import io.element.android.wysiwyg.compose.RichTextEditorState
|
||||
import io.element.android.wysiwyg.view.models.InlineFormat
|
||||
import kotlinx.coroutines.android.awaitFrame
|
||||
import uniffi.wysiwyg_composer.ActionState
|
||||
import uniffi.wysiwyg_composer.ComposerAction
|
||||
|
||||
|
|
@ -223,17 +220,11 @@ fun TextComposer(
|
|||
}
|
||||
}
|
||||
|
||||
// Request focus when changing mode, and show keyboard.
|
||||
val keyboard = LocalSoftwareKeyboardController.current
|
||||
LaunchedEffect(composerMode) {
|
||||
if (composerMode is MessageComposerMode.Special) {
|
||||
onRequestFocus()
|
||||
keyboard?.let {
|
||||
awaitFrame()
|
||||
it.show()
|
||||
}
|
||||
}
|
||||
SoftKeyboardEffect(composerMode, onRequestFocus) {
|
||||
it is MessageComposerMode.Special
|
||||
}
|
||||
|
||||
SoftKeyboardEffect(showTextFormatting, onRequestFocus) { it }
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -270,6 +261,8 @@ private fun TextInput(
|
|||
style = defaultTypography.copy(
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="rich_text_editor_a11y_add_attachment">"Anhang hinzufügen"</string>
|
||||
</resources>
|
||||
|
|
@ -195,9 +195,9 @@
|
|||
<string name="screen_notification_settings_additional_settings_section_title">"Další nastavení"</string>
|
||||
<string name="screen_notification_settings_calls_label">"Halsové a video hovory"</string>
|
||||
<string name="screen_notification_settings_configuration_mismatch">"Neshoda konfigurace"</string>
|
||||
<string name="screen_notification_settings_configuration_mismatch_description">"Zjednodušili jsme nastavení oznámení, abychom usnadnili hledání možností.
|
||||
<string name="screen_notification_settings_configuration_mismatch_description">"Zjednodušili jsme nastavení oznámení, abychom usnadnili hledání možností.
|
||||
|
||||
Některá vlastní nastavení, která jste si vybrali v minulosti, se zde nezobrazují, ale jsou stále aktivní.
|
||||
Některá vlastní nastavení, která jste si vybrali v minulosti, se zde nezobrazují, ale jsou stále aktivní.
|
||||
|
||||
Pokud budete pokračovat, některá nastavení se mohou změnit."</string>
|
||||
<string name="screen_notification_settings_direct_chats">"Přímé zprávy"</string>
|
||||
|
|
|
|||
|
|
@ -1,65 +1,265 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="a11y_hide_password">"Passwort verbergen"</string>
|
||||
<string name="a11y_notifications_mentions_only">"Nur Erwähnungen"</string>
|
||||
<string name="a11y_notifications_muted">"Stummgeschaltet"</string>
|
||||
<string name="a11y_poll">"Umfrage"</string>
|
||||
<string name="a11y_poll_end">"Umfrage beendet"</string>
|
||||
<string name="a11y_send_files">"Dateien senden"</string>
|
||||
<string name="a11y_show_password">"Passwort anzeigen"</string>
|
||||
<string name="a11y_user_menu">"Benutzermenü"</string>
|
||||
<string name="action_accept">"Akzeptieren"</string>
|
||||
<string name="action_back">"Zurück"</string>
|
||||
<string name="action_cancel">"Abbrechen"</string>
|
||||
<string name="action_choose_photo">"Foto auswählen"</string>
|
||||
<string name="action_clear">"Löschen"</string>
|
||||
<string name="action_close">"Schließen"</string>
|
||||
<string name="action_complete_verification">"Verifizierung abschließen"</string>
|
||||
<string name="action_confirm">"Bestätigen"</string>
|
||||
<string name="action_continue">"Weiter"</string>
|
||||
<string name="action_copy">"Kopieren"</string>
|
||||
<string name="action_copy_link">"Link kopieren"</string>
|
||||
<string name="action_copy_link_to_message">"Link zur Nachricht kopieren"</string>
|
||||
<string name="action_create">"Erstellen"</string>
|
||||
<string name="action_create_a_room">"Raum erstellen"</string>
|
||||
<string name="action_decline">"Ablehnen"</string>
|
||||
<string name="action_disable">"Deaktivieren"</string>
|
||||
<string name="action_done">"Erledigt"</string>
|
||||
<string name="action_edit">"Bearbeiten"</string>
|
||||
<string name="action_enable">"Aktivieren"</string>
|
||||
<string name="action_end_poll">"Umfrage beenden"</string>
|
||||
<string name="action_forgot_password">"Passwort vergessen?"</string>
|
||||
<string name="action_forward">"Weiter"</string>
|
||||
<string name="action_invite">"Einladen"</string>
|
||||
<string name="action_invite_friends">"Freunde einladen"</string>
|
||||
<string name="action_invite_friends_to_app">"Freunde einladen %1$s"</string>
|
||||
<string name="action_invite_people_to_app">"Laden Sie Personen in %1$s ein"</string>
|
||||
<string name="action_invites_list">"Einladungen"</string>
|
||||
<string name="action_learn_more">"Mehr erfahren"</string>
|
||||
<string name="action_leave">"Verlassen"</string>
|
||||
<string name="action_leave_room">"Raum verlassen"</string>
|
||||
<string name="action_manage_account">"Konto verwalten"</string>
|
||||
<string name="action_manage_devices">"Geräte verwalten"</string>
|
||||
<string name="action_next">"Weiter"</string>
|
||||
<string name="action_no">"Nein"</string>
|
||||
<string name="action_not_now">"Nicht jetzt"</string>
|
||||
<string name="action_ok">"OK"</string>
|
||||
<string name="action_open_with">"Öffnen mit"</string>
|
||||
<string name="action_quick_reply">"Schnelle Antwort"</string>
|
||||
<string name="action_quote">"Zitat"</string>
|
||||
<string name="action_react">"Reagieren"</string>
|
||||
<string name="action_remove">"Entfernen"</string>
|
||||
<string name="action_reply">"Antwort"</string>
|
||||
<string name="action_reply">"Antworten"</string>
|
||||
<string name="action_reply_in_thread">"Im Thread antworten"</string>
|
||||
<string name="action_report_bug">"Fehler melden"</string>
|
||||
<string name="action_report_content">"Inhalt melden"</string>
|
||||
<string name="action_retry">"Erneut versuchen"</string>
|
||||
<string name="action_retry_decryption">"Entschlüsselung wiederholen"</string>
|
||||
<string name="action_save">"Speichern"</string>
|
||||
<string name="action_search">"Suchen"</string>
|
||||
<string name="action_send">"Senden"</string>
|
||||
<string name="action_send_message">"Nachricht senden"</string>
|
||||
<string name="action_share">"Teilen"</string>
|
||||
<string name="action_share_link">"Link teilen"</string>
|
||||
<string name="action_skip">"Überspringen"</string>
|
||||
<string name="action_start">"Start"</string>
|
||||
<string name="action_start_chat">"Chat starten"</string>
|
||||
<string name="action_start_verification">"Überprüfung starten"</string>
|
||||
<string name="action_start_verification">"Verifizierung starten"</string>
|
||||
<string name="action_static_map_load">"Tippen Sie, um die Karte zu laden"</string>
|
||||
<string name="action_take_photo">"Foto machen"</string>
|
||||
<string name="action_view_source">"Quelle anzeigen"</string>
|
||||
<string name="action_yes">"Ja"</string>
|
||||
<string name="common_about">"Über"</string>
|
||||
<string name="common_acceptable_use_policy">"Nutzungsrichtlinie"</string>
|
||||
<string name="common_advanced_settings">"Erweiterte Einstellungen"</string>
|
||||
<string name="common_analytics">"Analysedaten"</string>
|
||||
<string name="common_audio">"Audio"</string>
|
||||
<string name="common_bubbles">"Blasen"</string>
|
||||
<string name="common_copyright">"Copyright"</string>
|
||||
<string name="common_creating_room">"Raum wird erstellt…"</string>
|
||||
<string name="common_current_user_left_room">"Raum verlassen"</string>
|
||||
<string name="common_decryption_error">"Dekodierungsfehler"</string>
|
||||
<string name="common_developer_options">"Entwickleroptionen"</string>
|
||||
<string name="common_edited_suffix">"(bearbeitet)"</string>
|
||||
<string name="common_editing">"Bearbeitung"</string>
|
||||
<string name="common_emote">"* %1$s %2$s"</string>
|
||||
<string name="common_encryption_enabled">"Verschlüsselung aktiviert"</string>
|
||||
<string name="common_error">"Fehler"</string>
|
||||
<string name="common_file">"Datei"</string>
|
||||
<string name="common_file_saved_on_disk_android">"Datei wurde unter Downloads gespeichert"</string>
|
||||
<string name="common_forward_message">"Nachricht weiterleiten"</string>
|
||||
<string name="common_gif">"GIF"</string>
|
||||
<string name="common_image">"Bild"</string>
|
||||
<string name="common_in_reply_to">"Als Antwort auf %1$s"</string>
|
||||
<string name="common_invite_unknown_profile">"Diese Matrix-ID kann nicht gefunden werden, daher wird die Einladung möglicherweise nicht empfangen."</string>
|
||||
<string name="common_leaving_room">"Raum verlassen"</string>
|
||||
<string name="common_link_copied_to_clipboard">"Link in die Zwischenablage kopiert"</string>
|
||||
<string name="common_loading">"Laden…"</string>
|
||||
<string name="common_message">"Nachricht"</string>
|
||||
<string name="common_message_layout">"Nachrichtenlayout"</string>
|
||||
<string name="common_message_removed">"Nachricht entfernt"</string>
|
||||
<string name="common_modern">"Modern"</string>
|
||||
<string name="common_mute">"Stummschalten"</string>
|
||||
<string name="common_no_results">"Keine Ergebnisse"</string>
|
||||
<string name="common_offline">"Offline"</string>
|
||||
<string name="common_password">"Passwort"</string>
|
||||
<string name="common_people">"Menschen"</string>
|
||||
<string name="common_people">"Personen"</string>
|
||||
<string name="common_permalink">"Permalink"</string>
|
||||
<string name="common_poll_total_votes">"Stimmen insgesamt: %1$s"</string>
|
||||
<string name="common_poll_undisclosed_text">"Die Ergebnisse werden nach Ende der Umfrage angezeigt"</string>
|
||||
<string name="common_privacy_policy">"Datenschutzerklärung"</string>
|
||||
<string name="common_reaction">"Reaktion"</string>
|
||||
<string name="common_reactions">"Reaktionen"</string>
|
||||
<string name="common_refreshing">"Wird erneuert…"</string>
|
||||
<string name="common_replying_to">"%1$s antworten"</string>
|
||||
<string name="common_report_a_bug">"Einen Fehler melden"</string>
|
||||
<string name="common_report_submitted">"Bericht eingereicht"</string>
|
||||
<string name="common_rich_text_editor">"Rich-Text-Editor"</string>
|
||||
<string name="common_room_name">"Raumname"</string>
|
||||
<string name="common_room_name_placeholder">"z.B. Ihr Projektname"</string>
|
||||
<string name="common_search_for_someone">"Nach jemandem suchen"</string>
|
||||
<string name="common_search_results">"Suchergebnisse"</string>
|
||||
<string name="common_security">"Sicherheit"</string>
|
||||
<string name="common_select_your_server">"Wählen Sie Ihren Server aus"</string>
|
||||
<string name="common_sending">"Wird gesendet…"</string>
|
||||
<string name="common_server_not_supported">"Server wird nicht unterstützt"</string>
|
||||
<string name="common_server_url">"Server-URL"</string>
|
||||
<string name="common_settings">"Einstellungen"</string>
|
||||
<string name="common_shared_location">"Geteilter Standort"</string>
|
||||
<string name="common_starting_chat">"Chat wird gestartet…"</string>
|
||||
<string name="common_sticker">"Sticker"</string>
|
||||
<string name="common_success">"Erfolg"</string>
|
||||
<string name="common_suggestions">"Vorschläge"</string>
|
||||
<string name="common_syncing">"Synchronisieren"</string>
|
||||
<string name="common_text">"Text"</string>
|
||||
<string name="common_third_party_notices">"Hinweise von Drittanbietern"</string>
|
||||
<string name="common_thread">"Thread"</string>
|
||||
<string name="common_topic">"Thema"</string>
|
||||
<string name="common_topic_placeholder">"Worum geht es in diesem Raum?"</string>
|
||||
<string name="common_unable_to_decrypt">"Entschlüsselung nicht möglich"</string>
|
||||
<string name="common_unable_to_invite_message">"Einladungen konnten nicht an einen oder mehrere Benutzer gesendet werden."</string>
|
||||
<string name="common_unable_to_invite_title">"Einladung(en) konnte(n) nicht gesendet werden"</string>
|
||||
<string name="common_unmute">"Stummschaltung aufheben"</string>
|
||||
<string name="common_unsupported_event">"Nicht unterstütztes Ereignis"</string>
|
||||
<string name="common_username">"Benutzername"</string>
|
||||
<string name="common_verification_cancelled">"Verifizierung abgebrochen"</string>
|
||||
<string name="common_verification_complete">"Verifizierung abgeschlossen"</string>
|
||||
<string name="common_video">"Video"</string>
|
||||
<string name="common_waiting">"Warten…"</string>
|
||||
<string name="dialog_title_confirmation">"Bestätigung"</string>
|
||||
<string name="dialog_title_warning">"Warnung"</string>
|
||||
<string name="emoji_picker_category_activity">"Aktivitäten"</string>
|
||||
<string name="emoji_picker_category_flags">"Flaggen"</string>
|
||||
<string name="emoji_picker_category_foods">"Essen & Trinken"</string>
|
||||
<string name="emoji_picker_category_nature">"Tiere & Natur"</string>
|
||||
<string name="emoji_picker_category_objects">"Objekte"</string>
|
||||
<string name="emoji_picker_category_people">"Smileys & Menschen"</string>
|
||||
<string name="emoji_picker_category_places">"Reisen & Orte"</string>
|
||||
<string name="emoji_picker_category_symbols">"Symbole"</string>
|
||||
<string name="error_failed_creating_the_permalink">"Fehler beim Erstellen des Permalinks"</string>
|
||||
<string name="error_failed_loading_map">"%1$s konnte die Karte nicht laden. Bitte versuchen Sie es später erneut."</string>
|
||||
<string name="error_failed_loading_messages">"Fehler beim Laden der Nachrichten"</string>
|
||||
<string name="error_failed_locating_user">"%1$s konnte nicht auf Ihren Standort zugreifen. Bitte versuchen Sie es später erneut."</string>
|
||||
<string name="error_missing_location_auth_android">"%1$s hat keine Erlaubnis, auf Ihren Standort zuzugreifen. Sie können den Zugriff in den Einstellungen aktivieren."</string>
|
||||
<string name="error_missing_location_rationale_android">"%1$s hat keine Erlaubnis, auf Ihren Standort zuzugreifen. Aktivieren Sie unten den Zugriff."</string>
|
||||
<string name="error_some_messages_have_not_been_sent">"Einige Nachrichten wurden nicht gesendet"</string>
|
||||
<string name="error_unknown">"Entschuldigung, es ist ein Fehler aufgetreten"</string>
|
||||
<string name="invite_friends_rich_title">"🔐️ Begleite mich auf %1$s"</string>
|
||||
<string name="invite_friends_text">"Hey, sprechen Sie mit mir auf %1$s: %2$s"</string>
|
||||
<string name="leave_room_alert_empty_subtitle">"Sind Sie sicher, dass Sie diesen Raum verlassen möchten? Sie sind die einzige Person hier. Wenn Sie austreten, kann in Zukunft niemand mehr eintreten, auch Sie nicht."</string>
|
||||
<string name="leave_room_alert_private_subtitle">"Sind Sie sicher, dass Sie diesen Raum verlassen möchten? Dieser Raum ist nicht öffentlich und Sie können ihm ohne Einladung nicht erneut beitreten."</string>
|
||||
<string name="leave_room_alert_subtitle">"Sind Sie sicher, dass Sie den Raum verlassen wollen?"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<plurals name="common_member_count">
|
||||
<item quantity="one">"%1$d Mitglied"</item>
|
||||
<item quantity="other">"%1$d Mitglieder"</item>
|
||||
</plurals>
|
||||
<plurals name="common_poll_votes_count">
|
||||
<item quantity="one">"%d Stimme"</item>
|
||||
<item quantity="other">"%d Stimmen"</item>
|
||||
</plurals>
|
||||
<string name="preference_rageshake">"Schütteln Sie heftig zum Melden von Fehlern"</string>
|
||||
<string name="rageshake_dialog_content">"Sie scheinen das Telefon aus Frustration zu schütteln. Möchten Sie den Bildschirm für den Fehlerbericht öffnen?"</string>
|
||||
<string name="report_content_explanation">"Diese Meldung wird an den Administrator Ihres Homeservers weitergeleitet. Dieser kann keine verschlüsselten Nachrichten lesen."</string>
|
||||
<string name="report_content_hint">"Grund für die Meldung dieses Inhalts"</string>
|
||||
<string name="rich_text_editor_bullet_list">"Aufzählungsliste umschalten"</string>
|
||||
<string name="rich_text_editor_close_formatting_options">"Formatierungsoptionen schließen"</string>
|
||||
<string name="rich_text_editor_code_block">"Codeblock umschalten"</string>
|
||||
<string name="rich_text_editor_composer_placeholder">"Nachricht…"</string>
|
||||
<string name="rich_text_editor_create_link">"Einen Link erstellen"</string>
|
||||
<string name="rich_text_editor_edit_link">"Link bearbeiten"</string>
|
||||
<string name="rich_text_editor_format_bold">"Fettes Format anwenden"</string>
|
||||
<string name="rich_text_editor_format_italic">"Kursives Format anwenden"</string>
|
||||
<string name="rich_text_editor_format_strikethrough">"Durchgestrichenes Format anwenden"</string>
|
||||
<string name="rich_text_editor_format_underline">"Unterstreichungsformat anwenden"</string>
|
||||
<string name="rich_text_editor_full_screen_toggle">"Vollbildmodus umschalten"</string>
|
||||
<string name="rich_text_editor_indent">"Einrückung"</string>
|
||||
<string name="rich_text_editor_inline_code">"Inline-Codeformat anwenden"</string>
|
||||
<string name="rich_text_editor_link">"Link setzen"</string>
|
||||
<string name="rich_text_editor_numbered_list">"Nummerierte Liste umschalten"</string>
|
||||
<string name="rich_text_editor_open_compose_options">"Optionen zum Verfassen öffnen"</string>
|
||||
<string name="rich_text_editor_quote">"Vorschlag umschalten"</string>
|
||||
<string name="rich_text_editor_remove_link">"Link entfernen"</string>
|
||||
<string name="rich_text_editor_unindent">"Ohne Einrückung"</string>
|
||||
<string name="rich_text_editor_url_placeholder">"Link"</string>
|
||||
<string name="room_timeline_beginning_of_room">"Dies ist der Anfang von %1$s."</string>
|
||||
<string name="room_timeline_beginning_of_room_no_name">"Dies ist der Anfang dieses Gesprächs."</string>
|
||||
<string name="room_timeline_read_marker_title">"Neu"</string>
|
||||
<string name="screen_analytics_settings_share_data">"Analysedaten teilen"</string>
|
||||
<string name="screen_edit_profile_display_name">"Anzeigename"</string>
|
||||
<string name="screen_edit_profile_display_name_placeholder">"Ihr Anzeigename"</string>
|
||||
<string name="screen_edit_profile_error">"Ein unbekannter Fehler ist aufgetreten und die Informationen konnten nicht geändert werden."</string>
|
||||
<string name="screen_edit_profile_error_title">"Profil kann nicht aktualisiert werden"</string>
|
||||
<string name="screen_edit_profile_title">"Profil bearbeiten"</string>
|
||||
<string name="screen_edit_profile_updating_details">"Profil wird aktualisiert…"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Medienauswahl fehlgeschlagen, bitte versuchen Sie es erneut."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuchen Sie es erneut."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Das Hochladen der Medien ist fehlgeschlagen. Bitte versuchen Sie es erneut."</string>
|
||||
<string name="screen_notification_settings_additional_settings_section_title">"Zusätzliche Einstellungen"</string>
|
||||
<string name="screen_notification_settings_calls_label">"Audio- und Videoanrufe"</string>
|
||||
<string name="screen_notification_settings_configuration_mismatch">"Konfiguration stimmt nicht überein"</string>
|
||||
<string name="screen_notification_settings_configuration_mismatch_description">"Wir haben die Einstellungen für Benachrichtigungen vereinfacht, damit die Optionen leichter zu finden sind.
|
||||
|
||||
Einige benutzerdefinierte Einstellungen, die Sie in der Vergangenheit gewählt haben, werden hier nicht angezeigt, sind aber immer noch aktiv.
|
||||
|
||||
Wenn Sie fortfahren, können sich einige Ihrer Einstellungen ändern."</string>
|
||||
<string name="screen_notification_settings_direct_chats">"Direkte Chats"</string>
|
||||
<string name="screen_notification_settings_edit_custom_settings_section_title">"Benutzerdefinierte Einstellung pro Chat"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Beim Aktualisieren der Benachrichtigungseinstellungen ist ein Fehler aufgetreten."</string>
|
||||
<string name="screen_notification_settings_edit_mode_all_messages">"Alle Nachrichten"</string>
|
||||
<string name="screen_notification_settings_edit_mode_mentions_and_keywords">"Nur Erwähnungen und Schlüsselwörter"</string>
|
||||
<string name="screen_notification_settings_edit_screen_direct_section_header">"Bei direkten Chats, benachrichtigen Sie mich bei"</string>
|
||||
<string name="screen_notification_settings_edit_screen_group_section_header">"Bei Gruppenchats benachrichtigen Sie mich bei"</string>
|
||||
<string name="screen_notification_settings_enable_notifications">"Benachrichtigungen auf diesem Gerät aktivieren"</string>
|
||||
<string name="screen_notification_settings_failed_fixing_configuration">"Die Konfiguration wurde nicht korrigiert, bitte versuchen Sie es erneut."</string>
|
||||
<string name="screen_notification_settings_group_chats">"Gruppenchats"</string>
|
||||
<string name="screen_notification_settings_mentions_section_title">"Erwähnungen"</string>
|
||||
<string name="screen_notification_settings_mode_all">"Alle"</string>
|
||||
<string name="screen_notification_settings_mode_mentions">"Erwähnungen"</string>
|
||||
<string name="screen_notification_settings_notification_section_title">"Benachrichtige mich bei"</string>
|
||||
<string name="screen_notification_settings_room_mention_label">"Benachrichtige mich bei @room"</string>
|
||||
<string name="screen_notification_settings_system_notifications_action_required">"Um Benachrichtigungen zu erhalten, ändern Sie bitte Ihre %1$s."</string>
|
||||
<string name="screen_notification_settings_system_notifications_action_required_content_link">"Systemeinstellungen"</string>
|
||||
<string name="screen_notification_settings_system_notifications_turned_off">"Systembenachrichtigungen deaktiviert"</string>
|
||||
<string name="screen_notification_settings_title">"Benachrichtigungen"</string>
|
||||
<string name="screen_report_content_block_user_hint">"Prüfen Sie, ob Sie alle aktuellen und zukünftigen Nachrichten dieses Benutzers ausblenden möchten"</string>
|
||||
<string name="screen_settings_oidc_account">"Konto und Geräte"</string>
|
||||
<string name="screen_share_location_title">"Standort teilen"</string>
|
||||
<string name="screen_share_my_location_action">"Meinen Standort teilen"</string>
|
||||
<string name="screen_share_open_apple_maps">"In Apple Maps öffnen"</string>
|
||||
<string name="screen_share_open_google_maps">"In Google Maps öffnen"</string>
|
||||
<string name="screen_share_open_osm_maps">"In OpenStreetMap öffnen"</string>
|
||||
<string name="screen_share_this_location_action">"Diesen Standort teilen"</string>
|
||||
<string name="screen_view_location_title">"Standort"</string>
|
||||
<string name="settings_rageshake">"Rageshake"</string>
|
||||
<string name="settings_rageshake_detection_threshold">"Erkennungsschwelle"</string>
|
||||
<string name="settings_title_general">"Allgemein"</string>
|
||||
<string name="settings_version_number">"Version: %1$s (%2$s)"</string>
|
||||
<string name="test_language_identifier">"en"</string>
|
||||
<string name="dialog_title_error">"Fehler"</string>
|
||||
<string name="dialog_title_success">"Erfolg"</string>
|
||||
<string name="screen_analytics_settings_help_us_improve">"Teilen Sie anonyme Nutzungsdaten, um uns bei der Identifizierung von Problemen zu helfen."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Sie können alle unsere Bedingungen lesen%1$s."</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"hier"</string>
|
||||
<string name="screen_report_content_block_user">"Benutzer sperren"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
<string name="a11y_hide_password">"Masquer le mot de passe"</string>
|
||||
<string name="a11y_notifications_mentions_only">"Mentions uniquement"</string>
|
||||
<string name="a11y_notifications_muted">"En sourdine"</string>
|
||||
<string name="a11y_poll">"Sondage"</string>
|
||||
<string name="a11y_poll_end">"Sondage terminé"</string>
|
||||
<string name="a11y_send_files">"Envoyer des fichiers"</string>
|
||||
<string name="a11y_show_password">"Afficher le mot de passe"</string>
|
||||
<string name="a11y_user_menu">"Menu utilisateur"</string>
|
||||
|
|
@ -204,6 +206,12 @@
|
|||
<string name="room_timeline_beginning_of_room_no_name">"Ceci est le début de cette conversation."</string>
|
||||
<string name="room_timeline_read_marker_title">"Nouveau"</string>
|
||||
<string name="screen_analytics_settings_share_data">"Partagez des données de statistiques d\'utilisation"</string>
|
||||
<string name="screen_edit_profile_display_name">"Nom d\'affichage"</string>
|
||||
<string name="screen_edit_profile_display_name_placeholder">"Votre nom d\'affichage"</string>
|
||||
<string name="screen_edit_profile_error">"Une erreur inconnue s\'est produite et les informations n\'ont pas pu être modifiées."</string>
|
||||
<string name="screen_edit_profile_error_title">"Impossible de mettre à jour le profil"</string>
|
||||
<string name="screen_edit_profile_title">"Modifier le profil"</string>
|
||||
<string name="screen_edit_profile_updating_details">"Mise à jour du profil…"</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_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>
|
||||
|
|
|
|||
|
|
@ -194,9 +194,9 @@
|
|||
<string name="screen_notification_settings_additional_settings_section_title">"Дополнительные параметры"</string>
|
||||
<string name="screen_notification_settings_calls_label">"Аудио и видео звонки"</string>
|
||||
<string name="screen_notification_settings_configuration_mismatch">"Несоответствие конфигурации"</string>
|
||||
<string name="screen_notification_settings_configuration_mismatch_description">"Мы упростили настройки уведомлений, чтобы упростить поиск опций.
|
||||
<string name="screen_notification_settings_configuration_mismatch_description">"Мы упростили настройки уведомлений, чтобы упростить поиск опций.
|
||||
|
||||
Некоторые пользовательские настройки, выбранные вами ранее, не отображаются в данном меню, но они все еще активны.
|
||||
Некоторые пользовательские настройки, выбранные вами ранее, не отображаются в данном меню, но они все еще активны.
|
||||
|
||||
Если вы продолжите, некоторые настройки могут быть изменены."</string>
|
||||
<string name="screen_notification_settings_direct_chats">"Прямые чаты"</string>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
<string name="a11y_hide_password">"Skryť heslo"</string>
|
||||
<string name="a11y_notifications_mentions_only">"Iba zmienky"</string>
|
||||
<string name="a11y_notifications_muted">"Stlmené"</string>
|
||||
<string name="a11y_poll">"Anketa"</string>
|
||||
<string name="a11y_poll_end">"Ukončená anketa"</string>
|
||||
<string name="a11y_send_files">"Odoslať súbory"</string>
|
||||
<string name="a11y_show_password">"Zobraziť heslo"</string>
|
||||
<string name="a11y_user_menu">"Používateľské menu"</string>
|
||||
|
|
@ -36,6 +38,8 @@
|
|||
<string name="action_learn_more">"Zistiť viac"</string>
|
||||
<string name="action_leave">"Opustiť"</string>
|
||||
<string name="action_leave_room">"Opustiť miestnosť"</string>
|
||||
<string name="action_manage_account">"Spravovať účet"</string>
|
||||
<string name="action_manage_devices">"Spravovať zariadenia"</string>
|
||||
<string name="action_next">"Ďalej"</string>
|
||||
<string name="action_no">"Nie"</string>
|
||||
<string name="action_not_now">"Teraz nie"</string>
|
||||
|
|
@ -46,6 +50,7 @@
|
|||
<string name="action_react">"Reagovať"</string>
|
||||
<string name="action_remove">"Odstrániť"</string>
|
||||
<string name="action_reply">"Odpovedať"</string>
|
||||
<string name="action_reply_in_thread">"Odpovedať vo vlákne"</string>
|
||||
<string name="action_report_bug">"Nahlásiť chybu"</string>
|
||||
<string name="action_report_content">"Nahlásiť obsah"</string>
|
||||
<string name="action_retry">"Skúsiť znova"</string>
|
||||
|
|
@ -66,6 +71,7 @@
|
|||
<string name="action_yes">"Áno"</string>
|
||||
<string name="common_about">"O aplikácii"</string>
|
||||
<string name="common_acceptable_use_policy">"Zásady prijateľného používania"</string>
|
||||
<string name="common_advanced_settings">"Pokročilé nastavenia"</string>
|
||||
<string name="common_analytics">"Analytika"</string>
|
||||
<string name="common_audio">"Zvuk"</string>
|
||||
<string name="common_bubbles">"Bubliny"</string>
|
||||
|
|
@ -84,6 +90,7 @@
|
|||
<string name="common_forward_message">"Preposlať správu"</string>
|
||||
<string name="common_gif">"GIF"</string>
|
||||
<string name="common_image">"Obrázok"</string>
|
||||
<string name="common_in_reply_to">"V odpovedi na %1$s"</string>
|
||||
<string name="common_invite_unknown_profile">"Toto Matrix ID sa nedá nájsť, takže pozvánka nemusí byť prijatá."</string>
|
||||
<string name="common_leaving_room">"Opustenie miestnosti"</string>
|
||||
<string name="common_link_copied_to_clipboard">"Odkaz bol skopírovaný do schránky"</string>
|
||||
|
|
@ -101,11 +108,13 @@
|
|||
<string name="common_poll_total_votes">"Celkový počet hlasov: %1$s"</string>
|
||||
<string name="common_poll_undisclosed_text">"Výsledky sa zobrazia po ukončení ankety"</string>
|
||||
<string name="common_privacy_policy">"Zásady ochrany osobných údajov"</string>
|
||||
<string name="common_reaction">"Reakcia"</string>
|
||||
<string name="common_reactions">"Reakcie"</string>
|
||||
<string name="common_refreshing">"Obnovuje sa…"</string>
|
||||
<string name="common_replying_to">"Odpoveď na %1$s"</string>
|
||||
<string name="common_report_a_bug">"Nahlásiť chybu"</string>
|
||||
<string name="common_report_submitted">"Nahlásenie bolo odoslané"</string>
|
||||
<string name="common_rich_text_editor">"Rozšírený textový editor"</string>
|
||||
<string name="common_room_name">"Názov miestnosti"</string>
|
||||
<string name="common_room_name_placeholder">"napr. názov vášho projektu"</string>
|
||||
<string name="common_search_for_someone">"Vyhľadať niekoho"</string>
|
||||
|
|
@ -124,6 +133,7 @@
|
|||
<string name="common_syncing">"Synchronizuje sa"</string>
|
||||
<string name="common_text">"Text"</string>
|
||||
<string name="common_third_party_notices">"Oznámenia tretích strán"</string>
|
||||
<string name="common_thread">"Vlákno"</string>
|
||||
<string name="common_topic">"Téma"</string>
|
||||
<string name="common_topic_placeholder">"O čom je táto miestnosť?"</string>
|
||||
<string name="common_unable_to_decrypt">"Nie je možné dešifrovať"</string>
|
||||
|
|
@ -191,12 +201,19 @@
|
|||
<string name="rich_text_editor_numbered_list">"Prepnúť číslovaný zoznam"</string>
|
||||
<string name="rich_text_editor_open_compose_options">"Otvoriť možnosti písania"</string>
|
||||
<string name="rich_text_editor_quote">"Prepnúť citáciu"</string>
|
||||
<string name="rich_text_editor_remove_link">"Odstrániť odkaz"</string>
|
||||
<string name="rich_text_editor_unindent">"Zrušiť odsadenie"</string>
|
||||
<string name="rich_text_editor_url_placeholder">"Odkaz"</string>
|
||||
<string name="room_timeline_beginning_of_room">"Toto je začiatok %1$s."</string>
|
||||
<string name="room_timeline_beginning_of_room_no_name">"Toto je začiatok tejto konverzácie."</string>
|
||||
<string name="room_timeline_read_marker_title">"Nové"</string>
|
||||
<string name="screen_analytics_settings_share_data">"Zdieľať analytické údaje"</string>
|
||||
<string name="screen_edit_profile_display_name">"Zobrazované meno"</string>
|
||||
<string name="screen_edit_profile_display_name_placeholder">"Vaše zobrazované meno"</string>
|
||||
<string name="screen_edit_profile_error">"Vyskytla sa neznáma chyba a informácie nebolo možné zmeniť."</string>
|
||||
<string name="screen_edit_profile_error_title">"Nepodarilo sa aktualizovať profil"</string>
|
||||
<string name="screen_edit_profile_title">"Upraviť profil"</string>
|
||||
<string name="screen_edit_profile_updating_details">"Aktualizácia profilu…"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Nepodarilo sa vybrať médium, skúste to prosím znova."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Nepodarilo sa nahrať médiá, skúste to prosím znova."</string>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
<string name="a11y_hide_password">"Hide password"</string>
|
||||
<string name="a11y_notifications_mentions_only">"Mentions only"</string>
|
||||
<string name="a11y_notifications_muted">"Muted"</string>
|
||||
<string name="a11y_poll">"Poll"</string>
|
||||
<string name="a11y_poll_end">"Ended poll"</string>
|
||||
<string name="a11y_send_files">"Send files"</string>
|
||||
<string name="a11y_show_password">"Show password"</string>
|
||||
<string name="a11y_user_menu">"User menu"</string>
|
||||
|
|
@ -208,6 +210,7 @@
|
|||
<string name="screen_edit_profile_display_name_placeholder">"Your display name"</string>
|
||||
<string name="screen_edit_profile_error">"An unknown error was encountered and the information couldn\'t be changed."</string>
|
||||
<string name="screen_edit_profile_error_title">"Unable to update profile"</string>
|
||||
<string name="screen_edit_profile_title">"Edit profile"</string>
|
||||
<string name="screen_edit_profile_updating_details">"Updating profile…"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Failed selecting media, please try again."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Failed processing media to upload, please try again."</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue