Merge branch 'develop' into dla/feature/room_list_decoration

This commit is contained in:
David Langley 2023-09-18 10:34:32 +01:00 committed by GitHub
commit bc29a31986
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
138 changed files with 2169 additions and 336 deletions

View file

@ -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")

View file

@ -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)
}
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="error_no_compatible_app_found">"Für diese Aktion wurde keine kompatible App gefunden."</string>
</resources>

View file

@ -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

View file

@ -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",
)
}
}

View file

@ -43,5 +43,7 @@ enum class AvatarSize(val dp: Dp) {
RoomInviteItem(52.dp),
InviteSender(16.dp),
EditRoomDetails(70.dp),
NotificationsOptIn(32.dp),
}

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -98,6 +98,9 @@ internal class MatrixTimelineDiffProcessor(
TimelineChange.CLEAR -> {
clear()
}
TimelineChange.TRUNCATE -> {
// Not supported
}
}
}

View file

@ -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,
)
}

View file

@ -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
}
}

View file

@ -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,
)
}
}
}
}

View file

@ -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(

View file

@ -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)
}

View file

@ -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(

View file

@ -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)

View file

@ -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

View file

@ -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
}

View file

@ -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.

View file

@ -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)
}
}

View file

@ -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,

View file

@ -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),

View file

@ -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)
}
}

View file

@ -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")

View file

@ -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>

View file

@ -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 }
}

View file

@ -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.

View file

@ -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(

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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()
}
}
}

View file

@ -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,
)
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="rich_text_editor_a11y_add_attachment">"Anhang hinzufügen"</string>
</resources>

View file

@ -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>

View file

@ -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">"Datenschutz­erklä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 &amp; Trinken"</string>
<string name="emoji_picker_category_nature">"Tiere &amp; Natur"</string>
<string name="emoji_picker_category_objects">"Objekte"</string>
<string name="emoji_picker_category_people">"Smileys &amp; Menschen"</string>
<string name="emoji_picker_category_places">"Reisen &amp; 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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>