Merge pull request #2397 from element-hq/feature/fga/mark_room_as_favorite
Feature/fga/mark room as favorite
This commit is contained in:
commit
34ab25e0d1
61 changed files with 405 additions and 205 deletions
1
changelog.d/2208.feature
Normal file
1
changelog.d/2208.feature
Normal file
|
|
@ -0,0 +1 @@
|
|||
Mark a room or dm as favourite.
|
||||
|
|
@ -20,4 +20,5 @@ sealed interface RoomDetailsEvent {
|
|||
data object LeaveRoom : RoomDetailsEvent
|
||||
data object MuteNotification : RoomDetailsEvent
|
||||
data object UnmuteNotification : RoomDetailsEvent
|
||||
data class SetFavorite(val isFavorite: Boolean) : RoomDetailsEvent
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
|||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
|
|
@ -65,12 +66,13 @@ class RoomDetailsPresenter @Inject constructor(
|
|||
val scope = rememberCoroutineScope()
|
||||
val leaveRoomState = leaveRoomPresenter.present()
|
||||
val canShowNotificationSettings = remember { mutableStateOf(false) }
|
||||
val roomInfo = room.roomInfoFlow.collectAsState(initial = null).value
|
||||
val roomInfo by room.roomInfoFlow.collectAsState(initial = null)
|
||||
|
||||
val roomAvatar by remember { derivedStateOf { roomInfo?.avatarUrl ?: room.avatarUrl } }
|
||||
|
||||
val roomName by remember { derivedStateOf { (roomInfo?.name ?: room.name ?: room.displayName).trim() } }
|
||||
val roomTopic by remember { derivedStateOf { roomInfo?.topic ?: room.topic } }
|
||||
val isFavorite by remember { derivedStateOf { roomInfo?.isFavorite.orFalse() } }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
canShowNotificationSettings.value = featureFlagService.isFeatureEnabled(FeatureFlags.NotificationSettings)
|
||||
|
|
@ -122,6 +124,11 @@ class RoomDetailsPresenter @Inject constructor(
|
|||
client.notificationSettingsService().unmuteRoom(room.roomId, room.isEncrypted, room.isOneToOne)
|
||||
}
|
||||
}
|
||||
is RoomDetailsEvent.SetFavorite -> {
|
||||
scope.launch {
|
||||
room.setIsFavorite(event.isFavorite)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +149,7 @@ class RoomDetailsPresenter @Inject constructor(
|
|||
roomMemberDetailsState = roomMemberDetailsState,
|
||||
leaveRoomState = leaveRoomState,
|
||||
roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(),
|
||||
isFavorite = isFavorite,
|
||||
eventSink = ::handleEvents,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ data class RoomDetailsState(
|
|||
val canShowNotificationSettings: Boolean,
|
||||
val leaveRoomState: LeaveRoomState,
|
||||
val roomNotificationSettings: RoomNotificationSettings?,
|
||||
val isFavorite: Boolean,
|
||||
val eventSink: (RoomDetailsEvent) -> Unit
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ open class RoomDetailsStateProvider : PreviewParameterProvider<RoomDetailsState>
|
|||
aDmRoomDetailsState().copy(roomName = "Daniel"),
|
||||
aDmRoomDetailsState(isDmMemberIgnored = true).copy(roomName = "Daniel"),
|
||||
aRoomDetailsState().copy(canInvite = true),
|
||||
aRoomDetailsState().copy(isFavorite = true),
|
||||
aRoomDetailsState().copy(
|
||||
canEdit = true,
|
||||
// Also test the roomNotificationSettings ALL_MESSAGES in the same screenshot. Icon 'Mute' should be displayed
|
||||
|
|
@ -86,6 +87,7 @@ fun aRoomDetailsState() = RoomDetailsState(
|
|||
roomMemberDetailsState = null,
|
||||
leaveRoomState = aLeaveRoomState(),
|
||||
roomNotificationSettings = RoomNotificationSettings(mode = RoomNotificationMode.MUTE, isDefault = false),
|
||||
isFavorite = false,
|
||||
eventSink = {}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ import io.element.android.libraries.designsystem.components.button.BackButton
|
|||
import io.element.android.libraries.designsystem.components.button.MainActionButton
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
|
|
@ -163,6 +164,13 @@ fun RoomDetailsView(
|
|||
)
|
||||
}
|
||||
|
||||
FavoriteSection(
|
||||
isFavorite = state.isFavorite,
|
||||
onFavoriteChanges = {
|
||||
state.eventSink(RoomDetailsEvent.SetFavorite(it))
|
||||
}
|
||||
)
|
||||
|
||||
if (state.roomType is RoomDetailsType.Room) {
|
||||
MembersSection(
|
||||
memberCount = state.memberCount,
|
||||
|
|
@ -356,6 +364,22 @@ private fun NotificationSection(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FavoriteSection(
|
||||
isFavorite: Boolean,
|
||||
onFavoriteChanges: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
PreferenceCategory(modifier = modifier) {
|
||||
PreferenceSwitch(
|
||||
icon = CompoundIcons.Favourite(),
|
||||
title = stringResource(id = CommonStrings.common_favourite),
|
||||
isChecked = isFavorite,
|
||||
onCheckedChange = onFavoriteChanges
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MembersSection(
|
||||
memberCount: Long,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.roomdetails
|
|||
import androidx.lifecycle.Lifecycle
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.TurbineTestContext
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.createroom.test.FakeStartDMAction
|
||||
|
|
@ -27,6 +28,7 @@ import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
|||
import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter
|
||||
import io.element.android.features.roomdetails.impl.RoomDetailsEvent
|
||||
import io.element.android.features.roomdetails.impl.RoomDetailsPresenter
|
||||
import io.element.android.features.roomdetails.impl.RoomDetailsState
|
||||
import io.element.android.features.roomdetails.impl.RoomDetailsType
|
||||
import io.element.android.features.roomdetails.impl.RoomTopicState
|
||||
import io.element.android.features.roomdetails.impl.members.aRoomMember
|
||||
|
|
@ -71,10 +73,10 @@ class RoomDetailsPresenterTests {
|
|||
}
|
||||
|
||||
private fun TestScope.createRoomDetailsPresenter(
|
||||
room: MatrixRoom,
|
||||
room: MatrixRoom = aMatrixRoom(),
|
||||
leaveRoomPresenter: LeaveRoomPresenter = FakeLeaveRoomPresenter(),
|
||||
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService()
|
||||
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
||||
): RoomDetailsPresenter {
|
||||
val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService)
|
||||
val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory {
|
||||
|
|
@ -86,25 +88,29 @@ class RoomDetailsPresenterTests {
|
|||
mapOf(FeatureFlags.NotificationSettings.key to true)
|
||||
)
|
||||
return RoomDetailsPresenter(
|
||||
matrixClient,
|
||||
room,
|
||||
featureFlagService,
|
||||
matrixClient.notificationSettingsService(),
|
||||
roomMemberDetailsPresenterFactory,
|
||||
leaveRoomPresenter,
|
||||
dispatchers
|
||||
client = matrixClient,
|
||||
room = room,
|
||||
featureFlagService = featureFlagService,
|
||||
notificationSettingsService = matrixClient.notificationSettingsService(),
|
||||
roomMembersDetailsPresenterFactory = roomMemberDetailsPresenterFactory,
|
||||
leaveRoomPresenter = leaveRoomPresenter,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun RoomDetailsPresenter.test(validate: suspend TurbineTestContext<RoomDetailsState>.() -> Unit) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
present()
|
||||
}
|
||||
}.test(validate = validate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - initial state is created from room if roomInfo is null`() = runTest {
|
||||
val room = aMatrixRoom()
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.roomId).isEqualTo(room.roomId.value)
|
||||
assertThat(initialState.roomName).isEqualTo(room.name)
|
||||
|
|
@ -124,11 +130,7 @@ class RoomDetailsPresenterTests {
|
|||
givenRoomInfo(roomInfo)
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val updatedState = awaitItem()
|
||||
assertThat(updatedState.roomName).isEqualTo(roomInfo.name)
|
||||
|
|
@ -143,11 +145,7 @@ class RoomDetailsPresenterTests {
|
|||
fun `present - initial state with no room name`() = runTest {
|
||||
val room = aMatrixRoom(name = null)
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.roomName).isEqualTo(room.displayName)
|
||||
|
||||
|
|
@ -167,11 +165,7 @@ class RoomDetailsPresenterTests {
|
|||
givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.roomType).isEqualTo(RoomDetailsType.Dm(otherRoomMember))
|
||||
|
||||
|
|
@ -185,11 +179,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.success(true))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers())
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// Initially false
|
||||
assertThat(awaitItem().canInvite).isFalse()
|
||||
// Then the asynchronous check completes and it becomes true
|
||||
|
|
@ -205,11 +195,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.success(false))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
assertThat(awaitItem().canInvite).isFalse()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
|
|
@ -222,11 +208,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.failure(Throwable("Whoops")))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
assertThat(awaitItem().canInvite).isFalse()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
|
|
@ -242,11 +224,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.success(false))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// Initially false
|
||||
assertThat(awaitItem().canEdit).isFalse()
|
||||
// Then the asynchronous check completes and it becomes true
|
||||
|
|
@ -273,11 +251,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.success(false))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// Initially false
|
||||
assertThat(awaitItem().canEdit).isFalse()
|
||||
// Then the asynchronous check completes, but editing is still disallowed because it's a DM
|
||||
|
|
@ -305,11 +279,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
|
||||
// There's no topic, so we hide the entire UI for DMs
|
||||
|
|
@ -328,11 +298,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.success(false))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// Initially false
|
||||
assertThat(awaitItem().canEdit).isFalse()
|
||||
// Then the asynchronous check completes and it becomes true
|
||||
|
|
@ -351,11 +317,7 @@ class RoomDetailsPresenterTests {
|
|||
givenCanInviteResult(Result.success(false))
|
||||
}
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// Initially false, and no further events
|
||||
assertThat(awaitItem().canEdit).isFalse()
|
||||
|
||||
|
|
@ -371,11 +333,7 @@ class RoomDetailsPresenterTests {
|
|||
}
|
||||
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// The initial state is "hidden" and no further state changes happen
|
||||
assertThat(awaitItem().roomTopic).isEqualTo(RoomTopicState.Hidden)
|
||||
|
||||
|
|
@ -392,11 +350,7 @@ class RoomDetailsPresenterTests {
|
|||
}
|
||||
|
||||
val presenter = createRoomDetailsPresenter(room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
// Ignore the initial state
|
||||
skipItems(1)
|
||||
|
||||
|
|
@ -416,11 +370,7 @@ class RoomDetailsPresenterTests {
|
|||
leaveRoomPresenter = leaveRoomPresenter,
|
||||
dispatchers = testCoroutineDispatchers()
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
awaitItem().eventSink(RoomDetailsEvent.LeaveRoom)
|
||||
|
||||
assertThat(leaveRoomPresenter.events).contains(LeaveRoomEvent.ShowConfirmation(room.roomId))
|
||||
|
|
@ -439,11 +389,7 @@ class RoomDetailsPresenterTests {
|
|||
leaveRoomPresenter = leaveRoomPresenter,
|
||||
notificationSettingsService = notificationSettingsService,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
notificationSettingsService.setRoomNotificationMode(room.roomId, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
|
||||
val updatedState = consumeItemsUntilPredicate {
|
||||
it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
|
||||
|
|
@ -458,11 +404,7 @@ class RoomDetailsPresenterTests {
|
|||
val notificationSettingsService = FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
|
||||
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
|
||||
val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
awaitItem().eventSink(RoomDetailsEvent.MuteNotification)
|
||||
val updatedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) {
|
||||
it.roomNotificationSettings?.mode == RoomNotificationMode.MUTE
|
||||
|
|
@ -480,11 +422,7 @@ class RoomDetailsPresenterTests {
|
|||
)
|
||||
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
|
||||
val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
withFakeLifecycleOwner(fakeLifecycleOwner) {
|
||||
presenter.present()
|
||||
}
|
||||
}.test {
|
||||
presenter.test {
|
||||
awaitItem().eventSink(RoomDetailsEvent.UnmuteNotification)
|
||||
val updatedState = consumeItemsUntilPredicate {
|
||||
it.roomNotificationSettings?.mode == RoomNotificationMode.ALL_MESSAGES
|
||||
|
|
@ -493,6 +431,37 @@ class RoomDetailsPresenterTests {
|
|||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when set is favorite event is emitted, then the action is called`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = createRoomDetailsPresenter(room = room)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(RoomDetailsEvent.SetFavorite(true))
|
||||
assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true))
|
||||
initialState.eventSink(RoomDetailsEvent.SetFavorite(false))
|
||||
assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false))
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - changes in room info updates the is favorite flag`() = runTest {
|
||||
val room = aMatrixRoom()
|
||||
val presenter = createRoomDetailsPresenter(room = room)
|
||||
presenter.test {
|
||||
room.givenRoomInfo(aRoomInfo(isFavorite = true))
|
||||
consumeItemsUntilPredicate { it.isFavorite }.last().let { state ->
|
||||
assertThat(state.isFavorite).isTrue()
|
||||
}
|
||||
room.givenRoomInfo(aRoomInfo(isFavorite = false))
|
||||
consumeItemsUntilPredicate { !it.isFavorite }.last().let { state ->
|
||||
assertThat(state.isFavorite).isFalse()
|
||||
}
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun aMatrixRoom(
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
@Composable
|
||||
fun RoomListContextMenu(
|
||||
contextMenu: RoomListState.ContextMenu.Shown,
|
||||
eventSink: (RoomListEvents.RoomListBottomSheetEvents) -> Unit,
|
||||
eventSink: (RoomListEvents.ContextMenuEvents) -> Unit,
|
||||
onRoomSettingsClicked: (roomId: RoomId) -> Unit,
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
|
|
@ -64,7 +64,10 @@ fun RoomListContextMenu(
|
|||
onLeaveRoomClicked = {
|
||||
eventSink(RoomListEvents.HideContextMenu)
|
||||
eventSink(RoomListEvents.LeaveRoom(contextMenu.roomId))
|
||||
}
|
||||
},
|
||||
onFavoriteChanged = { isFavorite ->
|
||||
eventSink(RoomListEvents.SetRoomIsFavorite(contextMenu.roomId, isFavorite))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -72,10 +75,11 @@ fun RoomListContextMenu(
|
|||
@Composable
|
||||
private fun RoomListModalBottomSheetContent(
|
||||
contextMenu: RoomListState.ContextMenu.Shown,
|
||||
onRoomMarkReadClicked: () -> Unit,
|
||||
onRoomMarkUnreadClicked: () -> Unit,
|
||||
onRoomSettingsClicked: () -> Unit,
|
||||
onLeaveRoomClicked: () -> Unit,
|
||||
onFavoriteChanged: (isFavorite: Boolean) -> Unit,
|
||||
onRoomMarkReadClicked: () -> Unit,
|
||||
onRoomMarkUnreadClicked: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
|
|
@ -120,6 +124,30 @@ private fun RoomListModalBottomSheetContent(
|
|||
style = ListItemStyle.Primary,
|
||||
)
|
||||
}
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = stringResource(id = CommonStrings.common_favourite),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
},
|
||||
leadingContent = ListItemContent.Icon(
|
||||
iconSource = IconSource.Vector(
|
||||
CompoundIcons.Favourite(),
|
||||
contentDescription = stringResource(id = CommonStrings.common_favourite),
|
||||
)
|
||||
),
|
||||
trailingContent = ListItemContent.Switch(
|
||||
checked = contextMenu.isFavorite,
|
||||
onChange = { isFavorite ->
|
||||
onFavoriteChanged(isFavorite)
|
||||
},
|
||||
),
|
||||
onClick = {
|
||||
onFavoriteChanged(!contextMenu.isFavorite)
|
||||
},
|
||||
style = ListItemStyle.Primary,
|
||||
)
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
|
|
@ -170,7 +198,8 @@ internal fun RoomListModalBottomSheetContentPreview() = ElementPreview {
|
|||
onRoomMarkReadClicked = {},
|
||||
onRoomMarkUnreadClicked = {},
|
||||
onRoomSettingsClicked = {},
|
||||
onLeaveRoomClicked = {}
|
||||
onLeaveRoomClicked = {},
|
||||
onFavoriteChanged = {},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -182,6 +211,7 @@ internal fun RoomListModalBottomSheetContentForDmPreview() = ElementPreview {
|
|||
onRoomMarkReadClicked = {},
|
||||
onRoomMarkUnreadClicked = {},
|
||||
onRoomSettingsClicked = {},
|
||||
onLeaveRoomClicked = {}
|
||||
onLeaveRoomClicked = {},
|
||||
onFavoriteChanged = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,10 @@ sealed interface RoomListEvents {
|
|||
data object ToggleSearchResults : RoomListEvents
|
||||
data class ShowContextMenu(val roomListRoomSummary: RoomListRoomSummary) : RoomListEvents
|
||||
|
||||
sealed interface RoomListBottomSheetEvents : RoomListEvents
|
||||
data object HideContextMenu : RoomListBottomSheetEvents
|
||||
data class LeaveRoom(val roomId: RoomId) : RoomListBottomSheetEvents
|
||||
data class MarkAsRead(val roomId: RoomId) : RoomListBottomSheetEvents
|
||||
data class MarkAsUnread(val roomId: RoomId) : RoomListBottomSheetEvents
|
||||
sealed interface ContextMenuEvents : RoomListEvents
|
||||
data object HideContextMenu : ContextMenuEvents
|
||||
data class LeaveRoom(val roomId: RoomId) : ContextMenuEvents
|
||||
data class MarkAsRead(val roomId: RoomId) : ContextMenuEvents
|
||||
data class MarkAsUnread(val roomId: RoomId) : ContextMenuEvents
|
||||
data class SetRoomIsFavorite(val roomId: RoomId, val isFavorite: Boolean) : ContextMenuEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
|
|
@ -51,7 +52,14 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
|
|||
import io.element.android.libraries.matrix.api.user.getCurrentUser
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -111,15 +119,11 @@ class RoomListPresenter @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
val markAsUnreadFeatureFlagEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.MarkAsUnread)
|
||||
.collectAsState(initial = null)
|
||||
|
||||
// Avatar indicator
|
||||
val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator()
|
||||
|
||||
var displaySearchResults by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
var contextMenu by remember { mutableStateOf<RoomListState.ContextMenu>(RoomListState.ContextMenu.Hidden) }
|
||||
val contextMenu = remember { mutableStateOf<RoomListState.ContextMenu>(RoomListState.ContextMenu.Hidden) }
|
||||
|
||||
fun handleEvents(event: RoomListEvents) {
|
||||
when (event) {
|
||||
|
|
@ -134,16 +138,18 @@ class RoomListPresenter @Inject constructor(
|
|||
displaySearchResults = !displaySearchResults
|
||||
}
|
||||
is RoomListEvents.ShowContextMenu -> {
|
||||
contextMenu = RoomListState.ContextMenu.Shown(
|
||||
roomId = event.roomListRoomSummary.roomId,
|
||||
roomName = event.roomListRoomSummary.name,
|
||||
isDm = event.roomListRoomSummary.isDm,
|
||||
markAsUnreadFeatureFlagEnabled = markAsUnreadFeatureFlagEnabled == true,
|
||||
hasNewContent = event.roomListRoomSummary.hasNewContent
|
||||
)
|
||||
coroutineScope.showContextMenu(event, contextMenu)
|
||||
}
|
||||
is RoomListEvents.HideContextMenu -> {
|
||||
contextMenu.value = RoomListState.ContextMenu.Hidden
|
||||
}
|
||||
is RoomListEvents.HideContextMenu -> contextMenu = RoomListState.ContextMenu.Hidden
|
||||
is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(LeaveRoomEvent.ShowConfirmation(event.roomId))
|
||||
|
||||
is RoomListEvents.SetRoomIsFavorite -> coroutineScope.launch {
|
||||
client.getRoom(event.roomId)?.use { room ->
|
||||
room.setIsFavorite(event.isFavorite)
|
||||
}
|
||||
}
|
||||
is RoomListEvents.MarkAsRead -> coroutineScope.launch {
|
||||
client.getRoom(event.roomId)?.use { room ->
|
||||
room.setUnreadFlag(isUnread = false)
|
||||
|
|
@ -177,7 +183,7 @@ class RoomListPresenter @Inject constructor(
|
|||
hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online,
|
||||
invitesState = inviteStateDataSource.inviteState(),
|
||||
displaySearchResults = displaySearchResults,
|
||||
contextMenu = contextMenu,
|
||||
contextMenu = contextMenu.value,
|
||||
leaveRoomState = leaveRoomState,
|
||||
displayMigrationStatus = isMigrating,
|
||||
eventSink = ::handleEvents
|
||||
|
|
@ -188,6 +194,37 @@ class RoomListPresenter @Inject constructor(
|
|||
matrixUser.value = client.getCurrentUser()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun CoroutineScope.showContextMenu(event: RoomListEvents.ShowContextMenu, contextMenuState: MutableState<RoomListState.ContextMenu>) = launch {
|
||||
val initialState = RoomListState.ContextMenu.Shown(
|
||||
roomId = event.roomListRoomSummary.roomId,
|
||||
roomName = event.roomListRoomSummary.name,
|
||||
isDm = event.roomListRoomSummary.isDm,
|
||||
isFavorite = event.roomListRoomSummary.isFavorite,
|
||||
markAsUnreadFeatureFlagEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.MarkAsUnread),
|
||||
hasNewContent = event.roomListRoomSummary.hasNewContent
|
||||
)
|
||||
contextMenuState.value = initialState
|
||||
|
||||
client.getRoom(event.roomListRoomSummary.roomId)?.use { room ->
|
||||
|
||||
val isShowingContextMenuFlow = snapshotFlow { contextMenuState.value is RoomListState.ContextMenu.Shown }
|
||||
.distinctUntilChanged()
|
||||
|
||||
val isFavoriteFlow = room.roomInfoFlow
|
||||
.map { it.isFavorite }
|
||||
.distinctUntilChanged()
|
||||
|
||||
isFavoriteFlow
|
||||
.onEach { isFavorite ->
|
||||
contextMenuState.value = initialState.copy(isFavorite = isFavorite)
|
||||
}
|
||||
.flatMapLatest { isShowingContextMenuFlow }
|
||||
.takeWhile { isShowingContextMenu -> isShowingContextMenu }
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateVisibleRange(range: IntRange) {
|
||||
if (range.isEmpty()) return
|
||||
val midExtendedRangeSize = EXTENDED_RANGE_SIZE / 2
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ data class RoomListState(
|
|||
val roomId: RoomId,
|
||||
val roomName: String,
|
||||
val isDm: Boolean,
|
||||
val isFavorite: Boolean,
|
||||
val markAsUnreadFeatureFlagEnabled: Boolean,
|
||||
val hasNewContent: Boolean,
|
||||
) : ContextMenu
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
|
|||
aRoomListState().copy(displaySearchResults = true, filter = "", filteredRoomList = persistentListOf()),
|
||||
aRoomListState().copy(displaySearchResults = true),
|
||||
aRoomListState().copy(contextMenu = aContextMenuShown(roomName = "A nice room name")),
|
||||
aRoomListState().copy(contextMenu = aContextMenuShown(isFavorite = true)),
|
||||
aRoomListState().copy(displayRecoveryKeyPrompt = true),
|
||||
aRoomListState().copy(roomList = AsyncData.Success(persistentListOf())),
|
||||
aRoomListState().copy(roomList = AsyncData.Loading(prevData = RoomListRoomSummaryFactory.createFakeList())),
|
||||
|
|
@ -102,10 +103,12 @@ internal fun aContextMenuShown(
|
|||
roomName: String = "aRoom",
|
||||
isDm: Boolean = false,
|
||||
hasNewContent: Boolean = false,
|
||||
isFavorite: Boolean = false,
|
||||
) = RoomListState.ContextMenu.Shown(
|
||||
roomId = RoomId("!aRoom:aDomain"),
|
||||
roomName = roomName,
|
||||
isDm = isDm,
|
||||
markAsUnreadFeatureFlagEnabled = true,
|
||||
hasNewContent = hasNewContent,
|
||||
isFavorite = isFavorite,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ class RoomListRoomSummaryFactory @Inject constructor(
|
|||
userDefinedNotificationMode = null,
|
||||
hasRoomCall = false,
|
||||
isDm = false,
|
||||
isFavorite = false,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -84,6 +85,7 @@ class RoomListRoomSummaryFactory @Inject constructor(
|
|||
userDefinedNotificationMode = roomSummary.details.userDefinedNotificationMode,
|
||||
hasRoomCall = roomSummary.details.hasRoomCall,
|
||||
isDm = roomSummary.details.isDm,
|
||||
isFavorite = roomSummary.details.isFavorite,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ data class RoomListRoomSummary(
|
|||
val userDefinedNotificationMode: RoomNotificationMode?,
|
||||
val hasRoomCall: Boolean,
|
||||
val isDm: Boolean,
|
||||
val isFavorite: Boolean,
|
||||
) {
|
||||
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
||||
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ internal fun aRoomListRoomSummary(
|
|||
hasRoomCall: Boolean = false,
|
||||
avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem),
|
||||
isDm: Boolean = false,
|
||||
isFavorite: Boolean = false,
|
||||
) = RoomListRoomSummary(
|
||||
id = id,
|
||||
roomId = RoomId(id),
|
||||
|
|
@ -112,4 +113,5 @@ internal fun aRoomListRoomSummary(
|
|||
userDefinedNotificationMode = notificationMode,
|
||||
hasRoomCall = hasRoomCall,
|
||||
isDm = isDm,
|
||||
isFavorite = isFavorite,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.roomlist.impl
|
|||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureCalledOnceWithParam
|
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||
|
|
@ -128,4 +129,24 @@ class RoomListContextMenuTest {
|
|||
eventsRecorder.assertSingle(RoomListEvents.HideContextMenu)
|
||||
callback.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Favourites generates expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomListEvents>()
|
||||
val contextMenu = aContextMenuShown(isDm = false, isFavorite = false)
|
||||
val callback = EnsureNeverCalledWithParam<RoomId>()
|
||||
rule.setContent {
|
||||
RoomListContextMenu(
|
||||
contextMenu = contextMenu,
|
||||
eventSink = eventsRecorder,
|
||||
onRoomSettingsClicked = callback,
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.common_favourite)
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
RoomListEvents.SetRoomIsFavorite(contextMenu.roomId, true),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient
|
|||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
|
|
@ -340,26 +341,49 @@ class RoomListPresenterTests {
|
|||
@Test
|
||||
fun `present - show context menu`() = runTest {
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(coroutineScope = scope)
|
||||
val room = FakeMatrixRoom()
|
||||
val client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val presenter = createRoomListPresenter(client = client, coroutineScope = scope)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
|
||||
val initialState = awaitItem()
|
||||
val summary = createRoomListRoomSummary()
|
||||
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
|
||||
|
||||
val shownState = awaitItem()
|
||||
assertThat(shownState.contextMenu).isEqualTo(
|
||||
RoomListState.ContextMenu.Shown(
|
||||
roomId = summary.roomId,
|
||||
roomName = summary.name,
|
||||
isDm = false,
|
||||
markAsUnreadFeatureFlagEnabled = true,
|
||||
hasNewContent = false,
|
||||
)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contextMenu)
|
||||
.isEqualTo(
|
||||
RoomListState.ContextMenu.Shown(
|
||||
roomId = summary.roomId,
|
||||
roomName = summary.name,
|
||||
isDm = false,
|
||||
isFavorite = false,
|
||||
markAsUnreadFeatureFlagEnabled = true,
|
||||
hasNewContent = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
room.givenRoomInfo(
|
||||
aRoomInfo(isFavorite = true)
|
||||
)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.contextMenu)
|
||||
.isEqualTo(
|
||||
RoomListState.ContextMenu.Shown(
|
||||
roomId = summary.roomId,
|
||||
roomName = summary.name,
|
||||
isDm = false,
|
||||
isFavorite = true,
|
||||
markAsUnreadFeatureFlagEnabled = true,
|
||||
hasNewContent = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
|
@ -367,7 +391,11 @@ class RoomListPresenterTests {
|
|||
@Test
|
||||
fun `present - hide context menu`() = runTest {
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(coroutineScope = scope)
|
||||
val room = FakeMatrixRoom()
|
||||
val client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val presenter = createRoomListPresenter(client = client, coroutineScope = scope)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -378,15 +406,18 @@ class RoomListPresenterTests {
|
|||
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
|
||||
|
||||
val shownState = awaitItem()
|
||||
assertThat(shownState.contextMenu).isEqualTo(
|
||||
RoomListState.ContextMenu.Shown(
|
||||
roomId = summary.roomId,
|
||||
roomName = summary.name,
|
||||
isDm = false,
|
||||
markAsUnreadFeatureFlagEnabled = true,
|
||||
hasNewContent = false,
|
||||
assertThat(shownState.contextMenu)
|
||||
.isEqualTo(
|
||||
RoomListState.ContextMenu.Shown(
|
||||
roomId = summary.roomId,
|
||||
roomName = summary.name,
|
||||
isDm = false,
|
||||
isFavorite = false,
|
||||
markAsUnreadFeatureFlagEnabled = true,
|
||||
hasNewContent = false,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
shownState.eventSink(RoomListEvents.HideContextMenu)
|
||||
|
||||
val hiddenState = awaitItem()
|
||||
|
|
@ -440,6 +471,26 @@ class RoomListPresenterTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `present - when set is favorite event is emitted, then the action is called`() = runTest {
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val room = FakeMatrixRoom()
|
||||
val client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val presenter = createRoomListPresenter(client = client, coroutineScope = scope)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, true))
|
||||
assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true))
|
||||
initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, false))
|
||||
assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false))
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
fun `present - change in migration presenter state modifies isMigrating`() = runTest {
|
||||
val client = FakeMatrixClient(sessionId = A_SESSION_ID)
|
||||
val migrationStore = InMemoryMigrationScreenStore()
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ internal fun createRoomListRoomSummary(
|
|||
numberOfUnreadNotifications: Int = 0,
|
||||
isMarkedUnread: Boolean = false,
|
||||
userDefinedNotificationMode: RoomNotificationMode? = null,
|
||||
isFavorite: Boolean = false,
|
||||
) = RoomListRoomSummary(
|
||||
id = A_ROOM_ID.value,
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -95,4 +96,5 @@ internal fun createRoomListRoomSummary(
|
|||
userDefinedNotificationMode = userDefinedNotificationMode,
|
||||
hasRoomCall = false,
|
||||
isDm = false,
|
||||
isFavorite = isFavorite,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -152,6 +152,8 @@ interface MatrixRoom : Closeable {
|
|||
|
||||
suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result<Unit>
|
||||
|
||||
suspend fun setIsFavorite(isFavorite: Boolean): Result<Unit>
|
||||
|
||||
/**
|
||||
* Mark the room as read by trying to attach an unthreaded read receipt to the latest room event.
|
||||
* @param receiptType The type of receipt to send.
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ data class MatrixRoomInfo(
|
|||
val isPublic: Boolean,
|
||||
val isSpace: Boolean,
|
||||
val isTombstoned: Boolean,
|
||||
val isFavorite: Boolean,
|
||||
val canonicalAlias: String?,
|
||||
val alternativeAliases: ImmutableList<String>,
|
||||
val currentUserMembership: CurrentUserMembership,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ data class RoomSummaryDetails(
|
|||
val userDefinedNotificationMode: RoomNotificationMode?,
|
||||
val hasRoomCall: Boolean,
|
||||
val isDm: Boolean,
|
||||
val isFavorite: Boolean,
|
||||
) {
|
||||
val lastMessageTimestamp = lastMessage?.originServerTs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class MatrixRoomInfoMapper(
|
|||
isPublic = it.isPublic,
|
||||
isSpace = it.isSpace,
|
||||
isTombstoned = it.isTombstoned,
|
||||
isFavorite = it.isFavourite,
|
||||
canonicalAlias = it.canonicalAlias,
|
||||
alternativeAliases = it.alternativeAliases.toImmutableList(),
|
||||
currentUserMembership = it.membership.map(),
|
||||
|
|
|
|||
|
|
@ -442,6 +442,12 @@ class RustMatrixRoom(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun setIsFavorite(isFavorite: Boolean): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.setIsFavourite(isFavorite, null)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun markAsRead(receiptType: ReceiptType): Result<Unit> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.markAsRead(receiptType.toRustReceiptType())
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto
|
|||
userDefinedNotificationMode = roomInfo.userDefinedNotificationMode?.let(RoomNotificationSettingsMapper::mapMode),
|
||||
hasRoomCall = roomInfo.hasRoomCall,
|
||||
isDm = roomInfo.isDirect && roomInfo.activeMembersCount.toLong() == 2L,
|
||||
isFavorite = roomInfo.isFavourite,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ class FakeMatrixRoom(
|
|||
private var getWidgetDriverResult: Result<MatrixWidgetDriver> = Result.success(FakeWidgetDriver())
|
||||
private var canUserTriggerRoomNotificationResult: Result<Boolean> = Result.success(true)
|
||||
private var canUserJoinCallResult: Result<Boolean> = Result.success(true)
|
||||
private var setIsFavoriteResult = Result.success(Unit)
|
||||
var sendMessageMentions = emptyList<Mention>()
|
||||
val editMessageCalls = mutableListOf<Pair<String, String?>>()
|
||||
private val _typingRecord = mutableListOf<Boolean>()
|
||||
|
|
@ -378,6 +379,14 @@ class FakeMatrixRoom(
|
|||
return reportContentResult
|
||||
}
|
||||
|
||||
val setIsFavoriteCalls = mutableListOf<Boolean>()
|
||||
|
||||
override suspend fun setIsFavorite(isFavorite: Boolean): Result<Unit> {
|
||||
return setIsFavoriteResult.also {
|
||||
setIsFavoriteCalls.add(isFavorite)
|
||||
}
|
||||
}
|
||||
|
||||
val markAsReadCalls = mutableListOf<ReceiptType>()
|
||||
|
||||
override suspend fun markAsRead(receiptType: ReceiptType): Result<Unit> {
|
||||
|
|
@ -590,6 +599,10 @@ class FakeMatrixRoom(
|
|||
getWidgetDriverResult = result
|
||||
}
|
||||
|
||||
fun givenSetIsFavoriteResult(result: Result<Unit>) {
|
||||
setIsFavoriteResult = result
|
||||
}
|
||||
|
||||
fun givenRoomInfo(roomInfo: MatrixRoomInfo) {
|
||||
_roomInfoFlow.tryEmit(roomInfo)
|
||||
}
|
||||
|
|
@ -633,6 +646,7 @@ fun aRoomInfo(
|
|||
isPublic: Boolean = true,
|
||||
isSpace: Boolean = false,
|
||||
isTombstoned: Boolean = false,
|
||||
isFavorite: Boolean = false,
|
||||
canonicalAlias: String? = null,
|
||||
alternativeAliases: List<String> = emptyList(),
|
||||
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
|
||||
|
|
@ -655,6 +669,7 @@ fun aRoomInfo(
|
|||
isPublic = isPublic,
|
||||
isSpace = isSpace,
|
||||
isTombstoned = isTombstoned,
|
||||
isFavorite = isFavorite,
|
||||
canonicalAlias = canonicalAlias,
|
||||
alternativeAliases = alternativeAliases.toImmutableList(),
|
||||
currentUserMembership = currentUserMembership,
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ fun aRoomSummaryDetails(
|
|||
canonicalAlias: String? = null,
|
||||
hasRoomCall: Boolean = false,
|
||||
isDm: Boolean = false,
|
||||
isFavorite: Boolean = false,
|
||||
) = RoomSummaryDetails(
|
||||
roomId = roomId,
|
||||
name = name,
|
||||
|
|
@ -83,6 +84,7 @@ fun aRoomSummaryDetails(
|
|||
canonicalAlias = canonicalAlias,
|
||||
hasRoomCall = hasRoomCall,
|
||||
isDm = isDm,
|
||||
isFavorite = isFavorite,
|
||||
)
|
||||
|
||||
fun aRoomMessage(
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ fun aRoomSummaryDetails(
|
|||
numUnreadMessages: Int = 0,
|
||||
numUnreadNotifications: Int = 0,
|
||||
isMarkedUnread: Boolean = false,
|
||||
isFavorite: Boolean = false,
|
||||
) = RoomSummaryDetails(
|
||||
roomId = roomId,
|
||||
name = name,
|
||||
|
|
@ -132,4 +133,5 @@ fun aRoomSummaryDetails(
|
|||
numUnreadMessages = numUnreadMessages,
|
||||
numUnreadNotifications = numUnreadNotifications,
|
||||
isMarkedUnread = isMarkedUnread,
|
||||
isFavorite = isFavorite,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f2dd92e3cf76367608276fd10030d161ff370294b474c3e08fbf6463508fb1b0
|
||||
size 42064
|
||||
oid sha256:10c67ba7b16d81e030cb44719cc68a0a3fd57acba75e488a37c3bdf4f739cf95
|
||||
size 44537
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bb09fb2b24e1597e07898562b29cee3e62ad63040ab77895c234a13f4836ab08
|
||||
size 30442
|
||||
oid sha256:a17d073bf7c3e993c81e51e234c4f885d2264f6de8bf54a93306f14ee5a02a66
|
||||
size 33354
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a8e482480e6103d81fa6e08857c3308bde87255ec2bed55ea616eca1cf3941cc
|
||||
size 32698
|
||||
oid sha256:ea52227dcdf0806e292971e1b7332db28964eadd242882a80b6486d691e9c2c9
|
||||
size 35219
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:63e7bf82b003a869d6ccfbc5057ddfe0f447bd7eb31801df828c410744d15223
|
||||
size 32008
|
||||
oid sha256:0163f939dfc3103eff7ded67b2fbd31dd050f6a4150245c9f0fbf3c7d13daf3e
|
||||
size 34806
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:00ca19bd4146e4b7937627a9aadd900cbf928e809996d7e1c950c0ed03aa888f
|
||||
size 39635
|
||||
oid sha256:ab7c7c6ae6951bf173f24fddb70e99feacd92286b088a6921900c6b7e881000a
|
||||
size 42122
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6ea77bd41442b6686fcbe53a4ea1bb29a6040a78ceb8f6e5453fddc0b4ae6b35
|
||||
size 39892
|
||||
oid sha256:4063c0c9f763304aea54a73aaf95efc636d029cab45369b24a3bfc0b3bf3731d
|
||||
size 42774
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6ea77bd41442b6686fcbe53a4ea1bb29a6040a78ceb8f6e5453fddc0b4ae6b35
|
||||
size 39892
|
||||
oid sha256:4063c0c9f763304aea54a73aaf95efc636d029cab45369b24a3bfc0b3bf3731d
|
||||
size 42774
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:beeb97ce692ae6592dd61769cfdf87f3fc3518548b02443e9ef6134e3dec01bb
|
||||
size 43479
|
||||
oid sha256:dabfea9a1b7604977444e8fedc977a61ab5576c6115aa97bffd77ad1d5c65038
|
||||
size 46198
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7a5ef5d83bc9dd2e3bef092d6620baaf0f0f94657f41961bb1cf2b547ae10415
|
||||
size 41931
|
||||
oid sha256:d1dfdb2a8cca036992e06a4d2a626c32d40c899e85bd5d5cf3aaba4272d339d8
|
||||
size 44244
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fd7974c00bce66a8a1715e956c64aa578db303ff287aefb9dc5bb7906e4e27d5
|
||||
size 44406
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1a6e83b3b6880ee6eec8731e42e744c0fe6f806cbad6c1925cb297e19c1b811b
|
||||
size 43222
|
||||
oid sha256:258605b56cbfb5ade4ce390b7e38a37b0602612d45a751b0032e4ba757d8d76f
|
||||
size 45766
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e3d669d780e1b7a3c741d5f6c8f20e8ffe80b283291f133672221cddcca192dc
|
||||
size 31530
|
||||
oid sha256:c984e6333a0db11f1e52f246f019b927d97910dd35f5b79aae6d7c4f3094f913
|
||||
size 34667
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ee48e602a928b83bb547efe6352c419d1bf71ea8490226884f715f3266d493e5
|
||||
size 34080
|
||||
oid sha256:613b0bd1053c46b45fe6b58c754b03c69bab0a6cc2d90cd99278b4786f1253b0
|
||||
size 36681
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:22e8f719df1249e31f26022815930577f9baf135454ff260664fa65045c1b690
|
||||
size 32687
|
||||
oid sha256:97416e171d87d7290924270c175b44a10c2b25fd3541156bf9e5258975f0c3a4
|
||||
size 35627
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ee5dde97c04f0ccdc21349158150e11ad1a1b91c97bfd3aaefacc4e0e1a17a8a
|
||||
size 40828
|
||||
oid sha256:28a271552ea0e61954646c47bd8e87048c92cace7ca4176901c7b4d000bbc53f
|
||||
size 43375
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1bfd34cc11af2c77b25e19d4493db998d8cad0efed379dce3a9537b2c49fda28
|
||||
size 41039
|
||||
oid sha256:7a4ddad1a6f2afad344e8e88fd297c91a5064206b2dbcb1130dd15b5fe22fbc9
|
||||
size 44086
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1bfd34cc11af2c77b25e19d4493db998d8cad0efed379dce3a9537b2c49fda28
|
||||
size 41039
|
||||
oid sha256:7a4ddad1a6f2afad344e8e88fd297c91a5064206b2dbcb1130dd15b5fe22fbc9
|
||||
size 44086
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:567e1b09ae9383c8af71c7d66d2877091c0ce3d98eab1d92c7bc9d2f896875bc
|
||||
size 44629
|
||||
oid sha256:36e579b569043783e8890a6cf5ffa1ae511c524c20f923765825ab9a9ccf9bb4
|
||||
size 47592
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bb8863bcb78be69e715ec9bbf3ef7ee51fd142b484a52a22b381373708d5049e
|
||||
size 43101
|
||||
oid sha256:2b27ecb48f63242353d750c5fb2eb5cf2fcb3c11edd046cc9c03db57f3d26122
|
||||
size 45472
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6fb2205cfbb68ae2e808fe62765311b10921a8bd579e23803484a492de36d028
|
||||
size 45645
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d636ef74890668b0d2cc49af71b1e3734237fa1f9944afb89d8152cb9a839cd4
|
||||
size 17391
|
||||
oid sha256:404fc390042c6d7870e2fbef176eb497ac3fb474c53483c085cd04038d0192ef
|
||||
size 21918
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3c883b6e8c7ae3570f2d7370a929cc9afd2a1983bc6064f50090ad8e8c9295d4
|
||||
size 16289
|
||||
oid sha256:4791d9b6fa50a29c10b618d27786033b69983229a9ce2e10b83e35cbe4a6365a
|
||||
size 20671
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b6b6fe672b31db43d1aeed7d0835d638b729c09ac01b03fcdfd814b2e1d861d5
|
||||
size 15553
|
||||
oid sha256:d3b0fe4d5ca9f4b735ed43ea167c238d2086a2aa6f8760fe74841bc6f0a1be6c
|
||||
size 20289
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:178e6f2f9aa771831eb3f672cf212d5b3a37532c02dcc5126446965b49be7bdd
|
||||
size 14558
|
||||
oid sha256:1d80d0580828e0f657ff2f36915c2d0a7a9d56612007da87c3b9676f882131bc
|
||||
size 19023
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2523a28a889fd1739fe1e0c94edce5d86a5ebcbe6c3973a3c49fbb8fb8a19f79
|
||||
size 55617
|
||||
oid sha256:5f8802121f779f48dd556a9a3a08e6ac1a63e86d5695f6f8c133fbb346d76ef6
|
||||
size 89779
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:13dadbd502163a9bfe81cb1e67f9aa0933c4b7f4fd3c8f731e930a28709485d2
|
||||
size 51948
|
||||
oid sha256:2523a28a889fd1739fe1e0c94edce5d86a5ebcbe6c3973a3c49fbb8fb8a19f79
|
||||
size 55617
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:64c4eb481f40871925405ae317cb80927caf31cb552a8aa9549bfb5658ca91e4
|
||||
size 137589
|
||||
oid sha256:13dadbd502163a9bfe81cb1e67f9aa0933c4b7f4fd3c8f731e930a28709485d2
|
||||
size 51948
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:64c4eb481f40871925405ae317cb80927caf31cb552a8aa9549bfb5658ca91e4
|
||||
size 137589
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5f8802121f779f48dd556a9a3a08e6ac1a63e86d5695f6f8c133fbb346d76ef6
|
||||
size 89779
|
||||
oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0
|
||||
size 4462
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2b25e023153dd094299a0c794d5e488bdc6fc0c478abbfbce7337e52b233715d
|
||||
size 57463
|
||||
oid sha256:7cdd93c157565f7f8d3fa23a43c30e02531dab40578a315c5d0d10a99e74f259
|
||||
size 91394
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0a00391ef16a762a14dd7e348ce97b500ca1b42a32ee3ff926a10865f46cd06c
|
||||
size 53590
|
||||
oid sha256:2b25e023153dd094299a0c794d5e488bdc6fc0c478abbfbce7337e52b233715d
|
||||
size 57463
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a7bf47b0a25c455b108d7b3585a42849f03c6652af73a175fe2147cb1ad62a66
|
||||
size 161125
|
||||
oid sha256:0a00391ef16a762a14dd7e348ce97b500ca1b42a32ee3ff926a10865f46cd06c
|
||||
size 53590
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a7bf47b0a25c455b108d7b3585a42849f03c6652af73a175fe2147cb1ad62a66
|
||||
size 161125
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7cdd93c157565f7f8d3fa23a43c30e02531dab40578a315c5d0d10a99e74f259
|
||||
size 91394
|
||||
oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0
|
||||
size 4462
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue