Merge pull request #2620 from element-hq/feature/fga/room_directory
Feature/fga/room directory
This commit is contained in:
commit
a9f326c81c
78 changed files with 2114 additions and 41 deletions
|
|
@ -51,5 +51,7 @@ enum class AvatarSize(val dp: Dp) {
|
|||
|
||||
NotificationsOptIn(32.dp),
|
||||
|
||||
CustomRoomNotificationSetting(36.dp)
|
||||
CustomRoomNotificationSetting(36.dp),
|
||||
|
||||
RoomDirectoryItem(36.dp),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,4 +89,11 @@ enum class FeatureFlags(
|
|||
defaultValue = true,
|
||||
isFinished = false,
|
||||
),
|
||||
RoomDirectorySearch(
|
||||
key = "feature.roomdirectorysearch",
|
||||
title = "Room directory search",
|
||||
description = "Allow user to search for public rooms in their homeserver",
|
||||
defaultValue = false,
|
||||
isFinished = false,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class StaticFeatureFlagProvider @Inject constructor() :
|
|||
FeatureFlags.MarkAsUnread -> true
|
||||
FeatureFlags.RoomListFilters -> true
|
||||
FeatureFlags.RoomModeration -> false
|
||||
FeatureFlags.RoomDirectorySearch -> false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
|||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
||||
|
|
@ -58,12 +59,14 @@ interface MatrixClient : Closeable {
|
|||
suspend fun setDisplayName(displayName: String): Result<Unit>
|
||||
suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit>
|
||||
suspend fun removeAvatar(): Result<Unit>
|
||||
suspend fun joinRoom(roomId: RoomId): Result<RoomId>
|
||||
fun syncService(): SyncService
|
||||
fun sessionVerificationService(): SessionVerificationService
|
||||
fun pushersService(): PushersService
|
||||
fun notificationService(): NotificationService
|
||||
fun notificationSettingsService(): NotificationSettingsService
|
||||
fun encryptionService(): EncryptionService
|
||||
fun roomDirectoryService(): RoomDirectoryService
|
||||
suspend fun getCacheSize(): Long
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.roomdirectory
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
data class RoomDescription(
|
||||
val roomId: RoomId,
|
||||
val name: String?,
|
||||
val topic: String?,
|
||||
val alias: String?,
|
||||
val avatarUrl: String?,
|
||||
val joinRule: JoinRule,
|
||||
val isWorldReadable: Boolean,
|
||||
val numberOfMembers: Long
|
||||
) {
|
||||
enum class JoinRule {
|
||||
PUBLIC,
|
||||
KNOCK,
|
||||
UNKNOWN
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.roomdirectory
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface RoomDirectoryList {
|
||||
suspend fun filter(filter: String?, batchSize: Int): Result<Unit>
|
||||
suspend fun loadMore(): Result<Unit>
|
||||
val state: Flow<State>
|
||||
|
||||
data class State(
|
||||
val hasMoreToLoad: Boolean,
|
||||
val items: List<RoomDescription>,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.roomdirectory
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
interface RoomDirectoryService {
|
||||
fun createRoomDirectoryList(scope: CoroutineScope): RoomDirectoryList
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
|||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.roomlist.awaitLoaded
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
|
|
@ -53,6 +54,7 @@ import io.element.android.libraries.matrix.impl.room.MatrixRoomInfoMapper
|
|||
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
|
||||
import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber
|
||||
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
|
||||
import io.element.android.libraries.matrix.impl.roomdirectory.RustRoomDirectoryService
|
||||
import io.element.android.libraries.matrix.impl.roomlist.RoomListFactory
|
||||
import io.element.android.libraries.matrix.impl.roomlist.RustRoomListService
|
||||
import io.element.android.libraries.matrix.impl.roomlist.fullRoomWithTimeline
|
||||
|
|
@ -71,6 +73,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -101,6 +104,8 @@ import org.matrix.rustcomponents.sdk.use
|
|||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import org.matrix.rustcomponents.sdk.CreateRoomParameters as RustCreateRoomParameters
|
||||
import org.matrix.rustcomponents.sdk.RoomPreset as RustRoomPreset
|
||||
import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility
|
||||
|
|
@ -150,6 +155,12 @@ class RustMatrixClient(
|
|||
sessionCoroutineScope = sessionCoroutineScope,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
||||
private val roomDirectoryService = RustRoomDirectoryService(
|
||||
client = client,
|
||||
sessionDispatcher = sessionDispatcher,
|
||||
)
|
||||
|
||||
private val sessionDirectoryNameProvider = SessionDirectoryNameProvider()
|
||||
|
||||
private val isLoggingOut = AtomicBoolean(false)
|
||||
|
|
@ -309,6 +320,22 @@ class RustMatrixClient(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the room to be available in the room list.
|
||||
* @param roomId the room id to wait for
|
||||
* @param timeout the timeout to wait for the room to be available
|
||||
* @throws TimeoutCancellationException if the room is not available after the timeout
|
||||
*/
|
||||
private suspend fun awaitRoom(roomId: RoomId, timeout: Duration) {
|
||||
withTimeout(timeout) {
|
||||
roomListService.allRooms.summaries
|
||||
.filter { roomSummaries ->
|
||||
roomSummaries.map { it.identifier() }.contains(roomId.value)
|
||||
}
|
||||
.first()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun pairOfRoom(roomId: RoomId): Pair<RoomListItem, Room>? {
|
||||
val cachedRoomListItem = innerRoomListService.roomOrNull(roomId.value)
|
||||
val fullRoom = cachedRoomListItem?.fullRoomWithTimeline(filter = eventFilters)
|
||||
|
|
@ -358,14 +385,11 @@ class RustMatrixClient(
|
|||
powerLevelContentOverride = defaultRoomCreationPowerLevels,
|
||||
)
|
||||
val roomId = RoomId(client.createRoom(rustParams))
|
||||
|
||||
// Wait to receive the room back from the sync
|
||||
withTimeout(30_000L) {
|
||||
roomListService.allRooms.summaries
|
||||
.filter { roomSummaries ->
|
||||
roomSummaries.map { it.identifier() }.contains(roomId.value)
|
||||
}
|
||||
.first()
|
||||
// Wait to receive the room back from the sync but do not returns failure if it fails.
|
||||
try {
|
||||
awaitRoom(roomId, 30.seconds)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Timeout waiting for the room to be available in the room list")
|
||||
}
|
||||
roomId
|
||||
}
|
||||
|
|
@ -414,6 +438,18 @@ class RustMatrixClient(
|
|||
runCatching { client.removeAvatar() }
|
||||
}
|
||||
|
||||
override suspend fun joinRoom(roomId: RoomId): Result<RoomId> = withContext(sessionDispatcher) {
|
||||
runCatching {
|
||||
client.joinRoomById(roomId.value).destroy()
|
||||
try {
|
||||
awaitRoom(roomId, 10.seconds)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Timeout waiting for the room to be available in the room list")
|
||||
}
|
||||
roomId
|
||||
}
|
||||
}
|
||||
|
||||
override fun syncService(): SyncService = rustSyncService
|
||||
|
||||
override fun sessionVerificationService(): SessionVerificationService = verificationService
|
||||
|
|
@ -426,6 +462,8 @@ class RustMatrixClient(
|
|||
|
||||
override fun notificationSettingsService(): NotificationSettingsService = notificationSettingsService
|
||||
|
||||
override fun roomDirectoryService(): RoomDirectoryService = roomDirectoryService
|
||||
|
||||
override fun close() {
|
||||
sessionCoroutineScope.cancel()
|
||||
clientDelegateTaskHandle?.cancelAndDestroy()
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
|||
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -68,4 +69,9 @@ object SessionMatrixModule {
|
|||
fun provideSessionCoroutineScope(matrixClient: MatrixClient): CoroutineScope {
|
||||
return matrixClient.sessionCoroutineScope
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun providesRoomDirectoryService(matrixClient: MatrixClient): RoomDirectoryService {
|
||||
return matrixClient.roomDirectoryService()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.roomdirectory
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription
|
||||
import org.matrix.rustcomponents.sdk.PublicRoomJoinRule
|
||||
import org.matrix.rustcomponents.sdk.RoomDescription as RustRoomDescription
|
||||
|
||||
class RoomDescriptionMapper {
|
||||
fun map(roomDescription: RustRoomDescription): RoomDescription {
|
||||
return RoomDescription(
|
||||
roomId = RoomId(roomDescription.roomId),
|
||||
name = roomDescription.name,
|
||||
topic = roomDescription.topic,
|
||||
avatarUrl = roomDescription.avatarUrl,
|
||||
alias = roomDescription.alias,
|
||||
joinRule = when (roomDescription.joinRule) {
|
||||
PublicRoomJoinRule.PUBLIC -> RoomDescription.JoinRule.PUBLIC
|
||||
PublicRoomJoinRule.KNOCK -> RoomDescription.JoinRule.KNOCK
|
||||
null -> RoomDescription.JoinRule.UNKNOWN
|
||||
},
|
||||
isWorldReadable = roomDescription.isWorldReadable,
|
||||
numberOfMembers = roomDescription.joinedMembers.toLong(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.roomdirectory
|
||||
|
||||
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.buffer
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import org.matrix.rustcomponents.sdk.RoomDirectorySearch
|
||||
import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntriesListener
|
||||
import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate
|
||||
import timber.log.Timber
|
||||
|
||||
internal fun RoomDirectorySearch.resultsFlow(): Flow<List<RoomDirectorySearchEntryUpdate>> =
|
||||
callbackFlow {
|
||||
val listener = object : RoomDirectorySearchEntriesListener {
|
||||
override fun onUpdate(roomEntriesUpdate: List<RoomDirectorySearchEntryUpdate>) {
|
||||
trySendBlocking(roomEntriesUpdate)
|
||||
}
|
||||
}
|
||||
val result = results(listener)
|
||||
awaitClose {
|
||||
result.cancelAndDestroy()
|
||||
}
|
||||
}.catch {
|
||||
Timber.d(it, "timelineDiffFlow() failed")
|
||||
}.buffer(Channel.UNLIMITED)
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.roomdirectory
|
||||
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class RoomDirectorySearchProcessor(
|
||||
private val roomDescriptions: MutableSharedFlow<List<RoomDescription>>,
|
||||
private val coroutineContext: CoroutineContext,
|
||||
private val roomDescriptionMapper: RoomDescriptionMapper,
|
||||
) {
|
||||
private val mutex = Mutex()
|
||||
|
||||
suspend fun postUpdates(updates: List<RoomDirectorySearchEntryUpdate>) {
|
||||
updateRoomDescriptions {
|
||||
Timber.v("Update room descriptions from postUpdates (with ${updates.size} items) on ${Thread.currentThread()}")
|
||||
updates.forEach { update ->
|
||||
applyUpdate(update)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<RoomDescription>.applyUpdate(update: RoomDirectorySearchEntryUpdate) {
|
||||
when (update) {
|
||||
is RoomDirectorySearchEntryUpdate.Append -> {
|
||||
val roomSummaries = update.values.map(roomDescriptionMapper::map)
|
||||
addAll(roomSummaries)
|
||||
}
|
||||
is RoomDirectorySearchEntryUpdate.PushBack -> {
|
||||
val roomDescription = roomDescriptionMapper.map(update.value)
|
||||
add(roomDescription)
|
||||
}
|
||||
is RoomDirectorySearchEntryUpdate.PushFront -> {
|
||||
val roomDescription = roomDescriptionMapper.map(update.value)
|
||||
add(0, roomDescription)
|
||||
}
|
||||
is RoomDirectorySearchEntryUpdate.Set -> {
|
||||
val roomDescription = roomDescriptionMapper.map(update.value)
|
||||
this[update.index.toInt()] = roomDescription
|
||||
}
|
||||
is RoomDirectorySearchEntryUpdate.Insert -> {
|
||||
val roomDescription = roomDescriptionMapper.map(update.value)
|
||||
add(update.index.toInt(), roomDescription)
|
||||
}
|
||||
is RoomDirectorySearchEntryUpdate.Remove -> {
|
||||
removeAt(update.index.toInt())
|
||||
}
|
||||
is RoomDirectorySearchEntryUpdate.Reset -> {
|
||||
clear()
|
||||
addAll(update.values.map(roomDescriptionMapper::map))
|
||||
}
|
||||
RoomDirectorySearchEntryUpdate.PopBack -> {
|
||||
removeLastOrNull()
|
||||
}
|
||||
RoomDirectorySearchEntryUpdate.PopFront -> {
|
||||
removeFirstOrNull()
|
||||
}
|
||||
RoomDirectorySearchEntryUpdate.Clear -> {
|
||||
clear()
|
||||
}
|
||||
is RoomDirectorySearchEntryUpdate.Truncate -> {
|
||||
subList(update.length.toInt(), size).clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateRoomDescriptions(block: suspend MutableList<RoomDescription>.() -> Unit) = withContext(coroutineContext) {
|
||||
mutex.withLock {
|
||||
val current = roomDescriptions.replayCache.lastOrNull()
|
||||
val mutableRoomSummaries = current.orEmpty().toMutableList()
|
||||
block(mutableRoomSummaries)
|
||||
roomDescriptions.emit(mutableRoomSummaries)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.roomdirectory
|
||||
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.rustcomponents.sdk.RoomDirectorySearch
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class RustRoomDirectoryList(
|
||||
private val inner: RoomDirectorySearch,
|
||||
coroutineScope: CoroutineScope,
|
||||
private val coroutineContext: CoroutineContext,
|
||||
) : RoomDirectoryList {
|
||||
private val hasMoreToLoad = MutableStateFlow(true)
|
||||
private val items = MutableSharedFlow<List<RoomDescription>>(replay = 1)
|
||||
private val processor = RoomDirectorySearchProcessor(items, coroutineContext, RoomDescriptionMapper())
|
||||
|
||||
init {
|
||||
launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
private fun launchIn(coroutineScope: CoroutineScope) {
|
||||
inner
|
||||
.resultsFlow()
|
||||
.onEach { updates ->
|
||||
processor.postUpdates(updates)
|
||||
}
|
||||
.flowOn(coroutineContext)
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
override suspend fun filter(filter: String?, batchSize: Int): Result<Unit> {
|
||||
return execute {
|
||||
inner.search(filter = filter, batchSize = batchSize.toUInt())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadMore(): Result<Unit> {
|
||||
return execute {
|
||||
inner.nextPage()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun execute(action: suspend () -> Unit): Result<Unit> {
|
||||
return try {
|
||||
// We always assume there is more to load until we know there isn't.
|
||||
// As accessing hasMoreToLoad is otherwise blocked by the current action.
|
||||
hasMoreToLoad.value = true
|
||||
action()
|
||||
Result.success(Unit)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
hasMoreToLoad.value = hasMoreToLoad()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun hasMoreToLoad(): Boolean {
|
||||
return !inner.isAtLastPage()
|
||||
}
|
||||
|
||||
override val state: Flow<RoomDirectoryList.State> =
|
||||
combine(hasMoreToLoad, items) { hasMoreToLoad, items ->
|
||||
RoomDirectoryList.State(
|
||||
hasMoreToLoad = hasMoreToLoad,
|
||||
items = items
|
||||
)
|
||||
}
|
||||
.flowOn(coroutineContext)
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.roomdirectory
|
||||
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.matrix.rustcomponents.sdk.Client
|
||||
|
||||
class RustRoomDirectoryService(
|
||||
private val client: Client,
|
||||
private val sessionDispatcher: CoroutineDispatcher,
|
||||
) : RoomDirectoryService {
|
||||
override fun createRoomDirectoryList(scope: CoroutineScope): RoomDirectoryList {
|
||||
return RustRoomDirectoryList(client.roomDirectorySearch(), scope, sessionDispatcher)
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
|||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
|
@ -39,6 +40,7 @@ import io.element.android.libraries.matrix.test.media.FakeMediaLoader
|
|||
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
|
||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.test.pushers.FakePushersService
|
||||
import io.element.android.libraries.matrix.test.roomdirectory.FakeRoomDirectoryService
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
|
|
@ -65,6 +67,7 @@ class FakeMatrixClient(
|
|||
private val notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
||||
private val syncService: FakeSyncService = FakeSyncService(),
|
||||
private val encryptionService: FakeEncryptionService = FakeEncryptionService(),
|
||||
private val roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService(),
|
||||
private val accountManagementUrlString: Result<String?> = Result.success(null),
|
||||
) : MatrixClient {
|
||||
var setDisplayNameCalled: Boolean = false
|
||||
|
|
@ -91,6 +94,9 @@ class FakeMatrixClient(
|
|||
private var setDisplayNameResult: Result<Unit> = Result.success(Unit)
|
||||
private var uploadAvatarResult: Result<Unit> = Result.success(Unit)
|
||||
private var removeAvatarResult: Result<Unit> = Result.success(Unit)
|
||||
var joinRoomLambda: suspend (RoomId) -> Result<RoomId> = {
|
||||
Result.success(it)
|
||||
}
|
||||
|
||||
override suspend fun getRoom(roomId: RoomId): MatrixRoom? {
|
||||
return getRoomResults[roomId]
|
||||
|
|
@ -126,6 +132,8 @@ class FakeMatrixClient(
|
|||
|
||||
override fun syncService() = syncService
|
||||
|
||||
override fun roomDirectoryService() = roomDirectoryService
|
||||
|
||||
override suspend fun getCacheSize(): Long {
|
||||
return 0
|
||||
}
|
||||
|
|
@ -176,6 +184,8 @@ class FakeMatrixClient(
|
|||
return removeAvatarResult
|
||||
}
|
||||
|
||||
override suspend fun joinRoom(roomId: RoomId): Result<RoomId> = joinRoomLambda(roomId)
|
||||
|
||||
override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService
|
||||
|
||||
override fun pushersService(): PushersService = pushersService
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.test.roomdirectory
|
||||
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
|
||||
class FakeRoomDirectoryList(
|
||||
override val state: Flow<RoomDirectoryList.State> = emptyFlow(),
|
||||
val filterLambda: (String?, Int) -> Result<Unit> = { _, _ -> Result.success(Unit) },
|
||||
val loadMoreLambda: () -> Result<Unit> = { Result.success(Unit) }
|
||||
) : RoomDirectoryList {
|
||||
override suspend fun filter(filter: String?, batchSize: Int) = filterLambda(filter, batchSize)
|
||||
|
||||
override suspend fun loadMore(): Result<Unit> = loadMoreLambda()
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.test.roomdirectory
|
||||
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
class FakeRoomDirectoryService(
|
||||
private val createRoomDirectoryListFactory: (CoroutineScope) -> RoomDirectoryList = { throw AssertionError("Configure a proper factory.") }
|
||||
) : RoomDirectoryService {
|
||||
override fun createRoomDirectoryList(scope: CoroutineScope) = createRoomDirectoryListFactory(scope)
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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.test.roomdirectory
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
|
||||
fun aRoomDescription(
|
||||
roomId: RoomId = A_ROOM_ID,
|
||||
name: String? = null,
|
||||
topic: String? = null,
|
||||
alias: String? = null,
|
||||
avatarUrl: String? = null,
|
||||
joinRule: RoomDescription.JoinRule = RoomDescription.JoinRule.UNKNOWN,
|
||||
isWorldReadable: Boolean = true,
|
||||
joinedMembers: Long = 2L
|
||||
) = RoomDescription(
|
||||
roomId = roomId,
|
||||
name = name,
|
||||
topic = topic,
|
||||
alias = alias,
|
||||
avatarUrl = avatarUrl,
|
||||
joinRule = joinRule,
|
||||
isWorldReadable = isWorldReadable,
|
||||
numberOfMembers = joinedMembers
|
||||
)
|
||||
|
|
@ -100,4 +100,9 @@ object TestTags {
|
|||
* Timeline item.
|
||||
*/
|
||||
val timelineItemSenderInfo = TestTag("timeline_item-sender_info")
|
||||
|
||||
/**
|
||||
* Search field.
|
||||
*/
|
||||
val searchTextField = TestTag("search_text_field")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -258,8 +258,6 @@
|
|||
<string name="screen_media_picker_error_failed_selection">"Не ўдалося выбраць носьбіт, паўтарыце спробу."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Не атрымалася загрузіць медыяфайлы, паспрабуйце яшчэ раз."</string>
|
||||
<string name="screen_room_directory_search_loading_error">"Памылка загрузкі"</string>
|
||||
<string name="screen_room_directory_search_title">"Каталог пакояў"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Не ўдалося атрымаць інфармацыю пра карыстальніка"</string>
|
||||
<string name="screen_room_member_details_block_alert_action">"Заблакіраваць"</string>
|
||||
|
|
|
|||
|
|
@ -258,8 +258,6 @@
|
|||
<string name="screen_media_picker_error_failed_selection">"Výběr média se nezdařil, zkuste to prosím znovu."</string>
|
||||
<string name="screen_media_upload_preview_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_room_directory_search_loading_error">"Načítání se nezdařilo"</string>
|
||||
<string name="screen_room_directory_search_title">"Adresář místností"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Nahrání média se nezdařilo, zkuste to prosím znovu."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Nepodařilo se načíst údaje o uživateli"</string>
|
||||
<string name="screen_room_member_details_block_alert_action">"Zablokovat"</string>
|
||||
|
|
|
|||
|
|
@ -254,8 +254,6 @@
|
|||
<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 beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Das Hochladen der Medien ist fehlgeschlagen. Bitte versuche es erneut."</string>
|
||||
<string name="screen_room_directory_search_loading_error">"Fehler beim Laden"</string>
|
||||
<string name="screen_room_directory_search_title">"Raumverzeichnis"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Benutzerdetails konnten nicht abgerufen werden"</string>
|
||||
<string name="screen_room_member_details_block_alert_action">"Blockieren"</string>
|
||||
|
|
|
|||
|
|
@ -254,8 +254,6 @@
|
|||
<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>
|
||||
<string name="screen_room_directory_search_loading_error">"Échec du chargement"</string>
|
||||
<string name="screen_room_directory_search_title">"Annuaire des salons"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Échec du traitement des médias à télécharger, veuillez réessayer."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Impossible de récupérer les détails de l’utilisateur"</string>
|
||||
<string name="screen_room_member_details_block_alert_action">"Bloquer"</string>
|
||||
|
|
|
|||
|
|
@ -256,8 +256,6 @@
|
|||
<string name="screen_media_picker_error_failed_selection">"Не удалось выбрать носитель, попробуйте еще раз."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Не удалось обработать медиафайл для загрузки, попробуйте еще раз."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Не удалось загрузить медиафайлы, попробуйте еще раз."</string>
|
||||
<string name="screen_room_directory_search_loading_error">"Сбой загрузки"</string>
|
||||
<string name="screen_room_directory_search_title">"Каталог комнат"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Не удалось обработать медиафайл для загрузки, попробуйте еще раз."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Не удалось получить данные о пользователе"</string>
|
||||
<string name="screen_room_member_details_block_alert_action">"Заблокировать"</string>
|
||||
|
|
|
|||
|
|
@ -257,8 +257,6 @@
|
|||
<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>
|
||||
<string name="screen_room_directory_search_loading_error">"Načítanie zlyhalo"</string>
|
||||
<string name="screen_room_directory_search_title">"Adresár miestností"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Nepodarilo sa získať údaje o používateľovi"</string>
|
||||
<string name="screen_room_member_details_block_alert_action">"Zablokovať"</string>
|
||||
|
|
|
|||
|
|
@ -256,8 +256,6 @@
|
|||
<string name="screen_media_picker_error_failed_selection">"Не вдалося вибрати медіафайл, спробуйте ще раз."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Не вдалося обробити медіафайл для завантаження, спробуйте ще раз."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Не вдалося завантажити медіафайл, спробуйте ще раз."</string>
|
||||
<string name="screen_room_directory_search_loading_error">"Не вдалося завантажити"</string>
|
||||
<string name="screen_room_directory_search_title">"Каталог кімнат"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Не вдалося обробити медіафайл для завантаження, спробуйте ще раз."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Не вдалося отримати дані користувача"</string>
|
||||
<string name="screen_room_member_details_block_alert_action">"Заблокувати"</string>
|
||||
|
|
|
|||
|
|
@ -254,8 +254,6 @@
|
|||
<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>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Failed uploading media, please try again."</string>
|
||||
<string name="screen_room_directory_search_loading_error">"Failed loading"</string>
|
||||
<string name="screen_room_directory_search_title">"Room directory"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Failed processing media to upload, please try again."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Could not retrieve user details"</string>
|
||||
<string name="screen_room_member_details_block_alert_action">"Block"</string>
|
||||
|
|
@ -274,4 +272,55 @@
|
|||
<string name="settings_version_number">"Version: %1$s (%2$s)"</string>
|
||||
<string name="test_language_identifier">"en"</string>
|
||||
<string name="test_untranslated_default_language_identifier">"en"</string>
|
||||
<string name="troubleshoot_notifications_entry_point_section">"Troubleshoot"</string>
|
||||
<string name="troubleshoot_notifications_entry_point_title">"Troubleshoot notifications"</string>
|
||||
<string name="troubleshoot_notifications_screen_action">"Run tests"</string>
|
||||
<string name="troubleshoot_notifications_screen_action_again">"Run tests again"</string>
|
||||
<string name="troubleshoot_notifications_screen_failure">"Some tests failed. Please check the details."</string>
|
||||
<string name="troubleshoot_notifications_screen_notice">"Run the tests to detect any issue in your configuration that may make notifications not behave as expected."</string>
|
||||
<string name="troubleshoot_notifications_screen_quick_fix_action">"Attempt to fix"</string>
|
||||
<string name="troubleshoot_notifications_screen_success">"All tests passed successfully."</string>
|
||||
<string name="troubleshoot_notifications_screen_title">"Troubleshoot notifications"</string>
|
||||
<string name="troubleshoot_notifications_screen_waiting">"Some tests require your attention. Please check the details."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_description">"Check that the application can show notifications."</string>
|
||||
<string name="troubleshoot_notifications_test_check_permission_title">"Check permissions"</string>
|
||||
<string name="troubleshoot_notifications_test_current_push_provider_description">"Get the name of the current provider."</string>
|
||||
<string name="troubleshoot_notifications_test_current_push_provider_failure">"No push providers selected."</string>
|
||||
<string name="troubleshoot_notifications_test_current_push_provider_success">"Current push provider: %1$s."</string>
|
||||
<string name="troubleshoot_notifications_test_current_push_provider_title">"Current push provider"</string>
|
||||
<string name="troubleshoot_notifications_test_detect_push_provider_description">"Ensure that the application has at least one push provider."</string>
|
||||
<string name="troubleshoot_notifications_test_detect_push_provider_failure">"No push providers found."</string>
|
||||
<plurals name="troubleshoot_notifications_test_detect_push_provider_success">
|
||||
<item quantity="one">"Found %1$d push provider: %2$s"</item>
|
||||
<item quantity="other">"Found %1$d push providers: %2$s"</item>
|
||||
</plurals>
|
||||
<string name="troubleshoot_notifications_test_detect_push_provider_title">"Detect push providers"</string>
|
||||
<string name="troubleshoot_notifications_test_display_notification_description">"Check that the application can display notification."</string>
|
||||
<string name="troubleshoot_notifications_test_display_notification_failure">"The notification has not been clicked."</string>
|
||||
<string name="troubleshoot_notifications_test_display_notification_permission_failure">"Cannot display the notification."</string>
|
||||
<string name="troubleshoot_notifications_test_display_notification_success">"The notification has been clicked!"</string>
|
||||
<string name="troubleshoot_notifications_test_display_notification_title">"Display notification"</string>
|
||||
<string name="troubleshoot_notifications_test_display_notification_waiting">"Please click on the notification to continue the test."</string>
|
||||
<string name="troubleshoot_notifications_test_firebase_availability_description">"Ensure that Firebase is available."</string>
|
||||
<string name="troubleshoot_notifications_test_firebase_availability_failure">"Firebase is not available."</string>
|
||||
<string name="troubleshoot_notifications_test_firebase_availability_success">"Firebase is available."</string>
|
||||
<string name="troubleshoot_notifications_test_firebase_availability_title">"Check Firebase"</string>
|
||||
<string name="troubleshoot_notifications_test_firebase_token_description">"Ensure that Firebase token is available."</string>
|
||||
<string name="troubleshoot_notifications_test_firebase_token_failure">"Firebase token is not known."</string>
|
||||
<string name="troubleshoot_notifications_test_firebase_token_success">"Firebase token: %1$s."</string>
|
||||
<string name="troubleshoot_notifications_test_firebase_token_title">"Check Firebase token"</string>
|
||||
<string name="troubleshoot_notifications_test_push_loop_back_description">"Ensure that the application is receiving push."</string>
|
||||
<string name="troubleshoot_notifications_test_push_loop_back_failure_1">"Error: pusher has rejected the request."</string>
|
||||
<string name="troubleshoot_notifications_test_push_loop_back_failure_2">"Error: %1$s."</string>
|
||||
<string name="troubleshoot_notifications_test_push_loop_back_failure_3">"Error, cannot test push."</string>
|
||||
<string name="troubleshoot_notifications_test_push_loop_back_failure_4">"Error, timeout waiting for push."</string>
|
||||
<string name="troubleshoot_notifications_test_push_loop_back_success">"Push loop back took %1$d ms."</string>
|
||||
<string name="troubleshoot_notifications_test_push_loop_back_title">"Test Push loop back"</string>
|
||||
<string name="troubleshoot_notifications_test_unified_push_description">"Ensure that UnifiedPush distributors are available."</string>
|
||||
<string name="troubleshoot_notifications_test_unified_push_failure">"No push distributors found."</string>
|
||||
<plurals name="troubleshoot_notifications_test_unified_push_success">
|
||||
<item quantity="one">"%1$d distributor found: %2$s."</item>
|
||||
<item quantity="other">"%1$d distributors found: %2$s."</item>
|
||||
</plurals>
|
||||
<string name="troubleshoot_notifications_test_unified_push_title">"Check UnifiedPush"</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue