Skip to content

Commit c76ff38

Browse files
committed
Pinned messages : introduces banner view
1 parent 8cb5e9e commit c76ff38

File tree

12 files changed

+429
-16
lines changed

12 files changed

+429
-16
lines changed

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc
3939
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
4040
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
4141
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
42+
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerPresenter
43+
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
4244
import io.element.android.features.messages.impl.timeline.TimelineController
4345
import io.element.android.features.messages.impl.timeline.TimelineEvents
4446
import io.element.android.features.messages.impl.timeline.TimelinePresenter
@@ -98,6 +100,7 @@ class MessagesPresenter @AssistedInject constructor(
98100
private val customReactionPresenter: CustomReactionPresenter,
99101
private val reactionSummaryPresenter: ReactionSummaryPresenter,
100102
private val readReceiptBottomSheetPresenter: ReadReceiptBottomSheetPresenter,
103+
private val pinnedMessagesBannerPresenter: Presenter<PinnedMessagesBannerState>,
101104
private val networkMonitor: NetworkMonitor,
102105
private val snackbarDispatcher: SnackbarDispatcher,
103106
private val dispatchers: CoroutineDispatchers,
@@ -129,6 +132,7 @@ class MessagesPresenter @AssistedInject constructor(
129132
val customReactionState = customReactionPresenter.present()
130133
val reactionSummaryState = reactionSummaryPresenter.present()
131134
val readReceiptBottomSheetState = readReceiptBottomSheetPresenter.present()
135+
val pinnedMessagesBannerState = pinnedMessagesBannerPresenter.present()
132136

133137
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
134138
val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value)
@@ -231,6 +235,7 @@ class MessagesPresenter @AssistedInject constructor(
231235
enableVoiceMessages = enableVoiceMessages,
232236
appName = buildMeta.applicationName,
233237
callState = callState,
238+
pinnedMessagesBannerState = pinnedMessagesBannerState,
234239
eventSink = { handleEvents(it) }
235240
)
236241
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package io.element.android.features.messages.impl
1919
import androidx.compose.runtime.Immutable
2020
import io.element.android.features.messages.impl.actionlist.ActionListState
2121
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
22+
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
2223
import io.element.android.features.messages.impl.timeline.TimelineState
2324
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
2425
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
@@ -57,6 +58,7 @@ data class MessagesState(
5758
val enableVoiceMessages: Boolean,
5859
val callState: RoomCallState,
5960
val appName: String,
61+
val pinnedMessagesBannerState: PinnedMessagesBannerState,
6062
val eventSink: (MessagesEvents) -> Unit
6163
)
6264

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import io.element.android.features.messages.impl.actionlist.anActionListState
2222
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
2323
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
2424
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
25+
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
26+
import io.element.android.features.messages.impl.pinned.banner.aPinnedMessagesBannerState
2527
import io.element.android.features.messages.impl.timeline.TimelineState
2628
import io.element.android.features.messages.impl.timeline.aTimelineItemList
2729
import io.element.android.features.messages.impl.timeline.aTimelineState
@@ -87,6 +89,12 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
8789
aMessagesState(
8890
callState = RoomCallState.DISABLED,
8991
),
92+
aMessagesState(
93+
pinnedMessagesBannerState = aPinnedMessagesBannerState(
94+
pinnedMessagesCount = 4,
95+
currentPinnedMessageIndex = 0,
96+
),
97+
),
9098
)
9199
}
92100

@@ -116,6 +124,7 @@ fun aMessagesState(
116124
showReinvitePrompt: Boolean = false,
117125
enableVoiceMessages: Boolean = true,
118126
callState: RoomCallState = RoomCallState.ENABLED,
127+
pinnedMessagesBannerState: PinnedMessagesBannerState = aPinnedMessagesBannerState(),
119128
eventSink: (MessagesEvents) -> Unit = {},
120129
) = MessagesState(
121130
roomId = RoomId("!id:domain"),
@@ -142,6 +151,7 @@ fun aMessagesState(
142151
enableVoiceMessages = enableVoiceMessages,
143152
callState = callState,
144153
appName = "Element",
154+
pinnedMessagesBannerState = pinnedMessagesBannerState,
145155
eventSink = eventSink,
146156
)
147157

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import io.element.android.features.messages.impl.messagecomposer.AttachmentsBott
6868
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
6969
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
7070
import io.element.android.features.messages.impl.messagecomposer.MessageComposerView
71+
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerView
7172
import io.element.android.features.messages.impl.timeline.TimelineView
7273
import io.element.android.features.messages.impl.timeline.components.JoinCallMenuItem
7374
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
@@ -373,22 +374,24 @@ private fun MessagesViewContent(
373374
RectangleShape
374375
},
375376
content = { paddingValues ->
376-
TimelineView(
377-
state = state.timelineState,
378-
typingNotificationState = state.typingNotificationState,
379-
onUserDataClick = onUserDataClick,
380-
onLinkClick = onLinkClick,
381-
onMessageClick = onMessageClick,
382-
onMessageLongClick = onMessageLongClick,
383-
onSwipeToReply = onSwipeToReply,
384-
onReactionClick = onReactionClick,
385-
onReactionLongClick = onReactionLongClick,
386-
onMoreReactionsClick = onMoreReactionsClick,
387-
onReadReceiptClick = onReadReceiptClick,
388-
modifier = Modifier.padding(paddingValues),
389-
forceJumpToBottomVisibility = forceJumpToBottomVisibility,
390-
onJoinCallClick = onJoinCallClick,
391-
)
377+
Box(modifier = Modifier.padding(paddingValues)) {
378+
TimelineView(
379+
state = state.timelineState,
380+
typingNotificationState = state.typingNotificationState,
381+
onUserDataClick = onUserDataClick,
382+
onLinkClick = onLinkClick,
383+
onMessageClick = onMessageClick,
384+
onMessageLongClick = onMessageLongClick,
385+
onSwipeToReply = onSwipeToReply,
386+
onReactionClick = onReactionClick,
387+
onReactionLongClick = onReactionLongClick,
388+
onMoreReactionsClick = onMoreReactionsClick,
389+
onReadReceiptClick = onReadReceiptClick,
390+
forceJumpToBottomVisibility = forceJumpToBottomVisibility,
391+
onJoinCallClick = onJoinCallClick,
392+
)
393+
PinnedMessagesBannerView(state = state.pinnedMessagesBannerState)
394+
}
392395
},
393396
sheetContent = { subcomposing: Boolean ->
394397
MessagesViewComposerBottomSheetContents(
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2024 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.features.messages.impl.di
18+
19+
import com.squareup.anvil.annotations.ContributesTo
20+
import dagger.Binds
21+
import dagger.Module
22+
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerPresenter
23+
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
24+
import io.element.android.libraries.architecture.Presenter
25+
import io.element.android.libraries.di.SessionScope
26+
27+
@ContributesTo(SessionScope::class)
28+
@Module
29+
interface MessagesModule {
30+
@Binds
31+
fun bindPinnedMessagesBannerPresenter(presenter: PinnedMessagesBannerPresenter): Presenter<PinnedMessagesBannerState>
32+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) 2024 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.features.messages.impl.pinned.banner
18+
19+
sealed interface PinnedMessagesBannerEvents {
20+
data object MoveToNextPinned : PinnedMessagesBannerEvents
21+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2024 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.features.messages.impl.pinned.banner
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.getValue
21+
import androidx.compose.runtime.mutableIntStateOf
22+
import androidx.compose.runtime.remember
23+
import androidx.compose.runtime.saveable.rememberSaveable
24+
import androidx.compose.runtime.setValue
25+
import io.element.android.libraries.architecture.Presenter
26+
import javax.inject.Inject
27+
28+
class PinnedMessagesBannerPresenter @Inject constructor() : Presenter<PinnedMessagesBannerState> {
29+
30+
@Composable
31+
override fun present(): PinnedMessagesBannerState {
32+
var pinnedMessageCount by remember {
33+
mutableIntStateOf(0)
34+
}
35+
var currentPinnedMessageIndex by rememberSaveable {
36+
mutableIntStateOf(0)
37+
}
38+
39+
fun handleEvent(event: PinnedMessagesBannerEvents) {
40+
when (event) {
41+
is PinnedMessagesBannerEvents.MoveToNextPinned -> {
42+
if (currentPinnedMessageIndex < pinnedMessageCount - 1) {
43+
currentPinnedMessageIndex++
44+
} else {
45+
currentPinnedMessageIndex = 0
46+
}
47+
}
48+
}
49+
}
50+
51+
return PinnedMessagesBannerState(
52+
pinnedMessagesCount = pinnedMessageCount,
53+
currentPinnedMessageIndex = currentPinnedMessageIndex,
54+
eventSink = ::handleEvent
55+
)
56+
}
57+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2024 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.features.messages.impl.pinned.banner
18+
19+
data class PinnedMessagesBannerState(
20+
val pinnedMessagesCount: Int,
21+
val currentPinnedMessageIndex: Int,
22+
val eventSink: (PinnedMessagesBannerEvents) -> Unit
23+
) {
24+
val displayBanner = pinnedMessagesCount > 0 && currentPinnedMessageIndex < pinnedMessagesCount
25+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2024 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.features.messages.impl.pinned.banner
18+
19+
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
20+
21+
internal class PinnedMessagesBannerStateProvider : PreviewParameterProvider<PinnedMessagesBannerState> {
22+
23+
override val values: Sequence<PinnedMessagesBannerState>
24+
get() = sequenceOf(
25+
aPinnedMessagesBannerState(pinnedMessagesCount = 1, currentPinnedMessageIndex = 0),
26+
aPinnedMessagesBannerState(pinnedMessagesCount = 2, currentPinnedMessageIndex = 0),
27+
aPinnedMessagesBannerState(pinnedMessagesCount = 4, currentPinnedMessageIndex = 0),
28+
aPinnedMessagesBannerState(pinnedMessagesCount = 4, currentPinnedMessageIndex = 1),
29+
aPinnedMessagesBannerState(pinnedMessagesCount = 4, currentPinnedMessageIndex = 2),
30+
aPinnedMessagesBannerState(pinnedMessagesCount = 4, currentPinnedMessageIndex = 3),
31+
)
32+
}
33+
34+
internal fun aPinnedMessagesBannerState(
35+
pinnedMessagesCount: Int = 0,
36+
currentPinnedMessageIndex: Int = -1,
37+
eventSink: (PinnedMessagesBannerEvents) -> Unit = {}
38+
) = PinnedMessagesBannerState(
39+
pinnedMessagesCount = pinnedMessagesCount,
40+
currentPinnedMessageIndex = currentPinnedMessageIndex,
41+
eventSink = eventSink
42+
)

0 commit comments

Comments
 (0)