Pinned events : start branching sdk data in the banner.
This commit is contained in:
parent
ebc9fd745e
commit
9985526137
10 changed files with 201 additions and 56 deletions
|
|
@ -102,5 +102,6 @@ dependencies {
|
||||||
testImplementation(projects.features.poll.test)
|
testImplementation(projects.features.poll.test)
|
||||||
testImplementation(projects.features.poll.impl)
|
testImplementation(projects.features.poll.impl)
|
||||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||||
|
testImplementation(projects.libraries.eventformatter.test)
|
||||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ import io.element.android.features.messages.impl.messagecomposer.AttachmentsStat
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerView
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerView
|
||||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerView
|
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerView
|
||||||
|
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineView
|
import io.element.android.features.messages.impl.timeline.TimelineView
|
||||||
import io.element.android.features.messages.impl.timeline.components.JoinCallMenuItem
|
import io.element.android.features.messages.impl.timeline.components.JoinCallMenuItem
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
|
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
|
||||||
|
|
@ -401,6 +402,9 @@ private fun MessagesViewContent(
|
||||||
) {
|
) {
|
||||||
PinnedMessagesBannerView(
|
PinnedMessagesBannerView(
|
||||||
state = state.pinnedMessagesBannerState,
|
state = state.pinnedMessagesBannerState,
|
||||||
|
onClick = { pinnedEventId ->
|
||||||
|
//state.timelineState.eventSink(TimelineEvents.FocusOnEvent(pinnedEventId))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ import dagger.Module
|
||||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerPresenter
|
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerPresenter
|
||||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
|
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
|
|
||||||
@ContributesTo(SessionScope::class)
|
@ContributesTo(RoomScope::class)
|
||||||
@Module
|
@Module
|
||||||
interface MessagesModule {
|
interface MessagesModule {
|
||||||
@Binds
|
@Binds
|
||||||
|
|
|
||||||
|
|
@ -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.banner
|
||||||
|
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
|
|
||||||
|
data class PinnedMessagesBannerItem(
|
||||||
|
val eventId: EventId,
|
||||||
|
val formatted: AnnotatedString,
|
||||||
|
)
|
||||||
|
|
@ -17,28 +17,86 @@
|
||||||
package io.element.android.features.messages.impl.pinned.banner
|
package io.element.android.features.messages.impl.pinned.banner
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.eventformatter.api.PinnedMessagesBannerFormatter
|
||||||
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
class PinnedMessagesBannerPresenter @Inject constructor() : Presenter<PinnedMessagesBannerState> {
|
class PinnedMessagesBannerPresenter @Inject constructor(
|
||||||
|
private val room: MatrixRoom,
|
||||||
|
private val pinnedMessagesBannerFormatter: PinnedMessagesBannerFormatter,
|
||||||
|
) : Presenter<PinnedMessagesBannerState> {
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): PinnedMessagesBannerState {
|
override fun present(): PinnedMessagesBannerState {
|
||||||
var pinnedMessageCount by remember {
|
var pinnedMessages by remember {
|
||||||
mutableIntStateOf(0)
|
mutableStateOf<List<PinnedMessagesBannerItem>>(emptyList())
|
||||||
}
|
}
|
||||||
var currentPinnedMessageIndex by rememberSaveable {
|
var currentPinnedMessageIndex by rememberSaveable {
|
||||||
mutableIntStateOf(0)
|
mutableIntStateOf(0)
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(pinnedMessages) {
|
||||||
|
val pinnedMessageCount = pinnedMessages.size
|
||||||
|
if (currentPinnedMessageIndex >= pinnedMessageCount) {
|
||||||
|
currentPinnedMessageIndex = (pinnedMessageCount - 1).coerceAtLeast(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
val pinnedEventsTimeline = room.pinnedEventsTimeline().getOrNull() ?: return@LaunchedEffect
|
||||||
|
pinnedEventsTimeline.timelineItems
|
||||||
|
.debounce(300.milliseconds)
|
||||||
|
.map { timelineItems ->
|
||||||
|
timelineItems.mapNotNull { timelineItem ->
|
||||||
|
when (timelineItem) {
|
||||||
|
is MatrixTimelineItem.Event -> {
|
||||||
|
val eventId = timelineItem.eventId ?: return@mapNotNull null
|
||||||
|
val formatted = pinnedMessagesBannerFormatter.format(timelineItem.event)
|
||||||
|
PinnedMessagesBannerItem(
|
||||||
|
eventId = eventId,
|
||||||
|
formatted = if (formatted is AnnotatedString) {
|
||||||
|
formatted
|
||||||
|
} else {
|
||||||
|
AnnotatedString(formatted.toString())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
.onEach { newPinnedMessages ->
|
||||||
|
pinnedMessages = newPinnedMessages
|
||||||
|
}.onCompletion {
|
||||||
|
pinnedEventsTimeline.close()
|
||||||
|
}
|
||||||
|
.launchIn(this)
|
||||||
|
}
|
||||||
|
|
||||||
fun handleEvent(event: PinnedMessagesBannerEvents) {
|
fun handleEvent(event: PinnedMessagesBannerEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is PinnedMessagesBannerEvents.MoveToNextPinned -> {
|
is PinnedMessagesBannerEvents.MoveToNextPinned -> {
|
||||||
if (currentPinnedMessageIndex < pinnedMessageCount - 1) {
|
if (currentPinnedMessageIndex < pinnedMessages.size - 1) {
|
||||||
currentPinnedMessageIndex++
|
currentPinnedMessageIndex++
|
||||||
} else {
|
} else {
|
||||||
currentPinnedMessageIndex = 0
|
currentPinnedMessageIndex = 0
|
||||||
|
|
@ -48,7 +106,8 @@ class PinnedMessagesBannerPresenter @Inject constructor() : Presenter<PinnedMess
|
||||||
}
|
}
|
||||||
|
|
||||||
return PinnedMessagesBannerState(
|
return PinnedMessagesBannerState(
|
||||||
pinnedMessagesCount = pinnedMessageCount,
|
pinnedMessagesCount = pinnedMessages.size,
|
||||||
|
currentPinnedMessage = pinnedMessages.getOrNull(currentPinnedMessageIndex),
|
||||||
currentPinnedMessageIndex = currentPinnedMessageIndex,
|
currentPinnedMessageIndex = currentPinnedMessageIndex,
|
||||||
eventSink = ::handleEvent
|
eventSink = ::handleEvent
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ package io.element.android.features.messages.impl.pinned.banner
|
||||||
data class PinnedMessagesBannerState(
|
data class PinnedMessagesBannerState(
|
||||||
val pinnedMessagesCount: Int,
|
val pinnedMessagesCount: Int,
|
||||||
val currentPinnedMessageIndex: Int,
|
val currentPinnedMessageIndex: Int,
|
||||||
|
val currentPinnedMessage: PinnedMessagesBannerItem?,
|
||||||
val eventSink: (PinnedMessagesBannerEvents) -> Unit
|
val eventSink: (PinnedMessagesBannerEvents) -> Unit
|
||||||
) {
|
) {
|
||||||
val displayBanner = pinnedMessagesCount > 0 && currentPinnedMessageIndex < pinnedMessagesCount
|
val displayBanner = pinnedMessagesCount > 0 && currentPinnedMessage != null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,10 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.pinned.banner
|
package io.element.android.features.messages.impl.pinned.banner
|
||||||
|
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
internal class PinnedMessagesBannerStateProvider : PreviewParameterProvider<PinnedMessagesBannerState> {
|
internal class PinnedMessagesBannerStateProvider : PreviewParameterProvider<PinnedMessagesBannerState> {
|
||||||
override val values: Sequence<PinnedMessagesBannerState>
|
override val values: Sequence<PinnedMessagesBannerState>
|
||||||
|
|
@ -33,9 +36,14 @@ internal class PinnedMessagesBannerStateProvider : PreviewParameterProvider<Pinn
|
||||||
internal fun aPinnedMessagesBannerState(
|
internal fun aPinnedMessagesBannerState(
|
||||||
pinnedMessagesCount: Int = 0,
|
pinnedMessagesCount: Int = 0,
|
||||||
currentPinnedMessageIndex: Int = -1,
|
currentPinnedMessageIndex: Int = -1,
|
||||||
|
currentPinnedMessage: PinnedMessagesBannerItem = PinnedMessagesBannerItem(
|
||||||
|
eventId = EventId("\$" + Random.nextInt().toString()),
|
||||||
|
formatted = AnnotatedString("This is a pinned message")
|
||||||
|
),
|
||||||
eventSink: (PinnedMessagesBannerEvents) -> Unit = {}
|
eventSink: (PinnedMessagesBannerEvents) -> Unit = {}
|
||||||
) = PinnedMessagesBannerState(
|
) = PinnedMessagesBannerState(
|
||||||
pinnedMessagesCount = pinnedMessagesCount,
|
pinnedMessagesCount = pinnedMessagesCount,
|
||||||
currentPinnedMessageIndex = currentPinnedMessageIndex,
|
currentPinnedMessageIndex = currentPinnedMessageIndex,
|
||||||
|
currentPinnedMessage = currentPinnedMessage,
|
||||||
eventSink = eventSink
|
eventSink = eventSink
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.pinned.banner
|
package io.element.android.features.messages.impl.pinned.banner
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
||||||
|
|
@ -32,8 +33,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -42,6 +41,7 @@ import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -55,39 +55,44 @@ import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
import io.element.android.libraries.designsystem.theme.pinnedMessageBannerBorder
|
import io.element.android.libraries.designsystem.theme.pinnedMessageBannerBorder
|
||||||
import io.element.android.libraries.designsystem.theme.pinnedMessageBannerIndicator
|
import io.element.android.libraries.designsystem.theme.pinnedMessageBannerIndicator
|
||||||
import io.element.android.libraries.designsystem.utils.annotatedTextWithBold
|
import io.element.android.libraries.designsystem.utils.annotatedTextWithBold
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PinnedMessagesBannerView(
|
fun PinnedMessagesBannerView(
|
||||||
state: PinnedMessagesBannerState,
|
state: PinnedMessagesBannerState,
|
||||||
|
onClick: (EventId) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
if (state.currentPinnedMessage == null) return
|
||||||
|
|
||||||
val borderColor = ElementTheme.colors.pinnedMessageBannerBorder
|
val borderColor = ElementTheme.colors.pinnedMessageBannerBorder
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.background(color = ElementTheme.colors.bgCanvasDefault)
|
.background(color = ElementTheme.colors.bgCanvasDefault)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.drawBehind {
|
.drawBehind {
|
||||||
val strokeWidth = 0.5.dp.toPx()
|
val strokeWidth = 0.5.dp.toPx()
|
||||||
val y = size.height - strokeWidth / 2
|
val y = size.height - strokeWidth / 2
|
||||||
drawLine(
|
drawLine(
|
||||||
borderColor,
|
borderColor,
|
||||||
Offset(0f, y),
|
Offset(0f, y),
|
||||||
Offset(size.width, y),
|
Offset(size.width, y),
|
||||||
strokeWidth
|
strokeWidth
|
||||||
)
|
)
|
||||||
drawLine(
|
drawLine(
|
||||||
borderColor,
|
borderColor,
|
||||||
Offset(0f, 0f),
|
Offset(0f, 0f),
|
||||||
Offset(size.width, 0f),
|
Offset(size.width, 0f),
|
||||||
strokeWidth
|
strokeWidth
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.shadow(elevation = 5.dp, spotColor = Color.Transparent)
|
.shadow(elevation = 5.dp, spotColor = Color.Transparent)
|
||||||
.heightIn(min = 64.dp)
|
.heightIn(min = 64.dp)
|
||||||
.clickable {
|
.clickable {
|
||||||
state.eventSink(PinnedMessagesBannerEvents.MoveToNextPinned)
|
onClick(state.currentPinnedMessage.eventId)
|
||||||
},
|
state.eventSink(PinnedMessagesBannerEvents.MoveToNextPinned)
|
||||||
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = spacedBy(10.dp)
|
horizontalArrangement = spacedBy(10.dp)
|
||||||
) {
|
) {
|
||||||
|
|
@ -106,7 +111,7 @@ fun PinnedMessagesBannerView(
|
||||||
PinnedMessageItem(
|
PinnedMessageItem(
|
||||||
index = state.currentPinnedMessageIndex,
|
index = state.currentPinnedMessageIndex,
|
||||||
totalCount = state.pinnedMessagesCount,
|
totalCount = state.pinnedMessagesCount,
|
||||||
message = "This is a pinned message",
|
message = state.currentPinnedMessage.formatted,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
TextButton(text = stringResource(id = CommonStrings.screen_room_pinned_banner_view_all_button_title), onClick = { /*TODO*/ })
|
TextButton(text = stringResource(id = CommonStrings.screen_room_pinned_banner_view_all_button_title), onClick = { /*TODO*/ })
|
||||||
|
|
@ -119,14 +124,12 @@ private fun PinIndicators(
|
||||||
pinsCount: Int,
|
pinsCount: Int,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val indicatorHeight by remember {
|
val indicatorHeight = remember(pinsCount) {
|
||||||
derivedStateOf {
|
when (pinsCount) {
|
||||||
when (pinsCount) {
|
0 -> 0
|
||||||
0 -> 0
|
1 -> 32
|
||||||
1 -> 32
|
2 -> 18
|
||||||
2 -> 18
|
else -> 11
|
||||||
else -> 11
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
@ -141,20 +144,21 @@ private fun PinIndicators(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
verticalArrangement = spacedBy(2.dp),
|
verticalArrangement = spacedBy(2.dp),
|
||||||
userScrollEnabled = false
|
userScrollEnabled = false,
|
||||||
|
reverseLayout = true
|
||||||
) {
|
) {
|
||||||
items(pinsCount) { index ->
|
items(pinsCount) { index ->
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(2.dp)
|
.width(2.dp)
|
||||||
.height(indicatorHeight.dp)
|
.height(indicatorHeight.dp)
|
||||||
.background(
|
.background(
|
||||||
color = if (index == pinIndex) {
|
color = if (index == pinIndex) {
|
||||||
ElementTheme.colors.iconAccentPrimary
|
ElementTheme.colors.iconAccentPrimary
|
||||||
} else {
|
} else {
|
||||||
ElementTheme.colors.pinnedMessageBannerIndicator
|
ElementTheme.colors.pinnedMessageBannerIndicator
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -164,13 +168,13 @@ private fun PinIndicators(
|
||||||
private fun PinnedMessageItem(
|
private fun PinnedMessageItem(
|
||||||
index: Int,
|
index: Int,
|
||||||
totalCount: Int,
|
totalCount: Int,
|
||||||
message: String,
|
message: AnnotatedString,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val countMessage = stringResource(id = CommonStrings.screen_room_pinned_banner_indicator, index + 1, totalCount)
|
val countMessage = stringResource(id = CommonStrings.screen_room_pinned_banner_indicator, index + 1, totalCount)
|
||||||
val fullCountMessage = stringResource(id = CommonStrings.screen_room_pinned_banner_indicator_description, countMessage)
|
val fullCountMessage = stringResource(id = CommonStrings.screen_room_pinned_banner_indicator_description, countMessage)
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
if (totalCount > 1) {
|
AnimatedVisibility (totalCount > 1) {
|
||||||
Text(
|
Text(
|
||||||
text = annotatedTextWithBold(
|
text = annotatedTextWithBold(
|
||||||
text = fullCountMessage,
|
text = fullCountMessage,
|
||||||
|
|
@ -179,6 +183,7 @@ private fun PinnedMessageItem(
|
||||||
style = ElementTheme.typography.fontBodySmMedium,
|
style = ElementTheme.typography.fontBodySmMedium,
|
||||||
color = ElementTheme.colors.textActionAccent,
|
color = ElementTheme.colors.textActionAccent,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -196,5 +201,6 @@ private fun PinnedMessageItem(
|
||||||
internal fun PinnedMessagesBannerViewPreview(@PreviewParameter(PinnedMessagesBannerStateProvider::class) state: PinnedMessagesBannerState) = ElementPreview {
|
internal fun PinnedMessagesBannerViewPreview(@PreviewParameter(PinnedMessagesBannerStateProvider::class) state: PinnedMessagesBannerState) = ElementPreview {
|
||||||
PinnedMessagesBannerView(
|
PinnedMessagesBannerView(
|
||||||
state = state,
|
state = state,
|
||||||
|
onClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@
|
||||||
package io.element.android.features.messages.impl.pinned.banner
|
package io.element.android.features.messages.impl.pinned.banner
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.eventformatter.api.PinnedMessagesBannerFormatter
|
||||||
|
import io.element.android.libraries.eventformatter.test.FakePinnedMessagesBannerFormatter
|
||||||
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -29,6 +33,7 @@ class PinnedMessagesBannerPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.pinnedMessagesCount).isEqualTo(0)
|
assertThat(initialState.pinnedMessagesCount).isEqualTo(0)
|
||||||
assertThat(initialState.currentPinnedMessageIndex).isEqualTo(0)
|
assertThat(initialState.currentPinnedMessageIndex).isEqualTo(0)
|
||||||
|
assertThat(initialState.currentPinnedMessage).isNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,7 +48,15 @@ class PinnedMessagesBannerPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPinnedMessagesBannerPresenter(): PinnedMessagesBannerPresenter {
|
private fun createPinnedMessagesBannerPresenter(
|
||||||
return PinnedMessagesBannerPresenter()
|
room: MatrixRoom = FakeMatrixRoom(),
|
||||||
|
formatter: PinnedMessagesBannerFormatter = FakePinnedMessagesBannerFormatter(
|
||||||
|
formatLambda = { event -> "Content ${event.content}" }
|
||||||
|
)
|
||||||
|
): PinnedMessagesBannerPresenter {
|
||||||
|
return PinnedMessagesBannerPresenter(
|
||||||
|
room = room,
|
||||||
|
pinnedMessagesBannerFormatter = formatter
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
*
|
||||||
|
* 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.libraries.eventformatter.test
|
||||||
|
|
||||||
|
import io.element.android.libraries.eventformatter.api.PinnedMessagesBannerFormatter
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||||
|
|
||||||
|
class FakePinnedMessagesBannerFormatter(
|
||||||
|
val formatLambda: (event: EventTimelineItem) -> CharSequence
|
||||||
|
) : PinnedMessagesBannerFormatter {
|
||||||
|
override fun format(event: EventTimelineItem): CharSequence {
|
||||||
|
return formatLambda(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue