Room navigation : make it working with RoomDirectory
This commit is contained in:
parent
fc20b7399a
commit
bf7a94cc93
20 changed files with 226 additions and 163 deletions
|
|
@ -42,8 +42,8 @@ import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.appnav.loggedin.LoggedInNode
|
import io.element.android.appnav.loggedin.LoggedInNode
|
||||||
import io.element.android.appnav.room.RoomFlowNode
|
import io.element.android.appnav.room.RoomFlowNode
|
||||||
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
|
||||||
import io.element.android.appnav.room.RoomNavigationTarget
|
import io.element.android.appnav.room.RoomNavigationTarget
|
||||||
|
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
||||||
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
||||||
import io.element.android.features.ftue.api.FtueEntryPoint
|
import io.element.android.features.ftue.api.FtueEntryPoint
|
||||||
import io.element.android.features.ftue.api.state.FtueService
|
import io.element.android.features.ftue.api.state.FtueService
|
||||||
|
|
@ -55,6 +55,7 @@ import io.element.android.features.lockscreen.api.LockScreenService
|
||||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||||
import io.element.android.features.preferences.api.PreferencesEntryPoint
|
import io.element.android.features.preferences.api.PreferencesEntryPoint
|
||||||
|
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
|
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
|
||||||
import io.element.android.features.roomlist.api.RoomListEntryPoint
|
import io.element.android.features.roomlist.api.RoomListEntryPoint
|
||||||
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
|
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
|
||||||
|
|
@ -83,6 +84,7 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.Optional
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
class LoggedInFlowNode @AssistedInject constructor(
|
class LoggedInFlowNode @AssistedInject constructor(
|
||||||
|
|
@ -214,6 +216,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Room(
|
data class Room(
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
|
val roomDescription: RoomDescription? = null,
|
||||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages
|
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages
|
||||||
) : NavTarget
|
) : NavTarget
|
||||||
|
|
||||||
|
|
@ -304,7 +307,11 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings))
|
backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val inputs = RoomFlowNode.Inputs(roomId = navTarget.roomId, initialElement = navTarget.initialElement)
|
val inputs = RoomFlowNode.Inputs(
|
||||||
|
roomId = navTarget.roomId,
|
||||||
|
roomDescription = Optional.ofNullable(navTarget.roomDescription),
|
||||||
|
initialElement = navTarget.initialElement
|
||||||
|
)
|
||||||
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs, callback))
|
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs, callback))
|
||||||
}
|
}
|
||||||
is NavTarget.Settings -> {
|
is NavTarget.Settings -> {
|
||||||
|
|
@ -375,8 +382,12 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
NavTarget.RoomDirectorySearch -> {
|
NavTarget.RoomDirectorySearch -> {
|
||||||
roomDirectoryEntryPoint.nodeBuilder(this, buildContext)
|
roomDirectoryEntryPoint.nodeBuilder(this, buildContext)
|
||||||
.callback(object : RoomDirectoryEntryPoint.Callback {
|
.callback(object : RoomDirectoryEntryPoint.Callback {
|
||||||
override fun onOpenRoom(roomId: RoomId) {
|
override fun onRoomJoined(roomId: RoomId) {
|
||||||
coroutineScope.launch { attachRoom(roomId) }
|
backstack.push(NavTarget.Room(roomId))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResultClicked(roomDescription: RoomDescription) {
|
||||||
|
backstack.push(NavTarget.Room(roomDescription.roomId, roomDescription))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.appnav.room.joined.JoinedRoomFlowNode
|
import io.element.android.appnav.room.joined.JoinedRoomFlowNode
|
||||||
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
||||||
import io.element.android.features.joinroom.api.JoinRoomEntryPoint
|
import io.element.android.features.joinroom.api.JoinRoomEntryPoint
|
||||||
|
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
|
|
@ -53,6 +54,7 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.Optional
|
||||||
import kotlin.jvm.optionals.getOrNull
|
import kotlin.jvm.optionals.getOrNull
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
|
|
@ -72,6 +74,7 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
) {
|
) {
|
||||||
data class Inputs(
|
data class Inputs(
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
|
val roomDescription: Optional<RoomDescription>,
|
||||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages,
|
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages,
|
||||||
) : NodeInputs
|
) : NodeInputs
|
||||||
|
|
||||||
|
|
@ -102,6 +105,7 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
.launchIn(lifecycleScope)
|
.launchIn(lifecycleScope)
|
||||||
|
|
||||||
|
// When leaving the room from this session only, navigate up.
|
||||||
roomMembershipObserver.updates
|
roomMembershipObserver.updates
|
||||||
.filter { update -> update.roomId == inputs.roomId && !update.isUserInRoom }
|
.filter { update -> update.roomId == inputs.roomId && !update.isUserInRoom }
|
||||||
.onEach {
|
.onEach {
|
||||||
|
|
@ -114,7 +118,7 @@ class RoomFlowNode @AssistedInject constructor(
|
||||||
return when (navTarget) {
|
return when (navTarget) {
|
||||||
NavTarget.Loading -> loadingNode(buildContext)
|
NavTarget.Loading -> loadingNode(buildContext)
|
||||||
NavTarget.JoinRoom -> {
|
NavTarget.JoinRoom -> {
|
||||||
val inputs = JoinRoomEntryPoint.Inputs(inputs.roomId)
|
val inputs = JoinRoomEntryPoint.Inputs(inputs.roomId, roomDescription = inputs.roomDescription)
|
||||||
joinRoomEntryPoint.createNode(this, buildContext, inputs)
|
joinRoomEntryPoint.createNode(this, buildContext, inputs)
|
||||||
}
|
}
|
||||||
NavTarget.JoinedRoom -> {
|
NavTarget.JoinedRoom -> {
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
|
||||||
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -60,10 +59,9 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
|
||||||
private val appNavigationStateService: AppNavigationStateService,
|
private val appNavigationStateService: AppNavigationStateService,
|
||||||
private val appCoroutineScope: CoroutineScope,
|
private val appCoroutineScope: CoroutineScope,
|
||||||
roomComponentFactory: RoomComponentFactory,
|
roomComponentFactory: RoomComponentFactory,
|
||||||
roomMembershipObserver: RoomMembershipObserver,
|
|
||||||
) : BaseFlowNode<JoinedRoomLoadedFlowNode.NavTarget>(
|
) : BaseFlowNode<JoinedRoomLoadedFlowNode.NavTarget>(
|
||||||
backstack = BackStack(
|
backstack = BackStack(
|
||||||
initialElement = when(plugins.filterIsInstance(Inputs::class.java).first().initialElement){
|
initialElement = when (plugins.filterIsInstance(Inputs::class.java).first().initialElement) {
|
||||||
RoomNavigationTarget.Messages -> NavTarget.Messages
|
RoomNavigationTarget.Messages -> NavTarget.Messages
|
||||||
RoomNavigationTarget.Details -> NavTarget.RoomDetails
|
RoomNavigationTarget.Details -> NavTarget.RoomDetails
|
||||||
RoomNavigationTarget.NotificationSettings -> NavTarget.RoomNotificationSettings
|
RoomNavigationTarget.NotificationSettings -> NavTarget.RoomNotificationSettings
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,5 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
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.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
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.FeatureEntryPoint
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import java.util.Optional
|
||||||
|
|
||||||
interface JoinRoomEntryPoint : FeatureEntryPoint {
|
interface JoinRoomEntryPoint : FeatureEntryPoint {
|
||||||
|
|
||||||
|
|
@ -28,6 +30,7 @@ interface JoinRoomEntryPoint : FeatureEntryPoint {
|
||||||
|
|
||||||
data class Inputs(
|
data class Inputs(
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
|
val roomDescription: Optional<RoomDescription>,
|
||||||
) : NodeInputs
|
) : NodeInputs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ dependencies {
|
||||||
implementation(projects.libraries.matrixui)
|
implementation(projects.libraries.matrixui)
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(projects.features.invite.api)
|
implementation(projects.features.invite.api)
|
||||||
|
implementation(projects.features.roomdirectory.api)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class JoinRoomNode @AssistedInject constructor(
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
private val inputs: JoinRoomEntryPoint.Inputs = inputs()
|
private val inputs: JoinRoomEntryPoint.Inputs = inputs()
|
||||||
private val presenter = presenterFactory.create(inputs.roomId)
|
private val presenter = presenterFactory.create(inputs.roomId, inputs.roomDescription)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
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.AcceptDeclineInviteEvents
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter
|
import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter
|
||||||
import io.element.android.features.invite.api.response.InviteData
|
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.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
|
@ -36,34 +37,29 @@ import kotlin.jvm.optionals.getOrNull
|
||||||
|
|
||||||
class JoinRoomPresenter @AssistedInject constructor(
|
class JoinRoomPresenter @AssistedInject constructor(
|
||||||
@Assisted private val roomId: RoomId,
|
@Assisted private val roomId: RoomId,
|
||||||
|
@Assisted private val roomDescription: Optional<RoomDescription>,
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
private val acceptDeclineInvitePresenter: AcceptDeclineInvitePresenter,
|
private val acceptDeclineInvitePresenter: AcceptDeclineInvitePresenter,
|
||||||
) : Presenter<JoinRoomState> {
|
) : Presenter<JoinRoomState> {
|
||||||
|
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(roomId: RoomId): JoinRoomPresenter
|
fun create(roomId: RoomId, roomDescription: Optional<RoomDescription>): JoinRoomPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): JoinRoomState {
|
override fun present(): JoinRoomState {
|
||||||
val mxRoomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
|
val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
|
||||||
val joinAuthorisationStatus = joinAuthorisationStatus(mxRoomInfo)
|
val joinAuthorisationStatus = joinAuthorisationStatus(roomInfo)
|
||||||
val acceptDeclineInviteState = acceptDeclineInvitePresenter.present()
|
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 {
|
value = when {
|
||||||
mxRoomInfo.isPresent -> {
|
roomInfo.isPresent -> {
|
||||||
val roomInfo = mxRoomInfo.get().let {
|
val contentState = roomInfo.get().toContentState()
|
||||||
RoomInfo(
|
AsyncData.Success(contentState)
|
||||||
roomId = roomId,
|
}
|
||||||
roomName = it.name,
|
roomDescription.isPresent -> {
|
||||||
roomAlias = it.canonicalAlias,
|
val contentState = roomDescription.get().toContentState()
|
||||||
memberCount = it.activeMembersCount,
|
AsyncData.Success(contentState)
|
||||||
isDirect = it.isDirect,
|
|
||||||
topic = it.topic,
|
|
||||||
roomAvatarUrl = it.avatarUrl
|
|
||||||
)
|
|
||||||
}
|
|
||||||
AsyncData.Success(roomInfo)
|
|
||||||
}
|
}
|
||||||
else -> AsyncData.Uninitialized
|
else -> AsyncData.Uninitialized
|
||||||
}
|
}
|
||||||
|
|
@ -73,30 +69,68 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||||
when (event) {
|
when (event) {
|
||||||
JoinRoomEvents.AcceptInvite, JoinRoomEvents.JoinRoom -> {
|
JoinRoomEvents.AcceptInvite, JoinRoomEvents.JoinRoom -> {
|
||||||
acceptDeclineInviteState.eventSink(
|
acceptDeclineInviteState.eventSink(
|
||||||
AcceptDeclineInviteEvents.AcceptInvite(roomInfo.toInviteData())
|
AcceptDeclineInviteEvents.AcceptInvite(contentState.toInviteData())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
JoinRoomEvents.DeclineInvite -> {
|
JoinRoomEvents.DeclineInvite -> {
|
||||||
acceptDeclineInviteState.eventSink(
|
acceptDeclineInviteState.eventSink(
|
||||||
AcceptDeclineInviteEvents.DeclineInvite(roomInfo.toInviteData())
|
AcceptDeclineInviteEvents.DeclineInvite(contentState.toInviteData())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JoinRoomState(
|
return JoinRoomState(
|
||||||
roomInfo = roomInfo,
|
contentState = contentState,
|
||||||
joinAuthorisationStatus = joinAuthorisationStatus,
|
joinAuthorisationStatus = joinAuthorisationStatus,
|
||||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||||
eventSink = ::handleEvents
|
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 {
|
return dataOrNull().let {
|
||||||
InviteData(
|
InviteData(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
roomName = it?.roomName ?: "",
|
roomName = it?.name ?: "",
|
||||||
isDirect = it?.isDirect ?: false
|
isDirect = it?.isDirect ?: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,27 +25,27 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class JoinRoomState(
|
data class JoinRoomState(
|
||||||
val roomInfo: AsyncData<RoomInfo>,
|
val contentState: AsyncData<ContentState>,
|
||||||
val joinAuthorisationStatus: JoinAuthorisationStatus,
|
val joinAuthorisationStatus: JoinAuthorisationStatus,
|
||||||
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
||||||
val eventSink: (JoinRoomEvents) -> Unit
|
val eventSink: (JoinRoomEvents) -> Unit
|
||||||
) {
|
)
|
||||||
val showMemberCount = roomInfo.dataOrNull()?.memberCount != null
|
|
||||||
}
|
|
||||||
|
|
||||||
data class RoomInfo(
|
data class ContentState(
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
val roomName: String?,
|
val name: String,
|
||||||
val roomAlias: String?,
|
val description: String?,
|
||||||
val memberCount: Long?,
|
val numberOfMembers: Long?,
|
||||||
val topic: String?,
|
|
||||||
val isDirect: Boolean,
|
val isDirect: Boolean,
|
||||||
val roomAvatarUrl: String?,
|
val roomAvatarUrl: String?,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val showMemberCount = numberOfMembers != null
|
||||||
|
|
||||||
fun avatarData(size: AvatarSize): AvatarData {
|
fun avatarData(size: AvatarSize): AvatarData {
|
||||||
return AvatarData(
|
return AvatarData(
|
||||||
id = roomId.value,
|
id = roomId.value,
|
||||||
name = roomName,
|
name = name,
|
||||||
url = roomAvatarUrl,
|
url = roomAvatarUrl,
|
||||||
size = size,
|
size = size,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
||||||
override val values: Sequence<JoinRoomState>
|
override val values: Sequence<JoinRoomState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
roomInfo = AsyncData.Uninitialized
|
contentState = AsyncData.Uninitialized
|
||||||
),
|
),
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin
|
joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin
|
||||||
|
|
@ -41,14 +41,13 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aJoinRoomState(
|
fun aJoinRoomState(
|
||||||
roomInfo: AsyncData<RoomInfo> = AsyncData.Success(
|
contentState: AsyncData<ContentState> = AsyncData.Success(
|
||||||
RoomInfo(
|
ContentState(
|
||||||
roomId = RoomId("@exa:matrix.org"),
|
roomId = RoomId("@exa:matrix.org"),
|
||||||
roomName = "Element x android",
|
name = "Element x android",
|
||||||
roomAlias = "#exa:matrix.org",
|
description = "#exa:matrix.org",
|
||||||
memberCount = null,
|
numberOfMembers = null,
|
||||||
isDirect = false,
|
isDirect = false,
|
||||||
topic = null,
|
|
||||||
roomAvatarUrl = null
|
roomAvatarUrl = null
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
@ -56,7 +55,7 @@ fun aJoinRoomState(
|
||||||
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
||||||
eventSink: (JoinRoomEvents) -> Unit = {}
|
eventSink: (JoinRoomEvents) -> Unit = {}
|
||||||
) = JoinRoomState(
|
) = JoinRoomState(
|
||||||
roomInfo = roomInfo,
|
contentState = contentState,
|
||||||
joinAuthorisationStatus = joinAuthorisationStatus,
|
joinAuthorisationStatus = joinAuthorisationStatus,
|
||||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||||
eventSink = eventSink
|
eventSink = eventSink
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
|
@ -33,7 +32,6 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
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.PreviewLightDark
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
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.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
|
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.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.atomic.pages.HeaderFooterPage
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
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.avatar.AvatarSize
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
|
@ -66,10 +62,10 @@ fun JoinRoomView(
|
||||||
HeaderFooterPage(
|
HeaderFooterPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
topBar = {
|
topBar = {
|
||||||
JoinRoomTopBar(asyncRoomInfo = state.roomInfo, onBackClicked = onBackPressed)
|
JoinRoomTopBar(onBackClicked = onBackPressed)
|
||||||
},
|
},
|
||||||
content = {
|
content = {
|
||||||
JoinRoomContent(state = state)
|
JoinRoomContent(asyncContentState = state.contentState)
|
||||||
},
|
},
|
||||||
footer = {
|
footer = {
|
||||||
JoinRoomFooter(
|
JoinRoomFooter(
|
||||||
|
|
@ -127,40 +123,65 @@ private fun JoinRoomFooter(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun JoinRoomContent(
|
private fun JoinRoomContent(
|
||||||
state: JoinRoomState,
|
asyncContentState: AsyncData<ContentState>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(
|
|
||||||
modifier = modifier
|
@Composable
|
||||||
.fillMaxWidth()
|
fun ContentScaffold(
|
||||||
.padding(all = 16.dp),
|
avatar: @Composable () -> Unit,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
title: String,
|
||||||
|
description: String,
|
||||||
|
memberCount: @Composable (() -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
when (state.roomInfo) {
|
avatar()
|
||||||
is AsyncData.Success -> {
|
|
||||||
val roomInfo = state.roomInfo.data
|
|
||||||
Avatar(avatarData = roomInfo.avatarData(AvatarSize.RoomHeader))
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.screen_join_room_title_no_preview),
|
text = title,
|
||||||
style = ElementTheme.typography.fontHeadingMdBold,
|
style = ElementTheme.typography.fontHeadingMdBold,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
color = ElementTheme.colors.textPrimary,
|
color = ElementTheme.colors.textPrimary,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.screen_join_room_subtitle_no_preview),
|
text = description,
|
||||||
style = ElementTheme.typography.fontBodyMdRegular,
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
color = ElementTheme.colors.textSecondary,
|
color = ElementTheme.colors.textSecondary,
|
||||||
)
|
)
|
||||||
if (state.showMemberCount) {
|
memberCount?.invoke()
|
||||||
JoinRoomMembersCount(memberCount = state.roomInfo.dataOrNull()?.memberCount ?: 0)
|
}
|
||||||
|
|
||||||
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun JoinRoomTopBar(
|
private fun JoinRoomTopBar(
|
||||||
asyncRoomInfo: AsyncData<RoomInfo>,
|
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
) {
|
) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
|
|
@ -200,44 +220,11 @@ private fun JoinRoomTopBar(
|
||||||
BackButton(onClick = onBackClicked)
|
BackButton(onClick = onBackClicked)
|
||||||
},
|
},
|
||||||
title = {
|
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
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
fun JoinRoomViewPreview(@PreviewParameter(JoinRoomStateProvider::class) state: JoinRoomState) = ElementPreview {
|
fun JoinRoomViewPreview(@PreviewParameter(JoinRoomStateProvider::class) state: JoinRoomState) = ElementPreview {
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,12 @@ import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter
|
import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter
|
||||||
import io.element.android.features.joinroom.impl.JoinRoomPresenter
|
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.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
|
import java.util.Optional
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@ContributesTo(SessionScope::class)
|
@ContributesTo(SessionScope::class)
|
||||||
|
|
@ -36,9 +38,10 @@ object JoinRoomModule {
|
||||||
acceptDeclineInvitePresenter: AcceptDeclineInvitePresenter,
|
acceptDeclineInvitePresenter: AcceptDeclineInvitePresenter,
|
||||||
): JoinRoomPresenter.Factory {
|
): JoinRoomPresenter.Factory {
|
||||||
return object : JoinRoomPresenter.Factory {
|
return object : JoinRoomPresenter.Factory {
|
||||||
override fun create(roomId: RoomId): JoinRoomPresenter {
|
override fun create(roomId: RoomId, roomDescription: Optional<RoomDescription>): JoinRoomPresenter {
|
||||||
return JoinRoomPresenter(
|
return JoinRoomPresenter(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
|
roomDescription = roomDescription,
|
||||||
matrixClient = client,
|
matrixClient = client,
|
||||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-library")
|
id("io.element.android-library")
|
||||||
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,26 @@
|
||||||
|
|
||||||
package io.element.android.features.roomdirectory.api
|
package io.element.android.features.roomdirectory.api
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
data class RoomDescription(
|
data class RoomDescription(
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
val name: String,
|
val name: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
val avatarData: AvatarData,
|
val avatarUrl: String?,
|
||||||
val canBeJoined: Boolean,
|
val canBeJoined: Boolean,
|
||||||
)
|
val numberOfMembers: Long,
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
fun avatarData(size: AvatarSize) = AvatarData(
|
||||||
|
id = roomId.value,
|
||||||
|
name = name,
|
||||||
|
url = avatarUrl,
|
||||||
|
size = size,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ interface RoomDirectoryEntryPoint : FeatureEntryPoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onOpenRoom(roomId: RoomId)
|
fun onRoomJoined(roomId: RoomId)
|
||||||
|
fun onResultClicked(roomDescription: RoomDescription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import com.bumble.appyx.core.plugin.plugins
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
|
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
|
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -35,9 +36,16 @@ class RoomDirectoryNode @AssistedInject constructor(
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
private val presenter: RoomDirectoryPresenter,
|
private val presenter: RoomDirectoryPresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
|
private fun onResultClicked(roomDescription: RoomDescription) {
|
||||||
|
plugins<RoomDirectoryEntryPoint.Callback>().forEach {
|
||||||
|
it.onResultClicked(roomDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onRoomJoined(roomId: RoomId) {
|
private fun onRoomJoined(roomId: RoomId) {
|
||||||
plugins<RoomDirectoryEntryPoint.Callback>().forEach {
|
plugins<RoomDirectoryEntryPoint.Callback>().forEach {
|
||||||
it.onOpenRoom(roomId)
|
it.onRoomJoined(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,6 +55,7 @@ class RoomDirectoryNode @AssistedInject constructor(
|
||||||
RoomDirectoryView(
|
RoomDirectoryView(
|
||||||
state = state,
|
state = state,
|
||||||
onRoomJoined = ::onRoomJoined,
|
onRoomJoined = ::onRoomJoined,
|
||||||
|
onResultClicked = ::onResultClicked,
|
||||||
onBackPressed = ::navigateUp,
|
onBackPressed = ::navigateUp,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,6 @@ package io.element.android.features.roomdirectory.impl.root
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -71,25 +69,17 @@ fun aRoomDescriptionList(): ImmutableList<RoomDescription> {
|
||||||
roomId = RoomId("!exa:matrix.org"),
|
roomId = RoomId("!exa:matrix.org"),
|
||||||
name = "Element X Android",
|
name = "Element X Android",
|
||||||
description = "Element X is a secure, private and decentralized messenger.",
|
description = "Element X is a secure, private and decentralized messenger.",
|
||||||
avatarData = AvatarData(
|
avatarUrl = null,
|
||||||
id = "!exa:matrix.org",
|
|
||||||
name = "Element X Android",
|
|
||||||
url = null,
|
|
||||||
size = AvatarSize.RoomDirectoryItem
|
|
||||||
),
|
|
||||||
canBeJoined = true,
|
canBeJoined = true,
|
||||||
|
numberOfMembers = 2765,
|
||||||
),
|
),
|
||||||
RoomDescription(
|
RoomDescription(
|
||||||
roomId = RoomId("!exi:matrix.org"),
|
roomId = RoomId("!exi:matrix.org"),
|
||||||
name = "Element X iOS",
|
name = "Element X iOS",
|
||||||
description = "Element X is a secure, private and decentralized messenger.",
|
description = "Element X is a secure, private and decentralized messenger.",
|
||||||
avatarData = AvatarData(
|
avatarUrl = null,
|
||||||
id = "!exi:matrix.org",
|
|
||||||
name = "Element X iOS",
|
|
||||||
url = null,
|
|
||||||
size = AvatarSize.RoomDirectoryItem
|
|
||||||
),
|
|
||||||
canBeJoined = false,
|
canBeJoined = false,
|
||||||
|
numberOfMembers = 356,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ import io.element.android.features.roomdirectory.api.RoomDescription
|
||||||
import io.element.android.features.roomdirectory.impl.R
|
import io.element.android.features.roomdirectory.impl.R
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
@ -70,12 +71,13 @@ import kotlinx.collections.immutable.ImmutableList
|
||||||
@Composable
|
@Composable
|
||||||
fun RoomDirectoryView(
|
fun RoomDirectoryView(
|
||||||
state: RoomDirectoryState,
|
state: RoomDirectoryState,
|
||||||
|
onResultClicked: (RoomDescription) -> Unit,
|
||||||
onRoomJoined: (RoomId) -> Unit,
|
onRoomJoined: (RoomId) -> Unit,
|
||||||
onBackPressed: () -> Unit,
|
onBackPressed: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
fun joinRoom(roomId: RoomId) {
|
fun joinRoom(roomDescription: RoomDescription) {
|
||||||
state.eventSink(RoomDirectoryEvents.JoinRoom(roomId))
|
state.eventSink(RoomDirectoryEvents.JoinRoom(roomDescription.roomId))
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|
@ -86,10 +88,11 @@ fun RoomDirectoryView(
|
||||||
content = { padding ->
|
content = { padding ->
|
||||||
RoomDirectoryContent(
|
RoomDirectoryContent(
|
||||||
state = state,
|
state = state,
|
||||||
onResultClicked = ::joinRoom,
|
onResultClicked = onResultClicked,
|
||||||
|
onJoinClicked = ::joinRoom,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.consumeWindowInsets(padding)
|
.consumeWindowInsets(padding)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -128,7 +131,8 @@ private fun RoomDirectoryTopBar(
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomDirectoryContent(
|
private fun RoomDirectoryContent(
|
||||||
state: RoomDirectoryState,
|
state: RoomDirectoryState,
|
||||||
onResultClicked: (RoomId) -> Unit,
|
onResultClicked: (RoomDescription) -> Unit,
|
||||||
|
onJoinClicked: (RoomDescription) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
|
|
@ -143,6 +147,7 @@ private fun RoomDirectoryContent(
|
||||||
displayLoadMoreIndicator = state.displayLoadMoreIndicator,
|
displayLoadMoreIndicator = state.displayLoadMoreIndicator,
|
||||||
displayEmptyState = state.displayEmptyState,
|
displayEmptyState = state.displayEmptyState,
|
||||||
onResultClicked = onResultClicked,
|
onResultClicked = onResultClicked,
|
||||||
|
onJoinClicked = onJoinClicked,
|
||||||
onReachedLoadMore = { state.eventSink(RoomDirectoryEvents.LoadMore) },
|
onReachedLoadMore = { state.eventSink(RoomDirectoryEvents.LoadMore) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -153,7 +158,8 @@ private fun RoomDirectoryRoomList(
|
||||||
roomDescriptions: ImmutableList<RoomDescription>,
|
roomDescriptions: ImmutableList<RoomDescription>,
|
||||||
displayLoadMoreIndicator: Boolean,
|
displayLoadMoreIndicator: Boolean,
|
||||||
displayEmptyState: Boolean,
|
displayEmptyState: Boolean,
|
||||||
onResultClicked: (RoomId) -> Unit,
|
onResultClicked: (RoomDescription) -> Unit,
|
||||||
|
onJoinClicked: (RoomDescription) -> Unit,
|
||||||
onReachedLoadMore: () -> Unit,
|
onReachedLoadMore: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -161,7 +167,12 @@ private fun RoomDirectoryRoomList(
|
||||||
items(roomDescriptions) { roomDescription ->
|
items(roomDescriptions) { roomDescription ->
|
||||||
RoomDirectoryRoomRow(
|
RoomDirectoryRoomRow(
|
||||||
roomDescription = roomDescription,
|
roomDescription = roomDescription,
|
||||||
onClick = onResultClicked,
|
onClick = {
|
||||||
|
onResultClicked(roomDescription)
|
||||||
|
},
|
||||||
|
onJoinClick = {
|
||||||
|
onJoinClicked(roomDescription)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (displayEmptyState) {
|
if (displayEmptyState) {
|
||||||
|
|
@ -188,10 +199,10 @@ private fun RoomDirectoryRoomList(
|
||||||
@Composable
|
@Composable
|
||||||
private fun LoadMoreIndicator(modifier: Modifier = Modifier) {
|
private fun LoadMoreIndicator(modifier: Modifier = Modifier) {
|
||||||
Box(
|
Box(
|
||||||
modifier
|
modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
.padding(24.dp),
|
.padding(24.dp),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
|
|
@ -256,30 +267,29 @@ private fun SearchTextField(
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomDirectoryRoomRow(
|
private fun RoomDirectoryRoomRow(
|
||||||
roomDescription: RoomDescription,
|
roomDescription: RoomDescription,
|
||||||
onClick: (RoomId) -> Unit,
|
onClick: () -> Unit,
|
||||||
|
onJoinClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(enabled = roomDescription.canBeJoined) {
|
.clickable(enabled = roomDescription.canBeJoined, onClick = onClick)
|
||||||
onClick(roomDescription.roomId)
|
.padding(
|
||||||
}
|
top = 12.dp,
|
||||||
.padding(
|
bottom = 12.dp,
|
||||||
top = 12.dp,
|
start = 16.dp,
|
||||||
bottom = 12.dp,
|
)
|
||||||
start = 16.dp,
|
.height(IntrinsicSize.Min),
|
||||||
)
|
|
||||||
.height(IntrinsicSize.Min),
|
|
||||||
) {
|
) {
|
||||||
Avatar(
|
Avatar(
|
||||||
avatarData = roomDescription.avatarData,
|
avatarData = roomDescription.avatarData(AvatarSize.RoomDirectoryItem),
|
||||||
modifier = Modifier.align(Alignment.CenterVertically)
|
modifier = Modifier.align(Alignment.CenterVertically)
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = roomDescription.name,
|
text = roomDescription.name,
|
||||||
|
|
@ -301,8 +311,9 @@ private fun RoomDirectoryRoomRow(
|
||||||
text = stringResource(id = CommonStrings.action_join),
|
text = stringResource(id = CommonStrings.action_join),
|
||||||
color = ElementTheme.colors.textSuccessPrimary,
|
color = ElementTheme.colors.textSuccessPrimary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterVertically)
|
.align(Alignment.CenterVertically)
|
||||||
.padding(start = 4.dp, end = 12.dp)
|
.clickable(onClick = onJoinClick)
|
||||||
|
.padding(start = 4.dp, end = 12.dp)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Spacer(modifier = Modifier.width(24.dp))
|
Spacer(modifier = Modifier.width(24.dp))
|
||||||
|
|
@ -315,6 +326,7 @@ private fun RoomDirectoryRoomRow(
|
||||||
internal fun RoomDirectoryViewPreview(@PreviewParameter(RoomDirectoryStateProvider::class) state: RoomDirectoryState) = ElementPreview {
|
internal fun RoomDirectoryViewPreview(@PreviewParameter(RoomDirectoryStateProvider::class) state: RoomDirectoryState) = ElementPreview {
|
||||||
RoomDirectoryView(
|
RoomDirectoryView(
|
||||||
state = state,
|
state = state,
|
||||||
|
onResultClicked = {},
|
||||||
onRoomJoined = {},
|
onRoomJoined = {},
|
||||||
onBackPressed = {},
|
onBackPressed = {},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -42,12 +42,8 @@ fun MatrixRoomDescription.toFeatureModel(): RoomDescription {
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
name = name(),
|
name = name(),
|
||||||
description = description(),
|
description = description(),
|
||||||
avatarData = AvatarData(
|
avatarUrl = avatarUrl,
|
||||||
id = roomId.value,
|
numberOfMembers = numberOfMembers,
|
||||||
name = name,
|
|
||||||
url = avatarUrl,
|
|
||||||
size = AvatarSize.RoomDirectoryItem,
|
|
||||||
),
|
|
||||||
canBeJoined = joinRule == MatrixRoomDescription.JoinRule.PUBLIC,
|
canBeJoined = joinRule == MatrixRoomDescription.JoinRule.PUBLIC,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomD
|
||||||
setContent {
|
setContent {
|
||||||
RoomDirectoryView(
|
RoomDirectoryView(
|
||||||
state = state,
|
state = state,
|
||||||
onRoomJoined = onRoomJoined,
|
onRoomClicked = onRoomJoined,
|
||||||
onBackPressed = onBackPressed,
|
onBackPressed = onBackPressed,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue