diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index f022fd0caf..ca4bf907ac 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -41,6 +41,7 @@ import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewNode import io.element.android.features.messages.impl.forward.ForwardMessagesNode +import io.element.android.features.messages.impl.pinned.list.PinnedMessagesListNode import io.element.android.features.messages.impl.report.ReportMessageNode import io.element.android.features.messages.impl.timeline.debug.EventDebugInfoNode import io.element.android.features.messages.impl.timeline.model.TimelineItem @@ -81,7 +82,6 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize -import timber.log.Timber @ContributesNode(RoomScope::class) class MessagesFlowNode @AssistedInject constructor( @@ -148,6 +148,9 @@ class MessagesFlowNode @AssistedInject constructor( @Parcelize data class EditPoll(val eventId: EventId) : NavTarget + + @Parcelize + data object PinnedEvents : NavTarget } private val callbacks = plugins() @@ -220,7 +223,7 @@ class MessagesFlowNode @AssistedInject constructor( } override fun onViewAllPinnedEvents() { - Timber.d("On View All Pinned Events not implemented yet.") + backstack.push(NavTarget.PinnedEvents) } } val inputs = MessagesNode.Inputs( @@ -276,6 +279,9 @@ class MessagesFlowNode @AssistedInject constructor( .params(CreatePollEntryPoint.Params(mode = CreatePollMode.EditPoll(eventId = navTarget.eventId))) .build() } + NavTarget.PinnedEvents -> { + createNode(buildContext) + } NavTarget.Empty -> { node(buildContext) {} } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt new file mode 100644 index 0000000000..e259df4d62 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt @@ -0,0 +1,19 @@ +/* + * 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 + * + * https://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.messages.impl.pinned.list + +sealed interface PinnedMessagesListEvents diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt new file mode 100644 index 0000000000..5aee2bc3bb --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt @@ -0,0 +1,52 @@ +/* + * 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 + * + * https://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.messages.impl.pinned.list + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +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 dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories +import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories +import io.element.android.libraries.di.RoomScope + +@ContributesNode(RoomScope::class) +class PinnedMessagesListNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: PinnedMessagesListPresenter, + private val timelineItemPresenterFactories: TimelineItemPresenterFactories, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + CompositionLocalProvider( + LocalTimelineItemPresenterFactories provides timelineItemPresenterFactories, + ) { + val state = presenter.present() + PinnedMessagesListView( + state = state, + modifier = modifier + ) + } + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt new file mode 100644 index 0000000000..cde419f463 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt @@ -0,0 +1,58 @@ +/* + * 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 + * + * https://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.messages.impl.pinned.list + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +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.roomMembers +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.launchIn +import javax.inject.Inject + +class PinnedMessagesListPresenter @Inject constructor( + private val room: MatrixRoom, + private val timelineItemsFactory: TimelineItemsFactory, +) : Presenter { + + @Composable + override fun present(): PinnedMessagesListState { + val timelineItems by timelineItemsFactory.collectItemsAsState() + + LaunchedEffect(Unit) { + val timeline = room.pinnedEventsTimeline().getOrNull() ?: return@LaunchedEffect + combine(timeline.timelineItems, room.membersStateFlow) { items, membersState -> + timelineItemsFactory.replaceWith( + timelineItems = items, + roomMembers = membersState.roomMembers().orEmpty() + ) + items + }.launchIn(this) + } + + fun handleEvents(event: PinnedMessagesListEvents) { + } + + return PinnedMessagesListState( + timelineItems = timelineItems, + eventSink = ::handleEvents + ) + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListState.kt new file mode 100644 index 0000000000..4c1d779b62 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListState.kt @@ -0,0 +1,25 @@ +/* + * 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 + * + * https://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.messages.impl.pinned.list + +import io.element.android.features.messages.impl.timeline.model.TimelineItem +import kotlinx.collections.immutable.ImmutableList + +data class PinnedMessagesListState( + val timelineItems: ImmutableList, + val eventSink: (PinnedMessagesListEvents) -> Unit +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt new file mode 100644 index 0000000000..f5e1d6a173 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt @@ -0,0 +1,110 @@ +/* + * 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 + * + * https://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.messages.impl.pinned.list + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +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.runtime.Composable +import androidx.compose.ui.Modifier +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.features.messages.impl.timeline.components.TimelineItemRow +import io.element.android.features.messages.impl.timeline.model.TimelineItem +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 + +@Composable +fun PinnedMessagesListView( + state: PinnedMessagesListState, + modifier: Modifier = Modifier, +) { + Scaffold( + modifier = modifier, + ) { padding -> + PinnedMessagesListContent( + state = state, + modifier = Modifier + .padding(padding) + .consumeWindowInsets(padding), + ) + } +} + +@Composable +fun PinnedMessagesListContent( + state: PinnedMessagesListState, + modifier: Modifier = Modifier, +) { + Box(modifier) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = rememberLazyListState(), + reverseLayout = true, + contentPadding = PaddingValues(vertical = 8.dp), + ) { + items( + items = state.timelineItems, + contentType = { timelineItem -> timelineItem.contentType() }, + key = { timelineItem -> timelineItem.identifier() }, + ) { timelineItem -> + TimelineItemRow( + timelineItem = timelineItem, + timelineRoomInfo = TimelineRoomInfo( + isDm = false, + name = null, + userHasPermissionToSendMessage = false, + userHasPermissionToSendReaction = false, + isCallOngoing = false, + ), + renderReadReceipts = false, + isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true && + state.timelineItems.first().identifier() == timelineItem.identifier(), + focusedEventId = null, + onClick = {}, + onLongClick = {}, + onUserDataClick = { }, + onLinkClick = {}, + inReplyToClick = {}, + onReactionClick = { _, _ -> }, + onReactionLongClick = { _, _ -> }, + onMoreReactionsClick = {}, + onReadReceiptClick = {}, + eventSink = {}, + onSwipeToReply = {}, + onJoinCallClick = {}, + ) + } + } + } +} + +@PreviewsDayNight +@Composable +fun PinnedMessagesTimelineViewPreview(@PreviewParameter(PinnedMessagesTimelineStateProvider::class) state: PinnedMessagesListState) = + ElementPreview { + PinnedMessagesListView( + state = state, + ) + } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesTimelineListProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesTimelineListProvider.kt new file mode 100644 index 0000000000..e70e893909 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesTimelineListProvider.kt @@ -0,0 +1,35 @@ +/* + * 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 + * + * https://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.messages.impl.pinned.list + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.messages.impl.timeline.model.TimelineItem +import kotlinx.collections.immutable.toImmutableList + +open class PinnedMessagesTimelineStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + pinnedMessagesListState(), + ) +} + +fun pinnedMessagesListState( + timelineItems: List = emptyList(), +) = PinnedMessagesListState( + timelineItems = timelineItems.toImmutableList(), + eventSink = {} +)