Pinned messages : start branching ui to the timeline
This commit is contained in:
@@ -280,7 +280,20 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||
.build()
|
||||
}
|
||||
NavTarget.PinnedEvents -> {
|
||||
createNode<PinnedMessagesListNode>(buildContext)
|
||||
val callback = object : PinnedMessagesListNode.Callback {
|
||||
override fun onEventClick(event: TimelineItem.Event) {
|
||||
processEventClick(event)
|
||||
}
|
||||
|
||||
override fun onUserDataClick(userId: UserId) {
|
||||
callbacks.forEach { it.onUserDataClick(userId) }
|
||||
}
|
||||
|
||||
override fun onPermalinkClick(data: PermalinkData) {
|
||||
callbacks.forEach { it.onPermalinkClick(data) }
|
||||
}
|
||||
}
|
||||
createNode<PinnedMessagesListNode>(buildContext, plugins = listOf(callback))
|
||||
}
|
||||
NavTarget.Empty -> {
|
||||
node(buildContext) {}
|
||||
|
||||
@@ -16,18 +16,27 @@
|
||||
|
||||
package io.element.android.features.messages.impl.pinned.list
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
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.messages.impl.MessagesNode.Callback
|
||||
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
||||
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.di.RoomScope
|
||||
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
|
||||
|
||||
@ContributesNode(RoomScope::class)
|
||||
class PinnedMessagesListNode @AssistedInject constructor(
|
||||
@@ -35,16 +44,55 @@ class PinnedMessagesListNode @AssistedInject constructor(
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: PinnedMessagesListPresenter,
|
||||
private val timelineItemPresenterFactories: TimelineItemPresenterFactories,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onEventClick(event: TimelineItem.Event)
|
||||
fun onUserDataClick(userId: UserId)
|
||||
fun onPermalinkClick(data: PermalinkData)
|
||||
}
|
||||
|
||||
private val callbacks = plugins<Callback>()
|
||||
|
||||
private fun onEventClick(event: TimelineItem.Event) {
|
||||
return callbacks.forEach { it.onEventClick(event) }
|
||||
}
|
||||
|
||||
private fun onUserDataClick(userId: UserId) {
|
||||
callbacks.forEach { it.onUserDataClick(userId) }
|
||||
}
|
||||
|
||||
private fun onLinkClick(context: Context, url: String) {
|
||||
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
|
||||
callbacks.forEach { it.onUserDataClick(permalink.userId) }
|
||||
}
|
||||
is PermalinkData.RoomLink -> {
|
||||
callbacks.forEach { it.onPermalinkClick(permalink) }
|
||||
}
|
||||
is PermalinkData.FallbackLink,
|
||||
is PermalinkData.RoomEmailInviteLink -> {
|
||||
context.openUrlInExternalApp(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
CompositionLocalProvider(
|
||||
LocalTimelineItemPresenterFactories provides timelineItemPresenterFactories,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val state = presenter.present()
|
||||
PinnedMessagesListView(
|
||||
state = state,
|
||||
onBackClick = ::navigateUp,
|
||||
onEventClick = ::onEventClick,
|
||||
onUserDataClick = ::onUserDataClick,
|
||||
onLinkClick = { url -> onLinkClick(context, url) },
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,9 +19,12 @@ package io.element.android.features.messages.impl.pinned.list
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@@ -35,7 +38,15 @@ class PinnedMessagesListPresenter @Inject constructor(
|
||||
@Composable
|
||||
override fun present(): PinnedMessagesListState {
|
||||
val timelineItems by timelineItemsFactory.collectItemsAsState()
|
||||
|
||||
val timelineRoomInfo = remember {
|
||||
TimelineRoomInfo(
|
||||
isDm = room.isDm,
|
||||
name = room.displayName,
|
||||
userHasPermissionToSendMessage = false,
|
||||
userHasPermissionToSendReaction = false,
|
||||
isCallOngoing = false,
|
||||
)
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
val timeline = room.pinnedEventsTimeline().getOrNull() ?: return@LaunchedEffect
|
||||
combine(timeline.timelineItems, room.membersStateFlow) { items, membersState ->
|
||||
@@ -51,6 +62,7 @@ class PinnedMessagesListPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
return PinnedMessagesListState(
|
||||
timelineRoomInfo = timelineRoomInfo,
|
||||
timelineItems = timelineItems,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
|
||||
@@ -16,10 +16,19 @@
|
||||
|
||||
package io.element.android.features.messages.impl.pinned.list
|
||||
|
||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class PinnedMessagesListState(
|
||||
val timelineRoomInfo: TimelineRoomInfo,
|
||||
val timelineItems: ImmutableList<TimelineItem>,
|
||||
val eventSink: (PinnedMessagesListEvents) -> Unit
|
||||
val eventSink: (PinnedMessagesListEvents) -> Unit,
|
||||
|
||||
val pinnedMessagesCount: String =
|
||||
timelineItems
|
||||
.count { timelineItem -> timelineItem is TimelineItem.Event }
|
||||
.takeIf { it > 0 }
|
||||
?.toString()
|
||||
?: ""
|
||||
)
|
||||
|
||||
@@ -24,37 +24,77 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.timeline.components.TimelineItemRow
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
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.Scaffold
|
||||
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.UserId
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
fun PinnedMessagesListView(
|
||||
state: PinnedMessagesListState,
|
||||
onBackClick: () -> Unit,
|
||||
onEventClick: (event: TimelineItem.Event) -> Unit,
|
||||
onUserDataClick: (UserId) -> Unit,
|
||||
onLinkClick: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
) { padding ->
|
||||
PinnedMessagesListContent(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding),
|
||||
)
|
||||
}
|
||||
topBar = {
|
||||
PinnedMessagesListTopBar(state, onBackClick)
|
||||
},
|
||||
content = { padding ->
|
||||
PinnedMessagesListContent(
|
||||
state = state,
|
||||
onEventClick = onEventClick,
|
||||
onUserDataClick = onUserDataClick,
|
||||
onLinkClick = onLinkClick,
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding),
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun PinnedMessagesListTopBar(
|
||||
state: PinnedMessagesListState,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = CommonStrings.screen_room_pinned_banner_indicator_description, state.pinnedMessagesCount),
|
||||
style = ElementTheme.typography.fontBodyLgMedium
|
||||
)
|
||||
},
|
||||
navigationIcon = { BackButton(onClick = onBackClick) },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PinnedMessagesListContent(
|
||||
private fun PinnedMessagesListContent(
|
||||
state: PinnedMessagesListState,
|
||||
onEventClick: (event: TimelineItem.Event) -> Unit,
|
||||
onUserDataClick: (UserId) -> Unit,
|
||||
onLinkClick: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier) {
|
||||
@@ -71,21 +111,14 @@ fun PinnedMessagesListContent(
|
||||
) { timelineItem ->
|
||||
TimelineItemRow(
|
||||
timelineItem = timelineItem,
|
||||
timelineRoomInfo = TimelineRoomInfo(
|
||||
isDm = false,
|
||||
name = null,
|
||||
userHasPermissionToSendMessage = false,
|
||||
userHasPermissionToSendReaction = false,
|
||||
isCallOngoing = false,
|
||||
),
|
||||
timelineRoomInfo = state.timelineRoomInfo,
|
||||
renderReadReceipts = false,
|
||||
isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true &&
|
||||
state.timelineItems.first().identifier() == timelineItem.identifier(),
|
||||
isLastOutgoingMessage = false,
|
||||
focusedEventId = null,
|
||||
onClick = {},
|
||||
onClick = onEventClick,
|
||||
onLongClick = {},
|
||||
onUserDataClick = { },
|
||||
onLinkClick = {},
|
||||
onUserDataClick = onUserDataClick,
|
||||
onLinkClick = onLinkClick,
|
||||
inReplyToClick = {},
|
||||
onReactionClick = { _, _ -> },
|
||||
onReactionLongClick = { _, _ -> },
|
||||
@@ -106,5 +139,9 @@ fun PinnedMessagesTimelineViewPreview(@PreviewParameter(PinnedMessagesTimelineSt
|
||||
ElementPreview {
|
||||
PinnedMessagesListView(
|
||||
state = state,
|
||||
onBackClick = {},
|
||||
onEventClick = { },
|
||||
onUserDataClick = {},
|
||||
onLinkClick = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package io.element.android.features.messages.impl.pinned.list
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@@ -28,8 +30,10 @@ open class PinnedMessagesTimelineStateProvider : PreviewParameterProvider<Pinned
|
||||
}
|
||||
|
||||
fun pinnedMessagesListState(
|
||||
timelineRoomInfo: TimelineRoomInfo = aTimelineRoomInfo(),
|
||||
timelineItems: List<TimelineItem> = emptyList(),
|
||||
) = PinnedMessagesListState(
|
||||
timelineRoomInfo = timelineRoomInfo,
|
||||
timelineItems = timelineItems.toImmutableList(),
|
||||
eventSink = {}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user