Merge branch 'develop' into jonny/timeline-poll-edited

This commit is contained in:
Benoit Marty 2023-12-04 16:01:09 +01:00 committed by GitHub
commit f6ec76b5ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
602 changed files with 5298 additions and 1508 deletions

View file

@ -41,7 +41,7 @@ interface MatrixClient : Closeable {
val roomListService: RoomListService
val mediaLoader: MatrixMediaLoader
suspend fun getRoom(roomId: RoomId): MatrixRoom?
suspend fun findDM(userId: UserId): MatrixRoom?
suspend fun findDM(userId: UserId): RoomId?
suspend fun ignoreUser(userId: UserId): Result<Unit>
suspend fun unignoreUser(userId: UserId): Result<Unit>
suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId>

View file

@ -16,14 +16,12 @@
package io.element.android.libraries.matrix.api.encryption
import androidx.compose.runtime.Immutable
@Immutable
sealed interface BackupUploadState {
data object Unknown : BackupUploadState
data class CheckingIfUploadNeeded(
val backedUpCount: Int,
val totalCount: Int,
) : BackupUploadState
data object Waiting : BackupUploadState
data class Uploading(

View file

@ -17,9 +17,10 @@
package io.element.android.libraries.matrix.api.encryption
sealed interface EnableRecoveryProgress {
data object Unknown : EnableRecoveryProgress
data object CreatingRecoveryKey : EnableRecoveryProgress
data object Starting : EnableRecoveryProgress
data object CreatingBackup : EnableRecoveryProgress
data object CreatingRecoveryKey : EnableRecoveryProgress
data class BackingUp(val backedUpCount: Int, val totalCount: Int) : EnableRecoveryProgress
data object RoomKeyUploadError : EnableRecoveryProgress
data class Done(val recoveryKey: String) : EnableRecoveryProgress
}

View file

@ -16,6 +16,9 @@
package io.element.android.libraries.matrix.api.encryption
import androidx.compose.runtime.Immutable
@Immutable
sealed interface SteadyStateException {
/**
* The backup can be deleted.

View file

@ -16,9 +16,10 @@
package io.element.android.libraries.matrix.api.media
import java.time.Duration
import kotlinx.collections.immutable.ImmutableList
import kotlin.time.Duration
data class AudioDetails(
val duration: Duration,
val waveform: List<Float>,
val waveform: ImmutableList<Float>,
)

View file

@ -16,7 +16,7 @@
package io.element.android.libraries.matrix.api.media
import java.time.Duration
import kotlin.time.Duration
data class AudioInfo(
val duration: Duration?,

View file

@ -16,7 +16,7 @@
package io.element.android.libraries.matrix.api.media
import java.time.Duration
import kotlin.time.Duration
data class VideoInfo(
val duration: Duration?,

View file

@ -17,18 +17,21 @@
package io.element.android.libraries.matrix.api.permalink
import android.net.Uri
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
/**
* This sealed class represents all the permalink cases.
* You don't have to instantiate yourself but should use [PermalinkParser] instead.
*/
@Immutable
sealed interface PermalinkData {
data class RoomLink(
val roomIdOrAlias: String,
val isRoomAlias: Boolean,
val eventId: String?,
val viaParameters: List<String>
val viaParameters: ImmutableList<String>
) : PermalinkData
/*

View file

@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.api.permalink
import android.net.Uri
import android.net.UrlQuerySanitizer
import io.element.android.libraries.matrix.api.core.MatrixPatterns
import kotlinx.collections.immutable.toImmutableList
import timber.log.Timber
import java.net.URLDecoder
@ -80,7 +81,7 @@ object PermalinkParser {
roomIdOrAlias = decodedIdentifier,
isRoomAlias = true,
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
viaParameters = viaQueryParameters
viaParameters = viaQueryParameters.toImmutableList()
)
}
else -> PermalinkData.FallbackLink(uri, MatrixPatterns.isGroupId(identifier))
@ -119,7 +120,7 @@ object PermalinkParser {
roomIdOrAlias = identifier,
isRoomAlias = false,
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
viaParameters = viaQueryParameters
viaParameters = viaQueryParameters.toImmutableList()
)
}
}

View file

@ -132,6 +132,8 @@ interface MatrixRoom : Closeable {
suspend fun canUserTriggerRoomNotification(userId: UserId): Result<Boolean>
suspend fun canUserJoinCall(userId: UserId): Result<Boolean>
suspend fun updateAvatar(mimeType: String, data: ByteArray): Result<Unit>
suspend fun removeAvatar(): Result<Unit>
@ -238,5 +240,7 @@ interface MatrixRoom : Closeable {
*/
fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result<MatrixWidgetDriver>
suspend fun pollHistory(): MatrixTimeline
override fun close() = destroy()
}

View file

@ -16,8 +16,11 @@
package io.element.android.libraries.matrix.api.room
import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import kotlinx.collections.immutable.ImmutableList
@Immutable
data class MatrixRoomInfo(
val id: String,
val name: String?,
@ -28,7 +31,7 @@ data class MatrixRoomInfo(
val isSpace: Boolean,
val isTombstoned: Boolean,
val canonicalAlias: String?,
val alternativeAliases: List<String>,
val alternativeAliases: ImmutableList<String>,
val currentUserMembership: CurrentUserMembership,
val latestEvent: EventTimelineItem?,
val inviter: RoomMember?,
@ -39,5 +42,5 @@ data class MatrixRoomInfo(
val notificationCount: Long,
val userDefinedNotificationMode: RoomNotificationMode?,
val hasRoomCall: Boolean,
val activeRoomCallParticipants: List<String>
val activeRoomCallParticipants: ImmutableList<String>
)

View file

@ -17,13 +17,14 @@
package io.element.android.libraries.matrix.api.room
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
@Immutable
sealed interface MatrixRoomMembersState {
data object Unknown : MatrixRoomMembersState
data class Pending(val prevRoomMembers: List<RoomMember>? = null) : MatrixRoomMembersState
data class Error(val failure: Throwable, val prevRoomMembers: List<RoomMember>? = null) : MatrixRoomMembersState
data class Ready(val roomMembers: List<RoomMember>) : MatrixRoomMembersState
data class Pending(val prevRoomMembers: ImmutableList<RoomMember>? = null) : MatrixRoomMembersState
data class Error(val failure: Throwable, val prevRoomMembers: ImmutableList<RoomMember>? = null) : MatrixRoomMembersState
data class Ready(val roomMembers: ImmutableList<RoomMember>) : MatrixRoomMembersState
}
fun MatrixRoomMembersState.roomMembers(): List<RoomMember>? {

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.room
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
/**
* Try to find an existing DM with the given user, or create one if none exists.
*/
suspend fun MatrixClient.startDM(userId: UserId): StartDMResult {
val existingDM = findDM(userId)
return if (existingDM != null) {
StartDMResult.Success(existingDM, isNew = false)
} else {
createDM(userId).fold(
{ StartDMResult.Success(it, isNew = true) },
{ StartDMResult.Failure(it) }
)
}
}
sealed interface StartDMResult {
data class Success(val roomId: RoomId, val isNew: Boolean) : StartDMResult
data class Failure(val throwable: Throwable) : StartDMResult
}

View file

@ -16,6 +16,12 @@
package io.element.android.libraries.matrix.api.roomlist
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
/**
* RoomList with dynamic filtering and loading.
* This is useful for large lists of rooms.
@ -23,17 +29,17 @@ package io.element.android.libraries.matrix.api.roomlist
*/
interface DynamicRoomList : RoomList {
companion object {
const val DEFAULT_PAGE_SIZE = 20
const val DEFAULT_PAGES_TO_LOAD = 10
}
sealed interface Filter {
/**
* No filter applied.
*/
data object All : Filter
/**
* Filter only the left rooms.
*/
data object AllNonLeft : Filter
/**
* Filter all rooms.
*/
@ -45,6 +51,10 @@ interface DynamicRoomList : RoomList {
data class NormalizedMatchRoomName(val pattern: String) : Filter
}
val currentFilter: StateFlow<Filter>
val loadedPages: StateFlow<Int>
val pageSize: Int
/**
* Load more rooms into the list if possible.
*/
@ -61,3 +71,29 @@ interface DynamicRoomList : RoomList {
*/
suspend fun updateFilter(filter: Filter)
}
/**
* Offers a way to load all the rooms incrementally.
* It will load more room until all are loaded.
* If total number of rooms increase, it will load more pages if needed.
* The number of rooms is independent of the filter.
*/
fun DynamicRoomList.loadAllIncrementally(coroutineScope: CoroutineScope) {
combine(
loadedPages,
loadingState,
) { loadedPages, loadingState ->
loadedPages to loadingState
}
.onEach { (loadedPages, loadingState) ->
when (loadingState) {
is RoomList.LoadingState.Loaded -> {
if (pageSize * loadedPages < loadingState.numberOfRooms) {
loadMore()
}
}
RoomList.LoadingState.NotLoaded -> Unit
}
}
.launchIn(coroutineScope)
}

View file

@ -16,6 +16,7 @@
package io.element.android.libraries.matrix.api.roomlist
import androidx.compose.runtime.Immutable
import kotlinx.coroutines.flow.StateFlow
/**
@ -25,6 +26,7 @@ import kotlinx.coroutines.flow.StateFlow
*/
interface RoomListService {
@Immutable
sealed interface State {
data object Idle : State
data object Running : State
@ -32,16 +34,17 @@ interface RoomListService {
data object Terminated : State
}
@Immutable
sealed interface SyncIndicator {
data object Show : SyncIndicator
data object Hide : SyncIndicator
}
/**
* returns a [RoomList] object of all rooms we want to display.
* returns a [DynamicRoomList] object of all rooms we want to display.
* This will exclude some rooms like the invites, or spaces.
*/
val allRooms: RoomList
val allRooms: DynamicRoomList
/**
* returns a [RoomList] object of all invites.

View file

@ -20,7 +20,7 @@ import io.element.android.libraries.matrix.api.core.EventId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
interface MatrixTimeline {
interface MatrixTimeline: AutoCloseable {
data class PaginationState(
val isBackPaginating: Boolean,

View file

@ -16,11 +16,15 @@
package io.element.android.libraries.matrix.api.timeline.item.event
import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.poll.PollAnswer
import io.element.android.libraries.matrix.api.poll.PollKind
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
@Immutable
sealed interface EventContent
data class MessageContent(
@ -43,8 +47,8 @@ data class PollContent(
val question: String,
val kind: PollKind,
val maxSelections: ULong,
val answers: List<PollAnswer>,
val votes: Map<String, List<UserId>>,
val answers: ImmutableList<PollAnswer>,
val votes: ImmutableMap<String, ImmutableList<UserId>>,
val endTime: ULong?,
val isEdited: Boolean,
) : EventContent
@ -52,6 +56,8 @@ data class PollContent(
data class UnableToDecryptContent(
val data: Data
) : EventContent {
@Immutable
sealed interface Data {
data class OlmV1Curve25519AesSha2(
val senderKey: String

View file

@ -16,7 +16,11 @@
package io.element.android.libraries.matrix.api.timeline.item.event
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
@Immutable
data class EventReaction(
val key: String,
val senders: List<ReactionSender>
val senders: ImmutableList<ReactionSender>
)

View file

@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import kotlinx.collections.immutable.ImmutableList
data class EventTimelineItem(
val eventId: EventId?,
@ -29,8 +30,8 @@ data class EventTimelineItem(
val isOwn: Boolean,
val isRemote: Boolean,
val localSendState: LocalEventSendState?,
val reactions: List<EventReaction>,
val receipts: List<Receipt>,
val reactions: ImmutableList<EventReaction>,
val receipts: ImmutableList<Receipt>,
val sender: UserId,
val senderProfile: ProfileTimelineDetails,
val timestamp: Long,

View file

@ -16,9 +16,11 @@
package io.element.android.libraries.matrix.api.timeline.item.event
import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
@Immutable
sealed interface InReplyTo {
/** The event details are not loaded yet. We can fetch them. */
data class NotLoaded(val eventId: EventId) : InReplyTo

View file

@ -16,8 +16,10 @@
package io.element.android.libraries.matrix.api.timeline.item.event
import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.core.EventId
@Immutable
sealed interface LocalEventSendState {
data object NotSentYet : LocalEventSendState
data object Canceled : LocalEventSendState

View file

@ -16,6 +16,7 @@
package io.element.android.libraries.matrix.api.timeline.item.event
import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.media.AudioDetails
import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.media.FileInfo
@ -23,6 +24,7 @@ import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.media.VideoInfo
@Immutable
sealed interface MessageType
data class EmoteMessageType(

View file

@ -16,6 +16,9 @@
package io.element.android.libraries.matrix.api.timeline.item.event
import androidx.compose.runtime.Immutable
@Immutable
sealed interface OtherState {
data object PolicyRuleRoom : OtherState
data object PolicyRuleServer : OtherState

View file

@ -16,6 +16,9 @@
package io.element.android.libraries.matrix.api.timeline.item.event
import androidx.compose.runtime.Immutable
@Immutable
sealed interface ProfileTimelineDetails {
data object Unavailable : ProfileTimelineDetails

View file

@ -16,7 +16,9 @@
package io.element.android.libraries.matrix.api.user
import kotlinx.collections.immutable.ImmutableList
data class MatrixSearchUserResults(
val results: List<MatrixUser>,
val results: ImmutableList<MatrixUser>,
val limited: Boolean,
)

View file

@ -16,6 +16,8 @@
package io.element.android.libraries.matrix.api.verification
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@ -75,6 +77,7 @@ interface SessionVerificationService {
}
/** Verification status of the current session. */
@Immutable
sealed interface SessionVerifiedStatus {
/** Unknown status, we couldn't read the actual value from the SDK. */
data object Unknown : SessionVerifiedStatus
@ -87,6 +90,7 @@ sealed interface SessionVerifiedStatus {
}
/** States produced by the [SessionVerificationService]. */
@Immutable
sealed interface VerificationFlowState {
/** Initial state. */
data object Initial : VerificationFlowState
@ -98,7 +102,7 @@ sealed interface VerificationFlowState {
data object StartedSasVerification : VerificationFlowState
/** Verification data for the SAS verification (emojis) received. */
data class ReceivedVerificationData(val emoji: List<VerificationEmoji>) : VerificationFlowState
data class ReceivedVerificationData(val emoji: ImmutableList<VerificationEmoji>) : VerificationFlowState
/** Verification completed successfully. */
data object Finished : VerificationFlowState

View file

@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.api.permalink
import com.google.common.truth.Truth.assertThat
import kotlinx.collections.immutable.persistentListOf
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@ -66,7 +67,7 @@ class PermalinkParserTest {
roomIdOrAlias = "!aBCD1234:matrix.org",
isRoomAlias = false,
eventId = null,
viaParameters = emptyList(),
viaParameters = persistentListOf(),
)
)
}
@ -79,7 +80,7 @@ class PermalinkParserTest {
roomIdOrAlias = "!aBCD1234:matrix.org",
isRoomAlias = false,
eventId = "\$1234567890abcdef:matrix.org",
viaParameters = emptyList(),
viaParameters = persistentListOf(),
)
)
}
@ -92,7 +93,7 @@ class PermalinkParserTest {
roomIdOrAlias = "!aBCD1234:matrix.org",
isRoomAlias = false,
eventId = null,
viaParameters = emptyList(),
viaParameters = persistentListOf(),
)
)
}
@ -105,7 +106,7 @@ class PermalinkParserTest {
roomIdOrAlias = "!aBCD1234:matrix.org",
isRoomAlias = false,
eventId = "\$1234567890abcdef:matrix.org",
viaParameters = listOf("matrix.org", "matrix.com"),
viaParameters = persistentListOf("matrix.org", "matrix.com"),
)
)
}
@ -118,7 +119,7 @@ class PermalinkParserTest {
roomIdOrAlias = "#element-android:matrix.org",
isRoomAlias = true,
eventId = null,
viaParameters = emptyList(),
viaParameters = persistentListOf(),
)
)
}