Show a room list decoration for notification setting applied
- Add the UI - Rebuild room summaries when push rules change or when user disables notifications(hide them all)
This commit is contained in:
parent
3406b8a85f
commit
ed1949aa51
13 changed files with 145 additions and 12 deletions
|
|
@ -49,6 +49,7 @@ dependencies {
|
|||
implementation(projects.libraries.dateformatter.api)
|
||||
implementation(projects.libraries.eventformatter.api)
|
||||
implementation(projects.libraries.deeplink)
|
||||
implementation(projects.libraries.pushstore.api)
|
||||
implementation(projects.features.invitelist.api)
|
||||
implementation(projects.features.networkmonitor.api)
|
||||
implementation(projects.features.leaveroom.api)
|
||||
|
|
|
|||
|
|
@ -19,14 +19,17 @@ package io.element.android.features.roomlist.impl.components
|
|||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -38,6 +41,9 @@ import androidx.compose.ui.geometry.Size
|
|||
import androidx.compose.ui.graphics.Outline
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -48,16 +54,20 @@ import androidx.compose.ui.unit.dp
|
|||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryProvider
|
||||
import io.element.android.libraries.core.extensions.orEmpty
|
||||
import io.element.android.libraries.designsystem.VectorIcons
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
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.room.RoomNotificationMode
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
internal val minHeight = 84.dp
|
||||
|
||||
|
|
@ -168,11 +178,39 @@ private fun RowScope.LastMessageAndIndicatorRow(room: RoomListRoomSummary) {
|
|||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
// Unread
|
||||
UnreadIndicatorAtom(
|
||||
modifier = Modifier.padding(top = 3.dp),
|
||||
isVisible = room.hasUnread,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
NotificationIcon(room)
|
||||
if (room.hasUnread) {
|
||||
UnreadIndicatorAtom(
|
||||
modifier = Modifier.padding(top = 3.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NotificationIcon(room: RoomListRoomSummary) {
|
||||
val tint = if(room.hasUnread) ElementTheme.colors.unreadIndicator else ElementTheme.colors.iconQuaternary
|
||||
when(room.notificationMode) {
|
||||
null, RoomNotificationMode.ALL_MESSAGES -> return
|
||||
RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY ->
|
||||
Icon(
|
||||
contentDescription = stringResource(CommonStrings.screen_notification_settings_mode_mentions),
|
||||
imageVector = ImageVector.vectorResource(VectorIcons.Mention),
|
||||
tint = tint,
|
||||
)
|
||||
RoomNotificationMode.MUTE ->
|
||||
Icon(
|
||||
contentDescription = stringResource(CommonStrings.common_mute),
|
||||
imageVector = ImageVector.vectorResource(VectorIcons.Mute),
|
||||
tint = tint,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val TextPlaceholderShape = PercentRectangleSizeShape(0.5f)
|
||||
|
|
|
|||
|
|
@ -26,30 +26,57 @@ import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormat
|
|||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class RoomListDataSource @Inject constructor(
|
||||
private val roomListService: RoomListService,
|
||||
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
|
||||
private val roomLastMessageFormatter: RoomLastMessageFormatter,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
notificationSettingsService: NotificationSettingsService,
|
||||
appScope: CoroutineScope,
|
||||
userPushStoreFactory: UserPushStoreFactory,
|
||||
matrixClient: MatrixClient,
|
||||
) {
|
||||
init {
|
||||
notificationSettingsService.notificationSettingsChangeFlow
|
||||
.debounce(0.5.seconds)
|
||||
.onEach {
|
||||
roomListService.rebuildRoomSummaries()
|
||||
}
|
||||
.launchIn(appScope)
|
||||
|
||||
val userPushStore = userPushStoreFactory.create(matrixClient.sessionId)
|
||||
userPushStore.getNotificationEnabledForDevice().distinctUntilChanged()
|
||||
.onEach {
|
||||
roomListService.rebuildRoomSummaries()
|
||||
}
|
||||
.launchIn(appScope)
|
||||
}
|
||||
private val _filter = MutableStateFlow("")
|
||||
private val _allRooms = MutableStateFlow<ImmutableList<RoomListRoomSummary>>(persistentListOf())
|
||||
private val _filteredRooms = MutableStateFlow<ImmutableList<RoomListRoomSummary>>(persistentListOf())
|
||||
|
|
@ -59,13 +86,15 @@ class RoomListDataSource @Inject constructor(
|
|||
private val diffCacheUpdater = DiffCacheUpdater<RoomSummary, RoomListRoomSummary>(diffCache = diffCache, detectMoves = true) { old, new ->
|
||||
old?.identifier() == new?.identifier()
|
||||
}
|
||||
private val userPushStore = userPushStoreFactory.create(matrixClient.sessionId)
|
||||
|
||||
fun launchIn(coroutineScope: CoroutineScope) {
|
||||
|
||||
roomListService
|
||||
.allRooms()
|
||||
.summaries
|
||||
.onEach { roomSummaries ->
|
||||
replaceWith(roomSummaries)
|
||||
replaceWith(roomSummaries, userPushStore.getNotificationEnabledForDevice().first())
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
|
||||
|
|
@ -92,14 +121,14 @@ class RoomListDataSource @Inject constructor(
|
|||
val allRooms: StateFlow<ImmutableList<RoomListRoomSummary>> = _allRooms
|
||||
val filteredRooms: StateFlow<ImmutableList<RoomListRoomSummary>> = _filteredRooms
|
||||
|
||||
private suspend fun replaceWith(roomSummaries: List<RoomSummary>) = withContext(coroutineDispatchers.computation) {
|
||||
private suspend fun replaceWith(roomSummaries: List<RoomSummary>, notificationsEnabled: Boolean) = withContext(coroutineDispatchers.computation) {
|
||||
lock.withLock {
|
||||
diffCacheUpdater.updateWith(roomSummaries)
|
||||
buildAndEmitAllRooms(roomSummaries)
|
||||
buildAndEmitAllRooms(roomSummaries, notificationsEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun buildAndEmitAllRooms(roomSummaries: List<RoomSummary>) {
|
||||
private suspend fun buildAndEmitAllRooms(roomSummaries: List<RoomSummary>, notificationsEnabled: Boolean) {
|
||||
if (diffCache.isEmpty()) {
|
||||
_allRooms.emit(
|
||||
RoomListRoomSummaryPlaceholders.createFakeList(16).toImmutableList()
|
||||
|
|
@ -109,7 +138,7 @@ class RoomListDataSource @Inject constructor(
|
|||
for (index in diffCache.indices()) {
|
||||
val cacheItem = diffCache.get(index)
|
||||
if (cacheItem == null) {
|
||||
buildAndCacheItem(roomSummaries, index)?.also { timelineItemState ->
|
||||
buildAndCacheItem(roomSummaries, index, notificationsEnabled)?.also { timelineItemState ->
|
||||
roomListRoomSummaries.add(timelineItemState)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -122,11 +151,18 @@ class RoomListDataSource @Inject constructor(
|
|||
|
||||
private fun buildAndCacheItem(
|
||||
roomSummaries: List<RoomSummary>,
|
||||
index: Int
|
||||
index: Int,
|
||||
notificationsEnabled: Boolean,
|
||||
): RoomListRoomSummary? {
|
||||
val roomListRoomSummary = when (val roomSummary = roomSummaries.getOrNull(index)) {
|
||||
is RoomSummary.Empty -> RoomListRoomSummaryPlaceholders.create(roomSummary.identifier)
|
||||
is RoomSummary.Filled -> {
|
||||
// Only show a decoration if notifications are enabled and the mode is not ALL_MESSAGES
|
||||
val notificationMode = if (roomSummary.details.notificationMode == RoomNotificationMode.ALL_MESSAGES || !notificationsEnabled) {
|
||||
null
|
||||
} else {
|
||||
roomSummary.details.notificationMode
|
||||
}
|
||||
val avatarData = AvatarData(
|
||||
id = roomSummary.identifier(),
|
||||
name = roomSummary.details.name,
|
||||
|
|
@ -144,10 +180,12 @@ class RoomListDataSource @Inject constructor(
|
|||
roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
|
||||
}.orEmpty(),
|
||||
avatarData = avatarData,
|
||||
notificationMode = notificationMode
|
||||
)
|
||||
}
|
||||
null -> null
|
||||
}
|
||||
|
||||
diffCache[index] = roomListRoomSummary
|
||||
return roomListRoomSummary
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ 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.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
|
||||
@Immutable
|
||||
data class RoomListRoomSummary constructor(
|
||||
|
|
@ -31,4 +32,5 @@ data class RoomListRoomSummary constructor(
|
|||
val lastMessage: CharSequence? = null,
|
||||
val avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem),
|
||||
val isPlaceholder: Boolean = false,
|
||||
val notificationMode: RoomNotificationMode? = null,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,14 +20,16 @@ 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.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
|
||||
open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSummary> {
|
||||
override val values: Sequence<RoomListRoomSummary>
|
||||
get() = sequenceOf(
|
||||
aRoomListRoomSummary(),
|
||||
aRoomListRoomSummary().copy(lastMessage = null),
|
||||
aRoomListRoomSummary().copy(hasUnread = true),
|
||||
aRoomListRoomSummary().copy(timestamp = "88:88"),
|
||||
aRoomListRoomSummary().copy(hasUnread = true, notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY),
|
||||
aRoomListRoomSummary().copy(timestamp = "88:88", notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY),
|
||||
aRoomListRoomSummary().copy(timestamp = "88:88", notificationMode = RoomNotificationMode.MUTE),
|
||||
aRoomListRoomSummary().copy(timestamp = "88:88", hasUnread = true),
|
||||
aRoomListRoomSummary().copy(isPlaceholder = true, timestamp = "88:88"),
|
||||
aRoomListRoomSummary().copy(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue