Merge pull request #5378 from element-hq/feature/fga/join_space
Feature : Join Space (WIP)
This commit is contained in:
@@ -12,6 +12,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@@ -36,3 +37,11 @@ fun RoomInfo.toInviteData(): InviteData {
|
||||
isDm = isDm,
|
||||
)
|
||||
}
|
||||
|
||||
fun SpaceRoom.toInviteData(): InviteData {
|
||||
return InviteData(
|
||||
roomId = roomId,
|
||||
roomName = name ?: roomId.value,
|
||||
isDm = false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -43,4 +43,5 @@ dependencies {
|
||||
testImplementation(projects.features.invite.test)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.libraries.previewutils)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import androidx.compose.runtime.setValue
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.Inject
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
@@ -42,17 +43,21 @@ import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.matrix.api.exception.ErrorKind
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipDetails
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||
import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Optional
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
@Inject
|
||||
class JoinRoomPresenter(
|
||||
@@ -80,6 +85,8 @@ class JoinRoomPresenter(
|
||||
): JoinRoomPresenter
|
||||
}
|
||||
|
||||
private val spaceList = matrixClient.spaceService.spaceRoomList(roomId)
|
||||
|
||||
@Composable
|
||||
override fun present(): JoinRoomState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
@@ -87,6 +94,9 @@ class JoinRoomPresenter(
|
||||
val roomInfo by remember {
|
||||
matrixClient.getRoomInfoFlow(roomId)
|
||||
}.collectAsState(initial = Optional.empty())
|
||||
val spaceRoom by remember {
|
||||
spaceList.currentSpaceFlow()
|
||||
}.collectAsState()
|
||||
val joinAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
val knockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
val cancelKnockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
@@ -96,55 +106,41 @@ class JoinRoomPresenter(
|
||||
val hideInviteAvatars by matrixClient.rememberHideInvitesAvatar()
|
||||
val canReportRoom by produceState(false) { value = matrixClient.canReportRoom() }
|
||||
|
||||
val contentState by produceState<ContentState>(
|
||||
initialValue = ContentState.Loading,
|
||||
key1 = roomInfo,
|
||||
key2 = retryCount,
|
||||
key3 = isDismissingContent,
|
||||
) {
|
||||
var contentState by remember {
|
||||
mutableStateOf<ContentState>(ContentState.Loading)
|
||||
}
|
||||
LaunchedEffect(roomInfo, retryCount, isDismissingContent, spaceRoom) {
|
||||
when {
|
||||
isDismissingContent -> value = ContentState.Dismissing
|
||||
isDismissingContent -> contentState = ContentState.Dismissing
|
||||
roomInfo.isPresent -> {
|
||||
val notJoinedRoom = matrixClient.getRoomPreview(roomIdOrAlias, serverNames).getOrNull()
|
||||
val (sender, reason) = when (roomInfo.get().currentUserMembership) {
|
||||
CurrentUserMembership.BANNED -> {
|
||||
// Workaround to get info about the sender for banned rooms
|
||||
// TODO re-do this once we have a better API in the SDK
|
||||
val membershipDetails = notJoinedRoom?.membershipDetails()?.getOrNull()
|
||||
membershipDetails?.senderMember to membershipDetails?.currentUserMember?.membershipChangeReason
|
||||
}
|
||||
CurrentUserMembership.INVITED -> {
|
||||
roomInfo.get().inviter to null
|
||||
}
|
||||
else -> null to null
|
||||
}
|
||||
val membershipDetails = notJoinedRoom?.membershipDetails()?.getOrNull()
|
||||
val joinedMembersCountOverride = notJoinedRoom?.previewInfo?.numberOfJoinedMembers
|
||||
value = roomInfo.get().toContentState(
|
||||
membershipSender = sender,
|
||||
contentState = roomInfo.get().toContentState(
|
||||
joinedMembersCountOverride = joinedMembersCountOverride,
|
||||
reason = reason,
|
||||
membershipDetails = membershipDetails,
|
||||
childrenCount = spaceRoom.getOrNull()?.childrenCount,
|
||||
)
|
||||
}
|
||||
spaceRoom.isPresent -> {
|
||||
val spaceRoom = spaceRoom.get()
|
||||
// Only use this state when space is not locally known
|
||||
contentState = if (spaceRoom.state != null) {
|
||||
ContentState.Loading
|
||||
} else {
|
||||
spaceRoom.toContentState()
|
||||
}
|
||||
}
|
||||
roomDescription.isPresent -> {
|
||||
value = roomDescription.get().toContentState()
|
||||
contentState = roomDescription.get().toContentState()
|
||||
}
|
||||
else -> {
|
||||
value = ContentState.Loading
|
||||
contentState = ContentState.Loading
|
||||
val result = matrixClient.getRoomPreview(roomIdOrAlias, serverNames)
|
||||
value = result.fold(
|
||||
contentState = result.fold(
|
||||
onSuccess = { preview ->
|
||||
val membershipInfo = when (preview.previewInfo.membership) {
|
||||
CurrentUserMembership.INVITED,
|
||||
CurrentUserMembership.BANNED,
|
||||
CurrentUserMembership.KNOCKED -> {
|
||||
preview.membershipDetails().getOrNull()
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
preview.previewInfo.toContentState(
|
||||
senderMember = membershipInfo?.senderMember,
|
||||
reason = membershipInfo?.currentUserMember?.membershipChangeReason,
|
||||
)
|
||||
val membershipDetails = preview.membershipDetails().getOrNull()
|
||||
preview.previewInfo.toContentState(membershipDetails)
|
||||
},
|
||||
onFailure = { throwable ->
|
||||
if (throwable is ClientException.MatrixApi && (throwable.kind == ErrorKind.NotFound || throwable.kind == ErrorKind.Forbidden)) {
|
||||
@@ -252,30 +248,56 @@ class JoinRoomPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun RoomPreviewInfo.toContentState(senderMember: RoomMember?, reason: String?): ContentState {
|
||||
private fun RoomPreviewInfo.toContentState(membershipDetails: RoomMembershipDetails?): ContentState {
|
||||
return ContentState.Loaded(
|
||||
roomId = roomId,
|
||||
name = name,
|
||||
topic = topic,
|
||||
alias = canonicalAlias,
|
||||
numberOfMembers = numberOfJoinedMembers,
|
||||
isDm = false,
|
||||
roomType = roomType,
|
||||
roomAvatarUrl = avatarUrl,
|
||||
joinAuthorisationStatus = when (membership) {
|
||||
CurrentUserMembership.INVITED -> {
|
||||
JoinAuthorisationStatus.IsInvited(
|
||||
inviteData = toInviteData(),
|
||||
inviteSender = senderMember?.toInviteSender()
|
||||
)
|
||||
}
|
||||
CurrentUserMembership.BANNED -> JoinAuthorisationStatus.IsBanned(senderMember?.toInviteSender(), reason)
|
||||
CurrentUserMembership.KNOCKED -> JoinAuthorisationStatus.IsKnocked
|
||||
else -> joinRule.toJoinAuthorisationStatus()
|
||||
joinAuthorisationStatus = computeJoinAuthorisationStatus(
|
||||
membership,
|
||||
membershipDetails,
|
||||
joinRule,
|
||||
{ toInviteData() }
|
||||
),
|
||||
joinRule = joinRule,
|
||||
details = when (roomType) {
|
||||
is RoomType.Other,
|
||||
RoomType.Room -> LoadedDetails.Room(
|
||||
isDm = false,
|
||||
)
|
||||
RoomType.Space -> LoadedDetails.Space(
|
||||
childrenCount = 0,
|
||||
heroes = persistentListOf(),
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun SpaceRoom.toContentState(): ContentState {
|
||||
return ContentState.Loaded(
|
||||
roomId = roomId,
|
||||
name = name,
|
||||
topic = topic,
|
||||
alias = canonicalAlias,
|
||||
numberOfMembers = numJoinedMembers.toLong(),
|
||||
roomAvatarUrl = avatarUrl,
|
||||
joinAuthorisationStatus = computeJoinAuthorisationStatus(
|
||||
membership = state,
|
||||
membershipDetails = null,
|
||||
joinRule = joinRule,
|
||||
inviteData = { toInviteData() }
|
||||
),
|
||||
joinRule = joinRule,
|
||||
details = LoadedDetails.Space(
|
||||
childrenCount = childrenCount,
|
||||
heroes = heroes.toPersistentList(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun RoomDescription.toContentState(): ContentState {
|
||||
return ContentState.Loaded(
|
||||
@@ -284,22 +306,29 @@ internal fun RoomDescription.toContentState(): ContentState {
|
||||
topic = topic,
|
||||
alias = alias,
|
||||
numberOfMembers = numberOfMembers,
|
||||
isDm = false,
|
||||
roomType = RoomType.Room,
|
||||
roomAvatarUrl = avatarUrl,
|
||||
joinAuthorisationStatus = when (joinRule) {
|
||||
RoomDescription.JoinRule.KNOCK -> JoinAuthorisationStatus.CanKnock
|
||||
RoomDescription.JoinRule.PUBLIC -> JoinAuthorisationStatus.CanJoin
|
||||
else -> JoinAuthorisationStatus.Unknown
|
||||
}
|
||||
},
|
||||
joinRule = when (joinRule) {
|
||||
RoomDescription.JoinRule.KNOCK -> JoinRule.Knock
|
||||
RoomDescription.JoinRule.PUBLIC -> JoinRule.Public
|
||||
RoomDescription.JoinRule.RESTRICTED -> JoinRule.Restricted(persistentListOf())
|
||||
RoomDescription.JoinRule.KNOCK_RESTRICTED -> JoinRule.KnockRestricted(persistentListOf())
|
||||
RoomDescription.JoinRule.INVITE -> JoinRule.Invite
|
||||
RoomDescription.JoinRule.UNKNOWN -> null
|
||||
},
|
||||
details = LoadedDetails.Room(isDm = false)
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun RoomInfo.toContentState(
|
||||
membershipSender: RoomMember?,
|
||||
joinedMembersCountOverride: Long?,
|
||||
reason: String?,
|
||||
membershipDetails: RoomMembershipDetails?,
|
||||
childrenCount: Int?,
|
||||
): ContentState {
|
||||
return ContentState.Loaded(
|
||||
roomId = id,
|
||||
@@ -307,24 +336,49 @@ internal fun RoomInfo.toContentState(
|
||||
topic = topic,
|
||||
alias = canonicalAlias,
|
||||
numberOfMembers = joinedMembersCountOverride ?: joinedMembersCount,
|
||||
isDm = isDm,
|
||||
roomType = if (isSpace) RoomType.Space else RoomType.Room,
|
||||
roomAvatarUrl = avatarUrl,
|
||||
joinAuthorisationStatus = when (currentUserMembership) {
|
||||
CurrentUserMembership.INVITED -> JoinAuthorisationStatus.IsInvited(
|
||||
inviteData = toInviteData(),
|
||||
inviteSender = membershipSender?.toInviteSender(),
|
||||
joinAuthorisationStatus = computeJoinAuthorisationStatus(
|
||||
membership = currentUserMembership,
|
||||
membershipDetails = membershipDetails,
|
||||
joinRule = joinRule,
|
||||
inviteData = { toInviteData() }
|
||||
),
|
||||
joinRule = joinRule,
|
||||
details = if (isSpace) {
|
||||
LoadedDetails.Space(
|
||||
childrenCount = childrenCount ?: 0,
|
||||
heroes = heroes,
|
||||
)
|
||||
CurrentUserMembership.BANNED -> JoinAuthorisationStatus.IsBanned(
|
||||
banSender = membershipSender?.toInviteSender(),
|
||||
reason = reason,
|
||||
} else {
|
||||
LoadedDetails.Room(
|
||||
isDm = isDm,
|
||||
)
|
||||
CurrentUserMembership.KNOCKED -> JoinAuthorisationStatus.IsKnocked
|
||||
else -> joinRule.toJoinAuthorisationStatus()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun computeJoinAuthorisationStatus(
|
||||
membership: CurrentUserMembership?,
|
||||
membershipDetails: RoomMembershipDetails?,
|
||||
joinRule: JoinRule?,
|
||||
inviteData: () -> InviteData,
|
||||
): JoinAuthorisationStatus {
|
||||
return when (membership) {
|
||||
CurrentUserMembership.INVITED -> {
|
||||
JoinAuthorisationStatus.IsInvited(
|
||||
inviteData = inviteData(),
|
||||
inviteSender = membershipDetails?.senderMember?.toInviteSender()
|
||||
)
|
||||
}
|
||||
CurrentUserMembership.BANNED -> JoinAuthorisationStatus.IsBanned(
|
||||
membershipDetails?.senderMember?.toInviteSender(),
|
||||
membershipDetails?.membershipChangeReason
|
||||
)
|
||||
CurrentUserMembership.KNOCKED -> JoinAuthorisationStatus.IsKnocked
|
||||
else -> joinRule.toJoinAuthorisationStatus()
|
||||
}
|
||||
}
|
||||
|
||||
private fun JoinRule?.toJoinAuthorisationStatus(): JoinAuthorisationStatus {
|
||||
return when (this) {
|
||||
JoinRule.Knock,
|
||||
|
||||
@@ -16,9 +16,11 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal const val MAX_KNOCK_MESSAGE_LENGTH = 500
|
||||
|
||||
@@ -41,9 +43,6 @@ data class JoinRoomState(
|
||||
val joinAuthorisationStatus = when (contentState) {
|
||||
is ContentState.Loaded -> {
|
||||
when {
|
||||
contentState.roomType == RoomType.Space -> {
|
||||
JoinAuthorisationStatus.IsSpace(applicationName)
|
||||
}
|
||||
isJoinActionUnauthorized -> {
|
||||
JoinAuthorisationStatus.Unauthorized
|
||||
}
|
||||
@@ -77,12 +76,13 @@ sealed interface ContentState {
|
||||
val topic: String?,
|
||||
val alias: RoomAlias?,
|
||||
val numberOfMembers: Long?,
|
||||
val isDm: Boolean,
|
||||
val roomType: RoomType,
|
||||
val roomAvatarUrl: String?,
|
||||
val joinAuthorisationStatus: JoinAuthorisationStatus,
|
||||
val joinRule: JoinRule?,
|
||||
val details: LoadedDetails,
|
||||
) : ContentState {
|
||||
val showMemberCount = numberOfMembers != null
|
||||
val isSpace = details is LoadedDetails.Space
|
||||
|
||||
fun avatarData(size: AvatarSize): AvatarData {
|
||||
return AvatarData(
|
||||
@@ -95,9 +95,20 @@ sealed interface ContentState {
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface LoadedDetails {
|
||||
data class Room(
|
||||
val isDm: Boolean,
|
||||
) : LoadedDetails
|
||||
|
||||
data class Space(
|
||||
val childrenCount: Int,
|
||||
val heroes: ImmutableList<MatrixUser>,
|
||||
) : LoadedDetails
|
||||
}
|
||||
|
||||
sealed interface JoinAuthorisationStatus {
|
||||
data object None : JoinAuthorisationStatus
|
||||
data class IsSpace(val applicationName: String) : JoinAuthorisationStatus
|
||||
data class IsInvited(val inviteData: InviteData, val inviteSender: InviteSender?) : JoinAuthorisationStatus
|
||||
data class IsBanned(val banSender: InviteSender?, val reason: String?) : JoinAuthorisationStatus
|
||||
data object IsKnocked : JoinAuthorisationStatus
|
||||
|
||||
@@ -20,9 +20,11 @@ import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
||||
override val values: Sequence<JoinRoomState>
|
||||
@@ -77,13 +79,17 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
||||
name = "A space",
|
||||
alias = null,
|
||||
topic = "This is the topic of a space",
|
||||
roomType = RoomType.Space,
|
||||
details = aLoadedDetailsSpace(
|
||||
childrenCount = 42,
|
||||
),
|
||||
)
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(
|
||||
name = "A DM",
|
||||
isDm = true,
|
||||
details = aLoadedDetailsRoom(
|
||||
isDm = true,
|
||||
),
|
||||
)
|
||||
),
|
||||
aJoinRoomState(
|
||||
@@ -156,20 +162,34 @@ fun aLoadedContentState(
|
||||
alias: RoomAlias? = RoomAlias("#exa:matrix.org"),
|
||||
topic: String? = "Element X is a secure, private and decentralized messenger.",
|
||||
numberOfMembers: Long? = null,
|
||||
isDm: Boolean = false,
|
||||
roomType: RoomType = RoomType.Room,
|
||||
roomAvatarUrl: String? = null,
|
||||
joinAuthorisationStatus: JoinAuthorisationStatus = JoinAuthorisationStatus.Unknown,
|
||||
joinRule: JoinRule? = null,
|
||||
details: LoadedDetails = aLoadedDetailsRoom(isDm = false),
|
||||
) = ContentState.Loaded(
|
||||
roomId = roomId,
|
||||
name = name,
|
||||
alias = alias,
|
||||
topic = topic,
|
||||
numberOfMembers = numberOfMembers,
|
||||
isDm = isDm,
|
||||
roomType = roomType,
|
||||
roomAvatarUrl = roomAvatarUrl,
|
||||
joinAuthorisationStatus = joinAuthorisationStatus
|
||||
joinAuthorisationStatus = joinAuthorisationStatus,
|
||||
joinRule = joinRule,
|
||||
details = details,
|
||||
)
|
||||
|
||||
fun aLoadedDetailsRoom(
|
||||
isDm: Boolean = false,
|
||||
) = LoadedDetails.Room(
|
||||
isDm = isDm
|
||||
)
|
||||
|
||||
fun aLoadedDetailsSpace(
|
||||
childrenCount: Int = 0,
|
||||
heroes: List<MatrixUser> = emptyList(),
|
||||
) = LoadedDetails.Space(
|
||||
childrenCount = childrenCount,
|
||||
heroes = heroes.toPersistentList()
|
||||
)
|
||||
|
||||
fun aJoinRoomState(
|
||||
|
||||
@@ -7,21 +7,20 @@
|
||||
|
||||
package io.element.android.features.joinroom.impl
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -38,6 +37,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewDescriptionAtom
|
||||
@@ -65,14 +65,21 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.theme.placeholderBackground
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.ui.components.InviteSenderView
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.ui.components.SpaceInfoRow
|
||||
import io.element.android.libraries.matrix.ui.components.SpaceMembersView
|
||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
fun JoinRoomView(
|
||||
@@ -92,7 +99,7 @@ fun JoinRoomView(
|
||||
containerColor = Color.Transparent,
|
||||
contentPadding = PaddingValues(
|
||||
horizontal = 16.dp,
|
||||
vertical = 32.dp
|
||||
vertical = 24.dp
|
||||
),
|
||||
topBar = {
|
||||
JoinRoomTopBar(
|
||||
@@ -220,12 +227,14 @@ private fun JoinRoomFooter(
|
||||
onClick = { onDeclineInvite(joinAuthorisationStatus.inviteData, false) },
|
||||
modifier = Modifier.weight(1f),
|
||||
size = ButtonSize.LargeLowPadding,
|
||||
leadingIcon = IconSource.Vector(CompoundIcons.Close())
|
||||
)
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_accept),
|
||||
onClick = { onAcceptInvite(joinAuthorisationStatus.inviteData) },
|
||||
modifier = Modifier.weight(1f),
|
||||
size = ButtonSize.LargeLowPadding,
|
||||
leadingIcon = IconSource.Vector(CompoundIcons.Check())
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
@@ -278,7 +287,6 @@ private fun JoinRoomFooter(
|
||||
JoinAuthorisationStatus.Unknown -> JoinRestrictedFooter(onJoinRoom)
|
||||
JoinAuthorisationStatus.Restricted -> JoinRestrictedFooter(onJoinRoom)
|
||||
JoinAuthorisationStatus.Unauthorized -> JoinUnauthorizedFooter(onGoBack)
|
||||
is JoinAuthorisationStatus.IsSpace -> UnsupportedSpaceFooter(joinAuthorisationStatus.applicationName, onGoBack)
|
||||
JoinAuthorisationStatus.None -> Unit
|
||||
}
|
||||
}
|
||||
@@ -358,28 +366,6 @@ private fun JoinRestrictedFooter(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UnsupportedSpaceFooter(
|
||||
applicationName: String,
|
||||
onGoBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Announcement(
|
||||
title = stringResource(R.string.screen_join_room_space_not_supported_title),
|
||||
description = stringResource(R.string.screen_join_room_space_not_supported_description, applicationName),
|
||||
type = AnnouncementType.Informative(),
|
||||
)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_ok),
|
||||
onClick = onGoBack,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Large,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun JoinRoomContent(
|
||||
roomIdOrAlias: RoomIdOrAlias,
|
||||
@@ -397,19 +383,40 @@ private fun JoinRoomContent(
|
||||
IsKnockedLoadedContent()
|
||||
}
|
||||
else -> {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
val inviteSender = (contentState.joinAuthorisationStatus as? JoinAuthorisationStatus.IsInvited)?.inviteSender
|
||||
if (inviteSender != null) {
|
||||
InviteSenderView(inviteSender = inviteSender, hideAvatarImage = hideAvatarsImages)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
DefaultLoadedContent(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||
contentState = contentState,
|
||||
knockMessage = knockMessage,
|
||||
hideAvatarImage = hideAvatarsImages,
|
||||
onKnockMessageUpdate = onKnockMessageUpdate
|
||||
)
|
||||
when (contentState.joinAuthorisationStatus) {
|
||||
is JoinAuthorisationStatus.IsInvited -> {
|
||||
val inviteSender = contentState.joinAuthorisationStatus.inviteSender
|
||||
if (inviteSender != null) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InvitedByView(inviteSender, hideAvatarsImages)
|
||||
}
|
||||
}
|
||||
is JoinAuthorisationStatus.CanKnock -> {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
val supportingText = if (knockMessage.isNotEmpty()) {
|
||||
"${knockMessage.length}/$MAX_KNOCK_MESSAGE_LENGTH"
|
||||
} else {
|
||||
stringResource(R.string.screen_join_room_knock_message_description)
|
||||
}
|
||||
TextField(
|
||||
value = knockMessage,
|
||||
onValueChange = onKnockMessageUpdate,
|
||||
maxLines = 3,
|
||||
minLines = 3,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
supportingText = supportingText
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -422,6 +429,45 @@ private fun JoinRoomContent(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InvitedByView(
|
||||
sender: InviteSender,
|
||||
hideAvatarImage: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.screen_join_room_invited_by),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textSecondary
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Avatar(
|
||||
avatarData = sender.avatarData,
|
||||
avatarType = AvatarType.User,
|
||||
hideImage = hideAvatarImage,
|
||||
forcedAvatarSize = AvatarSize.RoomPreviewInviter.dp
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
text = sender.displayName,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
color = ElementTheme.colors.textPrimary
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = sender.userId.value,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = ElementTheme.colors.textSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UnknownRoomContent(
|
||||
modifier: Modifier = Modifier
|
||||
@@ -429,7 +475,21 @@ private fun UnknownRoomContent(
|
||||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
Spacer(modifier = Modifier.size(AvatarSize.RoomHeader.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(AvatarSize.RoomPreviewHeader.dp)
|
||||
.background(
|
||||
color = ElementTheme.colors.placeholderBackground,
|
||||
shape = CircleShape
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
tint = ElementTheme.colors.iconPrimary,
|
||||
imageVector = CompoundIcons.VisibilityOff(),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
title = {
|
||||
RoomPreviewTitleAtom(stringResource(R.string.screen_join_room_title_no_preview))
|
||||
@@ -448,7 +508,7 @@ private fun IncompleteContent(
|
||||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||
PlaceholderAtom(width = AvatarSize.RoomPreviewHeader.dp, height = AvatarSize.RoomPreviewHeader.dp)
|
||||
},
|
||||
title = {
|
||||
when (roomIdOrAlias) {
|
||||
@@ -471,43 +531,32 @@ private fun IncompleteContent(
|
||||
|
||||
@Composable
|
||||
private fun IsKnockedLoadedContent(modifier: Modifier = Modifier) {
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxHeight()
|
||||
.padding(horizontal = 16.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
IconTitleSubtitleMolecule(
|
||||
modifier = Modifier.sizeIn(minHeight = maxHeight * 0.7f),
|
||||
iconStyle = BigIcon.Style.SuccessSolid,
|
||||
title = stringResource(R.string.screen_join_room_knock_sent_title),
|
||||
subTitle = stringResource(R.string.screen_join_room_knock_sent_description),
|
||||
)
|
||||
}
|
||||
IconTitleSubtitleMolecule(
|
||||
modifier = modifier.padding(horizontal = 8.dp),
|
||||
iconStyle = BigIcon.Style.SuccessSolid,
|
||||
title = stringResource(R.string.screen_join_room_knock_sent_title),
|
||||
subTitle = stringResource(R.string.screen_join_room_knock_sent_description),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DefaultLoadedContent(
|
||||
contentState: ContentState.Loaded,
|
||||
knockMessage: String,
|
||||
hideAvatarImage: Boolean,
|
||||
onKnockMessageUpdate: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
Avatar(
|
||||
contentState.avatarData(AvatarSize.RoomHeader),
|
||||
contentState.avatarData(AvatarSize.RoomPreviewHeader),
|
||||
hideImage = hideAvatarImage,
|
||||
avatarType = AvatarType.Room(),
|
||||
avatarType = if (contentState.isSpace) AvatarType.Space() else AvatarType.Room(),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
if (contentState.name != null) {
|
||||
RoomPreviewTitleAtom(
|
||||
title = contentState.name,
|
||||
)
|
||||
RoomPreviewTitleAtom(title = contentState.name)
|
||||
} else {
|
||||
RoomPreviewTitleAtom(
|
||||
title = stringResource(id = CommonStrings.common_no_room_name),
|
||||
@@ -516,37 +565,32 @@ private fun DefaultLoadedContent(
|
||||
}
|
||||
},
|
||||
subtitle = {
|
||||
if (contentState.alias != null) {
|
||||
RoomPreviewSubtitleAtom(contentState.alias.value)
|
||||
}
|
||||
},
|
||||
description = {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
RoomPreviewDescriptionAtom(contentState.topic ?: "")
|
||||
if (contentState.joinAuthorisationStatus is JoinAuthorisationStatus.CanKnock) {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
val supportingText = if (knockMessage.isNotEmpty()) {
|
||||
"${knockMessage.length}/$MAX_KNOCK_MESSAGE_LENGTH"
|
||||
} else {
|
||||
stringResource(R.string.screen_join_room_knock_message_description)
|
||||
}
|
||||
TextField(
|
||||
value = knockMessage,
|
||||
onValueChange = onKnockMessageUpdate,
|
||||
maxLines = 3,
|
||||
minLines = 3,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
supportingText = supportingText
|
||||
when {
|
||||
contentState.details is LoadedDetails.Space -> {
|
||||
SpaceInfoRow(
|
||||
joinRule = contentState.joinRule ?: JoinRule.Public,
|
||||
numberOfRooms = contentState.details.childrenCount,
|
||||
)
|
||||
}
|
||||
contentState.alias != null -> {
|
||||
RoomPreviewSubtitleAtom(contentState.alias.value)
|
||||
}
|
||||
}
|
||||
},
|
||||
description = {
|
||||
RoomPreviewDescriptionAtom(
|
||||
contentState.topic ?: "",
|
||||
maxLines = if (contentState.joinAuthorisationStatus is JoinAuthorisationStatus.CanJoin) Int.MAX_VALUE else 2
|
||||
)
|
||||
},
|
||||
memberCount = {
|
||||
if (contentState.showMemberCount) {
|
||||
MembersCountMolecule(memberCount = contentState.numberOfMembers?.toInt() ?: 0)
|
||||
val membersCount = contentState.numberOfMembers?.toInt() ?: 0
|
||||
if (contentState.isSpace) {
|
||||
SpaceMembersView(persistentListOf(), membersCount)
|
||||
} else {
|
||||
MembersCountMolecule(memberCount = membersCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<string name="screen_join_room_fail_reason">"This room is either invite-only or there might be restrictions to access at space level."</string>
|
||||
<string name="screen_join_room_forget_action">"Forget this room"</string>
|
||||
<string name="screen_join_room_invite_required_message">"You need an invite in order to join this room"</string>
|
||||
<string name="screen_join_room_invited_by">"Invited by"</string>
|
||||
<string name="screen_join_room_join_action">"Join room"</string>
|
||||
<string name="screen_join_room_join_restricted_message">"You may need to be invited or be a member of a space in order to join."</string>
|
||||
<string name="screen_join_room_knock_action">"Send request to join"</string>
|
||||
|
||||
@@ -28,13 +28,11 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.matrix.api.exception.ErrorKind
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipDetails
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
@@ -50,8 +48,12 @@ import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import io.element.android.libraries.matrix.test.room.aRoomPreview
|
||||
import io.element.android.libraries.matrix.test.room.aRoomPreviewInfo
|
||||
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
|
||||
import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList
|
||||
import io.element.android.libraries.matrix.test.spaces.FakeSpaceService
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||
import io.element.android.libraries.previewutils.room.aSpaceRoom
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.any
|
||||
import io.element.android.tests.testutils.lambda.assert
|
||||
@@ -90,6 +92,9 @@ class JoinRoomPresenterTest {
|
||||
val roomInfo = aRoomInfo()
|
||||
val matrixClient = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
).apply {
|
||||
getRoomInfoFlowLambda = { _ ->
|
||||
flowOf(Optional.of(roomInfo))
|
||||
@@ -107,7 +112,7 @@ class JoinRoomPresenterTest {
|
||||
assertThat(contentState.topic).isEqualTo(roomInfo.topic)
|
||||
assertThat(contentState.alias).isEqualTo(roomInfo.canonicalAlias)
|
||||
assertThat(contentState.numberOfMembers).isEqualTo(roomInfo.joinedMembersCount)
|
||||
assertThat(contentState.isDm).isEqualTo(roomInfo.isDirect)
|
||||
assertThat(contentState.details).isEqualTo(aLoadedDetailsRoom(isDm = roomInfo.isDirect))
|
||||
assertThat(contentState.roomAvatarUrl).isEqualTo(roomInfo.avatarUrl)
|
||||
}
|
||||
}
|
||||
@@ -118,6 +123,9 @@ class JoinRoomPresenterTest {
|
||||
val roomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.INVITED)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
).apply {
|
||||
getRoomInfoFlowLambda = { _ ->
|
||||
flowOf(Optional.of(roomInfo))
|
||||
@@ -142,7 +150,7 @@ class JoinRoomPresenterTest {
|
||||
|
||||
@Test
|
||||
fun `present - when room is invited then join authorization is equal to invited, an inviter is provided`() = runTest {
|
||||
val inviter = aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob")
|
||||
val inviter = aRoomMember(userId = A_USER_ID_2, displayName = "Bob")
|
||||
val expectedInviteSender = inviter.toInviteSender()
|
||||
val roomInfo = aRoomInfo(
|
||||
currentUserMembership = CurrentUserMembership.INVITED,
|
||||
@@ -151,7 +159,21 @@ class JoinRoomPresenterTest {
|
||||
)
|
||||
val inviteData = roomInfo.toInviteData()
|
||||
val matrixClient = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.success(
|
||||
aRoomPreview(
|
||||
info = aRoomPreviewInfo(
|
||||
numberOfJoinedMembers = 5,
|
||||
),
|
||||
roomMembershipDetails = {
|
||||
Result.success(aRoomMembershipDetails())
|
||||
},
|
||||
)
|
||||
)
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
).apply {
|
||||
getRoomInfoFlowLambda = { _ ->
|
||||
flowOf(Optional.of(roomInfo))
|
||||
@@ -169,6 +191,137 @@ class JoinRoomPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when space is invited then join authorization is equal to invited, an inviter is provided`() = runTest {
|
||||
val inviter = aRoomMember(userId = A_USER_ID_2, displayName = "Bob")
|
||||
val expectedInviteSender = inviter.toInviteSender()
|
||||
val spaceHero = aMatrixUser()
|
||||
val roomInfo = aRoomInfo(
|
||||
isSpace = true,
|
||||
currentUserMembership = CurrentUserMembership.INVITED,
|
||||
joinedMembersCount = 5,
|
||||
inviter = inviter,
|
||||
heroes = listOf(spaceHero),
|
||||
)
|
||||
val inviteData = roomInfo.toInviteData()
|
||||
val matrixClient = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.success(
|
||||
aRoomPreview(
|
||||
info = aRoomPreviewInfo(
|
||||
numberOfJoinedMembers = 5,
|
||||
),
|
||||
roomMembershipDetails = {
|
||||
Result.success(aRoomMembershipDetails())
|
||||
},
|
||||
)
|
||||
)
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = {
|
||||
FakeSpaceRoomList(
|
||||
initialSpaceFlowValue = aSpaceRoom(
|
||||
childrenCount = 3,
|
||||
)
|
||||
)
|
||||
},
|
||||
),
|
||||
).apply {
|
||||
getRoomInfoFlowLambda = { _ ->
|
||||
flowOf(Optional.of(roomInfo))
|
||||
}
|
||||
}
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = matrixClient
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.IsInvited(inviteData, expectedInviteSender))
|
||||
assertThat((state.contentState as ContentState.Loaded).numberOfMembers).isEqualTo(5)
|
||||
// Space details are provided
|
||||
assertThat(state.contentState.details).isEqualTo(
|
||||
LoadedDetails.Space(
|
||||
childrenCount = 3,
|
||||
heroes = persistentListOf(spaceHero),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - space is invited - no room info`() = runTest {
|
||||
val spaceHero = aMatrixUser()
|
||||
val spaceRoom = aSpaceRoom(
|
||||
childrenCount = 3,
|
||||
heroes = listOf(spaceHero),
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.failure(Exception("Error"))
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = {
|
||||
FakeSpaceRoomList(
|
||||
initialSpaceFlowValue = spaceRoom,
|
||||
)
|
||||
},
|
||||
),
|
||||
).apply {
|
||||
getRoomInfoFlowLambda = { _ ->
|
||||
flowOf(Optional.ofNullable(null))
|
||||
}
|
||||
}
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = matrixClient
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
// Space details are provided
|
||||
assertThat((state.contentState as ContentState.Loaded).details).isEqualTo(
|
||||
LoadedDetails.Space(
|
||||
childrenCount = 3,
|
||||
heroes = persistentListOf(spaceHero),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - space is invited - no room info - space room state set`() = runTest {
|
||||
val spaceRoom = aSpaceRoom(
|
||||
state = CurrentUserMembership.INVITED,
|
||||
)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.failure(Exception("Error"))
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = {
|
||||
FakeSpaceRoomList(
|
||||
initialSpaceFlowValue = spaceRoom,
|
||||
)
|
||||
},
|
||||
),
|
||||
).apply {
|
||||
getRoomInfoFlowLambda = { _ ->
|
||||
flowOf(Optional.ofNullable(null))
|
||||
}
|
||||
}
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = matrixClient
|
||||
)
|
||||
presenter.test {
|
||||
awaitItem().also { state ->
|
||||
// Space details are provided
|
||||
assertThat(state.contentState).isInstanceOf(ContentState.Loading::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is invited read the number of member from the room preview`() = runTest {
|
||||
val roomInfo = aRoomInfo(
|
||||
@@ -182,10 +335,16 @@ class JoinRoomPresenterTest {
|
||||
aRoomPreview(
|
||||
info = aRoomPreviewInfo(
|
||||
numberOfJoinedMembers = 10,
|
||||
)
|
||||
),
|
||||
roomMembershipDetails = {
|
||||
Result.success(aRoomMembershipDetails())
|
||||
},
|
||||
)
|
||||
)
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
).apply {
|
||||
getRoomInfoFlowLambda = { _ ->
|
||||
flowOf(Optional.of(roomInfo))
|
||||
@@ -209,7 +368,11 @@ class JoinRoomPresenterTest {
|
||||
anAcceptDeclineInviteState(eventSink = eventSinkRecorder)
|
||||
}
|
||||
val roomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.INVITED)
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
val matrixClient = FakeMatrixClient(
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
).apply {
|
||||
getRoomInfoFlowLambda = { _ ->
|
||||
flowOf(Optional.of(roomInfo))
|
||||
}
|
||||
@@ -244,6 +407,9 @@ class JoinRoomPresenterTest {
|
||||
}
|
||||
val matrixClient = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = matrixClient,
|
||||
@@ -272,6 +438,9 @@ class JoinRoomPresenterTest {
|
||||
fun `present - when room is joined with error, it is possible to clear the error`() = runTest {
|
||||
val matrixClient = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = matrixClient,
|
||||
@@ -334,16 +503,14 @@ class JoinRoomPresenterTest {
|
||||
currentUserMembership = CurrentUserMembership.BANNED,
|
||||
),
|
||||
roomMembershipDetails = {
|
||||
Result.success(
|
||||
RoomMembershipDetails(
|
||||
currentUserMember = aRoomMember(userId = A_USER_ID, displayName = "Alice"),
|
||||
senderMember = aRoomMember(userId = A_USER_ID_2, displayName = "Bob"),
|
||||
)
|
||||
)
|
||||
Result.success(aRoomMembershipDetails())
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
).apply {
|
||||
getRoomInfoFlowLambda = { _ ->
|
||||
flowOf(Optional.of(roomInfo))
|
||||
@@ -371,6 +538,9 @@ class JoinRoomPresenterTest {
|
||||
val roomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.LEFT, joinRule = JoinRule.Public)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
).apply {
|
||||
getRoomInfoFlowLambda = { _ ->
|
||||
flowOf(Optional.of(roomInfo))
|
||||
@@ -392,6 +562,9 @@ class JoinRoomPresenterTest {
|
||||
val roomInfo = aRoomInfo(currentUserMembership = CurrentUserMembership.LEFT, joinRule = null)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
).apply {
|
||||
getRoomInfoFlowLambda = { _ ->
|
||||
flowOf(Optional.of(roomInfo))
|
||||
@@ -423,7 +596,7 @@ class JoinRoomPresenterTest {
|
||||
assertThat(contentState.topic).isEqualTo(roomDescription.topic)
|
||||
assertThat(contentState.alias).isEqualTo(roomDescription.alias)
|
||||
assertThat(contentState.numberOfMembers).isEqualTo(roomDescription.numberOfMembers)
|
||||
assertThat(contentState.isDm).isFalse()
|
||||
assertThat(contentState.details).isEqualTo(aLoadedDetailsRoom(isDm = false))
|
||||
assertThat(contentState.roomAvatarUrl).isEqualTo(roomDescription.avatarUrl)
|
||||
}
|
||||
}
|
||||
@@ -497,6 +670,9 @@ class JoinRoomPresenterTest {
|
||||
val fakeKnockRoom = FakeKnockRoom(knockRoomSuccess)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = matrixClient,
|
||||
@@ -542,6 +718,9 @@ class JoinRoomPresenterTest {
|
||||
val cancelKnockRoom = FakeCancelKnockRoom(cancelKnockRoomSuccess)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = matrixClient,
|
||||
@@ -586,6 +765,9 @@ class JoinRoomPresenterTest {
|
||||
val fakeForgetRoom = FakeForgetRoom(forgetRoomSuccess)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = matrixClient,
|
||||
@@ -634,10 +816,16 @@ class JoinRoomPresenterTest {
|
||||
isHistoryWorldReadable = false,
|
||||
joinRule = JoinRule.Public,
|
||||
currentUserMembership = null,
|
||||
)
|
||||
),
|
||||
roomMembershipDetails = {
|
||||
Result.success(aRoomMembershipDetails())
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
@@ -652,10 +840,10 @@ class JoinRoomPresenterTest {
|
||||
topic = "Room topic",
|
||||
alias = RoomAlias("#alias:matrix.org"),
|
||||
numberOfMembers = 2,
|
||||
isDm = false,
|
||||
roomType = RoomType.Room,
|
||||
roomAvatarUrl = "avatarUrl",
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin,
|
||||
joinRule = JoinRule.Public,
|
||||
details = aLoadedDetailsRoom(isDm = false),
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -681,16 +869,14 @@ class JoinRoomPresenterTest {
|
||||
currentUserMembership = CurrentUserMembership.INVITED,
|
||||
),
|
||||
roomMembershipDetails = {
|
||||
Result.success(
|
||||
RoomMembershipDetails(
|
||||
currentUserMember = aRoomMember(userId = A_USER_ID, displayName = "Alice"),
|
||||
senderMember = aRoomMember(userId = A_USER_ID_2, displayName = "Bob"),
|
||||
)
|
||||
)
|
||||
Result.success(aRoomMembershipDetails())
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
@@ -705,8 +891,6 @@ class JoinRoomPresenterTest {
|
||||
topic = "Room topic",
|
||||
alias = RoomAlias("#alias:matrix.org"),
|
||||
numberOfMembers = 2,
|
||||
isDm = false,
|
||||
roomType = RoomType.Room,
|
||||
roomAvatarUrl = "avatarUrl",
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(
|
||||
inviteData = InviteData(
|
||||
@@ -724,7 +908,9 @@ class JoinRoomPresenterTest {
|
||||
),
|
||||
membershipChangeReason = null,
|
||||
),
|
||||
)
|
||||
),
|
||||
joinRule = JoinRule.Public,
|
||||
details = aLoadedDetailsRoom(isDm = false),
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -751,15 +937,15 @@ class JoinRoomPresenterTest {
|
||||
),
|
||||
roomMembershipDetails = {
|
||||
Result.success(
|
||||
RoomMembershipDetails(
|
||||
currentUserMember = aRoomMember(userId = A_USER_ID, displayName = "Alice"),
|
||||
senderMember = aRoomMember(userId = A_USER_ID_2, displayName = "Bob"),
|
||||
)
|
||||
aRoomMembershipDetails(),
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
@@ -774,8 +960,6 @@ class JoinRoomPresenterTest {
|
||||
topic = "Room topic",
|
||||
alias = RoomAlias("#alias:matrix.org"),
|
||||
numberOfMembers = 2,
|
||||
isDm = false,
|
||||
roomType = RoomType.Room,
|
||||
roomAvatarUrl = "avatarUrl",
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.IsBanned(
|
||||
banSender = InviteSender(
|
||||
@@ -789,7 +973,9 @@ class JoinRoomPresenterTest {
|
||||
membershipChangeReason = null,
|
||||
),
|
||||
reason = null,
|
||||
)
|
||||
),
|
||||
joinRule = JoinRule.Public,
|
||||
details = aLoadedDetailsRoom(isDm = false),
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -815,16 +1001,14 @@ class JoinRoomPresenterTest {
|
||||
currentUserMembership = CurrentUserMembership.KNOCKED,
|
||||
),
|
||||
roomMembershipDetails = {
|
||||
Result.success(
|
||||
RoomMembershipDetails(
|
||||
currentUserMember = aRoomMember(userId = A_USER_ID, displayName = "Alice"),
|
||||
senderMember = aRoomMember(userId = A_USER_ID_2, displayName = "Bob"),
|
||||
)
|
||||
)
|
||||
Result.success(aRoomMembershipDetails())
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
@@ -839,10 +1023,10 @@ class JoinRoomPresenterTest {
|
||||
topic = "Room topic",
|
||||
alias = RoomAlias("#alias:matrix.org"),
|
||||
numberOfMembers = 2,
|
||||
isDm = false,
|
||||
roomType = RoomType.Room,
|
||||
roomAvatarUrl = "avatarUrl",
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked,
|
||||
joinRule = JoinRule.Public,
|
||||
details = aLoadedDetailsRoom(isDm = false),
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -854,9 +1038,17 @@ class JoinRoomPresenterTest {
|
||||
val client = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.success(
|
||||
aRoomPreview(info = aRoomPreviewInfo(joinRule = JoinRule.Private))
|
||||
aRoomPreview(
|
||||
info = aRoomPreviewInfo(joinRule = JoinRule.Private),
|
||||
roomMembershipDetails = {
|
||||
Result.success(aRoomMembershipDetails())
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
@@ -874,9 +1066,17 @@ class JoinRoomPresenterTest {
|
||||
val client = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.success(
|
||||
aRoomPreview(info = aRoomPreviewInfo(joinRule = JoinRule.Custom("custom")))
|
||||
aRoomPreview(
|
||||
info = aRoomPreviewInfo(joinRule = JoinRule.Custom("custom")),
|
||||
roomMembershipDetails = {
|
||||
Result.success(aRoomMembershipDetails())
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
@@ -894,9 +1094,17 @@ class JoinRoomPresenterTest {
|
||||
val client = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.success(
|
||||
aRoomPreview(info = aRoomPreviewInfo(joinRule = JoinRule.Invite))
|
||||
aRoomPreview(
|
||||
info = aRoomPreviewInfo(joinRule = JoinRule.Invite),
|
||||
roomMembershipDetails = {
|
||||
Result.success(aRoomMembershipDetails())
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
@@ -914,9 +1122,19 @@ class JoinRoomPresenterTest {
|
||||
val client = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.success(
|
||||
aRoomPreview(info = aRoomPreviewInfo(joinRule = JoinRule.KnockRestricted(persistentListOf())))
|
||||
aRoomPreview(
|
||||
info = aRoomPreviewInfo(
|
||||
joinRule = JoinRule.KnockRestricted(persistentListOf())
|
||||
),
|
||||
roomMembershipDetails = {
|
||||
Result.success(aRoomMembershipDetails())
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
@@ -934,9 +1152,17 @@ class JoinRoomPresenterTest {
|
||||
val client = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.success(
|
||||
aRoomPreview(info = aRoomPreviewInfo(joinRule = JoinRule.Restricted(persistentListOf())))
|
||||
aRoomPreview(
|
||||
info = aRoomPreviewInfo(joinRule = JoinRule.Restricted(persistentListOf())),
|
||||
roomMembershipDetails = {
|
||||
Result.success(aRoomMembershipDetails())
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
@@ -949,32 +1175,15 @@ class JoinRoomPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded as Space`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.success(
|
||||
aRoomPreview(info = aRoomPreviewInfo(isSpace = true))
|
||||
)
|
||||
}
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.IsSpace("AppName"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded with error`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.failure(AN_EXCEPTION)
|
||||
}
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
@@ -1004,7 +1213,10 @@ class JoinRoomPresenterTest {
|
||||
val client = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.failure(AN_EXCEPTION)
|
||||
}
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
@@ -1029,7 +1241,10 @@ class JoinRoomPresenterTest {
|
||||
val client = FakeMatrixClient(
|
||||
getNotJoinedRoomResult = { _, _ ->
|
||||
Result.failure(ClientException.MatrixApi(ErrorKind.Forbidden, "403", "Forbidden", null))
|
||||
}
|
||||
},
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
@@ -1068,7 +1283,11 @@ internal fun createJoinRoomPresenter(
|
||||
roomDescription: Optional<RoomDescription> = Optional.empty(),
|
||||
serverNames: List<String> = emptyList(),
|
||||
trigger: JoinedRoom.Trigger = JoinedRoom.Trigger.Invite,
|
||||
matrixClient: MatrixClient = FakeMatrixClient(),
|
||||
matrixClient: MatrixClient = FakeMatrixClient(
|
||||
spaceService = FakeSpaceService(
|
||||
spaceRoomListResult = { FakeSpaceRoomList() },
|
||||
),
|
||||
),
|
||||
joinRoomLambda: (RoomIdOrAlias, List<String>, JoinedRoom.Trigger) -> Result<Unit> = { _, _, _ ->
|
||||
Result.success(Unit)
|
||||
},
|
||||
@@ -1095,3 +1314,8 @@ internal fun createJoinRoomPresenter(
|
||||
seenInvitesStore = seenInvitesStore,
|
||||
)
|
||||
}
|
||||
|
||||
private fun aRoomMembershipDetails() = RoomMembershipDetails(
|
||||
currentUserMember = aRoomMember(userId = A_USER_ID, displayName = "Alice"),
|
||||
senderMember = aRoomMember(userId = A_USER_ID_2, displayName = "Bob"),
|
||||
)
|
||||
|
||||
@@ -14,7 +14,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.features.invite.test.anInviteData
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||
@@ -218,21 +217,6 @@ class JoinRoomViewTest {
|
||||
eventsRecorder.assertSingle(JoinRoomEvents.RetryFetchingContent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on ok when a space is displayed invokes the expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
||||
ensureCalledOnce {
|
||||
rule.setJoinRoomView(
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(roomType = RoomType.Space),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onBackClick = it
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_ok)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on ok when user is unauthorized the expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
||||
|
||||
@@ -117,7 +117,7 @@ private fun RoomAliasResolverContent(
|
||||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||
PlaceholderAtom(width = AvatarSize.RoomPreviewHeader.dp, height = AvatarSize.RoomPreviewHeader.dp)
|
||||
},
|
||||
title = {
|
||||
RoomPreviewSubtitleAtom(roomAlias.value)
|
||||
|
||||
@@ -396,10 +396,10 @@ private fun RoomHeaderSection(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Avatar(
|
||||
avatarData = AvatarData(roomId.value, roomName, avatarUrl, AvatarSize.RoomHeader),
|
||||
avatarData = AvatarData(roomId.value, roomName, avatarUrl, AvatarSize.RoomDetailsHeader),
|
||||
avatarType = AvatarType.Room(
|
||||
heroes = heroes.map { user ->
|
||||
user.getAvatarData(size = AvatarSize.RoomHeader)
|
||||
user.getAvatarData(size = AvatarSize.RoomDetailsHeader)
|
||||
}.toPersistentList(),
|
||||
isTombstoned = isTombstoned,
|
||||
),
|
||||
|
||||
@@ -29,6 +29,8 @@ import kotlinx.collections.immutable.toPersistentSet
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Optional
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
@Inject
|
||||
class SpacePresenter(
|
||||
@@ -64,7 +66,7 @@ class SpacePresenter(
|
||||
}
|
||||
}.collectAsState()
|
||||
|
||||
val currentSpace by remember { spaceRoomList.currentSpaceFlow() }.collectAsState(null)
|
||||
val currentSpace by remember { spaceRoomList.currentSpaceFlow() }.collectAsState(Optional.empty())
|
||||
|
||||
fun handleEvents(event: SpaceEvents) {
|
||||
when (event) {
|
||||
@@ -72,7 +74,7 @@ class SpacePresenter(
|
||||
}
|
||||
}
|
||||
return SpaceState(
|
||||
currentSpace = currentSpace,
|
||||
currentSpace = currentSpace.getOrNull(),
|
||||
children = children.toPersistentList(),
|
||||
seenSpaceInvites = seenSpaceInvites,
|
||||
hideInvitesAvatar = hideInvitesAvatar,
|
||||
|
||||
@@ -18,7 +18,15 @@ open class SpaceStateProvider : PreviewParameterProvider<SpaceState> {
|
||||
override val values: Sequence<SpaceState>
|
||||
get() = sequenceOf(
|
||||
aSpaceState(),
|
||||
aSpaceState(hasMoreToLoad = true),
|
||||
aSpaceState(
|
||||
parentSpace = aSpaceRoom(
|
||||
name = null,
|
||||
numJoinedMembers = 5,
|
||||
childrenCount = 10,
|
||||
worldReadable = true,
|
||||
),
|
||||
hasMoreToLoad = true,
|
||||
),
|
||||
aSpaceState(
|
||||
hasMoreToLoad = true,
|
||||
children = aListOfSpaceRooms(),
|
||||
|
||||
@@ -183,7 +183,7 @@ private fun SpaceAvatarAndNameRow(
|
||||
.semantics {
|
||||
heading()
|
||||
},
|
||||
text = name ?: stringResource(CommonStrings.common_no_room_name),
|
||||
text = name ?: stringResource(CommonStrings.common_no_space_name),
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
fontStyle = FontStyle.Italic.takeIf { name == null },
|
||||
maxLines = 1,
|
||||
|
||||
@@ -15,14 +15,18 @@ import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
||||
@Composable
|
||||
fun RoomPreviewDescriptionAtom(description: String, modifier: Modifier = Modifier) {
|
||||
fun RoomPreviewDescriptionAtom(
|
||||
description: String,
|
||||
modifier: Modifier = Modifier,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = description,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
textAlign = TextAlign.Center,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
maxLines = 3,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
maxLines = maxLines,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ fun RoomPreviewSubtitleAtom(subtitle: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = subtitle,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
textAlign = TextAlign.Center,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@ fun RoomPreviewTitleAtom(
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = title,
|
||||
style = ElementTheme.typography.fontHeadingMdBold,
|
||||
style = ElementTheme.typography.fontHeadingLgBold,
|
||||
textAlign = TextAlign.Center,
|
||||
fontStyle = fontStyle,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
|
||||
@@ -34,14 +34,13 @@ fun RoomPreviewOrganism(
|
||||
title()
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
subtitle()
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
if (memberCount != null) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
memberCount()
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
if (description != null) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
description()
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ enum class AvatarSize(val dp: Dp) {
|
||||
CurrentUserTopBar(32.dp),
|
||||
|
||||
IncomingCall(140.dp),
|
||||
RoomHeader(96.dp),
|
||||
RoomDetailsHeader(96.dp),
|
||||
RoomListItem(52.dp),
|
||||
|
||||
SpaceListItem(52.dp),
|
||||
@@ -69,5 +69,7 @@ enum class AvatarSize(val dp: Dp) {
|
||||
|
||||
OrganizationHeader(64.dp),
|
||||
SpaceHeader(64.dp),
|
||||
RoomPreviewHeader(64.dp),
|
||||
RoomPreviewInviter(56.dp),
|
||||
SpaceMember(24.dp),
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.api.spaces
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.util.Optional
|
||||
|
||||
interface SpaceRoomList {
|
||||
sealed interface PaginationStatus {
|
||||
@@ -16,7 +17,7 @@ interface SpaceRoomList {
|
||||
data class Idle(val hasMoreToLoad: Boolean) : PaginationStatus
|
||||
}
|
||||
|
||||
fun currentSpaceFlow(): Flow<SpaceRoom?>
|
||||
fun currentSpaceFlow(): StateFlow<Optional<SpaceRoom>>
|
||||
|
||||
val spaceRoomsFlow: Flow<List<SpaceRoom>>
|
||||
val paginationStatusFlow: StateFlow<PaginationStatus>
|
||||
|
||||
@@ -13,13 +13,14 @@ import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import uniffi.matrix_sdk_ui.SpaceRoomListPaginationState
|
||||
import java.util.Optional
|
||||
import org.matrix.rustcomponents.sdk.SpaceRoomList as InnerSpaceRoomList
|
||||
|
||||
class RustSpaceRoomList(
|
||||
@@ -31,7 +32,7 @@ class RustSpaceRoomList(
|
||||
) : SpaceRoomList {
|
||||
private val inner = CompletableDeferred<InnerSpaceRoomList>()
|
||||
|
||||
override fun currentSpaceFlow(): Flow<SpaceRoom?> {
|
||||
override fun currentSpaceFlow(): StateFlow<Optional<SpaceRoom>> {
|
||||
return spaceRoomCache.getSpaceRoomFlow(roomId)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@ package io.element.android.libraries.matrix.impl.spaces
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import java.util.Optional
|
||||
|
||||
/**
|
||||
* An in memory cache of space rooms.
|
||||
@@ -20,8 +21,8 @@ import kotlinx.coroutines.flow.update
|
||||
*/
|
||||
class SpaceRoomCache {
|
||||
private val inMemoryCache = MutableStateFlow<Map<RoomId, SpaceRoom?>>(emptyMap())
|
||||
fun getSpaceRoomFlow(roomId: RoomId): Flow<SpaceRoom?> {
|
||||
return inMemoryCache.mapState { it[roomId] }
|
||||
fun getSpaceRoomFlow(roomId: RoomId): StateFlow<Optional<SpaceRoom>> {
|
||||
return inMemoryCache.mapState { Optional.ofNullable(it[roomId]) }
|
||||
}
|
||||
|
||||
fun update(spaceRooms: List<SpaceRoom>) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.matrix.rustcomponents.sdk.SpaceListUpdate
|
||||
import uniffi.matrix_sdk_ui.SpaceRoomListPaginationState
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import org.matrix.rustcomponents.sdk.SpaceRoomList as InnerSpaceRoomList
|
||||
|
||||
class RustSpaceRoomListTest {
|
||||
@@ -93,10 +94,10 @@ class RustSpaceRoomListTest {
|
||||
spaceRoomCache = spaceRoomCache,
|
||||
)
|
||||
sut.currentSpaceFlow().test {
|
||||
assertThat(awaitItem()).isNull()
|
||||
assertThat(awaitItem().getOrNull()).isNull()
|
||||
val spaceRoom = aSpaceRoom(roomId = A_ROOM_ID)
|
||||
spaceRoomCache.update(listOf(spaceRoom))
|
||||
assertThat(awaitItem()).isEqualTo(spaceRoom)
|
||||
assertThat(awaitItem().getOrNull()).isEqualTo(spaceRoom)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class SpaceRoomCacheTest {
|
||||
fun `getSpaceRoomFlow emits items`() = runTest {
|
||||
val sut = SpaceRoomCache()
|
||||
sut.getSpaceRoomFlow(A_ROOM_ID).test {
|
||||
assertThat(awaitItem()).isNull()
|
||||
assertThat(awaitItem().isEmpty).isTrue()
|
||||
val room = aSpaceRoom(
|
||||
roomId = A_ROOM_ID,
|
||||
roomType = RoomType.Room,
|
||||
@@ -34,7 +34,7 @@ class SpaceRoomCacheTest {
|
||||
roomType = RoomType.Space,
|
||||
)
|
||||
sut.update(listOf(space))
|
||||
assertThat(awaitItem()).isEqualTo(space)
|
||||
assertThat(awaitItem().get()).isEqualTo(space)
|
||||
val spaceOther = aSpaceRoom(
|
||||
roomId = A_ROOM_ID_2,
|
||||
roomType = RoomType.Space,
|
||||
|
||||
@@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import java.util.Optional
|
||||
|
||||
class FakeSpaceRoomList(
|
||||
initialSpaceFlowValue: SpaceRoom? = null,
|
||||
@@ -22,11 +23,11 @@ class FakeSpaceRoomList(
|
||||
initialSpaceRoomList: SpaceRoomList.PaginationStatus = SpaceRoomList.PaginationStatus.Loading,
|
||||
private val paginateResult: () -> Result<Unit> = { lambdaError() },
|
||||
) : SpaceRoomList {
|
||||
private val currentSpaceMutableStateFlow: MutableStateFlow<SpaceRoom?> = MutableStateFlow(initialSpaceFlowValue)
|
||||
override fun currentSpaceFlow(): Flow<SpaceRoom?> = currentSpaceMutableStateFlow.asStateFlow()
|
||||
private val currentSpaceMutableStateFlow: MutableStateFlow<Optional<SpaceRoom>> = MutableStateFlow(Optional.ofNullable(initialSpaceFlowValue))
|
||||
override fun currentSpaceFlow(): StateFlow<Optional<SpaceRoom>> = currentSpaceMutableStateFlow.asStateFlow()
|
||||
|
||||
fun emitCurrentSpace(value: SpaceRoom?) {
|
||||
currentSpaceMutableStateFlow.value = value
|
||||
currentSpaceMutableStateFlow.value = Optional.ofNullable(value)
|
||||
}
|
||||
|
||||
private val _spaceRoomsFlow: MutableStateFlow<List<SpaceRoom>> = MutableStateFlow(initialSpaceRoomsValue)
|
||||
|
||||
@@ -7,19 +7,16 @@
|
||||
|
||||
package io.element.android.libraries.matrix.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewDescriptionAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewTitleAtom
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.RoomPreviewOrganism
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
@@ -29,6 +26,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@@ -47,47 +45,45 @@ fun SpaceHeaderView(
|
||||
modifier: Modifier = Modifier,
|
||||
topicMaxLines: Int = Int.MAX_VALUE,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 32.dp, bottom = 24.dp, start = 16.dp, end = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Avatar(
|
||||
avatarData = avatarData,
|
||||
avatarType = AvatarType.Space(false),
|
||||
)
|
||||
name?.let {
|
||||
Text(
|
||||
text = name,
|
||||
style = ElementTheme.typography.fontHeadingLgBold,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
textAlign = TextAlign.Center,
|
||||
RoomPreviewOrganism(
|
||||
modifier = modifier.padding(24.dp),
|
||||
avatar = {
|
||||
Avatar(
|
||||
avatarData = avatarData,
|
||||
avatarType = AvatarType.Space(),
|
||||
)
|
||||
}
|
||||
if (joinRule != null) {
|
||||
SpaceInfoRow(
|
||||
joinRule = joinRule,
|
||||
numberOfRooms = numberOfRooms,
|
||||
},
|
||||
title = {
|
||||
if (name != null) {
|
||||
RoomPreviewTitleAtom(title = name)
|
||||
} else {
|
||||
RoomPreviewTitleAtom(
|
||||
title = stringResource(id = CommonStrings.common_no_space_name),
|
||||
fontStyle = FontStyle.Italic
|
||||
)
|
||||
}
|
||||
},
|
||||
subtitle = {
|
||||
if (joinRule != null) {
|
||||
SpaceInfoRow(
|
||||
joinRule = joinRule,
|
||||
numberOfRooms = numberOfRooms,
|
||||
)
|
||||
}
|
||||
},
|
||||
description = if (topic.isNullOrBlank()) {
|
||||
null
|
||||
} else {
|
||||
{ RoomPreviewDescriptionAtom(description = topic, maxLines = topicMaxLines) }
|
||||
},
|
||||
memberCount = {
|
||||
SpaceMembersView(
|
||||
heroes = heroes,
|
||||
numberOfMembers = numberOfMembers,
|
||||
modifier = Modifier.padding(horizontal = 32.dp),
|
||||
)
|
||||
}
|
||||
SpaceMembersView(
|
||||
heroes = heroes,
|
||||
numberOfMembers = numberOfMembers,
|
||||
modifier = Modifier.padding(horizontal = 32.dp),
|
||||
)
|
||||
topic?.let {
|
||||
Text(
|
||||
text = topic,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = topicMaxLines,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
|
||||
@@ -69,6 +69,7 @@ fun SpaceRoomItemView(
|
||||
onLongClick = onLongClick,
|
||||
) {
|
||||
NameAndIndicatorRow(
|
||||
isSpace = spaceRoom.isSpace,
|
||||
name = spaceRoom.name,
|
||||
showIndicator = showUnreadIndicator
|
||||
)
|
||||
@@ -130,6 +131,7 @@ private fun SubtitleRow(
|
||||
|
||||
@Composable
|
||||
private fun NameAndIndicatorRow(
|
||||
isSpace: Boolean,
|
||||
name: String?,
|
||||
showIndicator: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -142,7 +144,7 @@ private fun NameAndIndicatorRow(
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
text = name ?: stringResource(id = CommonStrings.common_no_room_name),
|
||||
text = name ?: stringResource(id = if (isSpace) CommonStrings.common_no_space_name else CommonStrings.common_no_room_name),
|
||||
fontStyle = FontStyle.Italic.takeIf { name == null },
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
maxLines = 1,
|
||||
|
||||
@@ -105,7 +105,7 @@ class DefaultNotificationConversationService(
|
||||
targetSize = defaultShortcutIconSize.toLong()
|
||||
)?.let(IconCompat::createWithBitmap)
|
||||
?: InitialsAvatarBitmapGenerator(useDarkTheme = useDarkTheme)
|
||||
.generateBitmap(defaultShortcutIconSize, AvatarData(id = roomId.value, name = roomName, size = AvatarSize.RoomHeader))
|
||||
.generateBitmap(defaultShortcutIconSize, AvatarData(id = roomId.value, name = roomName, size = AvatarSize.RoomDetailsHeader))
|
||||
?.let(IconCompat::createWithAdaptiveBitmap)
|
||||
|
||||
val shortcutInfo = ShortcutInfoCompat.Builder(context, createShortcutId(sessionId, roomId))
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user