Merge branch 'develop' into feature/fga/space_ui_tweaks
This commit is contained in:
commit
0dec3a1cb6
174 changed files with 2905 additions and 1323 deletions
|
|
@ -98,6 +98,18 @@ internal fun MatrixBadgeAtomNegativePreview() = ElementPreview {
|
|||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun MatrixBadgeAtomNeutralWrappingPreview() = ElementPreview {
|
||||
MatrixBadgeAtom.View(
|
||||
MatrixBadgeAtom.MatrixBadgeData(
|
||||
text = "How much wood could a wood chuck chuck if a wood chuck could chuck wood",
|
||||
icon = CompoundIcons.LockOff(),
|
||||
type = MatrixBadgeAtom.Type.Info,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun MatrixBadgeAtomInfoPreview() = ElementPreview {
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@
|
|||
package io.element.android.libraries.designsystem.atomic.molecules
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.MatrixBadgeAtom
|
||||
|
|
@ -22,10 +23,11 @@ fun MatrixBadgeRowMolecule(
|
|||
data: ImmutableList<MatrixBadgeAtom.MatrixBadgeData>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
FlowRow(
|
||||
modifier = modifier
|
||||
.padding(start = 16.dp, end = 16.dp, top = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
for (badge in data) {
|
||||
MatrixBadgeAtom.View(badge)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
|
|
@ -63,6 +64,8 @@ fun Badge(
|
|||
text = text,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = textColor,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
softWrap = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,13 @@ enum class FeatureFlags(
|
|||
defaultValue = { false },
|
||||
isFinished = false,
|
||||
),
|
||||
RoomListSpaceFilters(
|
||||
key = "feature.roomListSpaceFilters",
|
||||
title = "Room list space filters",
|
||||
description = "Allow filtering the room list by space.",
|
||||
defaultValue = { false },
|
||||
isFinished = false,
|
||||
),
|
||||
PrintLogsToLogcat(
|
||||
key = "feature.print_logs_to_logcat",
|
||||
title = "Print logs to logcat",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ sealed class QrLoginException : Exception() {
|
|||
data object ConnectionInsecure : QrLoginException()
|
||||
data object Declined : QrLoginException()
|
||||
data object Expired : QrLoginException()
|
||||
data object NotFound : QrLoginException()
|
||||
data object LinkingNotSupported : QrLoginException()
|
||||
data object OidcMetadataInvalid : QrLoginException()
|
||||
data object SlidingSyncNotAvailable : QrLoginException()
|
||||
|
|
@ -20,5 +21,4 @@ sealed class QrLoginException : Exception() {
|
|||
data object CheckCodeAlreadySent : QrLoginException()
|
||||
data object CheckCodeCannotBeSent : QrLoginException()
|
||||
data object Unknown : QrLoginException()
|
||||
data object NotFound : QrLoginException()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@
|
|||
|
||||
package io.element.android.libraries.matrix.api.room.history
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
sealed interface RoomHistoryVisibility {
|
||||
/**
|
||||
* Previous events are accessible to newly joined members from the point
|
||||
|
|
|
|||
|
|
@ -28,4 +28,10 @@ sealed interface LatestEventValue {
|
|||
val senderProfile: ProfileDetails,
|
||||
val isSending: Boolean,
|
||||
) : LatestEventValue
|
||||
|
||||
data class RoomInvite(
|
||||
val timestamp: Long,
|
||||
val inviterId: UserId?,
|
||||
val invitedProfile: ProfileDetails,
|
||||
) : LatestEventValue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
package io.element.android.libraries.matrix.api.roomlist
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
sealed interface RoomListFilter {
|
||||
companion object {
|
||||
/**
|
||||
|
|
@ -41,6 +43,10 @@ sealed interface RoomListFilter {
|
|||
val filters: List<RoomListFilter>
|
||||
) : RoomListFilter
|
||||
|
||||
data class Identifiers(
|
||||
val values: List<RoomId>,
|
||||
) : RoomListFilter
|
||||
|
||||
/**
|
||||
* A filter that matches rooms that are unread.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ data class RoomSummary(
|
|||
is LatestEventValue.None -> null
|
||||
is LatestEventValue.Local -> latestEvent.timestamp
|
||||
is LatestEventValue.Remote -> latestEvent.timestamp
|
||||
is LatestEventValue.RoomInvite -> latestEvent.timestamp
|
||||
}
|
||||
val isOneToOne get() = info.activeMembersCount == 2L
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,8 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
|||
import kotlinx.coroutines.flow.SharedFlow
|
||||
|
||||
interface SpaceService {
|
||||
val spaceRoomsFlow: SharedFlow<List<SpaceRoom>>
|
||||
suspend fun joinedSpaces(): Result<List<SpaceRoom>>
|
||||
|
||||
val topLevelSpacesFlow: SharedFlow<List<SpaceRoom>>
|
||||
val spaceFiltersFlow: SharedFlow<List<SpaceServiceFilter>>
|
||||
suspend fun joinedParents(spaceId: RoomId): Result<List<SpaceRoom>>
|
||||
|
||||
suspend fun getSpaceRoom(spaceId: RoomId): SpaceRoom?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.spaces
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
/**
|
||||
* Represents a space filter for filtering rooms by space membership.
|
||||
*
|
||||
* @property spaceRoom The space room associated with this filter.
|
||||
* @property level The nesting level of the space (0 = top level, 1 = first level child, etc.).
|
||||
* @property descendants The list of room IDs that are descendants of this space.
|
||||
*/
|
||||
data class SpaceServiceFilter(
|
||||
val spaceRoom: SpaceRoom,
|
||||
val level: Int,
|
||||
val descendants: List<RoomId>,
|
||||
)
|
||||
|
|
@ -20,6 +20,7 @@ import org.matrix.rustcomponents.sdk.ClientDelegate
|
|||
import org.matrix.rustcomponents.sdk.ClientSessionDelegate
|
||||
import org.matrix.rustcomponents.sdk.Session
|
||||
import timber.log.Timber
|
||||
import uniffi.matrix_sdk_common.BackgroundTaskFailureReason
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
|
|
@ -120,6 +121,11 @@ class RustClientSessionDelegate(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onBackgroundTaskErrorReport(taskName: String, error: BackgroundTaskFailureReason) {
|
||||
// TODO actually implement the missing logic to report to sentry and crash the app
|
||||
Timber.tag(loggerTag.value).e("onBackgroundTaskErrorReport(taskName=$taskName, error=$error)")
|
||||
}
|
||||
|
||||
override fun retrieveSessionFromKeychain(userId: String): Session {
|
||||
// This should never be called, as it's only used for multi-process setups
|
||||
error("retrieveSessionFromKeychain should never be called for Android")
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
|
|||
import io.element.android.libraries.matrix.impl.room.RoomInfoMapper
|
||||
import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber
|
||||
import io.element.android.libraries.matrix.impl.room.RustRoomFactory
|
||||
import io.element.android.libraries.matrix.impl.room.TimelineEventTypeFilterFactory
|
||||
import io.element.android.libraries.matrix.impl.room.TimelineEventFilterFactory
|
||||
import io.element.android.libraries.matrix.impl.room.history.map
|
||||
import io.element.android.libraries.matrix.impl.room.join.map
|
||||
import io.element.android.libraries.matrix.impl.room.preview.RoomPreviewInfoMapper
|
||||
|
|
@ -141,7 +141,7 @@ class RustMatrixClient(
|
|||
dispatchers: CoroutineDispatchers,
|
||||
baseCacheDirectory: File,
|
||||
clock: SystemClock,
|
||||
timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
|
||||
timelineEventFilterFactory: TimelineEventFilterFactory,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val workManagerScheduler: WorkManagerScheduler,
|
||||
|
|
@ -225,7 +225,7 @@ class RustMatrixClient(
|
|||
systemClock = clock,
|
||||
roomContentForwarder = RoomContentForwarder(innerRoomListService),
|
||||
roomSyncSubscriber = roomSyncSubscriber,
|
||||
timelineEventTypeFilterFactory = timelineEventTypeFilterFactory,
|
||||
timelineEventFilterFactory = timelineEventFilterFactory,
|
||||
roomMembershipObserver = roomMembershipObserver,
|
||||
roomInfoMapper = roomInfoMapper,
|
||||
featureFlagService = featureFlagService,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import io.element.android.libraries.matrix.impl.certificates.UserCertificatesPro
|
|||
import io.element.android.libraries.matrix.impl.paths.SessionPaths
|
||||
import io.element.android.libraries.matrix.impl.paths.getSessionPaths
|
||||
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
|
||||
import io.element.android.libraries.matrix.impl.room.TimelineEventTypeFilterFactory
|
||||
import io.element.android.libraries.matrix.impl.room.TimelineEventFilterFactory
|
||||
import io.element.android.libraries.matrix.impl.storage.SqliteStoreBuilderProvider
|
||||
import io.element.android.libraries.matrix.impl.util.anonymizedTokens
|
||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
||||
|
|
@ -61,7 +61,7 @@ class RustMatrixClientFactory(
|
|||
private val clock: SystemClock,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
|
||||
private val timelineEventFilterFactory: TimelineEventFilterFactory,
|
||||
private val clientBuilderProvider: ClientBuilderProvider,
|
||||
private val sqliteStoreBuilderProvider: SqliteStoreBuilderProvider,
|
||||
private val workManagerScheduler: WorkManagerScheduler,
|
||||
|
|
@ -115,7 +115,7 @@ class RustMatrixClientFactory(
|
|||
dispatchers = coroutineDispatchers,
|
||||
baseCacheDirectory = cacheDirectory,
|
||||
clock = clock,
|
||||
timelineEventTypeFilterFactory = timelineEventTypeFilterFactory,
|
||||
timelineEventFilterFactory = timelineEventFilterFactory,
|
||||
featureFlagService = featureFlagService,
|
||||
analyticsService = analyticsService,
|
||||
workManagerScheduler = workManagerScheduler,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ object QrErrorMapper {
|
|||
is RustHumanQrLoginException.ConnectionInsecure -> QrLoginException.ConnectionInsecure
|
||||
is RustHumanQrLoginException.Declined -> QrLoginException.Declined
|
||||
is RustHumanQrLoginException.Expired -> QrLoginException.Expired
|
||||
is RustHumanQrLoginException.NotFound -> QrLoginException.NotFound
|
||||
is RustHumanQrLoginException.OtherDeviceNotSignedIn -> QrLoginException.OtherDeviceNotSignedIn
|
||||
is RustHumanQrLoginException.LinkingNotSupported -> QrLoginException.LinkingNotSupported
|
||||
is RustHumanQrLoginException.Unknown -> QrLoginException.Unknown
|
||||
|
|
@ -45,6 +46,5 @@ object QrErrorMapper {
|
|||
is RustHumanQrLoginException.SlidingSyncNotAvailable -> QrLoginException.SlidingSyncNotAvailable
|
||||
is RustHumanQrLoginException.CheckCodeAlreadySent -> QrLoginException.CheckCodeAlreadySent
|
||||
is RustHumanQrLoginException.CheckCodeCannotBeSent -> QrLoginException.CheckCodeCannotBeSent
|
||||
is RustHumanQrLoginException.NotFound -> QrLoginException.NotFound
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ import org.matrix.rustcomponents.sdk.getElementCallRequiredPermissions
|
|||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
import uniffi.matrix_sdk.RoomPowerLevelChanges
|
||||
import uniffi.matrix_sdk_ui.TimelineEventFocusThreadMode
|
||||
import uniffi.matrix_sdk_ui.TimelineReadReceiptTracking
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
import org.matrix.rustcomponents.sdk.IdentityStatusChange as RustIdentityStateChange
|
||||
|
|
@ -177,21 +178,18 @@ class JoinedRustRoom(
|
|||
): Result<Timeline> = withContext(roomDispatcher) {
|
||||
val hideThreadedEvents = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
|
||||
val focus = when (createTimelineParams) {
|
||||
is CreateTimelineParams.PinnedOnly -> TimelineFocus.PinnedEvents(
|
||||
maxEventsToLoad = 100u,
|
||||
maxConcurrentRequests = 10u,
|
||||
)
|
||||
is CreateTimelineParams.PinnedOnly -> TimelineFocus.PinnedEvents
|
||||
is CreateTimelineParams.MediaOnly -> TimelineFocus.Live(hideThreadedEvents = hideThreadedEvents)
|
||||
is CreateTimelineParams.Focused -> TimelineFocus.Event(
|
||||
eventId = createTimelineParams.focusedEventId.value,
|
||||
numContextEvents = 50u,
|
||||
hideThreadedEvents = hideThreadedEvents,
|
||||
threadMode = TimelineEventFocusThreadMode.Automatic(hideThreadedEvents),
|
||||
)
|
||||
is CreateTimelineParams.MediaOnlyFocused -> TimelineFocus.Event(
|
||||
eventId = createTimelineParams.focusedEventId.value,
|
||||
numContextEvents = 50u,
|
||||
// Never hide threaded events in media focused timeline
|
||||
hideThreadedEvents = false,
|
||||
threadMode = TimelineEventFocusThreadMode.Automatic(false),
|
||||
)
|
||||
is CreateTimelineParams.Threaded -> TimelineFocus.Thread(
|
||||
rootEventId = createTimelineParams.threadRootEventId.value,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class RustRoomFactory(
|
|||
private val roomListService: RoomListService,
|
||||
private val innerRoomListService: InnerRoomListService,
|
||||
private val roomSyncSubscriber: RoomSyncSubscriber,
|
||||
private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
|
||||
private val timelineEventFilterFactory: TimelineEventFilterFactory,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val roomMembershipObserver: RoomMembershipObserver,
|
||||
private val roomInfoMapper: RoomInfoMapper,
|
||||
|
|
@ -70,7 +70,7 @@ class RustRoomFactory(
|
|||
private val eventFilters = TimelineConfig.excludedEvents
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.let { listStateEventType ->
|
||||
timelineEventTypeFilterFactory.create(listStateEventType)
|
||||
timelineEventFilterFactory.create(listStateEventType)
|
||||
}
|
||||
|
||||
suspend fun destroy() {
|
||||
|
|
@ -133,7 +133,7 @@ class RustRoomFactory(
|
|||
sdkRoom.timelineWithConfiguration(
|
||||
TimelineConfiguration(
|
||||
focus = TimelineFocus.Live(hideThreadedEvents = hideThreadedEvents),
|
||||
filter = eventFilters?.let(TimelineFilter::EventTypeFilter) ?: TimelineFilter.All,
|
||||
filter = eventFilters?.let(TimelineFilter::EventFilter) ?: TimelineFilter.All,
|
||||
internalIdPrefix = "live",
|
||||
dateDividerMode = DateDividerMode.DAILY,
|
||||
trackReadReceipts = TimelineReadReceiptTracking.ALL_EVENTS,
|
||||
|
|
|
|||
|
|
@ -12,16 +12,16 @@ import dev.zacsweers.metro.AppScope
|
|||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||
import org.matrix.rustcomponents.sdk.FilterTimelineEventType
|
||||
import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter
|
||||
import org.matrix.rustcomponents.sdk.TimelineEventFilter
|
||||
|
||||
interface TimelineEventTypeFilterFactory {
|
||||
fun create(listStateEventType: List<StateEventType>): TimelineEventTypeFilter
|
||||
interface TimelineEventFilterFactory {
|
||||
fun create(listStateEventType: List<StateEventType>): TimelineEventFilter
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class RustTimelineEventTypeFilterFactory : TimelineEventTypeFilterFactory {
|
||||
override fun create(listStateEventType: List<StateEventType>): TimelineEventTypeFilter {
|
||||
return TimelineEventTypeFilter.exclude(
|
||||
class RustTimelineEventFilterFactory : TimelineEventFilterFactory {
|
||||
override fun create(listStateEventType: List<StateEventType>): TimelineEventFilter {
|
||||
return TimelineEventFilter.excludeEventTypes(
|
||||
listStateEventType.map { stateEventType ->
|
||||
FilterTimelineEventType.State(stateEventType.map())
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.Any
|
|||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.Category
|
||||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.DeduplicateVersions
|
||||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.Favourite
|
||||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.Identifiers
|
||||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.Invite
|
||||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.NonLeft
|
||||
import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind.NonSpace
|
||||
|
|
@ -60,6 +61,7 @@ internal object RoomListFilterMapper {
|
|||
return when (filter) {
|
||||
is RoomListFilter.All -> All(filters = filter.filters.map { mapFilter(it) })
|
||||
is RoomListFilter.Any -> Any(filters = filter.filters.map { mapFilter(it) })
|
||||
is RoomListFilter.Identifiers -> Identifiers(identifiers = filter.values.map { it.value })
|
||||
RoomListFilter.None -> None
|
||||
RoomListFilter.Category.Group -> Category(RoomListFilterCategory.GROUP)
|
||||
RoomListFilter.Category.People -> Category(RoomListFilterCategory.PEOPLE)
|
||||
|
|
|
|||
|
|
@ -53,6 +53,11 @@ class RoomSummaryFactory(
|
|||
senderProfile = event.profile.map(),
|
||||
isOwn = event.isOwn,
|
||||
)
|
||||
is RustLatestEventValue.RemoteInvite -> LatestEventValue.RoomInvite(
|
||||
timestamp = event.timestamp.toLong(),
|
||||
inviterId = event.inviter?.let(::UserId),
|
||||
invitedProfile = event.inviterProfile.map(),
|
||||
)
|
||||
}
|
||||
}
|
||||
return RoomSummary(
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import io.element.android.libraries.matrix.api.spaces.LeaveSpaceHandle
|
|||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
|
|
@ -31,9 +32,11 @@ import kotlinx.coroutines.flow.catch
|
|||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.SpaceFilterUpdate
|
||||
import org.matrix.rustcomponents.sdk.SpaceListUpdate
|
||||
import org.matrix.rustcomponents.sdk.SpaceServiceInterface
|
||||
import org.matrix.rustcomponents.sdk.SpaceServiceJoinedSpacesListener
|
||||
import org.matrix.rustcomponents.sdk.SpaceServiceSpaceFiltersListener
|
||||
import timber.log.Timber
|
||||
import org.matrix.rustcomponents.sdk.SpaceService as ClientSpaceService
|
||||
|
||||
|
|
@ -45,20 +48,20 @@ class RustSpaceService(
|
|||
private val analyticsService: AnalyticsService,
|
||||
) : SpaceService {
|
||||
private val spaceRoomMapper = SpaceRoomMapper()
|
||||
override val spaceRoomsFlow = MutableSharedFlow<List<SpaceRoom>>(replay = 1, extraBufferCapacity = 1)
|
||||
private val spaceFilterMapper = SpaceServiceFilterMapper(spaceRoomMapper)
|
||||
|
||||
override val topLevelSpacesFlow = MutableSharedFlow<List<SpaceRoom>>(replay = 1, extraBufferCapacity = 1)
|
||||
private val spaceListUpdateProcessor = SpaceListUpdateProcessor(
|
||||
spaceRoomsFlow = spaceRoomsFlow,
|
||||
spaceRoomsFlow = topLevelSpacesFlow,
|
||||
mapper = spaceRoomMapper,
|
||||
analyticsService = analyticsService,
|
||||
)
|
||||
|
||||
override suspend fun joinedSpaces(): Result<List<SpaceRoom>> = withContext(sessionDispatcher) {
|
||||
runCatchingExceptions {
|
||||
innerSpaceService
|
||||
.topLevelJoinedSpaces()
|
||||
.map(spaceRoomMapper::map)
|
||||
}
|
||||
}
|
||||
override val spaceFiltersFlow = MutableSharedFlow<List<SpaceServiceFilter>>(replay = 1, extraBufferCapacity = 1)
|
||||
private val spaceFilterUpdateProcessor = SpaceServiceFilterUpdateProcessor(
|
||||
spaceFiltersFlow = spaceFiltersFlow,
|
||||
mapper = spaceFilterMapper,
|
||||
)
|
||||
|
||||
override suspend fun joinedParents(spaceId: RoomId): Result<List<SpaceRoom>> = withContext(sessionDispatcher) {
|
||||
runCatchingExceptions {
|
||||
|
|
@ -123,6 +126,13 @@ class RustSpaceService(
|
|||
spaceListUpdateProcessor.postUpdates(updates)
|
||||
}
|
||||
.launchIn(sessionCoroutineScope)
|
||||
|
||||
innerSpaceService
|
||||
.spaceFilterListUpdate()
|
||||
.onEach { updates ->
|
||||
spaceFilterUpdateProcessor.postUpdates(updates)
|
||||
}
|
||||
.launchIn(sessionCoroutineScope)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -142,3 +152,20 @@ internal fun SpaceServiceInterface.spaceListUpdate(): Flow<List<SpaceListUpdate>
|
|||
}.catch {
|
||||
Timber.d(it, "spaceDiffFlow() failed")
|
||||
}.buffer(Channel.UNLIMITED)
|
||||
|
||||
internal fun SpaceServiceInterface.spaceFilterListUpdate(): Flow<List<SpaceFilterUpdate>> =
|
||||
callbackFlow {
|
||||
val listener = object : SpaceServiceSpaceFiltersListener {
|
||||
override fun onUpdate(filterUpdates: List<SpaceFilterUpdate>) {
|
||||
trySendBlocking(filterUpdates)
|
||||
}
|
||||
}
|
||||
Timber.d("Open spaceFilterDiffFlow for SpaceServiceInterface ${this@spaceFilterListUpdate}")
|
||||
val taskHandle = subscribeToSpaceFilters(listener)
|
||||
awaitClose {
|
||||
Timber.d("Close spaceFilterDiffFlow for SpaceServiceInterface ${this@spaceFilterListUpdate}")
|
||||
taskHandle.cancelAndDestroy()
|
||||
}
|
||||
}.catch {
|
||||
Timber.d(it, "spaceFilterListUpdate() failed")
|
||||
}.buffer(Channel.UNLIMITED)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.spaces
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||
import org.matrix.rustcomponents.sdk.SpaceFilter as RustSpaceFilter
|
||||
|
||||
class SpaceServiceFilterMapper(
|
||||
private val spaceRoomMapper: SpaceRoomMapper,
|
||||
) {
|
||||
fun map(spaceFilter: RustSpaceFilter): SpaceServiceFilter {
|
||||
return SpaceServiceFilter(
|
||||
spaceRoom = spaceRoomMapper.map(spaceFilter.spaceRoom),
|
||||
level = spaceFilter.level.toInt(),
|
||||
descendants = spaceFilter.descendants.map { RoomId(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.spaces
|
||||
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.matrix.rustcomponents.sdk.SpaceFilterUpdate
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SpaceServiceFilterUpdateProcessor(
|
||||
private val spaceFiltersFlow: MutableSharedFlow<List<SpaceServiceFilter>>,
|
||||
private val mapper: SpaceServiceFilterMapper,
|
||||
) {
|
||||
private val mutex = Mutex()
|
||||
|
||||
suspend fun postUpdates(updates: List<SpaceFilterUpdate>) {
|
||||
Timber.v("Update space filters from postUpdates (with ${updates.size} items) on ${Thread.currentThread()}")
|
||||
updateSpaceFilters {
|
||||
updates.forEach { update -> applyUpdate(update) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateSpaceFilters(block: MutableList<SpaceServiceFilter>.() -> Unit) =
|
||||
mutex.withLock {
|
||||
val spaceFilters = if (spaceFiltersFlow.replayCache.isNotEmpty()) {
|
||||
spaceFiltersFlow.first().toMutableList()
|
||||
} else {
|
||||
mutableListOf()
|
||||
}
|
||||
block(spaceFilters)
|
||||
spaceFiltersFlow.emit(spaceFilters)
|
||||
}
|
||||
|
||||
private fun MutableList<SpaceServiceFilter>.applyUpdate(update: SpaceFilterUpdate) {
|
||||
when (update) {
|
||||
is SpaceFilterUpdate.Append -> {
|
||||
val newFilters = update.values.map(mapper::map)
|
||||
addAll(newFilters)
|
||||
}
|
||||
SpaceFilterUpdate.Clear -> clear()
|
||||
is SpaceFilterUpdate.Insert -> {
|
||||
val newFilter = mapper.map(update.value)
|
||||
add(update.index.toInt(), newFilter)
|
||||
}
|
||||
SpaceFilterUpdate.PopBack -> {
|
||||
removeAt(lastIndex)
|
||||
}
|
||||
SpaceFilterUpdate.PopFront -> {
|
||||
removeAt(0)
|
||||
}
|
||||
is SpaceFilterUpdate.PushBack -> {
|
||||
val newFilter = mapper.map(update.value)
|
||||
add(newFilter)
|
||||
}
|
||||
is SpaceFilterUpdate.PushFront -> {
|
||||
val newFilter = mapper.map(update.value)
|
||||
add(0, newFilter)
|
||||
}
|
||||
is SpaceFilterUpdate.Remove -> {
|
||||
removeAt(update.index.toInt())
|
||||
}
|
||||
is SpaceFilterUpdate.Reset -> {
|
||||
clear()
|
||||
val newFilters = update.values.map(mapper::map)
|
||||
addAll(newFilters)
|
||||
}
|
||||
is SpaceFilterUpdate.Set -> {
|
||||
val newFilter = mapper.map(update.value)
|
||||
this[update.index.toInt()] = newFilter
|
||||
}
|
||||
is SpaceFilterUpdate.Truncate -> {
|
||||
subList(update.length.toInt(), size).clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
|||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.impl.auth.FakeProxyProvider
|
||||
import io.element.android.libraries.matrix.impl.auth.FakeUserCertificatesProvider
|
||||
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventTypeFilterFactory
|
||||
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventFilterFactory
|
||||
import io.element.android.libraries.matrix.impl.storage.FakeSqliteStoreBuilderProvider
|
||||
import io.element.android.libraries.network.useragent.SimpleUserAgentProvider
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
|
|
@ -63,7 +63,7 @@ fun TestScope.createRustMatrixClientFactory(
|
|||
clock = FakeSystemClock(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
featureFlagService = FakeFeatureFlagService(),
|
||||
timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(),
|
||||
timelineEventFilterFactory = FakeTimelineEventFilterFactory(),
|
||||
clientBuilderProvider = clientBuilderProvider,
|
||||
sqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(),
|
||||
workManagerScheduler = workManagerScheduler,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import io.element.android.libraries.core.data.bytes
|
|||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClient
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiSyncService
|
||||
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventTypeFilterFactory
|
||||
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventFilterFactory
|
||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||
import io.element.android.libraries.matrix.test.A_DEVICE_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
|
|
@ -150,7 +150,7 @@ class RustMatrixClientTest {
|
|||
dispatchers = testCoroutineDispatchers(),
|
||||
baseCacheDirectory = File(""),
|
||||
clock = FakeSystemClock(),
|
||||
timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(),
|
||||
timelineEventFilterFactory = FakeTimelineEventFilterFactory(),
|
||||
featureFlagService = FakeFeatureFlagService(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
workManagerScheduler = FakeWorkManagerScheduler(submitLambda = {}),
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ internal fun aRustNotificationItem(
|
|||
hasMention: Boolean? = false,
|
||||
threadId: ThreadId? = null,
|
||||
actions: List<Action>? = null,
|
||||
rawEvent: String = "",
|
||||
) = NotificationItem(
|
||||
event = event,
|
||||
senderInfo = senderInfo,
|
||||
|
|
@ -38,6 +39,7 @@ internal fun aRustNotificationItem(
|
|||
hasMention = hasMention,
|
||||
threadId = threadId?.value,
|
||||
actions = actions,
|
||||
rawEvent = rawEvent,
|
||||
)
|
||||
|
||||
internal fun aRustBatchNotificationResultOk(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@
|
|||
package io.element.android.libraries.matrix.impl.fixtures.fakes
|
||||
|
||||
import org.matrix.rustcomponents.sdk.NoHandle
|
||||
import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter
|
||||
import org.matrix.rustcomponents.sdk.TimelineEventFilter
|
||||
|
||||
class FakeFfiTimelineEventTypeFilter : TimelineEventTypeFilter(NoHandle)
|
||||
class FakeFfiTimelineEventFilter : TimelineEventFilter(NoHandle)
|
||||
|
|
@ -9,11 +9,11 @@
|
|||
package io.element.android.libraries.matrix.impl.room
|
||||
|
||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiTimelineEventTypeFilter
|
||||
import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiTimelineEventFilter
|
||||
import org.matrix.rustcomponents.sdk.TimelineEventFilter
|
||||
|
||||
class FakeTimelineEventTypeFilterFactory : TimelineEventTypeFilterFactory {
|
||||
override fun create(listStateEventType: List<StateEventType>): TimelineEventTypeFilter {
|
||||
return FakeFfiTimelineEventTypeFilter()
|
||||
class FakeTimelineEventFilterFactory : TimelineEventFilterFactory {
|
||||
override fun create(listStateEventType: List<StateEventType>): TimelineEventFilter {
|
||||
return FakeFfiTimelineEventFilter()
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.spaces.LeaveSpaceHandle
|
|||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
|
@ -20,7 +21,6 @@ import kotlinx.coroutines.flow.SharedFlow
|
|||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
|
||||
class FakeSpaceService(
|
||||
private val joinedSpacesResult: () -> Result<List<SpaceRoom>> = { lambdaError() },
|
||||
private val spaceRoomListResult: (RoomId) -> SpaceRoomList = { lambdaError() },
|
||||
private val leaveSpaceHandleResult: (RoomId) -> LeaveSpaceHandle = { lambdaError() },
|
||||
private val removeChildFromSpaceResult: (RoomId, RoomId) -> Result<Unit> = { _, _ -> lambdaError() },
|
||||
|
|
@ -29,16 +29,20 @@ class FakeSpaceService(
|
|||
private val editableSpacesResult: () -> Result<List<SpaceRoom>> = { lambdaError() },
|
||||
private val addChildToSpaceResult: (RoomId, RoomId) -> Result<Unit> = { _, _ -> lambdaError() },
|
||||
) : SpaceService {
|
||||
private val _spaceRoomsFlow = MutableSharedFlow<List<SpaceRoom>>()
|
||||
override val spaceRoomsFlow: SharedFlow<List<SpaceRoom>>
|
||||
get() = _spaceRoomsFlow.asSharedFlow()
|
||||
private val _topLevelSpacesFlow = MutableSharedFlow<List<SpaceRoom>>()
|
||||
override val topLevelSpacesFlow: SharedFlow<List<SpaceRoom>>
|
||||
get() = _topLevelSpacesFlow.asSharedFlow()
|
||||
|
||||
suspend fun emitSpaceRoomList(value: List<SpaceRoom>) {
|
||||
_spaceRoomsFlow.emit(value)
|
||||
suspend fun emitTopLevelSpaces(value: List<SpaceRoom>) {
|
||||
_topLevelSpacesFlow.emit(value)
|
||||
}
|
||||
|
||||
override suspend fun joinedSpaces(): Result<List<SpaceRoom>> = simulateLongTask {
|
||||
return joinedSpacesResult()
|
||||
private val _spaceFiltersFlow = MutableSharedFlow<List<SpaceServiceFilter>>()
|
||||
override val spaceFiltersFlow: SharedFlow<List<SpaceServiceFilter>>
|
||||
get() = _spaceFiltersFlow.asSharedFlow()
|
||||
|
||||
suspend fun emitSpaceFilters(value: List<SpaceServiceFilter>) {
|
||||
_spaceFiltersFlow.emit(value)
|
||||
}
|
||||
|
||||
override suspend fun joinedParents(spaceId: RoomId): Result<List<SpaceRoom>> {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
<item quantity="one">"%d varsel"</item>
|
||||
<item quantity="other">"%d varsler"</item>
|
||||
</plurals>
|
||||
<string name="notification_error_unified_push_unregistered_android">"UnifiedPush-varslingsdistributøren kunne ikke registreres, så du vil ikke motta varsler lenger. Sjekk varslingsinnstillingene til appen og statusen til push-distributøren."</string>
|
||||
<string name="notification_fallback_content">"Du har nye meldinger."</string>
|
||||
<string name="notification_incoming_call">"📹 Innkommende anrop"</string>
|
||||
<string name="notification_inline_reply_failed">"** Kunne ikke sende - vennligst åpne rommet"</string>
|
||||
|
|
@ -37,6 +38,8 @@
|
|||
<string name="notification_room_invite_body_with_sender">"%1$s inviterte deg til å bli med i rommet"</string>
|
||||
<string name="notification_sender_me">"Meg"</string>
|
||||
<string name="notification_sender_mention_reply">"%1$s nevnt eller besvart"</string>
|
||||
<string name="notification_space_invite_body">"Inviterte deg til å bli med i området"</string>
|
||||
<string name="notification_space_invite_body_with_sender">"%1$s inviterte deg til å bli med i området"</string>
|
||||
<string name="notification_test_push_notification_content">"Du ser på varselet! Klikk på meg!"</string>
|
||||
<string name="notification_thread_in_room">"Tråd i %1$s"</string>
|
||||
<string name="notification_ticker_text_dm">"%1$s: %2$s"</string>
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
<string name="a11y_your_avatar">"Váš avatar"</string>
|
||||
<string name="action_accept">"Přijmout"</string>
|
||||
<string name="action_add_caption">"Přidat titulek"</string>
|
||||
<string name="action_add_existing_rooms">"Přidat stávající místnosti"</string>
|
||||
<string name="action_add_to_timeline">"Přidat na časovou osu"</string>
|
||||
<string name="action_back">"Zpět"</string>
|
||||
<string name="action_call">"Hovor"</string>
|
||||
|
|
@ -77,6 +78,7 @@
|
|||
<string name="action_copy_text">"Kopírovat text"</string>
|
||||
<string name="action_create">"Vytvořit"</string>
|
||||
<string name="action_create_room">"Vytvořit místnost"</string>
|
||||
<string name="action_create_space">"Vytvořte prostor"</string>
|
||||
<string name="action_deactivate">"Deaktivovat"</string>
|
||||
<string name="action_deactivate_account">"Deaktivovat účet"</string>
|
||||
<string name="action_decline">"Odmítnout"</string>
|
||||
|
|
@ -93,6 +95,7 @@
|
|||
<string name="action_enable">"Povolit"</string>
|
||||
<string name="action_end_poll">"Ukončit hlasování"</string>
|
||||
<string name="action_enter_pin">"Zadejte PIN"</string>
|
||||
<string name="action_explore_public_spaces">"Prozkoumejte veřejné prostory"</string>
|
||||
<string name="action_finish">"Dokončit"</string>
|
||||
<string name="action_forgot_password">"Zapomněli jste heslo?"</string>
|
||||
<string name="action_forward">"Přeposlat"</string>
|
||||
|
|
@ -194,6 +197,7 @@
|
|||
<string name="common_copied_to_clipboard">"Zkopírováno do schránky"</string>
|
||||
<string name="common_copyright">"Autorská práva"</string>
|
||||
<string name="common_creating_room">"Vytváření místnosti…"</string>
|
||||
<string name="common_creating_space">"Vytváření prostoru…"</string>
|
||||
<string name="common_current_user_canceled_knock">"Žádost zrušena"</string>
|
||||
<string name="common_current_user_left_room">"Místnost opuštěna"</string>
|
||||
<string name="common_current_user_left_space">"Opustit prostor"</string>
|
||||
|
|
@ -295,6 +299,7 @@ Důvod: %1$s."</string>
|
|||
<string name="common_reason">"Důvod"</string>
|
||||
<string name="common_recovery_key">"Klíč pro obnovení"</string>
|
||||
<string name="common_refreshing">"Obnovování…"</string>
|
||||
<string name="common_removing">"Odstraňování…"</string>
|
||||
<plurals name="common_replies">
|
||||
<item quantity="other">"%1$d odpovědí"</item>
|
||||
</plurals>
|
||||
|
|
@ -303,6 +308,7 @@ Důvod: %1$s."</string>
|
|||
<string name="common_report_a_problem">"Nahlásit problém"</string>
|
||||
<string name="common_report_submitted">"Zpráva odeslána"</string>
|
||||
<string name="common_rich_text_editor">"Editor formátovaného textu"</string>
|
||||
<string name="common_role">"Role"</string>
|
||||
<string name="common_room">"Místnost"</string>
|
||||
<string name="common_room_name">"Název místnosti"</string>
|
||||
<string name="common_room_name_placeholder">"např. název vašeho projektu"</string>
|
||||
|
|
@ -319,6 +325,11 @@ Důvod: %1$s."</string>
|
|||
<string name="common_security">"Zabezpečení"</string>
|
||||
<string name="common_seen_by">"Viděno"</string>
|
||||
<string name="common_select_account">"Vybrat účet"</string>
|
||||
<plurals name="common_selected_count">
|
||||
<item quantity="one">"%1$d vybraný"</item>
|
||||
<item quantity="few">"%1$d vybrané"</item>
|
||||
<item quantity="other">"%1$d vybraných"</item>
|
||||
</plurals>
|
||||
<string name="common_send_to">"Odeslat do"</string>
|
||||
<string name="common_sending">"Odesílání…"</string>
|
||||
<string name="common_sending_failed">"Odeslání se nezdařilo"</string>
|
||||
|
|
@ -329,6 +340,7 @@ Důvod: %1$s."</string>
|
|||
<string name="common_server_url">"URL serveru"</string>
|
||||
<string name="common_settings">"Nastavení"</string>
|
||||
<string name="common_share_space">"Sdílet prostor"</string>
|
||||
<string name="common_shared_history">"Noví členové vidí historii"</string>
|
||||
<string name="common_shared_location">"Sdílená poloha"</string>
|
||||
<string name="common_shared_space">"Sdílený prostor"</string>
|
||||
<string name="common_signing_out">"Odhlašování"</string>
|
||||
|
|
@ -344,6 +356,7 @@ Důvod: %1$s."</string>
|
|||
<string name="common_starting_chat">"Zahajování chatu…"</string>
|
||||
<string name="common_sticker">"Nálepka"</string>
|
||||
<string name="common_success">"Úspěch"</string>
|
||||
<string name="common_suggested">"Doporučeno"</string>
|
||||
<string name="common_suggestions">"Návrhy"</string>
|
||||
<string name="common_syncing">"Synchronizace"</string>
|
||||
<string name="common_system">"Systém"</string>
|
||||
|
|
@ -380,7 +393,10 @@ Důvod: %1$s."</string>
|
|||
<string name="common_voice_message">"Hlasová zpráva"</string>
|
||||
<string name="common_waiting">"Čekání…"</string>
|
||||
<string name="common_waiting_for_decryption_key">"Čekání na dešifrovací klíč"</string>
|
||||
<string name="common_world_readable_history">"Kdokoli může vidět historii"</string>
|
||||
<string name="common_you">"Vy"</string>
|
||||
<string name="crypto_event_key_forwarded_known_profile_dialog_content">"%1$s (%2$s) sdílel(a) tuto zprávu v době, kdy jste nebyli v místnosti."</string>
|
||||
<string name="crypto_event_key_forwarded_unknown_profile_dialog_content">"%1$s sdílel(a) tuto zprávu v době, kdy jste nebyli v místnosti."</string>
|
||||
<string name="crypto_history_visible">"Tato místnost byla nastavena tak, aby noví členové mohli číst historii. %1$s"</string>
|
||||
<string name="crypto_identity_change_pin_violation">"Identita uživatele %1$s se změnila. %2$s"</string>
|
||||
<string name="crypto_identity_change_pin_violation_new">"Identita uživatele %1$s %2$s se změnila. %3$s"</string>
|
||||
|
|
@ -474,6 +490,7 @@ Opravdu chcete pokračovat?"</string>
|
|||
<string name="screen_share_this_location_action">"Sdílet tuto polohu"</string>
|
||||
<string name="screen_space_list_description">"Prostory, které jste vytvořili nebo se k nim připojili."</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_empty_state_title">"Vytvořte prostory pro uspořádání místností"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s prostor"</string>
|
||||
<string name="screen_space_list_title">"Prostory"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Zpráva nebyla odeslána, protože ověřená identita uživatele %1$s se změnila."</string>
|
||||
|
|
|
|||
|
|
@ -389,6 +389,7 @@ Põhjus: %1$s."</string>
|
|||
<string name="common_world_readable_history">"Kõik võivad ajalugu näha"</string>
|
||||
<string name="common_you">"Sina"</string>
|
||||
<string name="crypto_event_key_forwarded_known_profile_dialog_content">"Kuna sind polnud saatmise ajal jututoas, siis %1$s (%2$s) jagas seda sõnumit sinuga."</string>
|
||||
<string name="crypto_event_key_forwarded_unknown_profile_dialog_content">"%1$s jagas seda sõnumit, kuna sind ei olnud selle algse saatmise ajal jututoas."</string>
|
||||
<string name="crypto_history_visible">"See jututuba on seadistatud sedaviisi, et ka uued liikmed saavad lugeda varasemat ajalugu. %1$s"</string>
|
||||
<string name="crypto_identity_change_pin_violation">"Kasutaja %1$s võrguidentiteet on lähtestatud. %2$s"</string>
|
||||
<string name="crypto_identity_change_pin_violation_new">"Kasutaja %1$s %2$s võrguidentiteet on lähtestatud. %3$s"</string>
|
||||
|
|
|
|||
|
|
@ -285,8 +285,10 @@ Raison : %1$s."</string>
|
|||
</plurals>
|
||||
<string name="common_preparing">"Préparation…"</string>
|
||||
<string name="common_privacy_policy">"Politique de confidentialité"</string>
|
||||
<string name="common_private">"Privé"</string>
|
||||
<string name="common_private_room">"Salon privé"</string>
|
||||
<string name="common_private_space">"Espace privé"</string>
|
||||
<string name="common_public">"Public"</string>
|
||||
<string name="common_public_room">"Salon public"</string>
|
||||
<string name="common_public_space">"Espace public"</string>
|
||||
<string name="common_reaction">"Réaction"</string>
|
||||
|
|
@ -341,6 +343,7 @@ Raison : %1$s."</string>
|
|||
<string name="common_something_went_wrong">"Une erreur s’est produite"</string>
|
||||
<string name="common_something_went_wrong_message">"Nous avons rencontré un problème. Veuillez réessayer."</string>
|
||||
<string name="common_space">"Espace"</string>
|
||||
<string name="common_space_members">"Membres de l’espace"</string>
|
||||
<string name="common_space_topic_placeholder">"Quel est le sujet de cet espace ?"</string>
|
||||
<plurals name="common_spaces">
|
||||
<item quantity="one">"%1$d Espace"</item>
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@
|
|||
<string name="a11y_your_avatar">"Din avatar"</string>
|
||||
<string name="action_accept">"Godta"</string>
|
||||
<string name="action_add_caption">"Legg til bildetekst"</string>
|
||||
<string name="action_add_existing_rooms">"Legg til eksisterende rom"</string>
|
||||
<string name="action_add_to_timeline">"Legg til i tidslinjen"</string>
|
||||
<string name="action_back">"Tilbake"</string>
|
||||
<string name="action_call">"Ring"</string>
|
||||
|
|
@ -75,6 +76,7 @@
|
|||
<string name="action_copy_text">"Kopier tekst"</string>
|
||||
<string name="action_create">"Opprett"</string>
|
||||
<string name="action_create_room">"Opprett rom"</string>
|
||||
<string name="action_create_space">"Opprett område"</string>
|
||||
<string name="action_deactivate">"Deaktiver"</string>
|
||||
<string name="action_deactivate_account">"Deaktiver kontoen"</string>
|
||||
<string name="action_decline">"Avslå"</string>
|
||||
|
|
@ -162,6 +164,7 @@
|
|||
<string name="action_static_map_load">"Trykk for å laste inn kart"</string>
|
||||
<string name="action_take_photo">"Ta bilde"</string>
|
||||
<string name="action_tap_for_options">"Trykk for alternativer"</string>
|
||||
<string name="action_translate">"Oversett"</string>
|
||||
<string name="action_try_again">"Prøv igjen"</string>
|
||||
<string name="action_unpin">"Løsne"</string>
|
||||
<string name="action_view">"Vis"</string>
|
||||
|
|
@ -191,6 +194,7 @@
|
|||
<string name="common_copied_to_clipboard">"Kopiert til utklippstavlen"</string>
|
||||
<string name="common_copyright">"Opphavsrett"</string>
|
||||
<string name="common_creating_room">"Oppretter rom …"</string>
|
||||
<string name="common_creating_space">"Oppretter område…"</string>
|
||||
<string name="common_current_user_canceled_knock">"Forespørsel kansellert"</string>
|
||||
<string name="common_current_user_left_room">"Forlot rommet"</string>
|
||||
<string name="common_current_user_left_space">"Forlot område"</string>
|
||||
|
|
@ -236,6 +240,7 @@
|
|||
<string name="common_light">"Lys"</string>
|
||||
<string name="common_line_copied_to_clipboard">"Linje kopiert til utklippstavlen"</string>
|
||||
<string name="common_link_copied_to_clipboard">"Lenke kopiert til utklippstavlen"</string>
|
||||
<string name="common_link_new_device">"Koble til ny enhet"</string>
|
||||
<string name="common_loading">"Laster inn…"</string>
|
||||
<string name="common_loading_more">"Laster inn mer…"</string>
|
||||
<plurals name="common_many_members">
|
||||
|
|
@ -248,10 +253,12 @@
|
|||
</plurals>
|
||||
<string name="common_message">"Melding"</string>
|
||||
<string name="common_message_actions">"Meldingshandlinger"</string>
|
||||
<string name="common_message_failed_to_send">"Sending av beskjed feilet"</string>
|
||||
<string name="common_message_layout">"Meldingsoppsett"</string>
|
||||
<string name="common_message_removed">"Melding fjernet"</string>
|
||||
<string name="common_modern">"Moderne"</string>
|
||||
<string name="common_mute">"Demp"</string>
|
||||
<string name="common_name">"Navn"</string>
|
||||
<string name="common_name_and_id">"%1$s (%2$s)"</string>
|
||||
<string name="common_no_results">"Ingen resultater"</string>
|
||||
<string name="common_no_room_name">"Ingen romnavn"</string>
|
||||
|
|
@ -324,6 +331,7 @@
|
|||
<string name="common_something_went_wrong">"Noe gikk galt"</string>
|
||||
<string name="common_something_went_wrong_message">"Vi har støtt på et problem. Vennligst prøv igjen."</string>
|
||||
<string name="common_space">"Område"</string>
|
||||
<string name="common_space_topic_placeholder">"Hva handler dette området om?"</string>
|
||||
<plurals name="common_spaces">
|
||||
<item quantity="one">"%1$d Område"</item>
|
||||
<item quantity="other">"%1$d Områder"</item>
|
||||
|
|
@ -331,6 +339,7 @@
|
|||
<string name="common_starting_chat">"Starter chat…"</string>
|
||||
<string name="common_sticker">"Klistremerke"</string>
|
||||
<string name="common_success">"Suksess"</string>
|
||||
<string name="common_suggested">"Foreslått"</string>
|
||||
<string name="common_suggestions">"Forslag"</string>
|
||||
<string name="common_syncing">"Synkroniserer"</string>
|
||||
<string name="common_system">"System"</string>
|
||||
|
|
@ -368,6 +377,7 @@
|
|||
<string name="common_waiting">"Venter…"</string>
|
||||
<string name="common_waiting_for_decryption_key">"Venter på denne meldingen"</string>
|
||||
<string name="common_you">"Du"</string>
|
||||
<string name="crypto_history_visible">"Dette rommet er konfigurert slik at nye medlemmer kan lese historikken.%1$s"</string>
|
||||
<string name="crypto_identity_change_pin_violation">"%1$s\'s identitet ble tilbakestilt. %2$s"</string>
|
||||
<string name="crypto_identity_change_pin_violation_new">"%1$ss %2$s-identitet ble tilbakestilt. %3$s"</string>
|
||||
<string name="crypto_identity_change_pin_violation_new_user_id">"(%1$s)"</string>
|
||||
|
|
@ -459,6 +469,7 @@ Er du sikker på at du vil fortsette?"</string>
|
|||
<string name="screen_share_this_location_action">"Del denne lokasjonen"</string>
|
||||
<string name="screen_space_list_description">"Områder du har opprettet eller blitt med i."</string>
|
||||
<string name="screen_space_list_details">"%1$s • %2$s"</string>
|
||||
<string name="screen_space_list_empty_state_title">"Opprett område for å organisere rom"</string>
|
||||
<string name="screen_space_list_parent_space">"%1$s område"</string>
|
||||
<string name="screen_space_list_title">"Områder"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Meldingen ble ikke sendt fordi %1$ss verifiserte identitet er tilbakestilt."</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue