Merge pull request #2713 from element-hq/feature/bma/roomPreview
Permalink navigation - Navigation to rooms and users
This commit is contained in:
commit
e7172cbb85
112 changed files with 1884 additions and 340 deletions
|
|
@ -66,6 +66,9 @@ import io.element.android.libraries.di.SessionScope
|
|||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.MAIN_SPACE
|
||||
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.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -189,9 +192,9 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
|
||||
@Parcelize
|
||||
data class Room(
|
||||
val roomId: RoomId,
|
||||
val roomIdOrAlias: RoomIdOrAlias,
|
||||
val roomDescription: RoomDescription? = null,
|
||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages
|
||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages()
|
||||
) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
|
|
@ -228,7 +231,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
NavTarget.RoomList -> {
|
||||
val callback = object : RoomListEntryPoint.Callback {
|
||||
override fun onRoomClicked(roomId: RoomId) {
|
||||
backstack.push(NavTarget.Room(roomId))
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
|
||||
}
|
||||
|
||||
override fun onSettingsClicked() {
|
||||
|
|
@ -244,7 +247,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
}
|
||||
|
||||
override fun onRoomSettingsClicked(roomId: RoomId) {
|
||||
backstack.push(NavTarget.Room(roomId, initialElement = RoomNavigationTarget.Details))
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), initialElement = RoomNavigationTarget.Details))
|
||||
}
|
||||
|
||||
override fun onReportBugClicked() {
|
||||
|
|
@ -263,19 +266,41 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
is NavTarget.Room -> {
|
||||
val callback = object : JoinedRoomLoadedFlowNode.Callback {
|
||||
override fun onOpenRoom(roomId: RoomId) {
|
||||
backstack.push(NavTarget.Room(roomId))
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
|
||||
}
|
||||
|
||||
override fun onForwardedToSingleRoom(roomId: RoomId) {
|
||||
coroutineScope.launch { attachRoom(roomId) }
|
||||
}
|
||||
|
||||
override fun onPermalinkClicked(data: PermalinkData) {
|
||||
when (data) {
|
||||
is PermalinkData.UserLink -> {
|
||||
// FIXME Add a user profile screen.
|
||||
Timber.e("User link clicked: ${data.userId}. TODO Add a user profile screen")
|
||||
}
|
||||
is PermalinkData.RoomLink -> {
|
||||
backstack.push(
|
||||
NavTarget.Room(
|
||||
data.roomIdOrAlias,
|
||||
initialElement = RoomNavigationTarget.Messages(data.eventId),
|
||||
// TODO Use the viaParameters
|
||||
)
|
||||
)
|
||||
}
|
||||
is PermalinkData.FallbackLink,
|
||||
is PermalinkData.RoomEmailInviteLink -> {
|
||||
// Should not happen (handled by MessagesNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOpenGlobalNotificationSettings() {
|
||||
backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings))
|
||||
}
|
||||
}
|
||||
val inputs = RoomFlowNode.Inputs(
|
||||
roomId = navTarget.roomId,
|
||||
roomIdOrAlias = navTarget.roomIdOrAlias,
|
||||
roomDescription = Optional.ofNullable(navTarget.roomDescription),
|
||||
initialElement = navTarget.initialElement
|
||||
)
|
||||
|
|
@ -292,7 +317,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
}
|
||||
|
||||
override fun onOpenRoomNotificationSettings(roomId: RoomId) {
|
||||
backstack.push(NavTarget.Room(roomId, initialElement = RoomNavigationTarget.NotificationSettings))
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), initialElement = RoomNavigationTarget.NotificationSettings))
|
||||
}
|
||||
}
|
||||
val inputs = PreferencesEntryPoint.Params(navTarget.initialElement)
|
||||
|
|
@ -304,7 +329,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
NavTarget.CreateRoom -> {
|
||||
val callback = object : CreateRoomEntryPoint.Callback {
|
||||
override fun onSuccess(roomId: RoomId) {
|
||||
backstack.replace(NavTarget.Room(roomId))
|
||||
backstack.replace(NavTarget.Room(roomId.toRoomIdOrAlias()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -331,11 +356,11 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
roomDirectoryEntryPoint.nodeBuilder(this, buildContext)
|
||||
.callback(object : RoomDirectoryEntryPoint.Callback {
|
||||
override fun onRoomJoined(roomId: RoomId) {
|
||||
backstack.push(NavTarget.Room(roomId))
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
|
||||
}
|
||||
|
||||
override fun onResultClicked(roomDescription: RoomDescription) {
|
||||
backstack.push(NavTarget.Room(roomDescription.roomId, roomDescription))
|
||||
backstack.push(NavTarget.Room(roomDescription.roomId.toRoomIdOrAlias(), roomDescription))
|
||||
}
|
||||
})
|
||||
.build()
|
||||
|
|
@ -354,7 +379,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
if (!canShowRoomList()) return
|
||||
attachChild<RoomFlowNode> {
|
||||
backstack.singleTop(NavTarget.RoomList)
|
||||
backstack.push(NavTarget.Room(roomId))
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.JoinedRoomLoadedFlowNode
|
||||
import io.element.android.features.joinroom.api.JoinRoomEntryPoint
|
||||
import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
|
|
@ -46,12 +47,15 @@ import io.element.android.libraries.architecture.inputs
|
|||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
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.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import timber.log.Timber
|
||||
import java.util.Optional
|
||||
|
|
@ -64,6 +68,7 @@ class RoomFlowNode @AssistedInject constructor(
|
|||
private val client: MatrixClient,
|
||||
private val roomMembershipObserver: RoomMembershipObserver,
|
||||
private val joinRoomEntryPoint: JoinRoomEntryPoint,
|
||||
private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint,
|
||||
) : BaseFlowNode<RoomFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Loading,
|
||||
|
|
@ -73,9 +78,9 @@ class RoomFlowNode @AssistedInject constructor(
|
|||
plugins = plugins
|
||||
) {
|
||||
data class Inputs(
|
||||
val roomId: RoomId,
|
||||
val roomIdOrAlias: RoomIdOrAlias,
|
||||
val roomDescription: Optional<RoomDescription>,
|
||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages,
|
||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages(),
|
||||
) : NodeInputs
|
||||
|
||||
private val inputs: Inputs = inputs()
|
||||
|
|
@ -85,29 +90,51 @@ class RoomFlowNode @AssistedInject constructor(
|
|||
data object Loading : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object JoinRoom : NavTarget
|
||||
data class Resolving(val roomAlias: RoomAlias) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object JoinedRoom : NavTarget
|
||||
data class JoinRoom(val roomId: RoomId) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class JoinedRoom(val roomId: RoomId) : NavTarget
|
||||
}
|
||||
|
||||
override fun onBuilt() {
|
||||
super.onBuilt()
|
||||
client.getRoomInfoFlow(
|
||||
inputs.roomId
|
||||
).onEach { roomInfo ->
|
||||
Timber.d("Room membership: ${roomInfo.map { it.currentUserMembership }}")
|
||||
if (roomInfo.getOrNull()?.currentUserMembership == CurrentUserMembership.JOINED) {
|
||||
backstack.newRoot(NavTarget.JoinedRoom)
|
||||
} else {
|
||||
backstack.newRoot(NavTarget.JoinRoom)
|
||||
resolveRoomId()
|
||||
}
|
||||
|
||||
private fun resolveRoomId() {
|
||||
lifecycleScope.launch {
|
||||
when (val i = inputs.roomIdOrAlias) {
|
||||
is RoomIdOrAlias.Alias -> {
|
||||
backstack.newRoot(NavTarget.Resolving(i.roomAlias))
|
||||
}
|
||||
is RoomIdOrAlias.Id -> {
|
||||
subscribeToRoomInfoFlow(i.roomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeToRoomInfoFlow(roomId: RoomId) {
|
||||
client.getRoomInfoFlow(
|
||||
roomId = roomId
|
||||
)
|
||||
.onEach { roomInfo ->
|
||||
Timber.d("Room membership: ${roomInfo.map { it.currentUserMembership }}")
|
||||
val info = roomInfo.getOrNull()
|
||||
if (info?.currentUserMembership == CurrentUserMembership.JOINED) {
|
||||
backstack.newRoot(NavTarget.JoinedRoom(roomId))
|
||||
} else {
|
||||
backstack.newRoot(NavTarget.JoinRoom(roomId))
|
||||
}
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
// When leaving the room from this session only, navigate up.
|
||||
roomMembershipObserver.updates
|
||||
.filter { update -> update.roomId == inputs.roomId && !update.isUserInRoom }
|
||||
.filter { update -> update.roomId == roomId && !update.isUserInRoom }
|
||||
.onEach {
|
||||
navigateUp()
|
||||
}
|
||||
|
|
@ -116,14 +143,30 @@ class RoomFlowNode @AssistedInject constructor(
|
|||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
NavTarget.Loading -> loadingNode(buildContext)
|
||||
NavTarget.JoinRoom -> {
|
||||
val inputs = JoinRoomEntryPoint.Inputs(inputs.roomId, roomDescription = inputs.roomDescription)
|
||||
is NavTarget.Loading -> loadingNode(buildContext)
|
||||
is NavTarget.Resolving -> {
|
||||
val callback = object : RoomAliasResolverEntryPoint.Callback {
|
||||
override fun onAliasResolved(roomId: RoomId) {
|
||||
subscribeToRoomInfoFlow(roomId)
|
||||
}
|
||||
}
|
||||
val params = RoomAliasResolverEntryPoint.Params(navTarget.roomAlias)
|
||||
roomAliasResolverEntryPoint.nodeBuilder(this, buildContext)
|
||||
.callback(callback)
|
||||
.params(params)
|
||||
.build()
|
||||
}
|
||||
is NavTarget.JoinRoom -> {
|
||||
val inputs = JoinRoomEntryPoint.Inputs(
|
||||
roomId = navTarget.roomId,
|
||||
roomIdOrAlias = inputs.roomIdOrAlias,
|
||||
roomDescription = inputs.roomDescription,
|
||||
)
|
||||
joinRoomEntryPoint.createNode(this, buildContext, inputs)
|
||||
}
|
||||
NavTarget.JoinedRoom -> {
|
||||
is NavTarget.JoinedRoom -> {
|
||||
val roomFlowNodeCallback = plugins<JoinedRoomLoadedFlowNode.Callback>()
|
||||
val inputs = JoinedRoomFlowNode.Inputs(inputs.roomId, initialElement = inputs.initialElement)
|
||||
val inputs = JoinedRoomFlowNode.Inputs(navTarget.roomId, initialElement = inputs.initialElement)
|
||||
createNode<JoinedRoomFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,17 @@
|
|||
|
||||
package io.element.android.appnav.room
|
||||
|
||||
enum class RoomNavigationTarget {
|
||||
Messages,
|
||||
Details,
|
||||
NotificationSettings,
|
||||
import android.os.Parcelable
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
sealed interface RoomNavigationTarget : Parcelable {
|
||||
@Parcelize
|
||||
data class Messages(val focusedEventId: EventId? = null) : RoomNavigationTarget
|
||||
|
||||
@Parcelize
|
||||
data object Details : RoomNavigationTarget
|
||||
|
||||
@Parcelize
|
||||
data object NotificationSettings : RoomNavigationTarget
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ class JoinedRoomFlowNode @AssistedInject constructor(
|
|||
) {
|
||||
data class Inputs(
|
||||
val roomId: RoomId,
|
||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages,
|
||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages(),
|
||||
) : NodeInputs
|
||||
|
||||
private val inputs: Inputs = inputs()
|
||||
|
|
|
|||
|
|
@ -42,8 +42,10 @@ import io.element.android.libraries.architecture.inputs
|
|||
import io.element.android.libraries.di.DaggerComponentOwner
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
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.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -63,8 +65,8 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
|
|||
roomComponentFactory: RoomComponentFactory,
|
||||
) : BaseFlowNode<JoinedRoomLoadedFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = when (plugins.filterIsInstance(Inputs::class.java).first().initialElement) {
|
||||
RoomNavigationTarget.Messages -> NavTarget.Messages
|
||||
initialElement = when (val input = plugins.filterIsInstance(Inputs::class.java).first().initialElement) {
|
||||
is RoomNavigationTarget.Messages -> NavTarget.Messages(input.focusedEventId)
|
||||
RoomNavigationTarget.Details -> NavTarget.RoomDetails
|
||||
RoomNavigationTarget.NotificationSettings -> NavTarget.RoomNotificationSettings
|
||||
},
|
||||
|
|
@ -75,13 +77,14 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
|
|||
), DaggerComponentOwner {
|
||||
interface Callback : Plugin {
|
||||
fun onOpenRoom(roomId: RoomId)
|
||||
fun onPermalinkClicked(data: PermalinkData)
|
||||
fun onForwardedToSingleRoom(roomId: RoomId)
|
||||
fun onOpenGlobalNotificationSettings()
|
||||
}
|
||||
|
||||
data class Inputs(
|
||||
val room: MatrixRoom,
|
||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages,
|
||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages(),
|
||||
) : NodeInputs
|
||||
|
||||
private val inputs: Inputs = inputs()
|
||||
|
|
@ -139,7 +142,7 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
|
|||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
NavTarget.Messages -> {
|
||||
is NavTarget.Messages -> {
|
||||
val callback = object : MessagesEntryPoint.Callback {
|
||||
override fun onRoomDetailsClicked() {
|
||||
backstack.push(NavTarget.RoomDetails)
|
||||
|
|
@ -149,11 +152,18 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
|
|||
backstack.push(NavTarget.RoomMemberDetails(userId))
|
||||
}
|
||||
|
||||
override fun onPermalinkClicked(data: PermalinkData) {
|
||||
callbacks.forEach { it.onPermalinkClicked(data) }
|
||||
}
|
||||
|
||||
override fun onForwardedToSingleRoom(roomId: RoomId) {
|
||||
callbacks.forEach { it.onForwardedToSingleRoom(roomId) }
|
||||
}
|
||||
}
|
||||
messagesEntryPoint.createNode(this, buildContext, callback)
|
||||
messagesEntryPoint.nodeBuilder(this, buildContext)
|
||||
.params(MessagesEntryPoint.Params(navTarget.focusedEventId))
|
||||
.callback(callback)
|
||||
.build()
|
||||
}
|
||||
NavTarget.RoomDetails -> {
|
||||
createRoomDetailsNode(buildContext, RoomDetailsEntryPoint.InitialTarget.RoomDetails)
|
||||
|
|
@ -169,7 +179,7 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
|
|||
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
data object Messages : NavTarget
|
||||
data class Messages(val focusedEventId: EventId? = null) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object RoomDetails : NavTarget
|
||||
|
|
|
|||
|
|
@ -47,14 +47,30 @@ class JoinRoomLoadedFlowNodeTest {
|
|||
@get:Rule
|
||||
val mainDispatcherRule = MainDispatcherRule()
|
||||
|
||||
private class FakeMessagesEntryPoint : MessagesEntryPoint {
|
||||
private class FakeMessagesEntryPoint : MessagesEntryPoint, MessagesEntryPoint.NodeBuilder {
|
||||
var buildContext: BuildContext? = null
|
||||
var nodeId: String? = null
|
||||
var parameters: MessagesEntryPoint.Params? = null
|
||||
var callback: MessagesEntryPoint.Callback? = null
|
||||
|
||||
override fun createNode(parentNode: Node, buildContext: BuildContext, callback: MessagesEntryPoint.Callback): Node {
|
||||
return node(buildContext) {}.also {
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): MessagesEntryPoint.NodeBuilder {
|
||||
this.buildContext = buildContext
|
||||
return this
|
||||
}
|
||||
|
||||
override fun params(params: MessagesEntryPoint.Params): MessagesEntryPoint.NodeBuilder {
|
||||
parameters = params
|
||||
return this
|
||||
}
|
||||
|
||||
override fun callback(callback: MessagesEntryPoint.Callback): MessagesEntryPoint.NodeBuilder {
|
||||
this.callback = callback
|
||||
return this
|
||||
}
|
||||
|
||||
override fun build(): Node {
|
||||
return node(buildContext!!) {}.also {
|
||||
nodeId = it.id
|
||||
this.callback = callback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -118,9 +134,9 @@ class JoinRoomLoadedFlowNodeTest {
|
|||
val roomFlowNodeTestHelper = roomFlowNode.parentNodeTestHelper()
|
||||
|
||||
// THEN
|
||||
assertThat(roomFlowNode.backstack.activeElement).isEqualTo(JoinedRoomLoadedFlowNode.NavTarget.Messages)
|
||||
roomFlowNodeTestHelper.assertChildHasLifecycle(JoinedRoomLoadedFlowNode.NavTarget.Messages, Lifecycle.State.CREATED)
|
||||
val messagesNode = roomFlowNode.childNode(JoinedRoomLoadedFlowNode.NavTarget.Messages)!!
|
||||
assertThat(roomFlowNode.backstack.activeElement).isEqualTo(JoinedRoomLoadedFlowNode.NavTarget.Messages())
|
||||
roomFlowNodeTestHelper.assertChildHasLifecycle(JoinedRoomLoadedFlowNode.NavTarget.Messages(), Lifecycle.State.CREATED)
|
||||
val messagesNode = roomFlowNode.childNode(JoinedRoomLoadedFlowNode.NavTarget.Messages())!!
|
||||
assertThat(messagesNode.id).isEqualTo(fakeMessagesEntryPoint.nodeId)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteStateProvider
|
||||
|
|
@ -29,6 +28,7 @@ import io.element.android.features.invite.impl.R
|
|||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
|
@ -102,9 +102,9 @@ private fun DeclineConfirmationDialog(
|
|||
)
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun AcceptDeclineInviteViewLightPreview(@PreviewParameter(AcceptDeclineInviteStateProvider::class) state: AcceptDeclineInviteState) =
|
||||
internal fun AcceptDeclineInviteViewPreview(@PreviewParameter(AcceptDeclineInviteStateProvider::class) state: AcceptDeclineInviteState) =
|
||||
ElementPreview {
|
||||
AcceptDeclineInviteView(
|
||||
state = state,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ 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 io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import java.util.Optional
|
||||
|
||||
interface JoinRoomEntryPoint : FeatureEntryPoint {
|
||||
|
|
@ -29,6 +30,7 @@ interface JoinRoomEntryPoint : FeatureEntryPoint {
|
|||
|
||||
data class Inputs(
|
||||
val roomId: RoomId,
|
||||
val roomIdOrAlias: RoomIdOrAlias,
|
||||
val roomDescription: Optional<RoomDescription>,
|
||||
) : NodeInputs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.features.joinroom.impl
|
||||
|
||||
sealed interface JoinRoomEvents {
|
||||
data object RetryFetchingContent : JoinRoomEvents
|
||||
data object JoinRoom : JoinRoomEvents
|
||||
data object AcceptInvite : JoinRoomEvents
|
||||
data object DeclineInvite : JoinRoomEvents
|
||||
|
|
|
|||
|
|
@ -37,7 +37,11 @@ class JoinRoomNode @AssistedInject constructor(
|
|||
private val acceptDeclineInviteView: AcceptDeclineInviteView,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
private val inputs: JoinRoomEntryPoint.Inputs = inputs()
|
||||
private val presenter = presenterFactory.create(inputs.roomId, inputs.roomDescription)
|
||||
private val presenter = presenterFactory.create(
|
||||
inputs.roomId,
|
||||
inputs.roomIdOrAlias,
|
||||
inputs.roomDescription,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ import androidx.annotation.VisibleForTesting
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||
|
|
@ -30,33 +33,57 @@ import io.element.android.features.roomdirectory.api.RoomDescription
|
|||
import io.element.android.libraries.architecture.Presenter
|
||||
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.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||
import java.util.Optional
|
||||
|
||||
class JoinRoomPresenter @AssistedInject constructor(
|
||||
@Assisted private val roomId: RoomId,
|
||||
@Assisted private val roomIdOrAlias: RoomIdOrAlias,
|
||||
@Assisted private val roomDescription: Optional<RoomDescription>,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
) : Presenter<JoinRoomState> {
|
||||
interface Factory {
|
||||
fun create(roomId: RoomId, roomDescription: Optional<RoomDescription>): JoinRoomPresenter
|
||||
fun create(
|
||||
roomId: RoomId,
|
||||
roomIdOrAlias: RoomIdOrAlias,
|
||||
roomDescription: Optional<RoomDescription>,
|
||||
): JoinRoomPresenter
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): JoinRoomState {
|
||||
var retryCount by remember { mutableIntStateOf(0) }
|
||||
val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
|
||||
val contentState by produceState<ContentState>(initialValue = ContentState.Loading(roomId), key1 = roomInfo) {
|
||||
value = when {
|
||||
val contentState by produceState<ContentState>(
|
||||
initialValue = ContentState.Loading(roomIdOrAlias),
|
||||
key1 = roomInfo,
|
||||
key2 = retryCount,
|
||||
) {
|
||||
when {
|
||||
roomInfo.isPresent -> {
|
||||
roomInfo.get().toContentState()
|
||||
value = roomInfo.get().toContentState()
|
||||
}
|
||||
roomDescription.isPresent -> {
|
||||
roomDescription.get().toContentState()
|
||||
value = roomDescription.get().toContentState()
|
||||
}
|
||||
else -> {
|
||||
ContentState.Loading(roomId)
|
||||
value = ContentState.Loading(roomIdOrAlias)
|
||||
val result = matrixClient.getRoomPreview(roomId.toRoomIdOrAlias())
|
||||
value = result.fold(
|
||||
onSuccess = { it.toContentState() },
|
||||
onFailure = { throwable ->
|
||||
if (throwable.message?.contains("403") == true) {
|
||||
ContentState.UnknownRoom(roomIdOrAlias)
|
||||
} else {
|
||||
ContentState.Failure(roomIdOrAlias, throwable)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,7 +91,8 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
|
||||
fun handleEvents(event: JoinRoomEvents) {
|
||||
when (event) {
|
||||
JoinRoomEvents.AcceptInvite, JoinRoomEvents.JoinRoom -> {
|
||||
JoinRoomEvents.AcceptInvite,
|
||||
JoinRoomEvents.JoinRoom -> {
|
||||
val inviteData = contentState.toInviteData() ?: return
|
||||
acceptDeclineInviteState.eventSink(
|
||||
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
||||
|
|
@ -76,6 +104,9 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
AcceptDeclineInviteEvents.DeclineInvite(inviteData)
|
||||
)
|
||||
}
|
||||
JoinRoomEvents.RetryFetchingContent -> {
|
||||
retryCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +118,24 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun RoomPreview.toContentState(): ContentState {
|
||||
return ContentState.Loaded(
|
||||
roomId = roomId,
|
||||
name = name,
|
||||
topic = topic,
|
||||
alias = canonicalAlias,
|
||||
numberOfMembers = numberOfJoinedMembers,
|
||||
isDirect = false,
|
||||
roomAvatarUrl = avatarUrl,
|
||||
joinAuthorisationStatus = when {
|
||||
isInvited -> JoinAuthorisationStatus.IsInvited
|
||||
canKnock -> JoinAuthorisationStatus.CanKnock
|
||||
isPublic -> JoinAuthorisationStatus.CanJoin
|
||||
else -> JoinAuthorisationStatus.Unknown
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun RoomDescription.toContentState(): ContentState {
|
||||
return ContentState.Loaded(
|
||||
|
|
@ -108,7 +157,7 @@ internal fun RoomDescription.toContentState(): ContentState {
|
|||
@VisibleForTesting
|
||||
internal fun MatrixRoomInfo.toContentState(): ContentState {
|
||||
return ContentState.Loaded(
|
||||
roomId = RoomId(id),
|
||||
roomId = id,
|
||||
name = name,
|
||||
topic = topic,
|
||||
alias = canonicalAlias,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ import androidx.compose.runtime.Immutable
|
|||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
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.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
|
||||
@Immutable
|
||||
data class JoinRoomState(
|
||||
|
|
@ -35,13 +37,14 @@ data class JoinRoomState(
|
|||
}
|
||||
|
||||
sealed interface ContentState {
|
||||
data class Loading(val roomId: RoomId) : ContentState
|
||||
data class UnknownRoom(val roomId: RoomId) : ContentState
|
||||
data class Loading(val roomIdOrAlias: RoomIdOrAlias) : ContentState
|
||||
data class Failure(val roomIdOrAlias: RoomIdOrAlias, val error: Throwable) : ContentState
|
||||
data class UnknownRoom(val roomIdOrAlias: RoomIdOrAlias) : ContentState
|
||||
data class Loaded(
|
||||
val roomId: RoomId,
|
||||
val name: String?,
|
||||
val topic: String?,
|
||||
val alias: String?,
|
||||
val alias: RoomAlias?,
|
||||
val numberOfMembers: Long?,
|
||||
val isDirect: Boolean,
|
||||
val roomAvatarUrl: String?,
|
||||
|
|
@ -50,7 +53,7 @@ sealed interface ContentState {
|
|||
val computedTitle = name ?: roomId.value
|
||||
|
||||
val computedSubtitle = when {
|
||||
alias != null -> alias
|
||||
alias != null -> alias.value
|
||||
name == null -> ""
|
||||
else -> roomId.value
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ package io.element.android.features.joinroom.impl
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
|
||||
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.toRoomIdOrAlias
|
||||
|
||||
open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
||||
override val values: Sequence<JoinRoomState>
|
||||
|
|
@ -34,22 +37,45 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
|||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin)
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock)
|
||||
contentState = aLoadedContentState(
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock,
|
||||
topic = "lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt" +
|
||||
" ut labore et dolore magna aliqua ut enim ad minim veniam quis nostrud exercitation ullamco" +
|
||||
" laboris nisi ut aliquip ex ea commodo consequat duis aute irure dolor in reprehenderit in" +
|
||||
" voluptate velit esse cillum dolore eu fugiat nulla pariatur excepteur sint occaecat cupidatat" +
|
||||
" non proident sunt in culpa qui officia deserunt mollit anim id est laborum",
|
||||
numberOfMembers = 888,
|
||||
)
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited)
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aFailureContentState()
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aFailureContentState(roomIdOrAlias = A_ROOM_ALIAS.toRoomIdOrAlias())
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun anUnknownContentState(roomId: RoomId = A_ROOM_ID) = ContentState.UnknownRoom(roomId)
|
||||
fun aFailureContentState(
|
||||
roomIdOrAlias: RoomIdOrAlias = A_ROOM_ID.toRoomIdOrAlias()
|
||||
): ContentState {
|
||||
return ContentState.Failure(
|
||||
roomIdOrAlias = roomIdOrAlias,
|
||||
error = Exception("Error"),
|
||||
)
|
||||
}
|
||||
|
||||
fun aLoadingContentState(roomId: RoomId = A_ROOM_ID) = ContentState.Loading(roomId)
|
||||
fun anUnknownContentState(roomId: RoomId = A_ROOM_ID) = ContentState.UnknownRoom(roomId.toRoomIdOrAlias())
|
||||
|
||||
fun aLoadingContentState(roomId: RoomId = A_ROOM_ID) = ContentState.Loading(roomId.toRoomIdOrAlias())
|
||||
|
||||
fun aLoadedContentState(
|
||||
roomId: RoomId = A_ROOM_ID,
|
||||
name: String = "Element X android",
|
||||
alias: String? = "#exa:matrix.org",
|
||||
alias: RoomAlias? = RoomAlias("#exa:matrix.org"),
|
||||
topic: String? = "Element X is a secure, private and decentralized messenger.",
|
||||
numberOfMembers: Long? = null,
|
||||
isDirect: Boolean = false,
|
||||
|
|
@ -77,3 +103,4 @@ fun aJoinRoomState(
|
|||
)
|
||||
|
||||
private val A_ROOM_ID = RoomId("!exa:matrix.org")
|
||||
private val A_ROOM_ALIAS = RoomAlias("#exa:matrix.org")
|
||||
|
|
|
|||
|
|
@ -16,42 +16,36 @@
|
|||
|
||||
package io.element.android.features.joinroom.impl
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
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
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewDescriptionAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewSubtitleAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewTitleAtom
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonRowMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.RoomPreviewMembersCountMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.RoomPreviewOrganism
|
||||
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.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
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.Icon
|
||||
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.TopAppBar
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
|
|
@ -71,7 +65,7 @@ fun JoinRoomView(
|
|||
},
|
||||
footer = {
|
||||
JoinRoomFooter(
|
||||
joinAuthorisationStatus = state.joinAuthorisationStatus,
|
||||
state = state,
|
||||
onAcceptInvite = {
|
||||
state.eventSink(JoinRoomEvents.AcceptInvite)
|
||||
},
|
||||
|
|
@ -81,6 +75,9 @@ fun JoinRoomView(
|
|||
onJoinRoom = {
|
||||
state.eventSink(JoinRoomEvents.JoinRoom)
|
||||
},
|
||||
onRetry = {
|
||||
state.eventSink(JoinRoomEvents.RetryFetchingContent)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -88,46 +85,57 @@ fun JoinRoomView(
|
|||
|
||||
@Composable
|
||||
private fun JoinRoomFooter(
|
||||
joinAuthorisationStatus: JoinAuthorisationStatus,
|
||||
state: JoinRoomState,
|
||||
onAcceptInvite: () -> Unit,
|
||||
onDeclineInvite: () -> Unit,
|
||||
onJoinRoom: () -> Unit,
|
||||
onRetry: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (joinAuthorisationStatus) {
|
||||
JoinAuthorisationStatus.IsInvited -> {
|
||||
ButtonRowMolecule(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(20.dp)) {
|
||||
OutlinedButton(
|
||||
text = stringResource(CommonStrings.action_decline),
|
||||
onClick = onDeclineInvite,
|
||||
modifier = Modifier.weight(1f),
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
if (state.contentState is ContentState.Failure) {
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_retry),
|
||||
onClick = onRetry,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
} else {
|
||||
val joinAuthorisationStatus = state.joinAuthorisationStatus
|
||||
when (joinAuthorisationStatus) {
|
||||
JoinAuthorisationStatus.IsInvited -> {
|
||||
ButtonRowMolecule(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(20.dp)) {
|
||||
OutlinedButton(
|
||||
text = stringResource(CommonStrings.action_decline),
|
||||
onClick = onDeclineInvite,
|
||||
modifier = Modifier.weight(1f),
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_accept),
|
||||
onClick = onAcceptInvite,
|
||||
modifier = Modifier.weight(1f),
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
}
|
||||
}
|
||||
JoinAuthorisationStatus.CanJoin -> {
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_accept),
|
||||
onClick = onAcceptInvite,
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(R.string.screen_join_room_join_action),
|
||||
onClick = onJoinRoom,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
}
|
||||
JoinAuthorisationStatus.CanKnock -> {
|
||||
Button(
|
||||
text = stringResource(R.string.screen_join_room_knock_action),
|
||||
onClick = onJoinRoom,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
}
|
||||
JoinAuthorisationStatus.Unknown -> Unit
|
||||
}
|
||||
JoinAuthorisationStatus.CanJoin -> {
|
||||
Button(
|
||||
text = stringResource(R.string.screen_join_room_join_action),
|
||||
onClick = onJoinRoom,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
}
|
||||
JoinAuthorisationStatus.CanKnock -> {
|
||||
Button(
|
||||
text = stringResource(R.string.screen_join_room_knock_action),
|
||||
onClick = onJoinRoom,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
}
|
||||
JoinAuthorisationStatus.Unknown -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -138,43 +146,43 @@ private fun JoinRoomContent(
|
|||
) {
|
||||
when (contentState) {
|
||||
is ContentState.Loaded -> {
|
||||
ContentScaffold(
|
||||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
Avatar(contentState.avatarData(AvatarSize.RoomHeader))
|
||||
},
|
||||
title = {
|
||||
Title(contentState.computedTitle)
|
||||
RoomPreviewTitleAtom(contentState.computedTitle)
|
||||
},
|
||||
subtitle = {
|
||||
Subtitle(contentState.computedSubtitle)
|
||||
RoomPreviewSubtitleAtom(contentState.computedSubtitle)
|
||||
},
|
||||
description = {
|
||||
Description(contentState.topic ?: "")
|
||||
RoomPreviewDescriptionAtom(contentState.topic ?: "")
|
||||
},
|
||||
memberCount = {
|
||||
if (contentState.showMemberCount) {
|
||||
MembersCount(memberCount = contentState.numberOfMembers ?: 0)
|
||||
RoomPreviewMembersCountMolecule(memberCount = contentState.numberOfMembers ?: 0)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
is ContentState.UnknownRoom -> {
|
||||
ContentScaffold(
|
||||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||
},
|
||||
title = {
|
||||
Title(stringResource(R.string.screen_join_room_title_no_preview))
|
||||
RoomPreviewTitleAtom(stringResource(R.string.screen_join_room_title_no_preview))
|
||||
},
|
||||
subtitle = {
|
||||
Subtitle(stringResource(R.string.screen_join_room_subtitle_no_preview))
|
||||
RoomPreviewSubtitleAtom(stringResource(R.string.screen_join_room_subtitle_no_preview))
|
||||
},
|
||||
)
|
||||
}
|
||||
is ContentState.Loading -> {
|
||||
ContentScaffold(
|
||||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||
|
|
@ -187,94 +195,31 @@ private fun JoinRoomContent(
|
|||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentScaffold(
|
||||
avatar: @Composable () -> Unit,
|
||||
title: @Composable () -> Unit,
|
||||
subtitle: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
description: @Composable (() -> Unit)? = null,
|
||||
memberCount: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
avatar()
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
title()
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
subtitle()
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
if (memberCount != null) {
|
||||
memberCount()
|
||||
is ContentState.Failure -> {
|
||||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||
},
|
||||
title = {
|
||||
when (contentState.roomIdOrAlias) {
|
||||
is RoomIdOrAlias.Alias -> {
|
||||
RoomPreviewTitleAtom(contentState.roomIdOrAlias.identifier)
|
||||
}
|
||||
is RoomIdOrAlias.Id -> {
|
||||
PlaceholderAtom(width = 200.dp, height = 22.dp)
|
||||
}
|
||||
}
|
||||
},
|
||||
subtitle = {
|
||||
Text(
|
||||
text = "Failed to get information about the room",
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
if (description != null) {
|
||||
description()
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Title(title: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = title,
|
||||
style = ElementTheme.typography.fontHeadingMdBold,
|
||||
textAlign = TextAlign.Center,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Subtitle(subtitle: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = subtitle,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
textAlign = TextAlign.Center,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Description(description: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = description,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
textAlign = TextAlign.Center,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MembersCount(memberCount: Long) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(color = ElementTheme.colors.bgSubtleSecondary, shape = CircleShape)
|
||||
.widthIn(min = 48.dp)
|
||||
.padding(all = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.UserProfile(),
|
||||
contentDescription = null,
|
||||
tint = ElementTheme.colors.iconSecondary,
|
||||
)
|
||||
Text(
|
||||
text = "$memberCount",
|
||||
style = ElementTheme.typography.fontBodySmMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -291,7 +236,7 @@ private fun JoinRoomTopBar(
|
|||
)
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun JoinRoomViewPreview(@PreviewParameter(JoinRoomStateProvider::class) state: JoinRoomState) = ElementPreview {
|
||||
JoinRoomView(
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import io.element.android.libraries.architecture.Presenter
|
|||
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.core.RoomIdOrAlias
|
||||
import java.util.Optional
|
||||
|
||||
@Module
|
||||
|
|
@ -37,9 +38,14 @@ object JoinRoomModule {
|
|||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
): JoinRoomPresenter.Factory {
|
||||
return object : JoinRoomPresenter.Factory {
|
||||
override fun create(roomId: RoomId, roomDescription: Optional<RoomDescription>): JoinRoomPresenter {
|
||||
override fun create(
|
||||
roomId: RoomId,
|
||||
roomIdOrAlias: RoomIdOrAlias,
|
||||
roomDescription: Optional<RoomDescription>,
|
||||
): JoinRoomPresenter {
|
||||
return JoinRoomPresenter(
|
||||
roomId = roomId,
|
||||
roomIdOrAlias = roomIdOrAlias,
|
||||
roomDescription = roomDescription,
|
||||
matrixClient = client,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||
|
|
|
|||
|
|
@ -23,8 +23,12 @@ import io.element.android.features.invite.api.response.anAcceptDeclineInviteStat
|
|||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
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.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
|
|
@ -49,9 +53,10 @@ class JoinRoomPresenterTest {
|
|||
val presenter = createJoinRoomPresenter()
|
||||
presenter.test {
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(ContentState.Loading(A_ROOM_ID))
|
||||
assertThat(state.contentState).isEqualTo(ContentState.Loading(A_ROOM_ID.toRoomIdOrAlias()))
|
||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.Unknown)
|
||||
assertThat(state.acceptDeclineInviteState).isEqualTo(anAcceptDeclineInviteState())
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -237,6 +242,110 @@ class JoinRoomPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewResult = {
|
||||
Result.success(
|
||||
RoomPreview(
|
||||
roomId = A_ROOM_ID,
|
||||
canonicalAlias = RoomAlias("#alias:matrix.org"),
|
||||
name = "Room name",
|
||||
topic = "Room topic",
|
||||
avatarUrl = "avatarUrl",
|
||||
numberOfJoinedMembers = 2,
|
||||
roomType = null,
|
||||
isHistoryWorldReadable = false,
|
||||
isJoined = false,
|
||||
isInvited = false,
|
||||
isPublic = true,
|
||||
canKnock = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(
|
||||
ContentState.Loaded(
|
||||
roomId = A_ROOM_ID,
|
||||
name = "Room name",
|
||||
topic = "Room topic",
|
||||
alias = RoomAlias("#alias:matrix.org"),
|
||||
numberOfMembers = 2,
|
||||
isDirect = false,
|
||||
roomAvatarUrl = "avatarUrl",
|
||||
joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded with error`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewResult = {
|
||||
Result.failure(AN_EXCEPTION)
|
||||
}
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(
|
||||
ContentState.Failure(
|
||||
roomIdOrAlias = A_ROOM_ID.toRoomIdOrAlias(),
|
||||
error = AN_EXCEPTION
|
||||
)
|
||||
)
|
||||
state.eventSink(JoinRoomEvents.RetryFetchingContent)
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(
|
||||
ContentState.Loading(A_ROOM_ID.toRoomIdOrAlias())
|
||||
)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(
|
||||
ContentState.Failure(
|
||||
roomIdOrAlias = A_ROOM_ID.toRoomIdOrAlias(),
|
||||
error = AN_EXCEPTION
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded with error 403`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewResult = {
|
||||
Result.failure(Exception("403"))
|
||||
}
|
||||
)
|
||||
val presenter = createJoinRoomPresenter(
|
||||
matrixClient = client
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contentState).isEqualTo(
|
||||
ContentState.UnknownRoom(
|
||||
roomIdOrAlias = A_ROOM_ID.toRoomIdOrAlias(),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createJoinRoomPresenter(
|
||||
roomId: RoomId = A_ROOM_ID,
|
||||
roomDescription: Optional<RoomDescription> = Optional.empty(),
|
||||
|
|
@ -245,6 +354,7 @@ class JoinRoomPresenterTest {
|
|||
): JoinRoomPresenter {
|
||||
return JoinRoomPresenter(
|
||||
roomId = roomId,
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
roomDescription = roomDescription,
|
||||
matrixClient = matrixClient,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter
|
||||
|
|
@ -255,7 +365,7 @@ class JoinRoomPresenterTest {
|
|||
roomId: RoomId = A_ROOM_ID,
|
||||
name: String? = A_ROOM_NAME,
|
||||
topic: String? = "A room about something",
|
||||
alias: String? = "#alias:matrix.org",
|
||||
alias: RoomAlias? = RoomAlias("#alias:matrix.org"),
|
||||
avatarUrl: String? = null,
|
||||
joinRule: RoomDescription.JoinRule = RoomDescription.JoinRule.UNKNOWN,
|
||||
numberOfMembers: Long = 2L
|
||||
|
|
|
|||
|
|
@ -20,19 +20,28 @@ import com.bumble.appyx.core.modality.BuildContext
|
|||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
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.permalink.PermalinkData
|
||||
|
||||
interface MessagesEntryPoint : FeatureEntryPoint {
|
||||
fun createNode(
|
||||
parentNode: Node,
|
||||
buildContext: BuildContext,
|
||||
callback: Callback,
|
||||
): Node
|
||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||
|
||||
interface NodeBuilder {
|
||||
fun params(params: Params): NodeBuilder
|
||||
fun callback(callback: Callback): NodeBuilder
|
||||
fun build(): Node
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val focusedEventId: EventId?,
|
||||
)
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onRoomDetailsClicked()
|
||||
fun onUserDataClicked(userId: UserId)
|
||||
fun onPermalinkClicked(data: PermalinkData)
|
||||
fun onForwardedToSingleRoom(roomId: RoomId)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.messages.impl
|
|||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.messages.api.MessagesEntryPoint
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
|
|
@ -26,11 +27,23 @@ import javax.inject.Inject
|
|||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultMessagesEntryPoint @Inject constructor() : MessagesEntryPoint {
|
||||
override fun createNode(
|
||||
parentNode: Node,
|
||||
buildContext: BuildContext,
|
||||
callback: MessagesEntryPoint.Callback
|
||||
): Node {
|
||||
return parentNode.createNode<MessagesFlowNode>(buildContext, listOf(callback))
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): MessagesEntryPoint.NodeBuilder {
|
||||
val plugins = ArrayList<Plugin>()
|
||||
|
||||
return object : MessagesEntryPoint.NodeBuilder {
|
||||
override fun params(params: MessagesEntryPoint.Params): MessagesEntryPoint.NodeBuilder {
|
||||
plugins += MessagesNode.Inputs(focusedEventId = params.focusedEventId)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun callback(callback: MessagesEntryPoint.Callback): MessagesEntryPoint.NodeBuilder {
|
||||
plugins += callback
|
||||
return this
|
||||
}
|
||||
|
||||
override fun build(): Node {
|
||||
return parentNode.createNode<MessagesFlowNode>(buildContext, plugins)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import io.element.android.features.poll.api.create.CreatePollEntryPoint
|
|||
import io.element.android.features.poll.api.create.CreatePollMode
|
||||
import io.element.android.libraries.architecture.BackstackWithOverlayBox
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.architecture.overlay.Overlay
|
||||
import io.element.android.libraries.architecture.overlay.operation.show
|
||||
|
|
@ -62,6 +63,7 @@ import io.element.android.libraries.matrix.api.core.EventId
|
|||
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.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.libraries.mediaviewer.api.local.MediaInfo
|
||||
import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode
|
||||
|
|
@ -79,7 +81,7 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
private val createPollEntryPoint: CreatePollEntryPoint,
|
||||
) : BaseFlowNode<MessagesFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Messages,
|
||||
initialElement = NavTarget.Messages(plugins.filterIsInstance<Inputs>().firstOrNull()?.focusedEventId),
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
overlay = Overlay(
|
||||
|
|
@ -88,12 +90,16 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
) {
|
||||
data class Inputs(val focusedEventId: EventId?) : NodeInputs
|
||||
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
data object Empty : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object Messages : NavTarget
|
||||
data class Messages(
|
||||
val focusedEventId: EventId? = null,
|
||||
) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class MediaViewer(
|
||||
|
|
@ -149,6 +155,10 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
callback?.onUserDataClicked(userId)
|
||||
}
|
||||
|
||||
override fun onPermalinkClicked(data: PermalinkData) {
|
||||
callback?.onPermalinkClicked(data)
|
||||
}
|
||||
|
||||
override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) {
|
||||
backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo))
|
||||
}
|
||||
|
|
@ -181,7 +191,10 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
ElementCallActivity.start(context, inputs)
|
||||
}
|
||||
}
|
||||
createNode<MessagesNode>(buildContext, listOf(callback))
|
||||
val params = MessagesNode.Inputs(
|
||||
focusedEventId = navTarget.focusedEventId,
|
||||
)
|
||||
createNode<MessagesNode>(buildContext, listOf(callback, params))
|
||||
}
|
||||
is NavTarget.MediaViewer -> {
|
||||
val inputs = MediaViewerNode.Inputs(
|
||||
|
|
|
|||
|
|
@ -34,7 +34,10 @@ import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPr
|
|||
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.libraries.androidutils.system.openUrlInExternalApp
|
||||
import io.element.android.libraries.androidutils.system.toast
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
|
@ -42,6 +45,7 @@ import io.element.android.libraries.matrix.api.core.UserId
|
|||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.alias.matches
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.libraries.mediaplayer.api.MediaPlayer
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
|
|
@ -58,15 +62,21 @@ class MessagesNode @AssistedInject constructor(
|
|||
private val timelineItemPresenterFactories: TimelineItemPresenterFactories,
|
||||
private val mediaPlayer: MediaPlayer,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
@ApplicationContext
|
||||
private val context: Context,
|
||||
) : Node(buildContext, plugins = plugins), MessagesNavigator {
|
||||
private val presenter = presenterFactory.create(this)
|
||||
private val callback = plugins<Callback>().firstOrNull()
|
||||
|
||||
// TODO Handle navigation to the Event
|
||||
data class Inputs(val focusedEventId: EventId?) : NodeInputs
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onRoomDetailsClicked()
|
||||
fun onEventClicked(event: TimelineItem.Event): Boolean
|
||||
fun onPreviewAttachments(attachments: ImmutableList<Attachment>)
|
||||
fun onUserDataClicked(userId: UserId)
|
||||
fun onPermalinkClicked(data: PermalinkData)
|
||||
fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo)
|
||||
fun onForwardEventClicked(eventId: EventId)
|
||||
fun onReportMessage(eventId: EventId, senderId: UserId)
|
||||
|
|
@ -109,16 +119,12 @@ class MessagesNode @AssistedInject constructor(
|
|||
) {
|
||||
when (val permalink = permalinkParser.parse(url)) {
|
||||
is PermalinkData.UserLink -> {
|
||||
// Open the room member profile, it will fallback to
|
||||
// the user profile if the user is not in the room
|
||||
callback?.onUserDataClicked(permalink.userId)
|
||||
}
|
||||
is PermalinkData.RoomLink -> {
|
||||
// TODO Implement room link handling
|
||||
}
|
||||
is PermalinkData.EventIdAliasLink -> {
|
||||
// TODO Implement room and Event link handling
|
||||
}
|
||||
is PermalinkData.EventIdLink -> {
|
||||
// TODO Implement room and Event link handling
|
||||
handleRoomLinkClicked(permalink)
|
||||
}
|
||||
is PermalinkData.FallbackLink,
|
||||
is PermalinkData.RoomEmailInviteLink -> {
|
||||
|
|
@ -127,6 +133,20 @@ class MessagesNode @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleRoomLinkClicked(roomLink: PermalinkData.RoomLink) {
|
||||
if (room.matches(roomLink.roomIdOrAlias)) {
|
||||
if (roomLink.eventId != null) {
|
||||
// TODO Handle navigation to the Event
|
||||
context.toast("TODO Handle navigation to the Event ${roomLink.eventId}")
|
||||
} else {
|
||||
// Click on the same room, ignore
|
||||
context.toast("Already viewing this room!")
|
||||
}
|
||||
} else {
|
||||
callback?.onPermalinkClicked(roomLink)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) {
|
||||
callback?.onShowEventDebugInfoClicked(eventId, debugInfo)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ class MessagesPresenter @AssistedInject constructor(
|
|||
|
||||
private fun MatrixRoomInfo.avatarData(): AvatarData {
|
||||
return AvatarData(
|
||||
id = id,
|
||||
id = id.value,
|
||||
name = name,
|
||||
url = avatarUrl ?: room.avatarUrl,
|
||||
size = AvatarSize.TimelineRoom
|
||||
|
|
|
|||
|
|
@ -720,7 +720,7 @@ class MessagesPresenterTest {
|
|||
private fun TestScope.createMessagesPresenter(
|
||||
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
matrixRoom: MatrixRoom = FakeMatrixRoom().apply {
|
||||
givenRoomInfo(aRoomInfo(id = roomId.value, name = ""))
|
||||
givenRoomInfo(aRoomInfo(id = roomId, name = ""))
|
||||
},
|
||||
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
||||
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ class TypingNotificationPresenterTest {
|
|||
|
||||
private fun createPresenter(
|
||||
matrixRoom: MatrixRoom = FakeMatrixRoom().apply {
|
||||
givenRoomInfo(aRoomInfo(id = roomId.value, name = ""))
|
||||
givenRoomInfo(aRoomInfo(id = roomId, name = ""))
|
||||
},
|
||||
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(
|
||||
isRenderTypingNotificationsEnabled = true
|
||||
|
|
|
|||
28
features/roomaliasresolver/api/build.gradle.kts
Normal file
28
features/roomaliasresolver/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.roomaliasresolver.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomaliasesolver.api
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
interface RoomAliasResolverEntryPoint : FeatureEntryPoint {
|
||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||
|
||||
interface NodeBuilder {
|
||||
fun callback(callback: Callback): NodeBuilder
|
||||
fun params(params: Params): NodeBuilder
|
||||
fun build(): Node
|
||||
}
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onAliasResolved(roomId: RoomId)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val roomAlias: RoomAlias
|
||||
) : NodeInputs
|
||||
}
|
||||
53
features/roomaliasresolver/impl/build.gradle.kts
Normal file
53
features/roomaliasresolver/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
alias(libs.plugins.ksp)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.roomaliasresolver.impl"
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
api(projects.features.roomaliasresolver.api)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
|
||||
ksp(libs.showkase.processor)
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomaliasresolver.impl
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultRoomAliasResolverEntryPoint @Inject constructor() : RoomAliasResolverEntryPoint {
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomAliasResolverEntryPoint.NodeBuilder {
|
||||
val plugins = ArrayList<Plugin>()
|
||||
|
||||
return object : RoomAliasResolverEntryPoint.NodeBuilder {
|
||||
override fun callback(callback: RoomAliasResolverEntryPoint.Callback): RoomAliasResolverEntryPoint.NodeBuilder {
|
||||
plugins += callback
|
||||
return this
|
||||
}
|
||||
|
||||
override fun params(params: RoomAliasResolverEntryPoint.Params): RoomAliasResolverEntryPoint.NodeBuilder {
|
||||
plugins += params
|
||||
return this
|
||||
}
|
||||
|
||||
override fun build(): Node {
|
||||
return parentNode.createNode<RoomAliasResolverNode>(buildContext, plugins)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomaliasresolver.impl
|
||||
|
||||
sealed interface RoomAliasResolverEvents {
|
||||
data object Retry : RoomAliasResolverEvents
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomaliasresolver.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.core.plugin.plugins
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class RoomAliasResolverNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
presenterFactory: RoomAliasResolverPresenter.Factory,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
private val inputs = inputs<RoomAliasResolverEntryPoint.Params>()
|
||||
|
||||
private val presenter = presenterFactory.create(
|
||||
inputs.roomAlias
|
||||
)
|
||||
|
||||
private fun onAliasResolved(roomId: RoomId) {
|
||||
plugins<RoomAliasResolverEntryPoint.Callback>().forEach { it.onAliasResolved(roomId) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
RoomAliasResolverView(
|
||||
state = state,
|
||||
onAliasResolved = ::onAliasResolved,
|
||||
onBackPressed = ::navigateUp,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomaliasresolver.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class RoomAliasResolverPresenter @AssistedInject constructor(
|
||||
@Assisted private val roomAlias: RoomAlias,
|
||||
private val matrixClient: MatrixClient,
|
||||
) : Presenter<RoomAliasResolverState> {
|
||||
interface Factory {
|
||||
fun create(
|
||||
roomAlias: RoomAlias,
|
||||
): RoomAliasResolverPresenter
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): RoomAliasResolverState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val resolveState: MutableState<AsyncData<RoomId>> = remember { mutableStateOf(AsyncData.Uninitialized) }
|
||||
LaunchedEffect(Unit) {
|
||||
resolveAlias(resolveState)
|
||||
}
|
||||
|
||||
fun handleEvents(event: RoomAliasResolverEvents) {
|
||||
when (event) {
|
||||
RoomAliasResolverEvents.Retry -> coroutineScope.resolveAlias(resolveState)
|
||||
}
|
||||
}
|
||||
|
||||
return RoomAliasResolverState(
|
||||
roomAlias = roomAlias,
|
||||
resolveState = resolveState.value,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.resolveAlias(resolveState: MutableState<AsyncData<RoomId>>) = launch {
|
||||
suspend {
|
||||
matrixClient.resolveRoomAlias(roomAlias).getOrThrow()
|
||||
}.runCatchingUpdatingState(resolveState)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomaliasresolver.impl
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
@Immutable
|
||||
data class RoomAliasResolverState(
|
||||
val roomAlias: RoomAlias,
|
||||
val resolveState: AsyncData<RoomId>,
|
||||
val eventSink: (RoomAliasResolverEvents) -> Unit
|
||||
)
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomaliasresolver.impl
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
open class RoomAliasResolverStateProvider : PreviewParameterProvider<RoomAliasResolverState> {
|
||||
override val values: Sequence<RoomAliasResolverState>
|
||||
get() = sequenceOf(
|
||||
aRoomAliasResolverState(),
|
||||
aRoomAliasResolverState(
|
||||
resolveState = AsyncData.Loading(),
|
||||
),
|
||||
aRoomAliasResolverState(
|
||||
resolveState = AsyncData.Failure(Exception("Error")),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun aRoomAliasResolverState(
|
||||
roomAlias: RoomAlias = A_ROOM_ALIAS,
|
||||
resolveState: AsyncData<RoomId> = AsyncData.Uninitialized,
|
||||
eventSink: (RoomAliasResolverEvents) -> Unit = {}
|
||||
) = RoomAliasResolverState(
|
||||
roomAlias = roomAlias,
|
||||
resolveState = resolveState,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
private val A_ROOM_ALIAS = RoomAlias("#exa:matrix.org")
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomaliasresolver.impl
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewTitleAtom
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.RoomPreviewOrganism
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
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
|
||||
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.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
fun RoomAliasResolverView(
|
||||
state: RoomAliasResolverState,
|
||||
onBackPressed: () -> Unit,
|
||||
onAliasResolved: (RoomId) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val latestOnAliasResolved by rememberUpdatedState(onAliasResolved)
|
||||
LaunchedEffect(state.resolveState) {
|
||||
if (state.resolveState is AsyncData.Success) {
|
||||
latestOnAliasResolved(state.resolveState.data)
|
||||
}
|
||||
}
|
||||
|
||||
HeaderFooterPage(
|
||||
modifier = modifier,
|
||||
paddingValues = PaddingValues(16.dp),
|
||||
topBar = {
|
||||
RoomAliasResolverTopBar(onBackClicked = onBackPressed)
|
||||
},
|
||||
content = {
|
||||
RoomAliasResolverContent(state = state)
|
||||
},
|
||||
footer = {
|
||||
RoomAliasResolverFooter(
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RoomAliasResolverFooter(
|
||||
state: RoomAliasResolverState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (state.resolveState) {
|
||||
is AsyncData.Failure -> {
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_retry),
|
||||
onClick = {
|
||||
state.eventSink(RoomAliasResolverEvents.Retry)
|
||||
},
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
}
|
||||
is AsyncData.Loading -> {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
AsyncData.Uninitialized,
|
||||
is AsyncData.Success -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RoomAliasResolverContent(
|
||||
state: RoomAliasResolverState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
RoomPreviewOrganism(
|
||||
modifier = modifier,
|
||||
avatar = {
|
||||
PlaceholderAtom(width = AvatarSize.RoomHeader.dp, height = AvatarSize.RoomHeader.dp)
|
||||
},
|
||||
title = {
|
||||
RoomPreviewTitleAtom(state.roomAlias.value)
|
||||
},
|
||||
subtitle = {
|
||||
},
|
||||
description = {
|
||||
if (state.resolveState.isFailure()) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.screen_room_alias_resolver_resolve_alias_failure),
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
},
|
||||
memberCount = {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun RoomAliasResolverTopBar(
|
||||
onBackClicked: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
navigationIcon = {
|
||||
BackButton(onClick = onBackClicked)
|
||||
},
|
||||
title = {},
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun RoomAliasResolverViewPreview(@PreviewParameter(RoomAliasResolverStateProvider::class) state: RoomAliasResolverState) = ElementPreview {
|
||||
RoomAliasResolverView(
|
||||
state = state,
|
||||
onAliasResolved = { },
|
||||
onBackPressed = { }
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomaliasresolver.impl.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.element.android.features.roomaliasresolver.impl.RoomAliasResolverPresenter
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
|
||||
@Module
|
||||
@ContributesTo(SessionScope::class)
|
||||
object RoomAliasResolverModule {
|
||||
@Provides
|
||||
fun providesJoinRoomPresenterFactory(
|
||||
client: MatrixClient,
|
||||
): RoomAliasResolverPresenter.Factory {
|
||||
return object : RoomAliasResolverPresenter.Factory {
|
||||
override fun create(roomAlias: RoomAlias): RoomAliasResolverPresenter {
|
||||
return RoomAliasResolverPresenter(
|
||||
roomAlias = roomAlias,
|
||||
matrixClient = client,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_room_alias_resolver_resolve_alias_failure">"Failed to resolve room alias."</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomaliasresolver.impl
|
||||
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class RoomAliasResolverPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
assertThat(awaitItem().resolveState.isUninitialized()).isTrue()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - resolve alias to roomId`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
resolveRoomAliasResult = { Result.success(A_ROOM_ID) }
|
||||
)
|
||||
val presenter = createPresenter(matrixClient = client)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
assertThat(awaitItem().resolveState.isUninitialized()).isTrue()
|
||||
assertThat(awaitItem().resolveState.isLoading()).isTrue()
|
||||
val resultState = awaitItem()
|
||||
assertThat(resultState.roomAlias).isEqualTo(A_ROOM_ALIAS)
|
||||
assertThat(resultState.resolveState.dataOrNull()).isEqualTo(A_ROOM_ID)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - resolve alias error and retry`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
resolveRoomAliasResult = { Result.failure(AN_EXCEPTION) }
|
||||
)
|
||||
val presenter = createPresenter(matrixClient = client)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
assertThat(awaitItem().resolveState.isUninitialized()).isTrue()
|
||||
assertThat(awaitItem().resolveState.isLoading()).isTrue()
|
||||
val resultState = awaitItem()
|
||||
assertThat(resultState.resolveState.errorOrNull()).isEqualTo(AN_EXCEPTION)
|
||||
resultState.eventSink(RoomAliasResolverEvents.Retry)
|
||||
val retryLoadingState = awaitItem()
|
||||
assertThat(retryLoadingState.resolveState.isLoading()).isTrue()
|
||||
val retryState = awaitItem()
|
||||
assertThat(retryState.resolveState.errorOrNull()).isEqualTo(AN_EXCEPTION)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPresenter(
|
||||
roomAlias: RoomAlias = A_ROOM_ALIAS,
|
||||
matrixClient: MatrixClient = FakeMatrixClient(),
|
||||
) = RoomAliasResolverPresenter(
|
||||
roomAlias = roomAlias,
|
||||
matrixClient = matrixClient,
|
||||
)
|
||||
}
|
||||
|
|
@ -128,7 +128,7 @@ class RoomDetailsPresenter @Inject constructor(
|
|||
val roomMemberDetailsState = roomMemberDetailsPresenter?.present()
|
||||
|
||||
return RoomDetailsState(
|
||||
roomId = room.roomId.value,
|
||||
roomId = room.roomId,
|
||||
roomName = roomName,
|
||||
roomAlias = room.alias,
|
||||
roomAvatarUrl = roomAvatar,
|
||||
|
|
|
|||
|
|
@ -18,13 +18,15 @@ package io.element.android.features.roomdetails.impl
|
|||
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
|
||||
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.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||
|
||||
data class RoomDetailsState(
|
||||
val roomId: String,
|
||||
val roomId: RoomId,
|
||||
val roomName: String,
|
||||
val roomAlias: String?,
|
||||
val roomAlias: RoomAlias?,
|
||||
val roomAvatarUrl: String?,
|
||||
val roomTopic: RoomTopicState,
|
||||
val memberCount: Long,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import io.element.android.features.leaveroom.api.LeaveRoomState
|
|||
import io.element.android.features.leaveroom.api.aLeaveRoomState
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
|
||||
import io.element.android.features.roomdetails.impl.members.details.aRoomMemberDetailsState
|
||||
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.UserId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
|
|
@ -71,9 +73,9 @@ fun aDmRoomMember(
|
|||
)
|
||||
|
||||
fun aRoomDetailsState(
|
||||
roomId: String = "a room id",
|
||||
roomId: RoomId = RoomId("!aRoomId:domain.com"),
|
||||
roomName: String = "Marketing",
|
||||
roomAlias: String? = "#marketing:domain.com",
|
||||
roomAlias: RoomAlias? = RoomAlias("#marketing:domain.com"),
|
||||
roomAvatarUrl: String? = null,
|
||||
roomTopic: RoomTopicState = RoomTopicState.ExistingTopic(
|
||||
"Welcome to #marketing, home of the Marketing team " +
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
|
|||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||
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.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.room.getBestName
|
||||
|
|
@ -302,9 +304,9 @@ private fun MainActionsSection(
|
|||
@Composable
|
||||
private fun RoomHeaderSection(
|
||||
avatarUrl: String?,
|
||||
roomId: String,
|
||||
roomId: RoomId,
|
||||
roomName: String,
|
||||
roomAlias: String?,
|
||||
roomAlias: RoomAlias?,
|
||||
openAvatarPreview: (url: String) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -314,7 +316,7 @@ private fun RoomHeaderSection(
|
|||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Avatar(
|
||||
avatarData = AvatarData(roomId, roomName, avatarUrl, AvatarSize.RoomHeader),
|
||||
avatarData = AvatarData(roomId.value, roomName, avatarUrl, AvatarSize.RoomHeader),
|
||||
modifier = Modifier
|
||||
.size(70.dp)
|
||||
.clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) }
|
||||
|
|
@ -329,7 +331,7 @@ private fun RoomHeaderSection(
|
|||
if (roomAlias != null) {
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text = roomAlias,
|
||||
text = roomAlias.value,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ class RoomDetailsPresenterTests {
|
|||
val presenter = createRoomDetailsPresenter(room)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.roomId).isEqualTo(room.roomId.value)
|
||||
assertThat(initialState.roomId).isEqualTo(room.roomId)
|
||||
assertThat(initialState.roomName).isEqualTo(room.name)
|
||||
assertThat(initialState.roomAvatarUrl).isEqualTo(room.avatarUrl)
|
||||
assertThat(initialState.roomTopic).isEqualTo(RoomTopicState.ExistingTopic(room.topic!!))
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import android.os.Parcelable
|
|||
import androidx.compose.runtime.Immutable
|
||||
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.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
|
@ -29,7 +30,7 @@ import kotlinx.parcelize.Parcelize
|
|||
data class RoomDescription(
|
||||
val roomId: RoomId,
|
||||
val name: String?,
|
||||
val alias: String?,
|
||||
val alias: RoomAlias?,
|
||||
val topic: String?,
|
||||
val avatarUrl: String?,
|
||||
val joinRule: JoinRule,
|
||||
|
|
@ -42,14 +43,14 @@ data class RoomDescription(
|
|||
}
|
||||
|
||||
@IgnoredOnParcel
|
||||
val computedName = name ?: alias ?: roomId.value
|
||||
val computedName = name ?: alias?.value ?: roomId.value
|
||||
|
||||
@IgnoredOnParcel
|
||||
val computedDescription: String
|
||||
get() {
|
||||
return when {
|
||||
topic != null -> topic
|
||||
name != null && alias != null -> alias
|
||||
name != null && alias != null -> alias.value
|
||||
name == null && alias == null -> ""
|
||||
else -> roomId.value
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.roomdirectory.impl.root
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -69,7 +70,7 @@ fun aRoomDescriptionList(): ImmutableList<RoomDescription> {
|
|||
roomId = RoomId("!exa:matrix.org"),
|
||||
name = "Element X Android",
|
||||
topic = "Element X is a secure, private and decentralized messenger.",
|
||||
alias = "#element-x-android:matrix.org",
|
||||
alias = RoomAlias("#element-x-android:matrix.org"),
|
||||
avatarUrl = null,
|
||||
joinRule = RoomDescription.JoinRule.PUBLIC,
|
||||
numberOfMembers = 2765,
|
||||
|
|
@ -78,7 +79,7 @@ fun aRoomDescriptionList(): ImmutableList<RoomDescription> {
|
|||
roomId = RoomId("!exi:matrix.org"),
|
||||
name = "Element X iOS",
|
||||
topic = "Element X is a secure, private and decentralized messenger.",
|
||||
alias = "#element-x-ios:matrix.org",
|
||||
alias = RoomAlias("#element-x-ios:matrix.org"),
|
||||
avatarUrl = null,
|
||||
joinRule = RoomDescription.JoinRule.UNKNOWN,
|
||||
numberOfMembers = 356,
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ import io.element.android.libraries.designsystem.theme.roomListRoomMessage
|
|||
import io.element.android.libraries.designsystem.theme.roomListRoomMessageDate
|
||||
import io.element.android.libraries.designsystem.theme.roomListRoomName
|
||||
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import timber.log.Timber
|
||||
|
|
@ -198,13 +199,13 @@ private fun NameAndTimestampRow(
|
|||
private fun InviteSubtitle(
|
||||
isDirect: Boolean,
|
||||
inviteSender: InviteSender?,
|
||||
canonicalAlias: String?,
|
||||
canonicalAlias: RoomAlias?,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val subtitle = if (isDirect) {
|
||||
inviteSender?.userId?.value
|
||||
} else {
|
||||
canonicalAlias
|
||||
canonicalAlias?.value
|
||||
}
|
||||
if (subtitle != null) {
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.roomlist.impl.model
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
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.room.RoomNotificationMode
|
||||
|
||||
|
|
@ -27,7 +28,7 @@ data class RoomListRoomSummary(
|
|||
val displayType: RoomSummaryDisplayType,
|
||||
val roomId: RoomId,
|
||||
val name: String,
|
||||
val canonicalAlias: String?,
|
||||
val canonicalAlias: RoomAlias?,
|
||||
val numberOfUnreadMessages: Int,
|
||||
val numberOfUnreadMentions: Int,
|
||||
val numberOfUnreadNotifications: Int,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.roomlist.impl.model
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
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.RoomAlias
|
||||
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.room.RoomNotificationMode
|
||||
|
|
@ -88,7 +89,7 @@ open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSu
|
|||
userId = "@alice:matrix.org",
|
||||
displayName = "Alice",
|
||||
),
|
||||
canonicalAlias = "#alias:matrix.org",
|
||||
canonicalAlias = RoomAlias("#alias:matrix.org"),
|
||||
),
|
||||
aRoomListRoomSummary(
|
||||
name = "Bob",
|
||||
|
|
@ -129,7 +130,7 @@ internal fun aRoomListRoomSummary(
|
|||
isFavorite: Boolean = false,
|
||||
inviteSender: InviteSender? = null,
|
||||
displayType: RoomSummaryDisplayType = RoomSummaryDisplayType.ROOM,
|
||||
canonicalAlias: String? = null,
|
||||
canonicalAlias: RoomAlias? = null,
|
||||
) = RoomListRoomSummary(
|
||||
id = id,
|
||||
roomId = RoomId(id),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.atomic.atoms
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
||||
@Composable
|
||||
fun RoomPreviewDescriptionAtom(description: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = description,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
textAlign = TextAlign.Center,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.atomic.atoms
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
||||
@Composable
|
||||
fun RoomPreviewSubtitleAtom(subtitle: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = subtitle,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
textAlign = TextAlign.Center,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.atomic.atoms
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
||||
@Composable
|
||||
fun RoomPreviewTitleAtom(title: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = title,
|
||||
style = ElementTheme.typography.fontHeadingMdBold,
|
||||
textAlign = TextAlign.Center,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.atomic.molecules
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
||||
@Composable
|
||||
fun RoomPreviewMembersCountMolecule(
|
||||
memberCount: Long,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.background(color = ElementTheme.colors.bgSubtleSecondary, shape = CircleShape)
|
||||
.widthIn(min = 48.dp)
|
||||
.padding(start = 2.dp, end = 6.dp, top = 2.dp, bottom = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.UserProfile(),
|
||||
contentDescription = null,
|
||||
tint = ElementTheme.colors.iconSecondary,
|
||||
)
|
||||
Text(
|
||||
text = "$memberCount",
|
||||
style = ElementTheme.typography.fontBodySmMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun RoomPreviewMembersCountMoleculePreview() = ElementPreview {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
RoomPreviewMembersCountMolecule(memberCount = 1)
|
||||
RoomPreviewMembersCountMolecule(memberCount = 888)
|
||||
RoomPreviewMembersCountMolecule(memberCount = 123_456)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.atomic.organisms
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun RoomPreviewOrganism(
|
||||
avatar: @Composable () -> Unit,
|
||||
title: @Composable () -> Unit,
|
||||
subtitle: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
description: @Composable (() -> Unit)? = null,
|
||||
memberCount: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
avatar()
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
title()
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
subtitle()
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
if (memberCount != null) {
|
||||
memberCount()
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
if (description != null) {
|
||||
description()
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,9 @@
|
|||
package io.element.android.libraries.matrix.api
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
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.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||
|
|
@ -30,6 +32,7 @@ import io.element.android.libraries.matrix.api.pusher.PushersService
|
|||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
|
|
@ -98,4 +101,6 @@ interface MatrixClient : Closeable {
|
|||
|
||||
suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit>
|
||||
suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>>
|
||||
suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result<RoomId>
|
||||
suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias): Result<RoomPreview>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import io.element.android.libraries.androidutils.metadata.isInDebug
|
||||
import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class RoomAlias(val value: String) : Serializable {
|
||||
init {
|
||||
if (isInDebug && !MatrixPatterns.isRoomAlias(value)) {
|
||||
error("`$value` is not a valid room alias.\n Example room alias: `#room_alias:domain`.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
sealed interface RoomIdOrAlias : Parcelable {
|
||||
@Parcelize
|
||||
@JvmInline
|
||||
value class Id(val roomId: RoomId) : RoomIdOrAlias
|
||||
|
||||
@Parcelize
|
||||
@JvmInline
|
||||
value class Alias(val roomAlias: RoomAlias) : RoomIdOrAlias
|
||||
|
||||
val identifier: String
|
||||
get() = when (this) {
|
||||
is Id -> roomId.value
|
||||
is Alias -> roomAlias.value
|
||||
}
|
||||
}
|
||||
|
||||
fun RoomId.toRoomIdOrAlias() = RoomIdOrAlias.Id(this)
|
||||
fun RoomAlias.toRoomIdOrAlias() = RoomIdOrAlias.Alias(this)
|
||||
|
|
@ -20,8 +20,10 @@ import android.net.Uri
|
|||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
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 kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* This sealed class represents all the permalink cases.
|
||||
|
|
@ -29,36 +31,11 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
*/
|
||||
@Immutable
|
||||
sealed interface PermalinkData {
|
||||
sealed interface RoomLink : PermalinkData {
|
||||
val viaParameters: ImmutableList<String>
|
||||
}
|
||||
|
||||
data class RoomIdLink(
|
||||
val roomId: RoomId,
|
||||
override val viaParameters: ImmutableList<String>
|
||||
) : RoomLink
|
||||
|
||||
data class RoomAliasLink(
|
||||
val roomAlias: String,
|
||||
override val viaParameters: ImmutableList<String>
|
||||
) : RoomLink
|
||||
|
||||
sealed interface EventLink : PermalinkData {
|
||||
val eventId: EventId
|
||||
val viaParameters: ImmutableList<String>
|
||||
}
|
||||
|
||||
data class EventIdLink(
|
||||
val roomId: RoomId,
|
||||
override val eventId: EventId,
|
||||
override val viaParameters: ImmutableList<String>
|
||||
) : EventLink
|
||||
|
||||
data class EventIdAliasLink(
|
||||
val roomAlias: String,
|
||||
override val eventId: EventId,
|
||||
override val viaParameters: ImmutableList<String>
|
||||
) : EventLink
|
||||
data class RoomLink(
|
||||
val roomIdOrAlias: RoomIdOrAlias,
|
||||
val eventId: EventId? = null,
|
||||
val viaParameters: ImmutableList<String> = persistentListOf()
|
||||
) : PermalinkData
|
||||
|
||||
/*
|
||||
* &room_name=Team2
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.api.room
|
|||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
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.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
|
|
@ -45,8 +46,8 @@ interface MatrixRoom : Closeable {
|
|||
val roomId: RoomId
|
||||
val name: String?
|
||||
val displayName: String
|
||||
val alias: String?
|
||||
val alternativeAliases: List<String>
|
||||
val alias: RoomAlias?
|
||||
val alternativeAliases: List<RoomAlias>
|
||||
val topic: String?
|
||||
val avatarUrl: String?
|
||||
val isEncrypted: Boolean
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
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.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -24,7 +26,7 @@ import kotlinx.collections.immutable.ImmutableMap
|
|||
|
||||
@Immutable
|
||||
data class MatrixRoomInfo(
|
||||
val id: String,
|
||||
val id: RoomId,
|
||||
val name: String?,
|
||||
val topic: String?,
|
||||
val avatarUrl: String?,
|
||||
|
|
@ -33,7 +35,7 @@ data class MatrixRoomInfo(
|
|||
val isSpace: Boolean,
|
||||
val isTombstoned: Boolean,
|
||||
val isFavorite: Boolean,
|
||||
val canonicalAlias: String?,
|
||||
val canonicalAlias: RoomAlias?,
|
||||
val alternativeAliases: ImmutableList<String>,
|
||||
val currentUserMembership: CurrentUserMembership,
|
||||
val latestEvent: EventTimelineItem?,
|
||||
|
|
|
|||
|
|
@ -23,5 +23,5 @@ sealed interface Mention {
|
|||
data class User(val userId: UserId) : Mention
|
||||
data object AtRoom : Mention
|
||||
data class Room(val roomId: RoomId) : Mention
|
||||
data class RoomAlias(val roomAlias: String?) : Mention
|
||||
data class RoomAlias(val roomAlias: RoomAlias?) : Mention
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room.alias
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
|
||||
/**
|
||||
* Return true if the given roomIdOrAlias is the same room as this room.
|
||||
*/
|
||||
fun MatrixRoom.matches(roomIdOrAlias: RoomIdOrAlias): Boolean {
|
||||
return when (roomIdOrAlias) {
|
||||
is RoomIdOrAlias.Id -> {
|
||||
roomIdOrAlias.roomId == roomId
|
||||
}
|
||||
is RoomIdOrAlias.Alias -> {
|
||||
roomIdOrAlias.roomAlias == alias || roomIdOrAlias.roomAlias in alternativeAliases
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room.preview
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
data class RoomPreview(
|
||||
/** The room id for this room. */
|
||||
val roomId: RoomId,
|
||||
/** The canonical alias for the room. */
|
||||
val canonicalAlias: RoomAlias?,
|
||||
/** The room's name, if set. */
|
||||
val name: String?,
|
||||
/** The room's topic, if set. */
|
||||
val topic: String?,
|
||||
/** The MXC URI to the room's avatar, if set. */
|
||||
val avatarUrl: String?,
|
||||
/** The number of joined members. */
|
||||
val numberOfJoinedMembers: Long,
|
||||
/** The room type (space, custom) or nothing, if it's a regular room. */
|
||||
val roomType: String?,
|
||||
/** Is the history world-readable for this room? */
|
||||
val isHistoryWorldReadable: Boolean,
|
||||
/** Is the room joined by the current user? */
|
||||
val isJoined: Boolean,
|
||||
/** Is the current user invited to this room? */
|
||||
val isInvited: Boolean,
|
||||
/** is the join rule public for this room? */
|
||||
val isPublic: Boolean,
|
||||
/** Can we knock (or restricted-knock) to this room? */
|
||||
val canKnock: Boolean,
|
||||
)
|
||||
|
|
@ -16,13 +16,14 @@
|
|||
|
||||
package io.element.android.libraries.matrix.api.roomdirectory
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
data class RoomDescription(
|
||||
val roomId: RoomId,
|
||||
val name: String?,
|
||||
val topic: String?,
|
||||
val alias: String?,
|
||||
val alias: RoomAlias?,
|
||||
val avatarUrl: String?,
|
||||
val joinRule: JoinRule,
|
||||
val isWorldReadable: Boolean,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.element.android.libraries.matrix.api.roomlist
|
||||
|
||||
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.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
|
|
@ -37,7 +38,7 @@ sealed interface RoomSummary {
|
|||
data class RoomSummaryDetails(
|
||||
val roomId: RoomId,
|
||||
val name: String,
|
||||
val canonicalAlias: String?,
|
||||
val canonicalAlias: RoomAlias?,
|
||||
val isDirect: Boolean,
|
||||
val avatarUrl: String?,
|
||||
val lastMessage: RoomMessage?,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
|||
import io.element.android.libraries.core.coroutine.childScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
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.createroom.CreateRoomParameters
|
||||
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
||||
|
|
@ -37,6 +39,7 @@ import io.element.android.libraries.matrix.api.pusher.PushersService
|
|||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.roomlist.awaitLoaded
|
||||
|
|
@ -460,6 +463,24 @@ class RustMatrixClient(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("TooGenericExceptionThrown")
|
||||
override suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result<RoomId> = withContext(sessionDispatcher) {
|
||||
runCatching {
|
||||
// TODO Waiting for SDK to be released
|
||||
throw Exception("Not implemented")
|
||||
// client.resolveRoomAlias(roomAlias.value).let(::RoomId)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("TooGenericExceptionThrown")
|
||||
override suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias): Result<RoomPreview> = withContext(sessionDispatcher) {
|
||||
runCatching {
|
||||
// TODO Waiting for SDK to be released
|
||||
throw Exception("Not implemented")
|
||||
// client.getRoomPreview(roomIdOrAlias.identifier).let(RoomPreviewMapper::map)
|
||||
}
|
||||
}
|
||||
|
||||
override fun syncService(): SyncService = rustSyncService
|
||||
|
||||
override fun sessionVerificationService(): SessionVerificationService = verificationService
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ import android.net.Uri
|
|||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
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.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.permalink.MatrixToConverter
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
|
|
@ -59,24 +61,24 @@ class DefaultPermalinkParser @Inject constructor(
|
|||
} else {
|
||||
val viaParameters = result.via.toImmutableList()
|
||||
when (val id = result.id) {
|
||||
is MatrixId.Room -> PermalinkData.RoomIdLink(
|
||||
roomId = RoomId(id.id),
|
||||
viaParameters = viaParameters,
|
||||
)
|
||||
is MatrixId.User -> PermalinkData.UserLink(
|
||||
userId = UserId(id.id),
|
||||
)
|
||||
is MatrixId.RoomAlias -> PermalinkData.RoomAliasLink(
|
||||
roomAlias = id.alias,
|
||||
is MatrixId.Room -> PermalinkData.RoomLink(
|
||||
roomIdOrAlias = RoomId(id.id).toRoomIdOrAlias(),
|
||||
viaParameters = viaParameters,
|
||||
)
|
||||
is MatrixId.EventOnRoomId -> PermalinkData.EventIdLink(
|
||||
roomId = RoomId(id.roomId),
|
||||
is MatrixId.RoomAlias -> PermalinkData.RoomLink(
|
||||
roomIdOrAlias = RoomAlias(id.alias).toRoomIdOrAlias(),
|
||||
viaParameters = viaParameters,
|
||||
)
|
||||
is MatrixId.EventOnRoomId -> PermalinkData.RoomLink(
|
||||
roomIdOrAlias = RoomId(id.roomId).toRoomIdOrAlias(),
|
||||
eventId = EventId(id.eventId),
|
||||
viaParameters = viaParameters,
|
||||
)
|
||||
is MatrixId.EventOnRoomAlias -> PermalinkData.EventIdAliasLink(
|
||||
roomAlias = id.alias,
|
||||
is MatrixId.EventOnRoomAlias -> PermalinkData.RoomLink(
|
||||
roomIdOrAlias = RoomAlias(id.alias).toRoomIdOrAlias(),
|
||||
eventId = EventId(id.eventId),
|
||||
viaParameters = viaParameters,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.room
|
||||
|
||||
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.UserId
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
|
|
@ -35,7 +37,7 @@ class MatrixRoomInfoMapper(
|
|||
) {
|
||||
fun map(rustRoomInfo: RustRoomInfo): MatrixRoomInfo = rustRoomInfo.use {
|
||||
return MatrixRoomInfo(
|
||||
id = it.id,
|
||||
id = RoomId(it.id),
|
||||
name = it.name,
|
||||
topic = it.topic,
|
||||
avatarUrl = it.avatarUrl,
|
||||
|
|
@ -44,7 +46,7 @@ class MatrixRoomInfoMapper(
|
|||
isSpace = it.isSpace,
|
||||
isTombstoned = it.isTombstoned,
|
||||
isFavorite = it.isFavourite,
|
||||
canonicalAlias = it.canonicalAlias,
|
||||
canonicalAlias = it.canonicalAlias?.let(::RoomAlias),
|
||||
alternativeAliases = it.alternativeAliases.toImmutableList(),
|
||||
currentUserMembership = it.membership.map(),
|
||||
latestEvent = it.latestEvent?.use(timelineItemMapper::map),
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
|||
import io.element.android.libraries.core.coroutine.childScope
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
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.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
|
|
@ -205,11 +206,11 @@ class RustMatrixRoom(
|
|||
override val isEncrypted: Boolean
|
||||
get() = runCatching { innerRoom.isEncrypted() }.getOrDefault(false)
|
||||
|
||||
override val alias: String?
|
||||
get() = runCatching { innerRoom.canonicalAlias() }.getOrDefault(null)
|
||||
override val alias: RoomAlias?
|
||||
get() = runCatching { innerRoom.canonicalAlias()?.let(::RoomAlias) }.getOrDefault(null)
|
||||
|
||||
override val alternativeAliases: List<String>
|
||||
get() = runCatching { innerRoom.alternativeAliases() }.getOrDefault(emptyList())
|
||||
override val alternativeAliases: List<RoomAlias>
|
||||
get() = runCatching { innerRoom.alternativeAliases().map { RoomAlias(it) } }.getOrDefault(emptyList())
|
||||
|
||||
override val isPublic: Boolean
|
||||
get() = runCatching { innerRoom.isPublic() }.getOrDefault(false)
|
||||
|
|
|
|||
|
|
@ -25,16 +25,16 @@ import org.matrix.rustcomponents.sdk.RoomMember as RustRoomMember
|
|||
|
||||
object RoomMemberMapper {
|
||||
fun map(roomMember: RustRoomMember): RoomMember = RoomMember(
|
||||
UserId(roomMember.userId),
|
||||
roomMember.displayName,
|
||||
roomMember.avatarUrl,
|
||||
mapMembership(roomMember.membership),
|
||||
roomMember.isNameAmbiguous,
|
||||
roomMember.powerLevel,
|
||||
roomMember.normalizedPowerLevel,
|
||||
roomMember.isIgnored,
|
||||
mapRole(roomMember.suggestedRoleForPowerLevel),
|
||||
)
|
||||
userId = UserId(roomMember.userId),
|
||||
displayName = roomMember.displayName,
|
||||
avatarUrl = roomMember.avatarUrl,
|
||||
membership = mapMembership(roomMember.membership),
|
||||
isNameAmbiguous = roomMember.isNameAmbiguous,
|
||||
powerLevel = roomMember.powerLevel,
|
||||
normalizedPowerLevel = roomMember.normalizedPowerLevel,
|
||||
isIgnored = roomMember.isIgnored,
|
||||
role = mapRole(roomMember.suggestedRoleForPowerLevel),
|
||||
)
|
||||
|
||||
fun mapRole(role: RoomMemberRole): RoomMember.Role =
|
||||
when (role) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.roomdirectory
|
||||
|
||||
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.roomdirectory.RoomDescription
|
||||
import org.matrix.rustcomponents.sdk.PublicRoomJoinRule
|
||||
|
|
@ -28,7 +29,7 @@ class RoomDescriptionMapper {
|
|||
name = roomDescription.name,
|
||||
topic = roomDescription.topic,
|
||||
avatarUrl = roomDescription.avatarUrl,
|
||||
alias = roomDescription.alias,
|
||||
alias = roomDescription.alias?.let(::RoomAlias),
|
||||
joinRule = when (roomDescription.joinRule) {
|
||||
PublicRoomJoinRule.PUBLIC -> RoomDescription.JoinRule.PUBLIC
|
||||
PublicRoomJoinRule.KNOCK -> RoomDescription.JoinRule.KNOCK
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.roomlist
|
||||
|
||||
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.roomlist.RoomSummaryDetails
|
||||
import io.element.android.libraries.matrix.impl.notificationsettings.RoomNotificationSettingsMapper
|
||||
|
|
@ -33,7 +34,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto
|
|||
return RoomSummaryDetails(
|
||||
roomId = RoomId(roomInfo.id),
|
||||
name = roomInfo.name ?: roomInfo.id,
|
||||
canonicalAlias = roomInfo.canonicalAlias,
|
||||
canonicalAlias = roomInfo.canonicalAlias?.let(::RoomAlias),
|
||||
isDirect = roomInfo.isDirect,
|
||||
avatarUrl = roomInfo.avatarUrl,
|
||||
numUnreadMentions = roomInfo.numUnreadMentions.toInt(),
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ package io.element.android.libraries.matrix.test
|
|||
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
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.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||
|
|
@ -31,6 +33,7 @@ import io.element.android.libraries.matrix.api.pusher.PushersService
|
|||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
||||
|
|
@ -73,6 +76,8 @@ class FakeMatrixClient(
|
|||
private val encryptionService: FakeEncryptionService = FakeEncryptionService(),
|
||||
private val roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService(),
|
||||
private val accountManagementUrlString: Result<String?> = Result.success(null),
|
||||
private val resolveRoomAliasResult: (RoomAlias) -> Result<RoomId> = { Result.success(A_ROOM_ID) },
|
||||
private val getRoomPreviewResult: (RoomIdOrAlias) -> Result<RoomPreview> = { Result.failure(AN_EXCEPTION) },
|
||||
) : MatrixClient {
|
||||
var setDisplayNameCalled: Boolean = false
|
||||
private set
|
||||
|
|
@ -276,6 +281,14 @@ class FakeMatrixClient(
|
|||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result<RoomId> = simulateLongTask {
|
||||
resolveRoomAliasResult(roomAlias)
|
||||
}
|
||||
|
||||
override suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias): Result<RoomPreview> = simulateLongTask {
|
||||
getRoomPreviewResult(roomIdOrAlias)
|
||||
}
|
||||
|
||||
override suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> {
|
||||
return Result.success(visitedRoomsId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.test
|
|||
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
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.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.SpaceId
|
||||
|
|
@ -50,6 +51,7 @@ val A_THREAD_ID = ThreadId("\$aThreadId")
|
|||
val A_THREAD_ID_2 = ThreadId("\$aThreadId2")
|
||||
val AN_EVENT_ID = EventId("\$anEventId")
|
||||
val AN_EVENT_ID_2 = EventId("\$anEventId2")
|
||||
val A_ROOM_ALIAS = RoomAlias("#alias1:domain")
|
||||
val A_TRANSACTION_ID = TransactionId("aTransactionId")
|
||||
const val A_UNIQUE_ID = "aUniqueId"
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.test.room
|
|||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
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.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
|
|
@ -74,8 +75,8 @@ class FakeMatrixRoom(
|
|||
override val topic: String? = null,
|
||||
override val avatarUrl: String? = null,
|
||||
override val isEncrypted: Boolean = false,
|
||||
override val alias: String? = null,
|
||||
override val alternativeAliases: List<String> = emptyList(),
|
||||
override val alias: RoomAlias? = null,
|
||||
override val alternativeAliases: List<RoomAlias> = emptyList(),
|
||||
override val isPublic: Boolean = true,
|
||||
override val isSpace: Boolean = false,
|
||||
override val isDirect: Boolean = false,
|
||||
|
|
@ -751,7 +752,7 @@ data class EndPollInvocation(
|
|||
)
|
||||
|
||||
fun aRoomInfo(
|
||||
id: String = A_ROOM_ID.value,
|
||||
id: RoomId = A_ROOM_ID,
|
||||
name: String? = A_ROOM_NAME,
|
||||
topic: String? = "A topic",
|
||||
avatarUrl: String? = AN_AVATAR_URL,
|
||||
|
|
@ -760,7 +761,7 @@ fun aRoomInfo(
|
|||
isSpace: Boolean = false,
|
||||
isTombstoned: Boolean = false,
|
||||
isFavorite: Boolean = false,
|
||||
canonicalAlias: String? = null,
|
||||
canonicalAlias: RoomAlias? = null,
|
||||
alternativeAliases: List<String> = emptyList(),
|
||||
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
|
||||
latestEvent: EventTimelineItem? = null,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.libraries.matrix.test.room
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
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.UserId
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
|
|
@ -72,7 +73,7 @@ fun aRoomSummaryDetails(
|
|||
isMarkedUnread: Boolean = false,
|
||||
notificationMode: RoomNotificationMode? = null,
|
||||
inviter: RoomMember? = null,
|
||||
canonicalAlias: String? = null,
|
||||
canonicalAlias: RoomAlias? = null,
|
||||
hasRoomCall: Boolean = false,
|
||||
isDm: Boolean = false,
|
||||
isFavorite: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.element.android.libraries.matrix.test.roomdirectory
|
||||
|
||||
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.roomdirectory.RoomDescription
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
|
|
@ -24,7 +25,7 @@ fun aRoomDescription(
|
|||
roomId: RoomId = A_ROOM_ID,
|
||||
name: String? = null,
|
||||
topic: String? = null,
|
||||
alias: String? = null,
|
||||
alias: RoomAlias? = null,
|
||||
avatarUrl: String? = null,
|
||||
joinRule: RoomDescription.JoinRule = RoomDescription.JoinRule.UNKNOWN,
|
||||
isWorldReadable: Boolean = true,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
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.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
|
|
@ -106,7 +107,7 @@ internal fun SelectedRoomPreview() = ElementPreview {
|
|||
fun aRoomSummaryDetails(
|
||||
roomId: RoomId = RoomId("!room:domain"),
|
||||
name: String = "roomName",
|
||||
canonicalAlias: String? = null,
|
||||
canonicalAlias: RoomAlias? = null,
|
||||
isDirect: Boolean = true,
|
||||
avatarUrl: String? = null,
|
||||
lastMessage: RoomMessage? = null,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.element.android.libraries.roomselect.impl
|
|||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
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.roomlist.RoomSummaryDetails
|
||||
import io.element.android.libraries.matrix.ui.components.aRoomSummaryDetails
|
||||
|
|
@ -65,6 +66,6 @@ private fun aForwardMessagesRoomList() = persistentListOf(
|
|||
aRoomSummaryDetails(
|
||||
roomId = RoomId("!room2:domain"),
|
||||
name = "Room with alias",
|
||||
canonicalAlias = "#alias:example.org",
|
||||
canonicalAlias = RoomAlias("#alias:example.org"),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ private fun RoomSummaryView(
|
|||
// Alias
|
||||
summary.canonicalAlias?.let { alias ->
|
||||
Text(
|
||||
text = alias,
|
||||
text = alias.value,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
maxLines = 1,
|
||||
|
|
|
|||
|
|
@ -39,8 +39,10 @@ import io.element.android.libraries.designsystem.theme.currentUserMentionPillBac
|
|||
import io.element.android.libraries.designsystem.theme.currentUserMentionPillText
|
||||
import io.element.android.libraries.designsystem.theme.mentionPillBackground
|
||||
import io.element.android.libraries.designsystem.theme.mentionPillText
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
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.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -139,8 +141,9 @@ internal fun MentionSpanPreview() {
|
|||
return when (uriString) {
|
||||
"https://matrix.to/#/@me:matrix.org" -> PermalinkData.UserLink(UserId("@me:matrix.org"))
|
||||
"https://matrix.to/#/@other:matrix.org" -> PermalinkData.UserLink(UserId("@other:matrix.org"))
|
||||
"https://matrix.to/#/#room:matrix.org" -> PermalinkData.RoomAliasLink(
|
||||
roomAlias = "#room:matrix.org",
|
||||
"https://matrix.to/#/#room:matrix.org" -> PermalinkData.RoomLink(
|
||||
roomIdOrAlias = RoomAlias("#room:matrix.org").toRoomIdOrAlias(),
|
||||
eventId = null,
|
||||
viaParameters = persistentListOf(),
|
||||
)
|
||||
else -> TODO()
|
||||
|
|
|
|||
|
|
@ -18,13 +18,14 @@ package io.element.android.libraries.textcomposer.impl.mentions
|
|||
|
||||
import android.graphics.Color
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
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.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
|
@ -68,9 +69,8 @@ class MentionSpanProviderTest {
|
|||
@Test
|
||||
fun `getting mention span for a room should return a MentionSpan with normal colors`() {
|
||||
permalinkParser.givenResult(
|
||||
PermalinkData.RoomAliasLink(
|
||||
roomAlias = "#room:matrix.org",
|
||||
viaParameters = persistentListOf(),
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = RoomAlias("#room:matrix.org").toRoomIdOrAlias(),
|
||||
)
|
||||
)
|
||||
val mentionSpan = mentionSpanProvider.getMentionSpanFor("#room:matrix.org", "https://matrix.to/#/#room:matrix.org")
|
||||
|
|
@ -81,12 +81,11 @@ class MentionSpanProviderTest {
|
|||
@Test
|
||||
fun `getting mention span for @room should return a MentionSpan with normal colors`() {
|
||||
permalinkParser.givenResult(
|
||||
PermalinkData.RoomAliasLink(
|
||||
roomAlias = "#",
|
||||
viaParameters = persistentListOf(),
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = RoomAlias("#room:matrix.org").toRoomIdOrAlias(),
|
||||
)
|
||||
)
|
||||
val mentionSpan = mentionSpanProvider.getMentionSpanFor("@room", "#")
|
||||
val mentionSpan = mentionSpanProvider.getMentionSpanFor("@room", "#room:matrix.org")
|
||||
assertThat(mentionSpan.backgroundColor).isEqualTo(otherColor)
|
||||
assertThat(mentionSpan.textColor).isEqualTo(otherColor)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bb0d3bfcfd75cbd75fd9270ff1dc27090e5dbac79ca8db8a46d91a4c12bc966b
|
||||
size 4457
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a32da6aa9e7137262ea70c3901fb058c97daf24ff445eda23dac8640d188f124
|
||||
size 25585
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c734484b6ac89c5540db52a62eb5ca17d5013c5901aea10876d769c6abb019dd
|
||||
size 26263
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9b2474c3058ae955f81985c02b9491cc79cd7d87001900dc449a1fb42684114f
|
||||
size 11970
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9b2474c3058ae955f81985c02b9491cc79cd7d87001900dc449a1fb42684114f
|
||||
size 11970
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8c89ac73df77c2bccb0c2aa80cee1420f78e7d07f0eda89a90bffef55e8cf753
|
||||
size 4464
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5f31dc397d61b8b088ece9680dd8c700a9a0d7bb6310859e82adba8c942effaf
|
||||
size 21861
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6083235628afa1c7aee7dd584d14b4b3ac7cd76b077b040675b2de42adbb36b7
|
||||
size 22413
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:812e571d895c87f6b8ab77638a716a28c125dc6e41310f4f5d376035d96b5dae
|
||||
size 9323
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:812e571d895c87f6b8ab77638a716a28c125dc6e41310f4f5d376035d96b5dae
|
||||
size 9323
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f7406a2a6fa8e4dd4423a5141147ecc1aa235d4dbff980b7ef72fa2e59f502fc
|
||||
size 7686
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:329ade4a3d0e1b013f03bf5d2d107ce2dacc235f7e2defc24c5cd1623330b107
|
||||
size 23944
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0ed85e4bb1351a1af82b64628c35aff7ef165c9df9b1ac0e1bba737a9d1e15a8
|
||||
size 25797
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0fcfb2b5cc790707f48dbd56f2c3f4909ebe1ae93b082309e7b0b89e82fec6c8
|
||||
size 39320
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3ba446e166858f6da8d79ae4af997ff142d73edebf178aadf75579b041a3d267
|
||||
size 28605
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7c34154223d9eef3a66fbc9c266010e3e51b293db7f4698d550638fa8912a231
|
||||
size 16864
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9e70d55552b46b1a34848651bf8bcf8048ba2d28c7d2d356f8c1a0f71bb6e049
|
||||
size 21104
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fa320f4667087c59776d08dfb9108a577e42eb51ba1e6b3d3ea6da3d55605495
|
||||
size 7523
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d4244c5d24935e2cf6df3cff5d31eb7980757a1e555aedbb97561c1827778e2b
|
||||
size 22816
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3725b3e21eb939bebcc44b9c62f21817f1ab227dc7a9f2367b721cdb0e750720
|
||||
size 25127
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e23f1d506bcfc603192d854945059b4f03e9215f20f3f7f9150728aee0c4bf7e
|
||||
size 38085
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue