Merge branch 'develop' into feature/fga/room_list_api

This commit is contained in:
ganfra 2023-06-27 10:47:50 +02:00
commit 2a24d0196e
113 changed files with 2259 additions and 132 deletions

View file

@ -17,9 +17,11 @@
package io.element.android.libraries.androidutils.file
import android.content.Context
import androidx.annotation.WorkerThread
import io.element.android.libraries.core.data.tryOrNull
import timber.log.Timber
import java.io.File
import java.util.Locale
import java.util.UUID
fun File.safeDelete() {
@ -52,3 +54,99 @@ fun Context.createTmpFile(baseDir: File = cacheDir, extension: String? = null):
val suffix = extension?.let { ".$extension" }
return File.createTempFile(UUID.randomUUID().toString(), suffix, baseDir).apply { mkdirs() }
}
// Implementation should return true in case of success
typealias ActionOnFile = (file: File) -> Boolean
/* ==========================================================================================
* Log
* ========================================================================================== */
fun lsFiles(context: Context) {
Timber.v("Content of cache dir:")
recursiveActionOnFile(context.cacheDir, ::logAction)
Timber.v("Content of files dir:")
recursiveActionOnFile(context.filesDir, ::logAction)
}
private fun logAction(file: File): Boolean {
if (file.isDirectory) {
Timber.v(file.toString())
} else {
Timber.v("$file ${file.length()} bytes")
}
return true
}
/* ==========================================================================================
* Private
* ========================================================================================== */
/**
* Return true in case of success.
*/
private fun recursiveActionOnFile(file: File, action: ActionOnFile): Boolean {
if (file.isDirectory) {
file.list()?.forEach {
val result = recursiveActionOnFile(File(file, it), action)
if (!result) {
// Break the loop
return false
}
}
}
return action.invoke(file)
}
/**
* Get the file extension of a fileUri or a filename.
*
* @param fileUri the fileUri (can be a simple filename)
* @return the file extension, in lower case, or null is extension is not available or empty
*/
fun getFileExtension(fileUri: String): String? {
var reducedStr = fileUri
if (reducedStr.isNotEmpty()) {
// Remove fragment
reducedStr = reducedStr.substringBeforeLast('#')
// Remove query
reducedStr = reducedStr.substringBeforeLast('?')
// Remove path
val filename = reducedStr.substringAfterLast('/')
// Contrary to method MimeTypeMap.getFileExtensionFromUrl, we do not check the pattern
// See https://stackoverflow.com/questions/14320527/android-should-i-use-mimetypemap-getfileextensionfromurl-bugs
if (filename.isNotEmpty()) {
val dotPos = filename.lastIndexOf('.')
if (0 <= dotPos) {
val ext = filename.substring(dotPos + 1)
if (ext.isNotBlank()) {
return ext.lowercase(Locale.ROOT)
}
}
}
}
return null
}
/* ==========================================================================================
* Size
* ========================================================================================== */
@WorkerThread
fun File.getSizeOfFiles(): Long {
return walkTopDown()
.onEnter {
Timber.v("Get size of ${it.absolutePath}")
true
}
.sumOf { it.length() }
}

View file

@ -0,0 +1,52 @@
/*
* 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.androidutils.filesize
import android.content.Context
import android.os.Build
import android.text.format.Formatter
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class AndroidFileSizeFormatter @Inject constructor(
@ApplicationContext private val context: Context,
) : FileSizeFormatter {
override fun format(fileSize: Long, useShortFormat: Boolean): String {
// Since Android O, the system considers that 1kB = 1000 bytes instead of 1024 bytes.
// We want to avoid that.
val normalizedSize = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
fileSize
} else {
// First convert the size
when {
fileSize < 1024 -> fileSize
fileSize < 1024 * 1024 -> fileSize * 1000 / 1024
fileSize < 1024 * 1024 * 1024 -> fileSize * 1000 / 1024 * 1000 / 1024
else -> fileSize * 1000 / 1024 * 1000 / 1024 * 1000 / 1024
}
}
return if (useShortFormat) {
Formatter.formatShortFileSize(context, normalizedSize)
} else {
Formatter.formatFileSize(context, normalizedSize)
}
}
}

View file

@ -0,0 +1,23 @@
/*
* 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.androidutils.filesize
class FakeFileSizeFormatter : FileSizeFormatter {
override fun format(fileSize: Long, useShortFormat: Boolean): String {
return "$fileSize Bytes"
}
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.androidutils.filesize
interface FileSizeFormatter {
/**
* Formats a content size to be in the form of bytes, kilobytes, megabytes, etc.
*/
fun format(fileSize: Long, useShortFormat: Boolean = true): String
}

View file

@ -17,6 +17,7 @@
package io.element.android.libraries.designsystem.components.preferences
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -26,7 +27,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.progressSemantics
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BugReport
@ -55,7 +55,7 @@ fun PreferenceText(
tintColor: Color? = null,
onClick: () -> Unit = {},
) {
val minHeight = if (subtitle == null) preferenceMinHeightOnlyTitle else preferenceMinHeight
val minHeight = if (subtitle == null) preferenceMinHeightOnlyTitle else preferenceMinHeight
Box(
modifier = modifier
.fillMaxWidth()
@ -69,9 +69,10 @@ fun PreferenceText(
.padding(vertical = preferencePaddingVertical)
) {
PreferenceIcon(icon = icon, tintColor = tintColor)
Column(modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically)
Column(
modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically)
) {
if (title != null) {
Text(
@ -92,15 +93,24 @@ fun PreferenceText(
}
}
if (currentValue != null) {
Text(currentValue, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary)
Spacer(Modifier.width(16.dp))
Text(
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(horizontal = 16.dp),
text = currentValue,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.secondary,
)
} else if (loadingCurrentValue) {
CircularProgressIndicator(modifier = Modifier
.progressSemantics()
.size(20.dp), strokeWidth = 2.dp)
Spacer(Modifier.width(16.dp))
CircularProgressIndicator(
modifier = Modifier
.progressSemantics()
.padding(horizontal = 16.dp)
.size(20.dp)
.align(Alignment.CenterVertically),
strokeWidth = 2.dp
)
}
}
}
}
@ -111,9 +121,39 @@ internal fun PreferenceTextPreview() = ElementThemedPreview { ContentToPreview()
@Composable
private fun ContentToPreview() {
PreferenceText(
title = "Title",
subtitle = "Some content",
icon = Icons.Default.BugReport,
)
Column(
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
PreferenceText(
title = "Title",
icon = Icons.Default.BugReport,
)
PreferenceText(
title = "Title",
subtitle = "Some content",
icon = Icons.Default.BugReport,
)
PreferenceText(
title = "Title",
subtitle = "Some content",
icon = Icons.Default.BugReport,
currentValue = "123",
)
PreferenceText(
title = "Title",
subtitle = "Some content",
icon = Icons.Default.BugReport,
loadingCurrentValue = true,
)
PreferenceText(
title = "Title",
icon = Icons.Default.BugReport,
currentValue = "123",
)
PreferenceText(
title = "Title",
icon = Icons.Default.BugReport,
loadingCurrentValue = true,
)
}
}

View file

@ -21,6 +21,7 @@ import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
@ -28,27 +29,31 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.res.stringResource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/**
* A global dispatcher of [SnackbarMessage] to be displayed in [Snackbar] via a [SnackbarHostState].
*/
class SnackbarDispatcher {
private val mutex = Mutex()
private val snackbarState = MutableStateFlow<SnackbarMessage?>(null)
val snackbarMessage: Flow<SnackbarMessage?> = snackbarState
private val _snackbarMessage = MutableStateFlow<SnackbarMessage?>(null)
val snackbarMessage: Flow<SnackbarMessage?> = _snackbarMessage.asStateFlow()
suspend fun post(message: SnackbarMessage) {
mutex.withLock {
snackbarState.update { message }
_snackbarMessage.update { message }
}
}
suspend fun clear() {
mutex.withLock {
snackbarState.update { null }
_snackbarMessage.update { null }
}
}
}
@ -59,10 +64,8 @@ val LocalSnackbarDispatcher = compositionLocalOf<SnackbarDispatcher> {
}
@Composable
fun handleSnackbarMessage(
snackbarDispatcher: SnackbarDispatcher
): SnackbarMessage? {
return snackbarDispatcher.snackbarMessage.collectAsState(initial = null).value
fun SnackbarDispatcher.collectSnackbarMessageAsState(): State<SnackbarMessage?> {
return snackbarMessage.collectAsState(initial = null)
}
@Composable

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="state_event_avatar_changed_too">"(obrázok bol tiež zmenený)"</string>
<string name="state_event_avatar_url_changed">"%1$s zmenili svoj obrázok"</string>
<string name="state_event_avatar_url_changed_by_you">"Zmenili ste svoj obrázok"</string>
<string name="state_event_display_name_changed_from">"%1$s zmenili svoje zobrazované meno z %2$s na %3$s"</string>
<string name="state_event_display_name_changed_from_by_you">"Zmenili ste si zobrazované meno z %1$s na %2$s"</string>
<string name="state_event_display_name_removed">"%1$s odstránili svoje zobrazované meno (predtým bolo %2$s)"</string>
<string name="state_event_display_name_removed_by_you">"Odstránili ste svoje zobrazované meno (predtým bolo %1$s)"</string>
<string name="state_event_display_name_set">"%1$s nastavili svoje zobrazované meno na %2$s"</string>
<string name="state_event_display_name_set_by_you">"Svoje zobrazované meno ste nastavili na %1$s"</string>
<string name="state_event_room_avatar_changed">"%1$s zmenil/a obrázok miestnosti"</string>
<string name="state_event_room_avatar_changed_by_you">"Zmenili ste obrázok miestnosti"</string>
<string name="state_event_room_avatar_removed">"%1$s odstránil/a obrázok miestnosti"</string>
<string name="state_event_room_avatar_removed_by_you">"Odstránili ste obrázok miestnosti"</string>
<string name="state_event_room_ban">"%1$s zakázal/a používateľa %2$s"</string>
<string name="state_event_room_ban_by_you">"Zakázali ste používateľa %1$s"</string>
<string name="state_event_room_created">"%1$s vytvoril/a miestnosť"</string>
<string name="state_event_room_created_by_you">"Vytvorili ste miestnosť"</string>
<string name="state_event_room_invite">"%1$s pozval/a používateľa %2$s"</string>
<string name="state_event_room_invite_accepted">"%1$s prijal/a pozvanie"</string>
<string name="state_event_room_invite_accepted_by_you">"Prijali ste pozvánku"</string>
<string name="state_event_room_invite_by_you">"Pozvali ste používateľa %1$s"</string>
<string name="state_event_room_invite_you">"%1$s vás pozval/a"</string>
<string name="state_event_room_join">"%1$s sa pripojil/a do miestnosti"</string>
<string name="state_event_room_join_by_you">"Vstúpili ste do miestnosti"</string>
<string name="state_event_room_knock">"%1$s požiadal o pripojenie"</string>
<string name="state_event_room_knock_accepted">"%1$s umožnil/a používateľovi %2$s pripojiť sa"</string>
<string name="state_event_room_knock_accepted_by_you">"%1$s vám umožnil/a pripojiť sa"</string>
<string name="state_event_room_knock_by_you">"Požiadali ste o pripojenie"</string>
<string name="state_event_room_knock_retracted_by_you">"Zrušili ste svoju žiadosť o pripojenie"</string>
<string name="state_event_room_leave">"%1$s opustil/a miestnosť"</string>
<string name="state_event_room_leave_by_you">"Opustili ste miestnosť"</string>
<string name="state_event_room_name_changed">"%1$s zmenil/a názov miestnosti na: %2$s"</string>
<string name="state_event_room_name_changed_by_you">"Zmenili ste názov miestnosti na: %1$s"</string>
<string name="state_event_room_name_removed">"%1$s odstránil/a názov miestnosti"</string>
<string name="state_event_room_name_removed_by_you">"Odstránili ste názov miestnosti"</string>
<string name="state_event_room_reject">"%1$s odmietol/a pozvánku"</string>
<string name="state_event_room_reject_by_you">"Odmietli ste pozvánku"</string>
<string name="state_event_room_remove">"%1$s odstránil/a %2$s"</string>
<string name="state_event_room_remove_by_you">"Odstránili ste %1$s"</string>
<string name="state_event_room_third_party_invite">"%1$s poslal/a pozvánku používateľovi %2$s, aby sa pripojil k miestnosti"</string>
<string name="state_event_room_third_party_invite_by_you">"Poslali ste pozvánku používateľovi %1$s, aby sa pripojil do miestnosti"</string>
<string name="state_event_room_topic_changed">"%1$s zmenil/a tému na: %2$s"</string>
<string name="state_event_room_topic_changed_by_you">"Zmenili ste tému na: %1$s"</string>
<string name="state_event_room_topic_removed">"%1$s odstránil/a tému miestnosti"</string>
<string name="state_event_room_topic_removed_by_you">"Odstránili ste tému miestnosti"</string>
<string name="state_event_room_unban">"%1$s zrušil/a zákaz pre %2$s"</string>
<string name="state_event_room_unban_by_you">"Zrušili ste zákaz pre %1$s"</string>
</resources>

View file

@ -49,6 +49,12 @@ interface MatrixClient : Closeable {
fun sessionVerificationService(): SessionVerificationService
fun pushersService(): PushersService
fun notificationService(): NotificationService
suspend fun getCacheSize(): Long
/**
* Will close the client and delete the cache data.
*/
suspend fun clearCache()
suspend fun logout()
suspend fun loadUserDisplayName(): Result<String>
suspend fun loadUserAvatarURLString(): Result<String?>

View file

@ -21,5 +21,5 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
interface NotificationService {
fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId): Result<NotificationData?>
fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId, filterByPushRules: Boolean): Result<NotificationData?>
}

View file

@ -116,4 +116,13 @@ interface MatrixRoom : Closeable {
suspend fun setTopic(topic: String): Result<Unit>
suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result<Unit>
/**
* Share a location message in the room.
*
* @param body A human readable textual representation of the location.
* @param geoUri A geo URI (RFC 5870) representing the location e.g. `geo:51.5008,0.1247;u=35`.
* Respectively: latitude, longitude, and (optional) uncertainty.
*/
suspend fun sendLocation(body: String, geoUri: String): Result<Unit>
}

View file

@ -32,6 +32,7 @@ dependencies {
// api(projects.libraries.rustsdk)
implementation(libs.matrix.sdk)
implementation(projects.libraries.di)
implementation(projects.libraries.androidutils)
implementation(projects.services.toolbox.api)
api(projects.libraries.matrix.api)
implementation(libs.dagger)

View file

@ -16,6 +16,8 @@
package io.element.android.libraries.matrix.impl
import io.element.android.libraries.androidutils.file.getSizeOfFiles
import io.element.android.libraries.androidutils.file.safeDelete
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScopeOf
import io.element.android.libraries.matrix.api.MatrixClient
@ -229,15 +231,25 @@ class RustMatrixClient constructor(
client.destroy()
}
override suspend fun getCacheSize(): Long {
// Do not use client.userId since it can throw if client has been closed (during clear cache)
return baseDirectory.getCacheSize(userID = sessionId.value)
}
override suspend fun clearCache() {
close()
baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = false)
}
override suspend fun logout() = withContext(dispatchers.io) {
try {
client.logout()
} catch (failure: Throwable) {
Timber.e(failure, "Fail to call logout on HS. Still delete local files.")
}
baseDirectory.deleteSessionDirectory(userID = client.userId())
sessionStore.removeSession(client.userId())
close()
baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = true)
sessionStore.removeSession(sessionId.value)
}
override suspend fun loadUserDisplayName(): Result<String> = withContext(dispatchers.io) {
@ -271,11 +283,48 @@ class RustMatrixClient constructor(
override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver
private fun File.deleteSessionDirectory(userID: String): Boolean {
private suspend fun File.getCacheSize(
userID: String,
includeCryptoDb: Boolean = false,
): Long = withContext(dispatchers.io) {
// Rust sanitises the user ID replacing invalid characters with an _
val sanitisedUserID = userID.replace(":", "_")
val sessionDirectory = File(this, sanitisedUserID)
return sessionDirectory.deleteRecursively()
val sessionDirectory = File(this@getCacheSize, sanitisedUserID)
if (includeCryptoDb) {
sessionDirectory.getSizeOfFiles()
} else {
listOf(
"matrix-sdk-state.sqlite3",
"matrix-sdk-state.sqlite3-shm",
"matrix-sdk-state.sqlite3-wal",
).map { fileName ->
File(sessionDirectory, fileName)
}.sumOf { file ->
file.length()
}
}
}
private suspend fun File.deleteSessionDirectory(
userID: String,
deleteCryptoDb: Boolean = false,
): Boolean = withContext(dispatchers.io) {
// Rust sanitises the user ID replacing invalid characters with an _
val sanitisedUserID = userID.replace(":", "_")
val sessionDirectory = File(this@deleteSessionDirectory, sanitisedUserID)
if (deleteCryptoDb) {
// Delete the folder and all its content
sessionDirectory.deleteRecursively()
} else {
// Delete only the state.db file
sessionDirectory.listFiles().orEmpty()
.filter { it.name.contains("matrix-sdk-state") }
.forEach { file ->
Timber.w("Deleting file ${file.name}...")
file.safeDelete()
}
true
}
}
}

View file

@ -32,10 +32,11 @@ class RustNotificationService(
override fun getNotification(
userId: SessionId,
roomId: RoomId,
eventId: EventId
eventId: EventId,
filterByPushRules: Boolean,
): Result<NotificationData?> {
return runCatching {
client.getNotificationItem(roomId.value, eventId.value)?.use(notificationMapper::map)
client.getNotificationItem(roomId.value, eventId.value, filterByPushRules)?.use(notificationMapper::map)
}
}
}

View file

@ -374,4 +374,13 @@ class RustMatrixRoom(
}
}
}
override suspend fun sendLocation(
body: String,
geoUri: String
): Result<Unit> = withContext(coroutineDispatchers.io) {
runCatching {
innerRoom.sendLocation(body, geoUri, genTransactionId())
}
}
}

View file

@ -101,6 +101,13 @@ class FakeMatrixClient(
override fun syncService() = syncService
override suspend fun getCacheSize(): Long {
return 0
}
override suspend fun clearCache() {
}
override suspend fun logout() {
delay(100)
logoutFailure?.let { throw it }

View file

@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.flowOf
val A_OIDC_DATA = OidcDetails(url = "a-url")
class FakeAuthenticationService : MatrixAuthenticationService {
private var homeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
private val homeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
private var oidcError: Throwable? = null
private var oidcCancelError: Throwable? = null
private var loginError: Throwable? = null

View file

@ -23,7 +23,7 @@ import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.notification.NotificationService
class FakeNotificationService : NotificationService {
override fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId): Result<NotificationData?> {
override fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId, filterByPushRules: Boolean): Result<NotificationData?> {
return Result.success(null)
}
}

View file

@ -77,6 +77,7 @@ class FakeMatrixRoom(
private var cancelSendResult = Result.success(Unit)
private var forwardEventResult = Result.success(Unit)
private var reportContentResult = Result.success(Unit)
private var sendLocationResult = Result.success(Unit)
var sendMediaCount = 0
private set
@ -93,6 +94,9 @@ class FakeMatrixRoom(
var reportedContentCount: Int = 0
private set
var sendLocationCount: Int = 0
private set
var isInviteAccepted: Boolean = false
private set
@ -262,6 +266,14 @@ class FakeMatrixRoom(
return reportContentResult
}
override suspend fun sendLocation(
body: String,
geoUri: String
): Result<Unit> = simulateLongTask {
sendLocationCount++
return sendLocationResult
}
override fun close() = Unit
fun givenLeaveRoomError(throwable: Throwable?) {
@ -355,4 +367,8 @@ class FakeMatrixRoom(
fun givenReportContentResult(result: Result<Unit>) {
reportContentResult = result
}
fun givenSendLocationResult(result: Result<Unit>) {
sendLocationResult = result
}
}

View file

@ -26,16 +26,17 @@ import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.matrix.api.MatrixClient
import okhttp3.OkHttpClient
import javax.inject.Inject
import javax.inject.Provider
class LoggedInImageLoaderFactory @Inject constructor(
@ApplicationContext private val context: Context,
private val matrixClient: MatrixClient,
private val okHttpClient: OkHttpClient,
private val okHttpClient: Provider<OkHttpClient>,
) : ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader
.Builder(context)
.okHttpClient(okHttpClient)
.okHttpClient { okHttpClient.get() }
.components {
// Add gif support
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@ -54,12 +55,12 @@ class LoggedInImageLoaderFactory @Inject constructor(
class NotLoggedInImageLoaderFactory @Inject constructor(
@ApplicationContext private val context: Context,
private val okHttpClient: OkHttpClient,
private val okHttpClient: Provider<OkHttpClient>,
) : ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader
.Builder(context)
.okHttpClient(okHttpClient)
.okHttpClient { okHttpClient.get() }
.build()
}
}

View file

@ -59,6 +59,7 @@ class NotifiableEventResolver @Inject constructor(
userId = sessionId,
roomId = roomId,
eventId = eventId,
filterByPushRules = true,
)
}.fold(
{

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_call">"Zavolať"</string>
<string name="notification_channel_silent">"Tiché oznámenia"</string>
<string name="notification_inline_reply_failed">"** Nepodarilo sa odoslať - prosím otvorte miestnosť"</string>
<string name="notification_invitation_action_join">"Pripojiť sa"</string>
<string name="notification_invitation_action_reject">"Zamietnuť"</string>
<string name="notification_new_messages">"Nové správy"</string>
<string name="notification_room_action_mark_as_read">"Označiť ako prečítané"</string>
<string name="notification_test_push_notification_content">"Prezeráte si oznámenie! Kliknite na mňa!"</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 a %2$s"</string>
<string name="notification_unread_notified_messages_in_room">"%1$s v %2$s"</string>
<string name="notification_unread_notified_messages_in_room_and_invitation">"%1$s v %2$s a %3$s"</string>
<plurals name="notification_compat_summary_line_for_room">
<item quantity="one">"%1$s: %2$d správa"</item>
<item quantity="few">"%1$s: %2$d správy"</item>
<item quantity="other">"%1$s: %2$d správ"</item>
</plurals>
<plurals name="notification_compat_summary_title">
<item quantity="one">"%d oznámenie"</item>
<item quantity="few">"%d oznámenia"</item>
<item quantity="other">"%d oznámení"</item>
</plurals>
<plurals name="notification_invitations">
<item quantity="one">"%d pozvánka"</item>
<item quantity="few">"%d pozvánky"</item>
<item quantity="other">"%d pozvánok"</item>
</plurals>
<plurals name="notification_new_messages_for_room">
<item quantity="one">"%d nová správa"</item>
<item quantity="few">"%d nové správy"</item>
<item quantity="other">"%d nových správ"</item>
</plurals>
<plurals name="notification_unread_notified_messages">
<item quantity="one">"%d neprečítaná oznámená správa"</item>
<item quantity="few">"%d neprečítané oznámené správy"</item>
<item quantity="other">"%d neprečítaných oznámených správ"</item>
</plurals>
<plurals name="notification_unread_notified_messages_in_room_rooms">
<item quantity="one">"%d miestnosť"</item>
<item quantity="few">"%d miestnosti"</item>
<item quantity="other">"%d miestností"</item>
</plurals>
<string name="push_choose_distributor_dialog_title_android">"Vyberte spôsob prijímania oznámení"</string>
<string name="push_distributor_background_sync_android">"Synchronizácia na pozadí"</string>
<string name="push_distributor_firebase_android">"Služby Google"</string>
<string name="notification_room_action_quick_reply">"Rýchla odpoveď"</string>
</resources>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="rich_text_editor_composer_placeholder">"Správa…"</string>
<string name="rich_text_editor_format_bold">"Použiť tučný formát"</string>
<string name="rich_text_editor_format_italic">"Použiť formát kurzívy"</string>
<string name="rich_text_editor_format_strikethrough">"Použiť formát prečiarknutia"</string>
<string name="rich_text_editor_format_underline">"Použiť formát podčiarknutia"</string>
<string name="rich_text_editor_link">"Nastaviť odkaz"</string>
</resources>

View file

@ -28,6 +28,7 @@
<string name="action_invite">"Pozvat"</string>
<string name="action_invite_friends">"Pozvat přátele"</string>
<string name="action_invite_friends_to_app">"Pozvat přátele do %1$s"</string>
<string name="action_invite_people_to_app">"Pozvat lidi na %1$s"</string>
<string name="action_invites_list">"Pozvánky"</string>
<string name="action_learn_more">"Zjistit více"</string>
<string name="action_leave">"Odejít"</string>
@ -108,6 +109,7 @@
<string name="common_server_not_supported">"Server není podporován"</string>
<string name="common_server_url">"URL serveru"</string>
<string name="common_settings">"Nastavení"</string>
<string name="common_shared_location">"Sdílená poloha"</string>
<string name="common_starting_chat">"Zahajování chatu…"</string>
<string name="common_sticker">"Nálepka"</string>
<string name="common_success">"Úspěch"</string>
@ -163,6 +165,8 @@
<string name="screen_media_upload_preview_error_failed_processing">"Nahrání média se nezdařilo, zkuste to prosím znovu."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Nahrání média se nezdařilo, zkuste to prosím znovu."</string>
<string name="screen_report_content_block_user_hint">"Zaškrtněte, pokud chcete skrýt všechny aktuální a budoucí zprávy od tohoto uživatele"</string>
<string name="screen_share_location_title">"Sdílet polohu"</string>
<string name="screen_share_my_location_action">"Sdílet moji polohu"</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Práh detekce"</string>
<string name="settings_title_general">"Obecné"</string>

View file

@ -158,7 +158,7 @@
<string name="screen_analytics_settings_share_data">"Teile Analyse-Daten"</string>
<string name="screen_media_picker_error_failed_selection">"Medienauswahl fehlgeschlagen, bitte versuche es erneut."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Fehler bei der Verarbeitung von Medien zum Hochladen, bitte versuchen Sie es erneut."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Medien hochladen fehlgeschlagen. Bitte versuchen Sie es erneut."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Hochladen von Medien fehlgeschlagen, bitte versuchen Sie es erneut."</string>
<string name="screen_report_content_block_user_hint">"Prüfe, ob du alle aktuellen und zukünftigen Nachrichten dieses Benutzers ausblenden möchtest"</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Erkennungsschwelle"</string>

View file

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="a11y_hide_password">"Skryť heslo"</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>
<string name="action_accept">"Prijať"</string>
<string name="action_back">"Späť"</string>
<string name="action_cancel">"Zrušiť"</string>
<string name="action_choose_photo">"Vybrať fotku"</string>
<string name="action_clear">"Vyčistiť"</string>
<string name="action_close">"Zavrieť"</string>
<string name="action_complete_verification">"Dokončiť overenie"</string>
<string name="action_confirm">"Potvrdiť"</string>
<string name="action_continue">"Pokračovať"</string>
<string name="action_copy">"Kopírovať"</string>
<string name="action_copy_link">"Kopírovať odkaz"</string>
<string name="action_copy_link_to_message">"Kopírovať odkaz do správy"</string>
<string name="action_create">"Vytvoriť"</string>
<string name="action_create_a_room">"Vytvoriť miestnosť"</string>
<string name="action_decline">"Odmietnuť"</string>
<string name="action_disable">"Vypnúť"</string>
<string name="action_done">"Hotovo"</string>
<string name="action_edit">"Upraviť"</string>
<string name="action_enable">"Povoliť"</string>
<string name="action_forgot_password">"Zabudnuté heslo?"</string>
<string name="action_invite">"Pozvať"</string>
<string name="action_invite_friends">"Pozvať priateľov"</string>
<string name="action_invite_friends_to_app">"Pozvať priateľov do %1$s"</string>
<string name="action_invite_people_to_app">"Pozvať ľudí do %1$s"</string>
<string name="action_invites_list">"Pozvánky"</string>
<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_next">"Ďalej"</string>
<string name="action_no">"Nie"</string>
<string name="action_not_now">"Teraz nie"</string>
<string name="action_ok">"OK"</string>
<string name="action_open_with">"Otvoriť pomocou"</string>
<string name="action_quick_reply">"Rýchla odpoveď"</string>
<string name="action_quote">"Citovať"</string>
<string name="action_remove">"Odstrániť"</string>
<string name="action_reply">"Odpovedať"</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>
<string name="action_retry_decryption">"Opakovať dešifrovanie"</string>
<string name="action_save">"Uložiť"</string>
<string name="action_search">"Hľadať"</string>
<string name="action_send">"Odoslať"</string>
<string name="action_send_message">"Odoslať správu"</string>
<string name="action_share">"Zdieľať"</string>
<string name="action_share_link">"Zdieľať odkaz"</string>
<string name="action_skip">"Preskočiť"</string>
<string name="action_start">"Spustiť"</string>
<string name="action_start_chat">"Začať konverzáciu"</string>
<string name="action_start_verification">"Spustiť overovanie"</string>
<string name="action_static_map_load">"Ťuknutím načítate mapu"</string>
<string name="action_take_photo">"Urobiť fotku"</string>
<string name="action_view_source">"Zobraziť zdroj"</string>
<string name="action_yes">"Áno"</string>
<string name="common_about">"O aplikácii"</string>
<string name="common_analytics">"Analytika"</string>
<string name="common_audio">"Zvuk"</string>
<string name="common_bubbles">"Bubliny"</string>
<string name="common_copyright">"Autorské práva"</string>
<string name="common_creating_room">"Vytváranie miestnosti…"</string>
<string name="common_current_user_left_room">"Opustil/a miestnosť"</string>
<string name="common_decryption_error">"Chyba dešifrovania"</string>
<string name="common_developer_options">"Možnosti pre vývojárov"</string>
<string name="common_edited_suffix">"(upravené)"</string>
<string name="common_editing">"Upravuje sa"</string>
<string name="common_emote">"* %1$s %2$s"</string>
<string name="common_encryption_enabled">"Šifrovanie zapnuté"</string>
<string name="common_error">"Chyba"</string>
<string name="common_file">"Súbor"</string>
<string name="common_file_saved_on_disk_android">"Súbor bol uložený do priečinka Stiahnuté súbory"</string>
<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_invite_unknown_profile">"Nedokážeme overiť Matrix ID tohto používateľa. Pozvánka nemusí byť prijatá."</string>
<string name="common_link_copied_to_clipboard">"Odkaz bol skopírovaný do schránky"</string>
<string name="common_loading">"Načítava sa…"</string>
<string name="common_message">"Správa"</string>
<string name="common_message_layout">"Rozloženie správy"</string>
<string name="common_message_removed">"Správa odstránená"</string>
<string name="common_modern">"Moderné"</string>
<string name="common_no_results">"Žiadne výsledky"</string>
<string name="common_offline">"Offline"</string>
<string name="common_password">"Heslo"</string>
<string name="common_people">"Ľudia"</string>
<string name="common_permalink">"Trvalý odkaz"</string>
<string name="common_reactions">"Reakcie"</string>
<string name="common_report_a_bug">"Nahlásiť chybu"</string>
<string name="common_report_submitted">"Nahlásenie bolo odoslané"</string>
<string name="common_room_name">"Názov miestnosti"</string>
<string name="common_search_results">"Výsledky hľadania"</string>
<string name="common_security">"Bezpečnosť"</string>
<string name="common_select_your_server">"Vyberte svoj server"</string>
<string name="common_sending">"Odosiela sa…"</string>
<string name="common_server_not_supported">"Server nie je podporovaný"</string>
<string name="common_server_url">"URL adresa servera"</string>
<string name="common_settings">"Nastavenia"</string>
<string name="common_shared_location">"Zdieľaná poloha"</string>
<string name="common_sticker">"Nálepka"</string>
<string name="common_success">"Úspech"</string>
<string name="common_suggestions">"Návrhy"</string>
<string name="common_syncing">"Synchronizuje sa"</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>
<string name="common_unsupported_event">"Nepodporovaná udalosť"</string>
<string name="common_username">"Používateľské meno"</string>
<string name="common_verification_cancelled">"Overovanie zrušené"</string>
<string name="common_verification_complete">"Overovanie je dokončené"</string>
<string name="common_video">"Video"</string>
<string name="common_waiting">"Čaká sa…"</string>
<string name="dialog_title_confirmation">"Potvrdenie"</string>
<string name="dialog_title_warning">"Upozornenie"</string>
<string name="emoji_picker_category_activity">"Aktivity"</string>
<string name="emoji_picker_category_flags">"Vlajky"</string>
<string name="emoji_picker_category_foods">"Jedlo a nápoje"</string>
<string name="emoji_picker_category_nature">"Zvieratá a príroda"</string>
<string name="emoji_picker_category_objects">"Predmety"</string>
<string name="emoji_picker_category_people">"Smajlíky a ľudia"</string>
<string name="emoji_picker_category_places">"Cestovanie a miesta"</string>
<string name="emoji_picker_category_symbols">"Symboly"</string>
<string name="error_failed_loading_messages">"Načítanie správ zlyhalo"</string>
<string name="error_some_messages_have_not_been_sent">"Niektoré správy neboli odoslané"</string>
<string name="error_unknown">"Prepáčte, vyskytla sa chyba"</string>
<string name="invite_friends_rich_title">"🔐️ Pripojte sa ku mne na %1$s"</string>
<string name="invite_friends_text">"Ahoj, porozprávajte sa so mnou na %1$s: %2$s"</string>
<string name="leave_room_alert_empty_subtitle">"Ste si istí, že chcete opustiť túto miestnosť? Ste tu jediná osoba. Ak odídete, nikto sa do nej nebude môcť v budúcnosti pripojiť, vrátane vás."</string>
<string name="leave_room_alert_private_subtitle">"Ste si istí, že chcete opustiť túto miestnosť? Táto miestnosť nie je verejná a bez pozvania sa do nej nebudete môcť vrátiť."</string>
<string name="leave_room_alert_subtitle">"Ste si istí, že chcete opustiť miestnosť?"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<plurals name="common_member_count">
<item quantity="one">"%1$d člen"</item>
<item quantity="few">"%1$d členovia"</item>
<item quantity="other">"%1$d členov"</item>
</plurals>
<string name="preference_rageshake">"Zúrivo potriasť pre nahlásenie chyby"</string>
<string name="rageshake_dialog_content">"Zdá sa, že zúrivo trasiete telefónom. Chcete otvoriť obrazovku s nahlásením chýb?"</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_share_location_title">"Zdieľať polohu"</string>
<string name="screen_share_my_location_action">"Zdieľať moju polohu"</string>
<string name="screen_share_this_location_action">"Zdieľajte túto polohu"</string>
<string name="settings_rageshake">"Zúrivé potrasenie"</string>
<string name="settings_rageshake_detection_threshold">"Prahová hodnota detekcie"</string>
<string name="settings_title_general">"Všeobecné"</string>
<string name="settings_version_number">"Verzia: %1$s (%2$s)"</string>
<string name="test_language_identifier">"sk"</string>
<string name="dialog_title_error">"Chyba"</string>
<string name="dialog_title_success">"Úspech"</string>
<string name="screen_report_content_block_user">"Zablokovať používateľa"</string>
</resources>

View file

@ -164,6 +164,9 @@
<string name="screen_media_upload_preview_error_failed_processing">"Failed processing media to upload, please try again."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Failed uploading media, please try again."</string>
<string name="screen_report_content_block_user_hint">"Check if you want to hide all current and future messages from this user"</string>
<string name="screen_share_location_title">"Share location"</string>
<string name="screen_share_my_location_action">"Share my location"</string>
<string name="screen_share_this_location_action">"Share this location"</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Detection threshold"</string>
<string name="settings_title_general">"General"</string>