Room navigation : make it working with RoomDirectory
This commit is contained in:
@@ -25,4 +25,5 @@ android {
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.features.roomdirectory.api)
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ package io.element.android.features.joinroom.api
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import java.util.Optional
|
||||
|
||||
interface JoinRoomEntryPoint : FeatureEntryPoint {
|
||||
|
||||
@@ -28,6 +30,7 @@ interface JoinRoomEntryPoint : FeatureEntryPoint {
|
||||
|
||||
data class Inputs(
|
||||
val roomId: RoomId,
|
||||
val roomDescription: Optional<RoomDescription>,
|
||||
) : NodeInputs
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ dependencies {
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.features.invite.api)
|
||||
implementation(projects.features.roomdirectory.api)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class JoinRoomNode @AssistedInject constructor(
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
||||
private val inputs: JoinRoomEntryPoint.Inputs = inputs()
|
||||
private val presenter = presenterFactory.create(inputs.roomId)
|
||||
private val presenter = presenterFactory.create(inputs.roomId, inputs.roomDescription)
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter
|
||||
import io.element.android.features.invite.api.response.InviteData
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
@@ -36,34 +37,29 @@ import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class JoinRoomPresenter @AssistedInject constructor(
|
||||
@Assisted private val roomId: RoomId,
|
||||
@Assisted private val roomDescription: Optional<RoomDescription>,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val acceptDeclineInvitePresenter: AcceptDeclineInvitePresenter,
|
||||
) : Presenter<JoinRoomState> {
|
||||
|
||||
interface Factory {
|
||||
fun create(roomId: RoomId): JoinRoomPresenter
|
||||
fun create(roomId: RoomId, roomDescription: Optional<RoomDescription>): JoinRoomPresenter
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): JoinRoomState {
|
||||
val mxRoomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
|
||||
val joinAuthorisationStatus = joinAuthorisationStatus(mxRoomInfo)
|
||||
val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
|
||||
val joinAuthorisationStatus = joinAuthorisationStatus(roomInfo)
|
||||
val acceptDeclineInviteState = acceptDeclineInvitePresenter.present()
|
||||
val roomInfo by produceState<AsyncData<RoomInfo>>(initialValue = AsyncData.Uninitialized, key1 = mxRoomInfo) {
|
||||
val contentState by produceState<AsyncData<ContentState>>(initialValue = AsyncData.Uninitialized, key1 = roomInfo) {
|
||||
value = when {
|
||||
mxRoomInfo.isPresent -> {
|
||||
val roomInfo = mxRoomInfo.get().let {
|
||||
RoomInfo(
|
||||
roomId = roomId,
|
||||
roomName = it.name,
|
||||
roomAlias = it.canonicalAlias,
|
||||
memberCount = it.activeMembersCount,
|
||||
isDirect = it.isDirect,
|
||||
topic = it.topic,
|
||||
roomAvatarUrl = it.avatarUrl
|
||||
)
|
||||
}
|
||||
AsyncData.Success(roomInfo)
|
||||
roomInfo.isPresent -> {
|
||||
val contentState = roomInfo.get().toContentState()
|
||||
AsyncData.Success(contentState)
|
||||
}
|
||||
roomDescription.isPresent -> {
|
||||
val contentState = roomDescription.get().toContentState()
|
||||
AsyncData.Success(contentState)
|
||||
}
|
||||
else -> AsyncData.Uninitialized
|
||||
}
|
||||
@@ -73,30 +69,68 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||
when (event) {
|
||||
JoinRoomEvents.AcceptInvite, JoinRoomEvents.JoinRoom -> {
|
||||
acceptDeclineInviteState.eventSink(
|
||||
AcceptDeclineInviteEvents.AcceptInvite(roomInfo.toInviteData())
|
||||
AcceptDeclineInviteEvents.AcceptInvite(contentState.toInviteData())
|
||||
)
|
||||
}
|
||||
JoinRoomEvents.DeclineInvite -> {
|
||||
acceptDeclineInviteState.eventSink(
|
||||
AcceptDeclineInviteEvents.DeclineInvite(roomInfo.toInviteData())
|
||||
AcceptDeclineInviteEvents.DeclineInvite(contentState.toInviteData())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JoinRoomState(
|
||||
roomInfo = roomInfo,
|
||||
contentState = contentState,
|
||||
joinAuthorisationStatus = joinAuthorisationStatus,
|
||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun AsyncData<RoomInfo>.toInviteData(): InviteData {
|
||||
private fun RoomDescription.toContentState(): ContentState {
|
||||
return ContentState(
|
||||
roomId = roomId,
|
||||
name = name,
|
||||
description = description,
|
||||
numberOfMembers = numberOfMembers,
|
||||
isDirect = false,
|
||||
roomAvatarUrl = avatarUrl
|
||||
)
|
||||
}
|
||||
|
||||
private fun MatrixRoomInfo.toContentState(): ContentState {
|
||||
fun title(): String {
|
||||
return name ?: canonicalAlias ?: roomId.value
|
||||
}
|
||||
|
||||
fun description(): String? {
|
||||
val topic = topic
|
||||
val alias = canonicalAlias
|
||||
val name = name
|
||||
return when {
|
||||
topic != null -> topic
|
||||
name != null && alias != null -> alias
|
||||
name == null && alias == null -> null
|
||||
else -> roomId.value
|
||||
}
|
||||
}
|
||||
|
||||
return ContentState(
|
||||
roomId = roomId,
|
||||
name = title(),
|
||||
description = description(),
|
||||
numberOfMembers = activeMembersCount,
|
||||
isDirect = isDirect,
|
||||
roomAvatarUrl = avatarUrl
|
||||
)
|
||||
}
|
||||
|
||||
private fun AsyncData<ContentState>.toInviteData(): InviteData {
|
||||
return dataOrNull().let {
|
||||
InviteData(
|
||||
roomId = roomId,
|
||||
roomName = it?.roomName ?: "",
|
||||
roomName = it?.name ?: "",
|
||||
isDirect = it?.isDirect ?: false
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,27 +25,27 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
@Immutable
|
||||
data class JoinRoomState(
|
||||
val roomInfo: AsyncData<RoomInfo>,
|
||||
val contentState: AsyncData<ContentState>,
|
||||
val joinAuthorisationStatus: JoinAuthorisationStatus,
|
||||
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
||||
val eventSink: (JoinRoomEvents) -> Unit
|
||||
) {
|
||||
val showMemberCount = roomInfo.dataOrNull()?.memberCount != null
|
||||
}
|
||||
)
|
||||
|
||||
data class RoomInfo(
|
||||
data class ContentState(
|
||||
val roomId: RoomId,
|
||||
val roomName: String?,
|
||||
val roomAlias: String?,
|
||||
val memberCount: Long?,
|
||||
val topic: String?,
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val numberOfMembers: Long?,
|
||||
val isDirect: Boolean,
|
||||
val roomAvatarUrl: String?,
|
||||
) {
|
||||
|
||||
val showMemberCount = numberOfMembers != null
|
||||
|
||||
fun avatarData(size: AvatarSize): AvatarData {
|
||||
return AvatarData(
|
||||
id = roomId.value,
|
||||
name = roomName,
|
||||
name = name,
|
||||
url = roomAvatarUrl,
|
||||
size = size,
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
||||
override val values: Sequence<JoinRoomState>
|
||||
get() = sequenceOf(
|
||||
aJoinRoomState(
|
||||
roomInfo = AsyncData.Uninitialized
|
||||
contentState = AsyncData.Uninitialized
|
||||
),
|
||||
aJoinRoomState(
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin
|
||||
@@ -41,14 +41,13 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
||||
}
|
||||
|
||||
fun aJoinRoomState(
|
||||
roomInfo: AsyncData<RoomInfo> = AsyncData.Success(
|
||||
RoomInfo(
|
||||
contentState: AsyncData<ContentState> = AsyncData.Success(
|
||||
ContentState(
|
||||
roomId = RoomId("@exa:matrix.org"),
|
||||
roomName = "Element x android",
|
||||
roomAlias = "#exa:matrix.org",
|
||||
memberCount = null,
|
||||
name = "Element x android",
|
||||
description = "#exa:matrix.org",
|
||||
numberOfMembers = null,
|
||||
isDirect = false,
|
||||
topic = null,
|
||||
roomAvatarUrl = null
|
||||
)
|
||||
),
|
||||
@@ -56,7 +55,7 @@ fun aJoinRoomState(
|
||||
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
||||
eventSink: (JoinRoomEvents) -> Unit = {}
|
||||
) = JoinRoomState(
|
||||
roomInfo = roomInfo,
|
||||
contentState = contentState,
|
||||
joinAuthorisationStatus = joinAuthorisationStatus,
|
||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
eventSink = eventSink
|
||||
|
||||
@@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -33,7 +32,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -42,10 +40,8 @@ import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonRowMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
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
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
@@ -66,10 +62,10 @@ fun JoinRoomView(
|
||||
HeaderFooterPage(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
JoinRoomTopBar(asyncRoomInfo = state.roomInfo, onBackClicked = onBackPressed)
|
||||
JoinRoomTopBar(onBackClicked = onBackPressed)
|
||||
},
|
||||
content = {
|
||||
JoinRoomContent(state = state)
|
||||
JoinRoomContent(asyncContentState = state.contentState)
|
||||
},
|
||||
footer = {
|
||||
JoinRoomFooter(
|
||||
@@ -127,40 +123,65 @@ private fun JoinRoomFooter(
|
||||
|
||||
@Composable
|
||||
private fun JoinRoomContent(
|
||||
state: JoinRoomState,
|
||||
asyncContentState: AsyncData<ContentState>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
|
||||
@Composable
|
||||
fun ContentScaffold(
|
||||
avatar: @Composable () -> Unit,
|
||||
title: String,
|
||||
description: String,
|
||||
memberCount: @Composable (() -> Unit)? = null
|
||||
) {
|
||||
when (state.roomInfo) {
|
||||
is AsyncData.Success -> {
|
||||
val roomInfo = state.roomInfo.data
|
||||
Avatar(avatarData = roomInfo.avatarData(AvatarSize.RoomHeader))
|
||||
}
|
||||
else -> {
|
||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||
}
|
||||
}
|
||||
avatar()
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.screen_join_room_title_no_preview),
|
||||
text = title,
|
||||
style = ElementTheme.typography.fontHeadingMdBold,
|
||||
textAlign = TextAlign.Center,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.screen_join_room_subtitle_no_preview),
|
||||
text = description,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
textAlign = TextAlign.Center,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
if (state.showMemberCount) {
|
||||
JoinRoomMembersCount(memberCount = state.roomInfo.dataOrNull()?.memberCount ?: 0)
|
||||
memberCount?.invoke()
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
when (asyncContentState) {
|
||||
is AsyncData.Success -> {
|
||||
val contentState = asyncContentState.data
|
||||
ContentScaffold(
|
||||
avatar = {
|
||||
Avatar(contentState.avatarData(AvatarSize.RoomHeader))
|
||||
},
|
||||
title = contentState.name,
|
||||
description = contentState.description ?: stringResource(R.string.screen_join_room_subtitle_no_preview)
|
||||
) {
|
||||
if (contentState.showMemberCount) {
|
||||
JoinRoomMembersCount(memberCount = contentState.numberOfMembers ?: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
ContentScaffold(
|
||||
avatar = {
|
||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||
},
|
||||
title = stringResource(R.string.screen_join_room_title_no_preview),
|
||||
description = stringResource(R.string.screen_join_room_subtitle_no_preview),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,7 +213,6 @@ fun JoinRoomMembersCount(memberCount: Long) {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun JoinRoomTopBar(
|
||||
asyncRoomInfo: AsyncData<RoomInfo>,
|
||||
onBackClicked: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
@@ -200,44 +220,11 @@ private fun JoinRoomTopBar(
|
||||
BackButton(onClick = onBackClicked)
|
||||
},
|
||||
title = {
|
||||
when (asyncRoomInfo) {
|
||||
is AsyncData.Success -> {
|
||||
val roomInfo = asyncRoomInfo.data
|
||||
if(roomInfo.roomName == null){
|
||||
IconTitlePlaceholdersRowMolecule(iconSize = AvatarSize.TimelineRoom.dp)
|
||||
}else {
|
||||
RoomAvatarAndNameRow(roomName = roomInfo.roomName, roomAvatar = roomInfo.avatarData(AvatarSize.TimelineRoom))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
IconTitlePlaceholdersRowMolecule(iconSize = AvatarSize.TimelineRoom.dp)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RoomAvatarAndNameRow(
|
||||
roomName: String,
|
||||
roomAvatar: AvatarData,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Avatar(roomAvatar)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = roomName,
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
fun JoinRoomViewPreview(@PreviewParameter(JoinRoomStateProvider::class) state: JoinRoomState) = ElementPreview {
|
||||
|
||||
@@ -21,10 +21,12 @@ import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter
|
||||
import io.element.android.features.joinroom.impl.JoinRoomPresenter
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import java.util.Optional
|
||||
|
||||
@Module
|
||||
@ContributesTo(SessionScope::class)
|
||||
@@ -36,9 +38,10 @@ object JoinRoomModule {
|
||||
acceptDeclineInvitePresenter: AcceptDeclineInvitePresenter,
|
||||
): JoinRoomPresenter.Factory {
|
||||
return object : JoinRoomPresenter.Factory {
|
||||
override fun create(roomId: RoomId): JoinRoomPresenter {
|
||||
override fun create(roomId: RoomId, roomDescription: Optional<RoomDescription>): JoinRoomPresenter {
|
||||
return JoinRoomPresenter(
|
||||
roomId = roomId,
|
||||
roomDescription = roomDescription,
|
||||
matrixClient = client,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user