From b15597509d174f717ce6822d05442934f49b2a48 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 31 Jan 2024 21:24:37 +0100 Subject: [PATCH 01/13] favorite : branch RoomNotableTags methods --- .../roomdetails/impl/RoomDetailsEvent.kt | 1 + .../roomdetails/impl/RoomDetailsPresenter.kt | 15 +++++ .../roomdetails/impl/RoomDetailsState.kt | 1 + .../impl/RoomDetailsStateProvider.kt | 2 + .../roomdetails/impl/RoomDetailsView.kt | 24 +++++++ .../roomlist/impl/RoomListContextMenu.kt | 53 ++++++++++++--- .../features/roomlist/impl/RoomListEvents.kt | 1 + .../roomlist/impl/RoomListPresenter.kt | 64 ++++++++++++++++--- .../features/roomlist/impl/RoomListState.kt | 2 + .../roomlist/impl/RoomListStateProvider.kt | 2 + .../libraries/architecture/coroutine/Job.kt | 34 ++++++++++ .../libraries/matrix/api/room/MatrixRoom.kt | 5 ++ .../matrix/api/room/tags/RoomNotableTags.kt | 21 ++++++ .../matrix/impl/room/RustMatrixRoom.kt | 24 +++++++ .../impl/room/tags/RoomNotableTagsMapper.kt | 28 ++++++++ .../roomlist/RoomSummaryDetailsFactory.kt | 1 + .../item/event/EventTimelineItemMapper.kt | 2 +- .../matrix/test/room/FakeMatrixRoom.kt | 24 +++++++ 18 files changed, 285 insertions(+), 19 deletions(-) create mode 100644 libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coroutine/Job.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tags/RoomNotableTags.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tags/RoomNotableTagsMapper.kt diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt index fdad01d83c..db3d3dfe2d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt @@ -20,4 +20,5 @@ sealed interface RoomDetailsEvent { data object LeaveRoom : RoomDetailsEvent data object MuteNotification : RoomDetailsEvent data object UnmuteNotification : RoomDetailsEvent + data class SetIsFavorite(val isFavorite: Boolean) : RoomDetailsEvent } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index a232d4e87e..4b6e33b288 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -44,9 +44,11 @@ import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.powerlevels.canInvite import io.element.android.libraries.matrix.api.room.powerlevels.canSendState import io.element.android.libraries.matrix.api.room.roomNotificationSettings +import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags import io.element.android.libraries.matrix.ui.room.getDirectRoomMember import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject @@ -95,6 +97,7 @@ class RoomDetailsPresenter @Inject constructor( val dmMember by room.getDirectRoomMember(membersState) val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember) val roomType by getRoomType(dmMember) + val isFavorite by isFavorite() val topicState = remember(canEditTopic, roomTopic, roomType) { val topic = roomTopic @@ -122,6 +125,12 @@ class RoomDetailsPresenter @Inject constructor( client.notificationSettingsService().unmuteRoom(room.roomId, room.isEncrypted, room.isOneToOne) } } + is RoomDetailsEvent.SetIsFavorite -> { + scope.launch(dispatchers.io) { + val tags = RoomNotableTags(isFavorite = event.isFavorite) + room.updateNotableTags(tags) + } + } } } @@ -142,6 +151,7 @@ class RoomDetailsPresenter @Inject constructor( roomMemberDetailsState = roomMemberDetailsState, leaveRoomState = leaveRoomState, roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(), + isFavorite = isFavorite, eventSink = ::handleEvents, ) } @@ -164,6 +174,11 @@ class RoomDetailsPresenter @Inject constructor( } } + @Composable + private fun isFavorite() = remember { + room.notableTagsFlow.map { it.isFavorite } + }.collectAsState(initial = false) + @Composable private fun getCanInvite(membersState: MatrixRoomMembersState) = produceState(false, membersState) { value = room.canInvite().getOrElse { false } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt index 8dc6f81bf1..5ba39af5bf 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt @@ -36,6 +36,7 @@ data class RoomDetailsState( val canShowNotificationSettings: Boolean, val leaveRoomState: LeaveRoomState, val roomNotificationSettings: RoomNotificationSettings?, + val isFavorite: Boolean, val eventSink: (RoomDetailsEvent) -> Unit ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt index b84302463f..2493efc855 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -36,6 +36,7 @@ open class RoomDetailsStateProvider : PreviewParameterProvider 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 = {} ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index bbdccbc302..401f6d024a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -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.SetIsFavorite(it)) + } + ) + if (state.roomType is RoomDetailsType.Room) { MembersSection( memberCount = state.memberCount, @@ -355,6 +363,22 @@ private fun NotificationSection( } } +@Composable +private fun FavoriteSection( + isFavorite: Boolean, + onFavoriteChanges: (Boolean) -> Unit, + modifier: Modifier = Modifier +) { + PreferenceCategory(modifier = modifier) { + PreferenceSwitch( + icon = CompoundIcons.FavouriteOff, + title = "Favorite", + isChecked = isFavorite, + onCheckedChange = onFavoriteChanges + ) + } +} + @Composable private fun MembersSection( memberCount: Long, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt index 1d2cf2c910..f5ce9897b4 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt @@ -26,6 +26,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -56,7 +58,10 @@ fun RoomListContextMenu( onLeaveRoomClicked = { eventSink(RoomListEvents.HideContextMenu) eventSink(RoomListEvents.LeaveRoom(contextMenu.roomId)) - } + }, + onFavoriteChanged = { isFavorite -> + eventSink(RoomListEvents.MarkRoomAsFavorite(contextMenu.roomId, isFavorite)) + }, ) } } @@ -66,6 +71,7 @@ private fun RoomListModalBottomSheetContent( contextMenu: RoomListState.ContextMenu.Shown, onRoomSettingsClicked: (roomId: RoomId) -> Unit, onLeaveRoomClicked: (roomId: RoomId) -> Unit, + onFavoriteChanged: (isFavorite: Boolean) -> Unit, ) { Column( modifier = Modifier.fillMaxWidth() @@ -78,6 +84,31 @@ private fun RoomListModalBottomSheetContent( ) } ) + ListItem( + headlineContent = { + Text( + text = "Favourite", + style = MaterialTheme.typography.bodyLarge, + ) + }, + leadingContent = ListItemContent.Icon( + iconSource = IconSource.Vector( + CompoundIcons.FavouriteOff, + contentDescription = "Favourite" + ) + ), + trailingContent = ListItemContent.Switch( + checked = contextMenu.isFavorite.dataOrNull().orFalse(), + enabled = contextMenu.isFavorite is AsyncData.Success, + onChange = { isFavorite -> + onFavoriteChanged(isFavorite) + }, + ), + onClick = { + onFavoriteChanged(!contextMenu.isFavorite.dataOrNull().orFalse()) + }, + style = ListItemStyle.Primary, + ) ListItem( headlineContent = { Text( @@ -96,11 +127,13 @@ private fun RoomListModalBottomSheetContent( ) ListItem( headlineContent = { - val leaveText = stringResource(id = if (contextMenu.isDm) { - CommonStrings.action_leave_conversation - } else { - CommonStrings.action_leave_room - }) + val leaveText = stringResource( + id = if (contextMenu.isDm) { + CommonStrings.action_leave_conversation + } else { + CommonStrings.action_leave_room + } + ) Text(text = leaveText) }, modifier = Modifier.clickable { onLeaveRoomClicked(contextMenu.roomId) }, @@ -126,9 +159,11 @@ internal fun RoomListModalBottomSheetContentPreview() = ElementPreview { roomId = RoomId(value = "!aRoom:aDomain"), roomName = "aRoom", isDm = false, + isFavorite = AsyncData.Success(false), ), onRoomSettingsClicked = {}, - onLeaveRoomClicked = {} + onLeaveRoomClicked = {}, + onFavoriteChanged = {}, ) } @@ -140,8 +175,10 @@ internal fun RoomListModalBottomSheetContentForDmPreview() = ElementPreview { roomId = RoomId(value = "!aRoom:aDomain"), roomName = "aRoom", isDm = true, + isFavorite = AsyncData.Success(false), ), onRoomSettingsClicked = {}, - onLeaveRoomClicked = {} + onLeaveRoomClicked = {}, + onFavoriteChanged = {}, ) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt index d54bc604cf..b804db5315 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt @@ -28,4 +28,5 @@ sealed interface RoomListEvents { data class ShowContextMenu(val roomListRoomSummary: RoomListRoomSummary) : RoomListEvents data object HideContextMenu : RoomListEvents data class LeaveRoom(val roomId: RoomId) : RoomListEvents + data class MarkRoomAsFavorite(val roomId: RoomId, val isFavorite: Boolean) : RoomListEvents } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 8ab1bde3f4..595f75c2fa 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import io.element.android.features.leaveroom.api.LeaveRoomEvent @@ -32,7 +33,10 @@ import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource import io.element.android.features.roomlist.impl.datasource.RoomListDataSource +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.coroutine.cancel +import io.element.android.libraries.architecture.coroutine.rememberJob import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState import io.element.android.libraries.featureflag.api.FeatureFlagService @@ -41,11 +45,17 @@ import io.element.android.libraries.indicator.api.IndicatorService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState +import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags 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.Job +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject private const val EXTENDED_RANGE_SIZE = 40 @@ -72,12 +82,12 @@ class RoomListPresenter @Inject constructor( val filteredRoomList by roomListDataSource.filteredRooms.collectAsState() val filter by roomListDataSource.filter.collectAsState() val networkConnectionStatus by networkMonitor.connectivity.collectAsState() + val coroutineScope = rememberCoroutineScope() LaunchedEffect(Unit) { roomListDataSource.launchIn(this) initialLoad(matrixUser) } - // Session verification status (unknown, not verified, verified) val canVerifySession by sessionVerificationService.canVerifySessionFlow.collectAsState(initial = false) var verificationPromptDismissed by rememberSaveable { mutableStateOf(false) } @@ -101,8 +111,8 @@ class RoomListPresenter @Inject constructor( val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator() var displaySearchResults by rememberSaveable { mutableStateOf(false) } - - var contextMenu by remember { mutableStateOf(RoomListState.ContextMenu.Hidden) } + val contextMenu = remember { mutableStateOf(RoomListState.ContextMenu.Hidden) } + val showContextMenuJob = rememberJob() fun handleEvents(event: RoomListEvents) { when (event) { @@ -117,14 +127,14 @@ 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, - ) + showContextMenuJob.value = coroutineScope.showContextMenu(event, contextMenu) + } + is RoomListEvents.HideContextMenu -> { + showContextMenuJob.cancel() + contextMenu.value = RoomListState.ContextMenu.Hidden } - is RoomListEvents.HideContextMenu -> contextMenu = RoomListState.ContextMenu.Hidden is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(LeaveRoomEvent.ShowConfirmation(event.roomId)) + is RoomListEvents.MarkRoomAsFavorite -> coroutineScope.markRoomAsFavorite(event) } } @@ -142,7 +152,7 @@ class RoomListPresenter @Inject constructor( hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online, invitesState = inviteStateDataSource.inviteState(), displaySearchResults = displaySearchResults, - contextMenu = contextMenu, + contextMenu = contextMenu.value, leaveRoomState = leaveRoomState, eventSink = ::handleEvents ) @@ -152,6 +162,38 @@ class RoomListPresenter @Inject constructor( matrixUser.value = client.getCurrentUser() } + private fun CoroutineScope.showContextMenu(event: RoomListEvents.ShowContextMenu, contextMenuState: MutableState) = launch { + val initialState = RoomListState.ContextMenu.Shown( + roomId = event.roomListRoomSummary.roomId, + roomName = event.roomListRoomSummary.name, + isDm = event.roomListRoomSummary.isDm, + isFavorite = AsyncData.Loading(), + ) + contextMenuState.value = initialState + val room = client.getRoom(event.roomListRoomSummary.roomId) + if (room != null) { + room.notableTagsFlow + .distinctUntilChanged() + .onEach { tags -> + val newState = initialState.copy(isFavorite = AsyncData.Success(tags.isFavorite)) + contextMenuState.value = newState + } + .launchIn(this) + } else { + contextMenuState.value = initialState.copy(isFavorite = AsyncData.Failure(IllegalStateException("Room not found"))) + } + } + + private fun CoroutineScope.markRoomAsFavorite(event: RoomListEvents.MarkRoomAsFavorite) = launch { + val room = client.getRoom(event.roomId) + if (room != null) { + val notableTags = RoomNotableTags(isFavorite = event.isFavorite); + room.updateNotableTags(notableTags) + }else { + Timber.w("Room ${event.roomId} not found, can't mark as favorite"); + } + } + private fun updateVisibleRange(range: IntRange) { if (range.isEmpty()) return val midExtendedRangeSize = EXTENDED_RANGE_SIZE / 2 @@ -162,3 +204,5 @@ class RoomListPresenter @Inject constructor( client.roomListService.updateAllRoomsVisibleRange(extendedRange) } } + + diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt index 7003da36e5..5f5a5fda4c 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt @@ -19,6 +19,7 @@ package io.element.android.features.roomlist.impl import androidx.compose.runtime.Immutable import io.element.android.features.leaveroom.api.LeaveRoomState import io.element.android.features.roomlist.impl.model.RoomListRoomSummary +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.user.MatrixUser @@ -47,6 +48,7 @@ data class RoomListState( val roomId: RoomId, val roomName: String, val isDm: Boolean, + val isFavorite: AsyncData, ) : ContextMenu } } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt index b1e43f147e..7ac34668c7 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.leaveroom.api.aLeaveRoomState import io.element.android.features.roomlist.impl.model.RoomListRoomSummary import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage @@ -46,6 +47,7 @@ open class RoomListStateProvider : PreviewParameterProvider { roomId = RoomId("!aRoom:aDomain"), roomName = "A nice room name", isDm = false, + isFavorite = AsyncData.Success(true), ) ), aRoomListState().copy(displayRecoveryKeyPrompt = true), diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coroutine/Job.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coroutine/Job.kt new file mode 100644 index 0000000000..bf3d4f783e --- /dev/null +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coroutine/Job.kt @@ -0,0 +1,34 @@ +/* + * 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 + * + * http://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.architecture.coroutine + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import kotlinx.coroutines.Job +import kotlin.coroutines.cancellation.CancellationException + +@Composable +fun rememberJob(): MutableState = remember { + mutableStateOf(null) +} + +fun MutableState.cancel(cause: CancellationException? = null) { + value?.cancel(cause) + value = null +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 550bb7012a..630f87a0fb 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings @@ -57,6 +58,8 @@ interface MatrixRoom : Closeable { val roomInfoFlow: Flow + val notableTagsFlow: Flow + /** * A one-to-one is a room with exactly 2 members. * See [the Matrix spec](https://spec.matrix.org/latest/client-server-api/#default-underride-rules). @@ -150,6 +153,8 @@ interface MatrixRoom : Closeable { suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result + suspend fun updateNotableTags(notableTags: RoomNotableTags): Result + /** * Share a location message in the room. * diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tags/RoomNotableTags.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tags/RoomNotableTags.kt new file mode 100644 index 0000000000..25a19b5697 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tags/RoomNotableTags.kt @@ -0,0 +1,21 @@ +/* + * 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 + * + * http://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.matrix.api.room.tags + +data class RoomNotableTags( + val isFavorite: Boolean, +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 3fc7d65468..9c0cd11e3f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -39,6 +39,7 @@ import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.room.roomNotificationSettings +import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings @@ -50,6 +51,7 @@ import io.element.android.libraries.matrix.impl.notificationsettings.RustNotific import io.element.android.libraries.matrix.impl.poll.toInner import io.element.android.libraries.matrix.impl.room.location.toInner import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher +import io.element.android.libraries.matrix.impl.room.tags.map import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline import io.element.android.libraries.matrix.impl.util.mxCallbackFlow import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver @@ -70,6 +72,7 @@ import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.RoomInfoListener import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation +import org.matrix.rustcomponents.sdk.RoomNotableTagsListener import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle import org.matrix.rustcomponents.sdk.WidgetCapabilities import org.matrix.rustcomponents.sdk.WidgetCapabilitiesProvider @@ -79,6 +82,7 @@ import org.matrix.rustcomponents.sdk.use import timber.log.Timber import java.io.File import org.matrix.rustcomponents.sdk.Room as InnerRoom +import uniffi.matrix_sdk_base.RoomNotableTags as RustRoomNotableTags import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline @OptIn(ExperimentalCoroutinesApi::class) @@ -111,6 +115,15 @@ class RustMatrixRoom( }) } + override val notableTagsFlow: Flow = mxCallbackFlow { + innerRoom.subscribeToNotableTags(object : RoomNotableTagsListener { + override fun call(notableTags: RustRoomNotableTags) { + Timber.d("On notable tags update: $notableTags") + channel.trySend(notableTags.map()) + } + }) + } + // Create a dispatcher for all room methods... private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32) @@ -423,6 +436,17 @@ class RustMatrixRoom( } } + override suspend fun updateNotableTags(notableTags: RoomNotableTags): Result = withContext(roomDispatcher) { + runCatching { + Timber.i("Update notable tags with : $notableTags") + innerRoom.updateNotableTags(notableTags.map()) + }.onFailure { + Timber.w("Failed to update notable tags: $it") + }.onSuccess { + Timber.i("Successfully updated notable tags") + } + } + override suspend fun sendLocation( body: String, geoUri: String, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tags/RoomNotableTagsMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tags/RoomNotableTagsMapper.kt new file mode 100644 index 0000000000..36fe0eb98e --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tags/RoomNotableTagsMapper.kt @@ -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 + * + * http://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.matrix.impl.room.tags + +import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags +import uniffi.matrix_sdk_base.RoomNotableTags as RustRoomNotableTags + +fun RustRoomNotableTags.map() = RoomNotableTags( + isFavorite = isFavorite +) + +fun RoomNotableTags.map() = RustRoomNotableTags( + isFavorite = isFavorite +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt index 1b3e900f17..4934b0058b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt @@ -25,6 +25,7 @@ import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.use class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) { + fun create(roomInfo: RoomInfo): RoomSummaryDetails { val latestRoomMessage = roomInfo.latestEvent?.use { roomMessageFactory.create(it) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index ed56d4a8d0..20cd781dcb 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSende import io.element.android.libraries.matrix.api.timeline.item.event.Receipt import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import kotlinx.collections.immutable.ImmutableList +import uniffi.matrix_sdk_ui.EventItemOrigin as RustEventItemOrigin import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import org.matrix.rustcomponents.sdk.Reaction @@ -36,7 +37,6 @@ import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo as RustEventTimelineItemDebugInfo import org.matrix.rustcomponents.sdk.ProfileDetails as RustProfileDetails import org.matrix.rustcomponents.sdk.Receipt as RustReceipt -import uniffi.matrix_sdk_ui.EventItemOrigin as RustEventItemOrigin class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper()) { fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.use { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index c33a6d43b2..72508440ea 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -40,6 +40,7 @@ import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver @@ -113,6 +114,7 @@ class FakeMatrixRoom( private var getWidgetDriverResult: Result = Result.success(FakeWidgetDriver()) private var canUserTriggerRoomNotificationResult: Result = Result.success(true) private var canUserJoinCallResult: Result = Result.success(true) + private var updateNotableTagsResult = Result.success(Unit) var sendMessageMentions = emptyList() val editMessageCalls = mutableListOf>() private val _typingRecord = mutableListOf() @@ -169,6 +171,9 @@ class FakeMatrixRoom( private val _roomInfoFlow: MutableSharedFlow = MutableSharedFlow(replay = 1) override val roomInfoFlow: Flow = _roomInfoFlow + private val _notableTagsFlow: MutableSharedFlow = MutableStateFlow(aRoomNotableTags()) + override val notableTagsFlow: Flow = _notableTagsFlow + override val membersStateFlow: MutableStateFlow = MutableStateFlow(MatrixRoomMembersState.Unknown) override val roomNotificationSettingsStateFlow: MutableStateFlow = @@ -374,6 +379,14 @@ class FakeMatrixRoom( return reportContentResult } + override suspend fun updateNotableTags(notableTags: RoomNotableTags): Result { + return updateNotableTagsResult.also { result -> + if (result.isSuccess) { + _notableTagsFlow.emit(notableTags) + } + } + } + override suspend fun sendLocation( body: String, geoUri: String, @@ -571,6 +584,10 @@ class FakeMatrixRoom( getWidgetDriverResult = result } + fun givenUpdateNotableTagsResult(result: Result) { + updateNotableTagsResult = result + } + fun givenRoomInfo(roomInfo: MatrixRoomInfo) { _roomInfoFlow.tryEmit(roomInfo) } @@ -610,6 +627,7 @@ fun aRoomInfo( isPublic: Boolean = true, isSpace: Boolean = false, isTombstoned: Boolean = false, + isFavorite: Boolean = false, canonicalAlias: String? = null, alternativeAliases: List = emptyList(), currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED, @@ -646,3 +664,9 @@ fun aRoomInfo( hasRoomCall = hasRoomCall, activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(), ) + +fun aRoomNotableTags( + isFavorite: Boolean = false, +) = RoomNotableTags( + isFavorite = isFavorite, +) From d9017a098cfbc9a3fd848bb5b0c1a4f91f83e3b3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 2 Feb 2024 14:54:28 +0100 Subject: [PATCH 02/13] Favorite : rework and add tests --- features/roomactions/api/build.gradle.kts | 27 +++ .../api/SetRoomIsFavoriteAction.kt | 42 +++++ features/roomactions/impl/build.gradle.kts | 50 ++++++ .../impl/DefaultSetRoomIsFavoriteAction.kt | 56 ++++++ .../DefaultSetRoomIsFavoriteActionTests.kt | 59 ++++++ features/roomactions/test/build.gradle.kts | 28 +++ .../test/FakeSetRoomIsFavoriteAction.kt | 39 ++++ features/roomdetails/impl/build.gradle.kts | 2 + .../roomdetails/impl/RoomDetailsPresenter.kt | 8 +- .../roomdetails/RoomDetailsPresenterTests.kt | 170 ++++++++---------- features/roomlist/impl/build.gradle.kts | 2 + .../roomlist/impl/RoomListContextMenu.kt | 2 +- .../features/roomlist/impl/RoomListEvents.kt | 2 +- .../roomlist/impl/RoomListPresenter.kt | 44 ++--- .../roomlist/impl/RoomListPresenterTests.kt | 51 +++++- .../libraries/matrix/api/room/MatrixRoom.kt | 3 + .../matrix/api/room/tags/RoomNotableTags.kt | 6 +- .../matrix/impl/room/RustMatrixRoom.kt | 3 +- .../roomlist/RoomSummaryDetailsFactory.kt | 1 - .../item/event/EventTimelineItemMapper.kt | 2 +- .../matrix/test/room/FakeMatrixRoom.kt | 1 - 21 files changed, 454 insertions(+), 144 deletions(-) create mode 100644 features/roomactions/api/build.gradle.kts create mode 100644 features/roomactions/api/src/main/kotlin/io/element/android/features/roomactions/api/SetRoomIsFavoriteAction.kt create mode 100644 features/roomactions/impl/build.gradle.kts create mode 100644 features/roomactions/impl/src/main/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteAction.kt create mode 100644 features/roomactions/impl/src/test/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteActionTests.kt create mode 100644 features/roomactions/test/build.gradle.kts create mode 100644 features/roomactions/test/src/main/kotlin/io/element/android/features/roomactions/test/FakeSetRoomIsFavoriteAction.kt diff --git a/features/roomactions/api/build.gradle.kts b/features/roomactions/api/build.gradle.kts new file mode 100644 index 0000000000..6bfccef3e5 --- /dev/null +++ b/features/roomactions/api/build.gradle.kts @@ -0,0 +1,27 @@ +/* + * 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 + * + * http://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. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.roomactions.api" +} + +dependencies { + implementation(projects.libraries.matrix.api) +} diff --git a/features/roomactions/api/src/main/kotlin/io/element/android/features/roomactions/api/SetRoomIsFavoriteAction.kt b/features/roomactions/api/src/main/kotlin/io/element/android/features/roomactions/api/SetRoomIsFavoriteAction.kt new file mode 100644 index 0000000000..04f8bc8223 --- /dev/null +++ b/features/roomactions/api/src/main/kotlin/io/element/android/features/roomactions/api/SetRoomIsFavoriteAction.kt @@ -0,0 +1,42 @@ +/* + * 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 + * + * http://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.roomactions.api + +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.MatrixRoom + +/** + * Set the favorite status of a room. + * This will update the notable tags of the room. + */ +interface SetRoomIsFavoriteAction { + sealed interface Result { + data object Success : Result + data object RoomNotFound : Result + data class Exception(val inner: java.lang.Exception) : Result + } + + /** + * Set the favorite status of a room by its id, it'll try to load the room from the session. + */ + suspend operator fun invoke(roomId: RoomId, isFavorite: Boolean): Result + + /** + * Set the favorite status of a room using the provided instance. + */ + suspend operator fun invoke(room: MatrixRoom, isFavorite: Boolean): Result +} diff --git a/features/roomactions/impl/build.gradle.kts b/features/roomactions/impl/build.gradle.kts new file mode 100644 index 0000000000..61f666da08 --- /dev/null +++ b/features/roomactions/impl/build.gradle.kts @@ -0,0 +1,50 @@ +/* + * 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 + * + * http://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. + */ + +// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + id("io.element.android-compose-library") + alias(libs.plugins.anvil) + alias(libs.plugins.ksp) + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.features.roomactions.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(projects.anvilannotations) + anvil(projects.anvilcodegen) + api(projects.features.roomactions.api) + implementation(projects.libraries.core) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + + testImplementation(libs.test.junit) + testImplementation(libs.coroutines.test) + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) + testImplementation(projects.libraries.matrix.test) + + ksp(libs.showkase.processor) +} diff --git a/features/roomactions/impl/src/main/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteAction.kt b/features/roomactions/impl/src/main/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteAction.kt new file mode 100644 index 0000000000..947c56ebcb --- /dev/null +++ b/features/roomactions/impl/src/main/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteAction.kt @@ -0,0 +1,56 @@ +/* + * 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 + * + * http://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.roomactions.impl + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.MatrixRoom +import kotlinx.coroutines.flow.first +import javax.inject.Inject +import kotlin.coroutines.cancellation.CancellationException + +@ContributesBinding(SessionScope::class) +class DefaultSetRoomIsFavoriteAction @Inject constructor(private val client: MatrixClient) : SetRoomIsFavoriteAction { + override suspend operator fun invoke(roomId: RoomId, isFavorite: Boolean): SetRoomIsFavoriteAction.Result { + return client.getRoom(roomId).use { room -> + room?.setIsFavorite(isFavorite) ?: SetRoomIsFavoriteAction.Result.RoomNotFound + } + } + + override suspend fun invoke(room: MatrixRoom, isFavorite: Boolean): SetRoomIsFavoriteAction.Result { + return room.setIsFavorite(isFavorite) + } + + private suspend fun MatrixRoom.setIsFavorite(isFavorite: Boolean): SetRoomIsFavoriteAction.Result { + val notableTags = notableTagsFlow.first().copy(isFavorite = isFavorite) + return updateNotableTags(notableTags).fold( + onSuccess = { + SetRoomIsFavoriteAction.Result.Success + }, + onFailure = { throwable -> + if (throwable is Exception && throwable !is CancellationException) { + SetRoomIsFavoriteAction.Result.Exception(throwable) + } else { + throw throwable + } + } + ) + } +} diff --git a/features/roomactions/impl/src/test/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteActionTests.kt b/features/roomactions/impl/src/test/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteActionTests.kt new file mode 100644 index 0000000000..93c7518cd7 --- /dev/null +++ b/features/roomactions/impl/src/test/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteActionTests.kt @@ -0,0 +1,59 @@ +/* + * 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 + * + * http://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.roomactions.impl + +import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class DefaultSetRoomIsFavoriteActionTests { + private val room = FakeMatrixRoom() + + @Test + fun `given a room id and a client without rooms, when action is invoked, then it returns Result_RoomNotFound`() = runTest { + val action = DefaultSetRoomIsFavoriteAction(FakeMatrixClient()) + val result = action(room.roomId, true) + assert(result is SetRoomIsFavoriteAction.Result.RoomNotFound) + } + + @Test + fun `given a room, when action is invoked, then it returns Result_Success`() = runTest { + val action = DefaultSetRoomIsFavoriteAction(FakeMatrixClient()) + val result = action(room, true) + assert(result is SetRoomIsFavoriteAction.Result.Success) + } + + @Test + fun `given a room id and a client with a room, when action is invoked, then it returns Result_Success`() = runTest { + val client = FakeMatrixClient().apply { + givenGetRoomResult(room.roomId, room) + } + val action = DefaultSetRoomIsFavoriteAction(client) + val result = action(room.roomId, true) + assert(result is SetRoomIsFavoriteAction.Result.Success) + } + + @Test + fun `given a room, when action is invoked and fail, then it returns Result_Exception`() = runTest { + val action = DefaultSetRoomIsFavoriteAction(FakeMatrixClient()) + room.givenUpdateNotableTagsResult(Result.failure(Exception())) + val result = action(room, true) + assert(result is SetRoomIsFavoriteAction.Result.Exception) + } +} diff --git a/features/roomactions/test/build.gradle.kts b/features/roomactions/test/build.gradle.kts new file mode 100644 index 0000000000..d43603e54a --- /dev/null +++ b/features/roomactions/test/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 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 + * + * http://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. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.roomactions.test" +} + +dependencies { + implementation(projects.features.roomactions.api) + implementation(projects.libraries.matrix.api) + implementation(libs.coroutines.core) +} diff --git a/features/roomactions/test/src/main/kotlin/io/element/android/features/roomactions/test/FakeSetRoomIsFavoriteAction.kt b/features/roomactions/test/src/main/kotlin/io/element/android/features/roomactions/test/FakeSetRoomIsFavoriteAction.kt new file mode 100644 index 0000000000..c892079f7b --- /dev/null +++ b/features/roomactions/test/src/main/kotlin/io/element/android/features/roomactions/test/FakeSetRoomIsFavoriteAction.kt @@ -0,0 +1,39 @@ +/* + * 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 + * + * http://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.roomactions.test + +import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.MatrixRoom + +class FakeSetRoomIsFavoriteAction : SetRoomIsFavoriteAction { + private var counter = 0 + + fun assertCalled(number: Int) { + assert(counter == number) { "Expected $number calls, got $counter" } + } + + override suspend fun invoke(roomId: RoomId, isFavorite: Boolean): SetRoomIsFavoriteAction.Result { + counter++ + return SetRoomIsFavoriteAction.Result.Success + } + + override suspend fun invoke(room: MatrixRoom, isFavorite: Boolean): SetRoomIsFavoriteAction.Result { + counter++ + return SetRoomIsFavoriteAction.Result.Success + } +} diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index 0f1a139f40..d76ebbc9d8 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -54,6 +54,7 @@ dependencies { implementation(projects.features.createroom.api) implementation(projects.services.analytics.api) implementation(projects.features.poll.api) + implementation(projects.features.roomactions.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) @@ -70,6 +71,7 @@ dependencies { testImplementation(projects.tests.testutils) testImplementation(projects.features.leaveroom.test) testImplementation(projects.features.createroom.test) + testImplementation(projects.features.roomactions.test) ksp(libs.showkase.processor) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 4b6e33b288..ca17050181 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -29,6 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.lifecycle.Lifecycle import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomPresenter +import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -44,7 +45,6 @@ import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.powerlevels.canInvite import io.element.android.libraries.matrix.api.room.powerlevels.canSendState import io.element.android.libraries.matrix.api.room.roomNotificationSettings -import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags import io.element.android.libraries.matrix.ui.room.getDirectRoomMember import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn @@ -61,6 +61,7 @@ class RoomDetailsPresenter @Inject constructor( private val roomMembersDetailsPresenterFactory: RoomMemberDetailsPresenter.Factory, private val leaveRoomPresenter: LeaveRoomPresenter, private val dispatchers: CoroutineDispatchers, + private val setRoomIsFavorite: SetRoomIsFavoriteAction, ) : Presenter { @Composable override fun present(): RoomDetailsState { @@ -126,9 +127,8 @@ class RoomDetailsPresenter @Inject constructor( } } is RoomDetailsEvent.SetIsFavorite -> { - scope.launch(dispatchers.io) { - val tags = RoomNotableTags(isFavorite = event.isFavorite) - room.updateNotableTags(tags) + scope.launch { + setRoomIsFavorite(room, event.isFavorite) } } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt index 299a8120a5..0e0b392782 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -19,14 +19,18 @@ 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 import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomPresenter import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter +import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction +import io.element.android.features.roomactions.test.FakeSetRoomIsFavoriteAction 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 @@ -40,6 +44,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_SESSION_ID @@ -71,10 +76,11 @@ class RoomDetailsPresenterTests { } private fun TestScope.createRoomDetailsPresenter( - room: MatrixRoom, + room: MatrixRoom = aMatrixRoom(), leaveRoomPresenter: LeaveRoomPresenter = FakeLeaveRoomPresenter(), dispatchers: CoroutineDispatchers = testCoroutineDispatchers(), - notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService() + notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), + setRoomIsFavoriteAction: SetRoomIsFavoriteAction = FakeSetRoomIsFavoriteAction(), ): RoomDetailsPresenter { val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService) val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory { @@ -86,25 +92,30 @@ 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, + setRoomIsFavorite = setRoomIsFavoriteAction, ) } + private suspend fun RoomDetailsPresenter.test(validate: suspend TurbineTestContext.() -> 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 +135,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 +150,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 +170,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 +184,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 +200,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 +213,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 +229,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 +256,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 +284,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 +303,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 +322,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 +338,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 +355,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 +375,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 +394,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 +409,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 +427,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 +436,35 @@ class RoomDetailsPresenterTests { cancelAndIgnoreRemainingEvents() } } + + @Test + fun `present - when set is favorite event is emitted, then the action is called`() = runTest { + val setRoomIsFavoriteAction = FakeSetRoomIsFavoriteAction() + val presenter = createRoomDetailsPresenter(setRoomIsFavoriteAction = setRoomIsFavoriteAction) + presenter.test { + val initialState = awaitItem() + initialState.eventSink(RoomDetailsEvent.SetIsFavorite(true)) + setRoomIsFavoriteAction.assertCalled(1) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - changes in notable tags updates is favorite flag`() = runTest { + val room = aMatrixRoom() + val presenter = createRoomDetailsPresenter(room = room) + presenter.test { + room.updateNotableTags(RoomNotableTags(true)) + consumeItemsUntilPredicate { it.isFavorite }.last().let { state -> + assertThat(state.isFavorite).isTrue() + } + room.updateNotableTags(RoomNotableTags(false)) + consumeItemsUntilPredicate { !it.isFavorite }.last().let { state -> + assertThat(state.isFavorite).isFalse() + } + cancelAndIgnoreRemainingEvents() + } + } } fun aMatrixRoom( diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts index 4e415fd2a2..fdb0e7a3e2 100644 --- a/features/roomlist/impl/build.gradle.kts +++ b/features/roomlist/impl/build.gradle.kts @@ -56,6 +56,7 @@ dependencies { implementation(projects.features.networkmonitor.api) implementation(projects.features.leaveroom.api) implementation(projects.services.analytics.api) + implementation(projects.features.roomactions.api) api(projects.features.roomlist.api) ksp(libs.showkase.processor) @@ -75,4 +76,5 @@ dependencies { testImplementation(projects.features.networkmonitor.test) testImplementation(projects.tests.testutils) testImplementation(projects.features.leaveroom.test) + testImplementation(projects.features.roomactions.test) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt index f5ce9897b4..dc448a412c 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt @@ -60,7 +60,7 @@ fun RoomListContextMenu( eventSink(RoomListEvents.LeaveRoom(contextMenu.roomId)) }, onFavoriteChanged = { isFavorite -> - eventSink(RoomListEvents.MarkRoomAsFavorite(contextMenu.roomId, isFavorite)) + eventSink(RoomListEvents.SetRoomIsFavorite(contextMenu.roomId, isFavorite)) }, ) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt index b804db5315..b707de4055 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt @@ -28,5 +28,5 @@ sealed interface RoomListEvents { data class ShowContextMenu(val roomListRoomSummary: RoomListRoomSummary) : RoomListEvents data object HideContextMenu : RoomListEvents data class LeaveRoom(val roomId: RoomId) : RoomListEvents - data class MarkRoomAsFavorite(val roomId: RoomId, val isFavorite: Boolean) : RoomListEvents + data class SetRoomIsFavorite(val roomId: RoomId, val isFavorite: Boolean) : RoomListEvents } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 595f75c2fa..5b8c7a37ab 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -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.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus +import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource import io.element.android.features.roomlist.impl.datasource.RoomListDataSource import io.element.android.libraries.architecture.AsyncData @@ -45,17 +46,14 @@ import io.element.android.libraries.indicator.api.IndicatorService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState -import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags 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.Job +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import timber.log.Timber import javax.inject.Inject private const val EXTENDED_RANGE_SIZE = 40 @@ -71,6 +69,7 @@ class RoomListPresenter @Inject constructor( private val encryptionService: EncryptionService, private val featureFlagService: FeatureFlagService, private val indicatorService: IndicatorService, + private val setRoomIsFavorite: SetRoomIsFavoriteAction, ) : Presenter { @Composable override fun present(): RoomListState { @@ -134,7 +133,7 @@ class RoomListPresenter @Inject constructor( contextMenu.value = RoomListState.ContextMenu.Hidden } is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(LeaveRoomEvent.ShowConfirmation(event.roomId)) - is RoomListEvents.MarkRoomAsFavorite -> coroutineScope.markRoomAsFavorite(event) + is RoomListEvents.SetRoomIsFavorite -> coroutineScope.setRoomIsFavorite(event) } } @@ -170,28 +169,23 @@ class RoomListPresenter @Inject constructor( isFavorite = AsyncData.Loading(), ) contextMenuState.value = initialState - val room = client.getRoom(event.roomListRoomSummary.roomId) - if (room != null) { - room.notableTagsFlow - .distinctUntilChanged() - .onEach { tags -> - val newState = initialState.copy(isFavorite = AsyncData.Success(tags.isFavorite)) - contextMenuState.value = newState - } - .launchIn(this) - } else { - contextMenuState.value = initialState.copy(isFavorite = AsyncData.Failure(IllegalStateException("Room not found"))) + client.getRoom(event.roomListRoomSummary.roomId).use { room -> + if (room != null) { + room.notableTagsFlow + .distinctUntilChanged() + .onEach { tags -> + val newState = initialState.copy(isFavorite = AsyncData.Success(tags.isFavorite)) + contextMenuState.value = newState + } + .collect() + } else { + contextMenuState.value = initialState.copy(isFavorite = AsyncData.Failure(IllegalStateException("Room not found"))) + } } } - private fun CoroutineScope.markRoomAsFavorite(event: RoomListEvents.MarkRoomAsFavorite) = launch { - val room = client.getRoom(event.roomId) - if (room != null) { - val notableTags = RoomNotableTags(isFavorite = event.isFavorite); - room.updateNotableTags(notableTags) - }else { - Timber.w("Room ${event.roomId} not found, can't mark as favorite"); - } + private fun CoroutineScope.setRoomIsFavorite(event: RoomListEvents.SetRoomIsFavorite) = launch { + setRoomIsFavorite(event.roomId, event.isFavorite) } private fun updateVisibleRange(range: IntRange) { @@ -204,5 +198,3 @@ class RoomListPresenter @Inject constructor( client.roomListService.updateAllRoomsVisibleRange(extendedRange) } } - - diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index 839fa04cf7..b3da58220f 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -25,11 +25,14 @@ import io.element.android.features.leaveroom.api.LeaveRoomPresenter import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.test.FakeNetworkMonitor +import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction +import io.element.android.features.roomactions.test.FakeSetRoomIsFavoriteAction import io.element.android.features.roomlist.impl.datasource.FakeInviteDataSource import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource import io.element.android.features.roomlist.impl.datasource.RoomListDataSource import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory import io.element.android.features.roomlist.impl.model.RoomListRoomSummary +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -44,6 +47,7 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.test.AN_AVATAR_URL @@ -55,6 +59,7 @@ import io.element.android.libraries.matrix.test.A_USER_NAME 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.aRoomSummaryFilled import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService @@ -309,19 +314,29 @@ 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 = aRoomListRoomSummary initialState.eventSink(RoomListEvents.ShowContextMenu(summary)) - val shownState = awaitItem() - assertThat(shownState.contextMenu) - .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false)) + awaitItem().also { state -> + assertThat(state.contextMenu) + .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false, AsyncData.Success(false))) + } + + room.updateNotableTags(RoomNotableTags(isFavorite = true)) + awaitItem().also { state -> + assertThat(state.contextMenu) + .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false, AsyncData.Success(true))) + } scope.cancel() } } @@ -329,7 +344,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 { @@ -341,7 +360,7 @@ class RoomListPresenterTests { val shownState = awaitItem() assertThat(shownState.contextMenu) - .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false)) + .isEqualTo(RoomListState.ContextMenu.Shown(summary.roomId, summary.name, false, AsyncData.Success(false))) shownState.eventSink(RoomListEvents.HideContextMenu) val hiddenState = awaitItem() @@ -394,6 +413,22 @@ class RoomListPresenterTests { } } + @Test + fun `present - when set is favorite event is emitted, then the action is called`() = runTest { + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + val setRoomIsFavoriteAction = FakeSetRoomIsFavoriteAction() + val presenter = createRoomListPresenter(setRoomIsFavoriteAction = setRoomIsFavoriteAction, coroutineScope = scope) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, true)) + setRoomIsFavoriteAction.assertCalled(1) + cancelAndIgnoreRemainingEvents() + scope.cancel() + } + } + private fun TestScope.createRoomListPresenter( client: MatrixClient = FakeMatrixClient(), sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(), @@ -407,6 +442,7 @@ class RoomListPresenterTests { roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(), encryptionService: EncryptionService = FakeEncryptionService(), coroutineScope: CoroutineScope, + setRoomIsFavoriteAction: SetRoomIsFavoriteAction = FakeSetRoomIsFavoriteAction(), ) = RoomListPresenter( client = client, sessionVerificationService = sessionVerificationService, @@ -431,6 +467,7 @@ class RoomListPresenterTests { encryptionService = encryptionService, featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)), ), + setRoomIsFavorite = setRoomIsFavoriteAction, ) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 630f87a0fb..7c40d37155 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -58,6 +58,9 @@ interface MatrixRoom : Closeable { val roomInfoFlow: Flow + /** + * The current notable tags as a Flow. + */ val notableTagsFlow: Flow /** diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tags/RoomNotableTags.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tags/RoomNotableTags.kt index 25a19b5697..aabd6fc4f0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tags/RoomNotableTags.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tags/RoomNotableTags.kt @@ -16,6 +16,10 @@ package io.element.android.libraries.matrix.api.room.tags +/** + * Represents the notable tags of a room. + * @param isFavorite true if the room is marked as favorite. + */ data class RoomNotableTags( - val isFavorite: Boolean, + val isFavorite: Boolean = false, ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 9c0cd11e3f..d9bd6bfa96 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -82,8 +82,8 @@ import org.matrix.rustcomponents.sdk.use import timber.log.Timber import java.io.File import org.matrix.rustcomponents.sdk.Room as InnerRoom -import uniffi.matrix_sdk_base.RoomNotableTags as RustRoomNotableTags import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline +import uniffi.matrix_sdk_base.RoomNotableTags as RustRoomNotableTags @OptIn(ExperimentalCoroutinesApi::class) class RustMatrixRoom( @@ -118,7 +118,6 @@ class RustMatrixRoom( override val notableTagsFlow: Flow = mxCallbackFlow { innerRoom.subscribeToNotableTags(object : RoomNotableTagsListener { override fun call(notableTags: RustRoomNotableTags) { - Timber.d("On notable tags update: $notableTags") channel.trySend(notableTags.map()) } }) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt index 4934b0058b..1b3e900f17 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryDetailsFactory.kt @@ -25,7 +25,6 @@ import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.use class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) { - fun create(roomInfo: RoomInfo): RoomSummaryDetails { val latestRoomMessage = roomInfo.latestEvent?.use { roomMessageFactory.create(it) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index 20cd781dcb..ed56d4a8d0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -28,7 +28,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSende import io.element.android.libraries.matrix.api.timeline.item.event.Receipt import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import kotlinx.collections.immutable.ImmutableList -import uniffi.matrix_sdk_ui.EventItemOrigin as RustEventItemOrigin import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import org.matrix.rustcomponents.sdk.Reaction @@ -37,6 +36,7 @@ import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo as RustEventTimelineItemDebugInfo import org.matrix.rustcomponents.sdk.ProfileDetails as RustProfileDetails import org.matrix.rustcomponents.sdk.Receipt as RustReceipt +import uniffi.matrix_sdk_ui.EventItemOrigin as RustEventItemOrigin class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper()) { fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.use { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 72508440ea..c5d1f88a81 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -627,7 +627,6 @@ fun aRoomInfo( isPublic: Boolean = true, isSpace: Boolean = false, isTombstoned: Boolean = false, - isFavorite: Boolean = false, canonicalAlias: String? = null, alternativeAliases: List = emptyList(), currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED, From f3c1eb6738166fd2b0f5cae15e27e8c3e16277bc Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 6 Feb 2024 16:04:44 +0100 Subject: [PATCH 03/13] Use the new setIsFavorite api --- .../impl/DefaultSetRoomIsFavoriteAction.kt | 16 ++++++---------- .../DefaultSetRoomIsFavoriteActionTests.kt | 2 +- .../libraries/matrix/api/room/MatrixRoom.kt | 2 +- .../matrix/api/room/tags/RoomNotableTags.kt | 2 ++ .../matrix/impl/room/RustMatrixRoom.kt | 9 ++------- .../impl/room/tags/RoomNotableTagsMapper.kt | 6 ++++-- .../matrix/test/room/FakeMatrixRoom.kt | 18 ++++++++++++------ 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/features/roomactions/impl/src/main/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteAction.kt b/features/roomactions/impl/src/main/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteAction.kt index 947c56ebcb..dab51fa884 100644 --- a/features/roomactions/impl/src/main/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteAction.kt +++ b/features/roomactions/impl/src/main/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteAction.kt @@ -22,25 +22,20 @@ import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.MatrixRoom -import kotlinx.coroutines.flow.first import javax.inject.Inject import kotlin.coroutines.cancellation.CancellationException @ContributesBinding(SessionScope::class) class DefaultSetRoomIsFavoriteAction @Inject constructor(private val client: MatrixClient) : SetRoomIsFavoriteAction { + override suspend operator fun invoke(roomId: RoomId, isFavorite: Boolean): SetRoomIsFavoriteAction.Result { - return client.getRoom(roomId).use { room -> - room?.setIsFavorite(isFavorite) ?: SetRoomIsFavoriteAction.Result.RoomNotFound - } + return client.getRoom(roomId)?.use { room -> + invoke(room, isFavorite) + } ?: SetRoomIsFavoriteAction.Result.RoomNotFound } override suspend fun invoke(room: MatrixRoom, isFavorite: Boolean): SetRoomIsFavoriteAction.Result { - return room.setIsFavorite(isFavorite) - } - - private suspend fun MatrixRoom.setIsFavorite(isFavorite: Boolean): SetRoomIsFavoriteAction.Result { - val notableTags = notableTagsFlow.first().copy(isFavorite = isFavorite) - return updateNotableTags(notableTags).fold( + return room.setIsFavorite(isFavorite).fold( onSuccess = { SetRoomIsFavoriteAction.Result.Success }, @@ -53,4 +48,5 @@ class DefaultSetRoomIsFavoriteAction @Inject constructor(private val client: Mat } ) } + } diff --git a/features/roomactions/impl/src/test/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteActionTests.kt b/features/roomactions/impl/src/test/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteActionTests.kt index 93c7518cd7..0dd25d35ac 100644 --- a/features/roomactions/impl/src/test/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteActionTests.kt +++ b/features/roomactions/impl/src/test/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteActionTests.kt @@ -52,7 +52,7 @@ class DefaultSetRoomIsFavoriteActionTests { @Test fun `given a room, when action is invoked and fail, then it returns Result_Exception`() = runTest { val action = DefaultSetRoomIsFavoriteAction(FakeMatrixClient()) - room.givenUpdateNotableTagsResult(Result.failure(Exception())) + room.givenSetIsFavoriteResult(Result.failure(Exception())) val result = action(room, true) assert(result is SetRoomIsFavoriteAction.Result.Exception) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 7c40d37155..762121a8fe 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -156,7 +156,7 @@ interface MatrixRoom : Closeable { suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result - suspend fun updateNotableTags(notableTags: RoomNotableTags): Result + suspend fun setIsFavorite(isFavorite: Boolean): Result /** * Share a location message in the room. diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tags/RoomNotableTags.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tags/RoomNotableTags.kt index aabd6fc4f0..6d12749657 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tags/RoomNotableTags.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tags/RoomNotableTags.kt @@ -19,7 +19,9 @@ package io.element.android.libraries.matrix.api.room.tags /** * Represents the notable tags of a room. * @param isFavorite true if the room is marked as favorite. + * @param isLowPriority true if the room is marked as low priority. */ data class RoomNotableTags( val isFavorite: Boolean = false, + val isLowPriority: Boolean = false, ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index d9bd6bfa96..bf67064f2a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -435,14 +435,9 @@ class RustMatrixRoom( } } - override suspend fun updateNotableTags(notableTags: RoomNotableTags): Result = withContext(roomDispatcher) { + override suspend fun setIsFavorite(isFavorite: Boolean): Result = withContext(roomDispatcher) { runCatching { - Timber.i("Update notable tags with : $notableTags") - innerRoom.updateNotableTags(notableTags.map()) - }.onFailure { - Timber.w("Failed to update notable tags: $it") - }.onSuccess { - Timber.i("Successfully updated notable tags") + innerRoom.setIsFavorite(isFavorite, null) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tags/RoomNotableTagsMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tags/RoomNotableTagsMapper.kt index 36fe0eb98e..cd779d24a7 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tags/RoomNotableTagsMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tags/RoomNotableTagsMapper.kt @@ -20,9 +20,11 @@ import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags import uniffi.matrix_sdk_base.RoomNotableTags as RustRoomNotableTags fun RustRoomNotableTags.map() = RoomNotableTags( - isFavorite = isFavorite + isFavorite = isFavorite, + isLowPriority = isLowPriority ) fun RoomNotableTags.map() = RustRoomNotableTags( - isFavorite = isFavorite + isFavorite = isFavorite, + isLowPriority = isLowPriority ) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index c5d1f88a81..b0b77761d8 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -114,7 +114,7 @@ class FakeMatrixRoom( private var getWidgetDriverResult: Result = Result.success(FakeWidgetDriver()) private var canUserTriggerRoomNotificationResult: Result = Result.success(true) private var canUserJoinCallResult: Result = Result.success(true) - private var updateNotableTagsResult = Result.success(Unit) + private var setIsFavoriteResult = Result.success(Unit) var sendMessageMentions = emptyList() val editMessageCalls = mutableListOf>() private val _typingRecord = mutableListOf() @@ -171,7 +171,7 @@ class FakeMatrixRoom( private val _roomInfoFlow: MutableSharedFlow = MutableSharedFlow(replay = 1) override val roomInfoFlow: Flow = _roomInfoFlow - private val _notableTagsFlow: MutableSharedFlow = MutableStateFlow(aRoomNotableTags()) + private val _notableTagsFlow: MutableStateFlow = MutableStateFlow(aRoomNotableTags()) override val notableTagsFlow: Flow = _notableTagsFlow override val membersStateFlow: MutableStateFlow = MutableStateFlow(MatrixRoomMembersState.Unknown) @@ -379,9 +379,15 @@ class FakeMatrixRoom( return reportContentResult } - override suspend fun updateNotableTags(notableTags: RoomNotableTags): Result { - return updateNotableTagsResult.also { result -> + override suspend fun setIsFavorite(isFavorite: Boolean): Result { + return setIsFavoriteResult.also { result -> if (result.isSuccess) { + val lowPriority = if (isFavorite) { + false + } else { + _notableTagsFlow.value.isLowPriority + } + val notableTags = RoomNotableTags(isFavorite, lowPriority) _notableTagsFlow.emit(notableTags) } } @@ -584,8 +590,8 @@ class FakeMatrixRoom( getWidgetDriverResult = result } - fun givenUpdateNotableTagsResult(result: Result) { - updateNotableTagsResult = result + fun givenSetIsFavoriteResult(result: Result) { + setIsFavoriteResult = result } fun givenRoomInfo(roomInfo: MatrixRoomInfo) { From 5ad08c32798ed019dd16111240a2fbbeff798d18 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 12 Feb 2024 17:45:52 +0100 Subject: [PATCH 04/13] Favourite : use localized string --- .../android/features/roomdetails/impl/RoomDetailsView.kt | 2 +- .../android/features/roomlist/impl/RoomListContextMenu.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index d3dbdb7ba0..60d03ec5e1 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -373,7 +373,7 @@ private fun FavoriteSection( PreferenceCategory(modifier = modifier) { PreferenceSwitch( icon = CompoundIcons.FavouriteOff, - title = "Favorite", + title = stringResource(id = CommonStrings.common_favourite), isChecked = isFavorite, onCheckedChange = onFavoriteChanges ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt index d80cdf15a5..1225adbf50 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt @@ -129,14 +129,14 @@ private fun RoomListModalBottomSheetContent( ListItem( headlineContent = { Text( - text = "Favourite", + text = stringResource(id = CommonStrings.common_favourite), style = MaterialTheme.typography.bodyLarge, ) }, leadingContent = ListItemContent.Icon( iconSource = IconSource.Vector( CompoundIcons.FavouriteOff, - contentDescription = "Favourite" + contentDescription = stringResource(id = CommonStrings.common_favourite), ) ), trailingContent = ListItemContent.Switch( From 949ffc6f8ae30fc393994bb61ef185c6dd7f9a38 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 12 Feb 2024 17:46:25 +0100 Subject: [PATCH 05/13] Favourite : add ui test on click --- .../roomlist/impl/RoomListContextMenu.kt | 2 +- .../features/roomlist/impl/RoomListEvents.kt | 12 +++++----- .../roomlist/impl/RoomListContextMenuTest.kt | 22 +++++++++++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt index 1225adbf50..6d8e57223f 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt @@ -43,7 +43,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( diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt index 8ef496dc94..c808ae689b 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt @@ -27,10 +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 - data class SetRoomIsFavorite(val roomId: RoomId, val isFavorite: Boolean) : 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 } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenuTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenuTest.kt index 6030ea1c6f..1a17ab1002 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenuTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenuTest.kt @@ -19,6 +19,8 @@ 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.architecture.AsyncData +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 +130,24 @@ class RoomListContextMenuTest { eventsRecorder.assertSingle(RoomListEvents.HideContextMenu) callback.assertSuccess() } + + @Test + fun `clicking on Favourites generates expected Event`() { + val eventsRecorder = EventsRecorder() + val contextMenu = aContextMenuShown(isDm = false, isFavorite = AsyncData.Success(false)) + val callback = EnsureNeverCalledWithParam() + rule.setContent { + RoomListContextMenu( + contextMenu = contextMenu, + eventSink = eventsRecorder, + onRoomSettingsClicked = callback, + ) + } + rule.clickOn(CommonStrings.common_favourite) + eventsRecorder.assertList( + listOf( + RoomListEvents.SetRoomIsFavorite(contextMenu.roomId, true), + ) + ) + } } From 182b8c951b069c73ab7b64f038d98cad72fdece4 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 15 Feb 2024 11:07:49 +0100 Subject: [PATCH 06/13] favorite : simplify and branch live data again --- features/roomactions/api/build.gradle.kts | 27 --------- .../api/SetRoomIsFavoriteAction.kt | 46 --------------- features/roomactions/impl/build.gradle.kts | 50 ---------------- .../impl/DefaultSetRoomIsFavoriteAction.kt | 51 ---------------- .../DefaultSetRoomIsFavoriteActionTests.kt | 59 ------------------- features/roomactions/test/build.gradle.kts | 28 --------- .../test/FakeSetRoomIsFavoriteAction.kt | 39 ------------ features/roomdetails/impl/build.gradle.kts | 2 - .../roomdetails/impl/RoomDetailsPresenter.kt | 10 +--- features/roomlist/impl/build.gradle.kts | 2 - .../roomlist/impl/RoomListPresenter.kt | 14 ++++- .../roomlist/impl/RoomListContextMenuTest.kt | 2 +- .../roomlist/impl/RoomListPresenterTests.kt | 44 +++++++------- 13 files changed, 41 insertions(+), 333 deletions(-) delete mode 100644 features/roomactions/api/build.gradle.kts delete mode 100644 features/roomactions/api/src/main/kotlin/io/element/android/features/roomactions/api/SetRoomIsFavoriteAction.kt delete mode 100644 features/roomactions/impl/build.gradle.kts delete mode 100644 features/roomactions/impl/src/main/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteAction.kt delete mode 100644 features/roomactions/impl/src/test/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteActionTests.kt delete mode 100644 features/roomactions/test/build.gradle.kts delete mode 100644 features/roomactions/test/src/main/kotlin/io/element/android/features/roomactions/test/FakeSetRoomIsFavoriteAction.kt diff --git a/features/roomactions/api/build.gradle.kts b/features/roomactions/api/build.gradle.kts deleted file mode 100644 index 6bfccef3e5..0000000000 --- a/features/roomactions/api/build.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 - * - * http://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. - */ - -plugins { - id("io.element.android-library") -} - -android { - namespace = "io.element.android.features.roomactions.api" -} - -dependencies { - implementation(projects.libraries.matrix.api) -} diff --git a/features/roomactions/api/src/main/kotlin/io/element/android/features/roomactions/api/SetRoomIsFavoriteAction.kt b/features/roomactions/api/src/main/kotlin/io/element/android/features/roomactions/api/SetRoomIsFavoriteAction.kt deleted file mode 100644 index ecf1bcc4c9..0000000000 --- a/features/roomactions/api/src/main/kotlin/io/element/android/features/roomactions/api/SetRoomIsFavoriteAction.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 - * - * http://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.roomactions.api - -import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.MatrixRoom - -/** - * Set the favorite status of a room. - * Use either the room id or the room instance with the desired favorite status. - */ -interface SetRoomIsFavoriteAction { - - /** - * The result of the action? - */ - sealed interface Result { - data object Success : Result - data object RoomNotFound : Result - data class Exception(val inner: java.lang.Exception) : Result - } - - /** - * Set the favorite status of a room by its id, it'll try to load the room from the session. - */ - suspend operator fun invoke(roomId: RoomId, isFavorite: Boolean): Result - - /** - * Set the favorite status of a room using the provided instance. - */ - suspend operator fun invoke(room: MatrixRoom, isFavorite: Boolean): Result -} diff --git a/features/roomactions/impl/build.gradle.kts b/features/roomactions/impl/build.gradle.kts deleted file mode 100644 index 61f666da08..0000000000 --- a/features/roomactions/impl/build.gradle.kts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 - * - * http://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. - */ - -// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed -@Suppress("DSL_SCOPE_VIOLATION") -plugins { - id("io.element.android-compose-library") - alias(libs.plugins.anvil) - alias(libs.plugins.ksp) - id("kotlin-parcelize") -} - -android { - namespace = "io.element.android.features.roomactions.impl" -} - -anvil { - generateDaggerFactories.set(true) -} - -dependencies { - implementation(projects.anvilannotations) - anvil(projects.anvilcodegen) - api(projects.features.roomactions.api) - implementation(projects.libraries.core) - implementation(projects.libraries.architecture) - implementation(projects.libraries.matrix.api) - - testImplementation(libs.test.junit) - testImplementation(libs.coroutines.test) - testImplementation(libs.molecule.runtime) - testImplementation(libs.test.truth) - testImplementation(libs.test.turbine) - testImplementation(projects.libraries.matrix.test) - - ksp(libs.showkase.processor) -} diff --git a/features/roomactions/impl/src/main/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteAction.kt b/features/roomactions/impl/src/main/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteAction.kt deleted file mode 100644 index 94abd618a1..0000000000 --- a/features/roomactions/impl/src/main/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteAction.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 - * - * http://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.roomactions.impl - -import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction -import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.MatrixRoom -import javax.inject.Inject -import kotlin.coroutines.cancellation.CancellationException - -@ContributesBinding(SessionScope::class) -class DefaultSetRoomIsFavoriteAction @Inject constructor(private val client: MatrixClient) : SetRoomIsFavoriteAction { - - override suspend operator fun invoke(roomId: RoomId, isFavorite: Boolean): SetRoomIsFavoriteAction.Result { - return client.getRoom(roomId)?.use { room -> - invoke(room, isFavorite) - } ?: SetRoomIsFavoriteAction.Result.RoomNotFound - } - - override suspend fun invoke(room: MatrixRoom, isFavorite: Boolean): SetRoomIsFavoriteAction.Result { - return room.setIsFavorite(isFavorite).fold( - onSuccess = { - SetRoomIsFavoriteAction.Result.Success - }, - onFailure = { throwable -> - if (throwable is Exception && throwable !is CancellationException) { - SetRoomIsFavoriteAction.Result.Exception(throwable) - } else { - throw throwable - } - } - ) - } -} diff --git a/features/roomactions/impl/src/test/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteActionTests.kt b/features/roomactions/impl/src/test/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteActionTests.kt deleted file mode 100644 index 0dd25d35ac..0000000000 --- a/features/roomactions/impl/src/test/kotlin/io/element/android/features/roomactions/impl/DefaultSetRoomIsFavoriteActionTests.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 - * - * http://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.roomactions.impl - -import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction -import io.element.android.libraries.matrix.test.FakeMatrixClient -import io.element.android.libraries.matrix.test.room.FakeMatrixRoom -import kotlinx.coroutines.test.runTest -import org.junit.Test - -class DefaultSetRoomIsFavoriteActionTests { - private val room = FakeMatrixRoom() - - @Test - fun `given a room id and a client without rooms, when action is invoked, then it returns Result_RoomNotFound`() = runTest { - val action = DefaultSetRoomIsFavoriteAction(FakeMatrixClient()) - val result = action(room.roomId, true) - assert(result is SetRoomIsFavoriteAction.Result.RoomNotFound) - } - - @Test - fun `given a room, when action is invoked, then it returns Result_Success`() = runTest { - val action = DefaultSetRoomIsFavoriteAction(FakeMatrixClient()) - val result = action(room, true) - assert(result is SetRoomIsFavoriteAction.Result.Success) - } - - @Test - fun `given a room id and a client with a room, when action is invoked, then it returns Result_Success`() = runTest { - val client = FakeMatrixClient().apply { - givenGetRoomResult(room.roomId, room) - } - val action = DefaultSetRoomIsFavoriteAction(client) - val result = action(room.roomId, true) - assert(result is SetRoomIsFavoriteAction.Result.Success) - } - - @Test - fun `given a room, when action is invoked and fail, then it returns Result_Exception`() = runTest { - val action = DefaultSetRoomIsFavoriteAction(FakeMatrixClient()) - room.givenSetIsFavoriteResult(Result.failure(Exception())) - val result = action(room, true) - assert(result is SetRoomIsFavoriteAction.Result.Exception) - } -} diff --git a/features/roomactions/test/build.gradle.kts b/features/roomactions/test/build.gradle.kts deleted file mode 100644 index d43603e54a..0000000000 --- a/features/roomactions/test/build.gradle.kts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2023 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 - * - * http://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. - */ -plugins { - id("io.element.android-library") -} - -android { - namespace = "io.element.android.features.roomactions.test" -} - -dependencies { - implementation(projects.features.roomactions.api) - implementation(projects.libraries.matrix.api) - implementation(libs.coroutines.core) -} diff --git a/features/roomactions/test/src/main/kotlin/io/element/android/features/roomactions/test/FakeSetRoomIsFavoriteAction.kt b/features/roomactions/test/src/main/kotlin/io/element/android/features/roomactions/test/FakeSetRoomIsFavoriteAction.kt deleted file mode 100644 index c892079f7b..0000000000 --- a/features/roomactions/test/src/main/kotlin/io/element/android/features/roomactions/test/FakeSetRoomIsFavoriteAction.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 - * - * http://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.roomactions.test - -import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction -import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.MatrixRoom - -class FakeSetRoomIsFavoriteAction : SetRoomIsFavoriteAction { - private var counter = 0 - - fun assertCalled(number: Int) { - assert(counter == number) { "Expected $number calls, got $counter" } - } - - override suspend fun invoke(roomId: RoomId, isFavorite: Boolean): SetRoomIsFavoriteAction.Result { - counter++ - return SetRoomIsFavoriteAction.Result.Success - } - - override suspend fun invoke(room: MatrixRoom, isFavorite: Boolean): SetRoomIsFavoriteAction.Result { - counter++ - return SetRoomIsFavoriteAction.Result.Success - } -} diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index d76ebbc9d8..0f1a139f40 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -54,7 +54,6 @@ dependencies { implementation(projects.features.createroom.api) implementation(projects.services.analytics.api) implementation(projects.features.poll.api) - implementation(projects.features.roomactions.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) @@ -71,7 +70,6 @@ dependencies { testImplementation(projects.tests.testutils) testImplementation(projects.features.leaveroom.test) testImplementation(projects.features.createroom.test) - testImplementation(projects.features.roomactions.test) ksp(libs.showkase.processor) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 52719cf3f1..a6d60edbac 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -29,7 +29,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.lifecycle.Lifecycle import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomPresenter -import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction 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 @@ -62,19 +61,19 @@ class RoomDetailsPresenter @Inject constructor( private val roomMembersDetailsPresenterFactory: RoomMemberDetailsPresenter.Factory, private val leaveRoomPresenter: LeaveRoomPresenter, private val dispatchers: CoroutineDispatchers, - private val setRoomIsFavorite: SetRoomIsFavoriteAction, ) : Presenter { @Composable override fun present(): RoomDetailsState { 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) @@ -99,9 +98,6 @@ class RoomDetailsPresenter @Inject constructor( val dmMember by room.getDirectRoomMember(membersState) val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember) val roomType by getRoomType(dmMember) - val isFavorite by remember { - derivedStateOf { roomInfo?.isFavorite.orFalse() } - } val topicState = remember(canEditTopic, roomTopic, roomType) { val topic = roomTopic @@ -131,7 +127,7 @@ class RoomDetailsPresenter @Inject constructor( } is RoomDetailsEvent.SetIsFavorite -> { scope.launch { - setRoomIsFavorite(room, event.isFavorite) + room.setIsFavorite(event.isFavorite) } } } diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts index ff14371ced..61f40b53ec 100644 --- a/features/roomlist/impl/build.gradle.kts +++ b/features/roomlist/impl/build.gradle.kts @@ -57,7 +57,6 @@ dependencies { implementation(projects.features.networkmonitor.api) implementation(projects.features.leaveroom.api) implementation(projects.services.analytics.api) - implementation(projects.features.roomactions.api) api(projects.features.roomlist.api) ksp(libs.showkase.processor) @@ -80,5 +79,4 @@ dependencies { testImplementation(projects.features.networkmonitor.test) testImplementation(projects.tests.testutils) testImplementation(projects.features.leaveroom.test) - testImplementation(projects.features.roomactions.test) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 45fa71c6d6..59936db7d4 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -33,7 +33,6 @@ import io.element.android.features.leaveroom.api.LeaveRoomPresenter import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.preferences.api.store.SessionPreferencesStore -import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource import io.element.android.features.roomlist.impl.datasource.RoomListDataSource import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter @@ -54,8 +53,12 @@ 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.flow.collect import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject private const val EXTENDED_RANGE_SIZE = 40 @@ -201,6 +204,15 @@ class RoomListPresenter @Inject constructor( hasNewContent = event.roomListRoomSummary.hasNewContent ) contextMenuState.value = initialState + client.getRoom(event.roomListRoomSummary.roomId)?.use { room -> + room.roomInfoFlow + .onEach { roomInfo -> + contextMenuState.value = initialState.copy( + isFavorite = roomInfo.isFavorite, + ) + } + .collect() + } } private fun updateVisibleRange(range: IntRange) { diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenuTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenuTest.kt index 1a17ab1002..28177d6e0e 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenuTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenuTest.kt @@ -134,7 +134,7 @@ class RoomListContextMenuTest { @Test fun `clicking on Favourites generates expected Event`() { val eventsRecorder = EventsRecorder() - val contextMenu = aContextMenuShown(isDm = false, isFavorite = AsyncData.Success(false)) + val contextMenu = aContextMenuShown(isDm = false, isFavorite = false) val callback = EnsureNeverCalledWithParam() rule.setContent { RoomListContextMenu( diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index 8fae23e5d0..25e6d89b78 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -26,8 +26,6 @@ import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.test.FakeNetworkMonitor import io.element.android.features.preferences.api.store.SessionPreferencesStore -import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction -import io.element.android.features.roomactions.test.FakeSetRoomIsFavoriteAction import io.element.android.features.roomlist.impl.datasource.FakeInviteDataSource import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource import io.element.android.features.roomlist.impl.datasource.RoomListDataSource @@ -35,7 +33,6 @@ import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryF import io.element.android.features.roomlist.impl.migration.InMemoryMigrationScreenStore import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter import io.element.android.features.roomlist.impl.model.createRoomListRoomSummary -import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter import io.element.android.libraries.dateformatter.test.A_FORMATTED_DATE import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter @@ -363,10 +360,11 @@ class RoomListPresenterTests { roomId = summary.roomId, roomName = summary.name, isDm = false, - isFavorite = AsyncData.Success(false), + isFavorite = false, markAsUnreadFeatureFlagEnabled = true, hasNewContent = false, - )) + ) + ) } room.setIsFavorite(isFavorite = true) @@ -377,10 +375,11 @@ class RoomListPresenterTests { roomId = summary.roomId, roomName = summary.name, isDm = false, - isFavorite = AsyncData.Success(true), + isFavorite = true, markAsUnreadFeatureFlagEnabled = true, hasNewContent = false, - )) + ) + ) } scope.cancel() } @@ -405,14 +404,16 @@ class RoomListPresenterTests { val shownState = awaitItem() assertThat(shownState.contextMenu) - .isEqualTo(RoomListState.ContextMenu.Shown( - roomId = summary.roomId, - roomName = summary.name, - isDm = false, - isFavorite = AsyncData.Success(false), - markAsUnreadFeatureFlagEnabled = true, - hasNewContent = false, - )) + .isEqualTo( + RoomListState.ContextMenu.Shown( + roomId = summary.roomId, + roomName = summary.name, + isDm = false, + isFavorite = false, + markAsUnreadFeatureFlagEnabled = true, + hasNewContent = false, + ) + ) shownState.eventSink(RoomListEvents.HideContextMenu) @@ -469,14 +470,19 @@ class RoomListPresenterTests { @Test fun `present - when set is favorite event is emitted, then the action is called`() = runTest { val scope = CoroutineScope(coroutineContext + SupervisorJob()) - val setRoomIsFavoriteAction = FakeSetRoomIsFavoriteAction() - val presenter = createRoomListPresenter(setRoomIsFavoriteAction = setRoomIsFavoriteAction, 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 { val initialState = awaitItem() initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, true)) - setRoomIsFavoriteAction.assertCalled(1) + assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true)) + initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, false)) + assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false)) cancelAndIgnoreRemainingEvents() scope.cancel() } @@ -560,7 +566,6 @@ class RoomListPresenterTests { encryptionService: EncryptionService = FakeEncryptionService(), sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(), coroutineScope: CoroutineScope, - setRoomIsFavoriteAction: SetRoomIsFavoriteAction = FakeSetRoomIsFavoriteAction(), migrationScreenPresenter: MigrationScreenPresenter = MigrationScreenPresenter( matrixClient = client, migrationScreenStore = InMemoryMigrationScreenStore(), @@ -589,7 +594,6 @@ class RoomListPresenterTests { encryptionService = encryptionService, featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)), ), - setRoomIsFavorite = setRoomIsFavoriteAction, migrationScreenPresenter = migrationScreenPresenter, sessionPreferencesStore = sessionPreferencesStore, ) From e5e147af47d6960cce11194cc96ee344bffde6c0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 15 Feb 2024 11:20:58 +0100 Subject: [PATCH 07/13] favorites : clean up --- .../roomdetails/impl/RoomDetailsPresenter.kt | 1 - .../roomdetails/RoomDetailsPresenterTests.kt | 20 +++++++++---------- .../roomlist/impl/RoomListContextMenu.kt | 2 -- .../roomlist/impl/RoomListPresenter.kt | 2 -- .../roomlist/impl/RoomListContextMenuTest.kt | 1 - 5 files changed, 9 insertions(+), 17 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index a6d60edbac..52cb7a9d05 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -48,7 +48,6 @@ import io.element.android.libraries.matrix.api.room.roomNotificationSettings import io.element.android.libraries.matrix.ui.room.getDirectRoomMember import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt index 0e0b392782..c8a3b17835 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -26,8 +26,6 @@ import io.element.android.features.createroom.test.FakeStartDMAction import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomPresenter import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter -import io.element.android.features.roomactions.api.SetRoomIsFavoriteAction -import io.element.android.features.roomactions.test.FakeSetRoomIsFavoriteAction import io.element.android.features.roomdetails.impl.RoomDetailsEvent import io.element.android.features.roomdetails.impl.RoomDetailsPresenter import io.element.android.features.roomdetails.impl.RoomDetailsState @@ -41,10 +39,10 @@ import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.StateEventType -import io.element.android.libraries.matrix.api.room.tags.RoomNotableTags import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_SESSION_ID @@ -80,7 +78,6 @@ class RoomDetailsPresenterTests { leaveRoomPresenter: LeaveRoomPresenter = FakeLeaveRoomPresenter(), dispatchers: CoroutineDispatchers = testCoroutineDispatchers(), notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), - setRoomIsFavoriteAction: SetRoomIsFavoriteAction = FakeSetRoomIsFavoriteAction(), ): RoomDetailsPresenter { val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService) val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory { @@ -99,7 +96,6 @@ class RoomDetailsPresenterTests { roomMembersDetailsPresenterFactory = roomMemberDetailsPresenterFactory, leaveRoomPresenter = leaveRoomPresenter, dispatchers = dispatchers, - setRoomIsFavorite = setRoomIsFavoriteAction, ) } @@ -439,26 +435,28 @@ class RoomDetailsPresenterTests { @Test fun `present - when set is favorite event is emitted, then the action is called`() = runTest { - val setRoomIsFavoriteAction = FakeSetRoomIsFavoriteAction() - val presenter = createRoomDetailsPresenter(setRoomIsFavoriteAction = setRoomIsFavoriteAction) + val room = FakeMatrixRoom() + val presenter = createRoomDetailsPresenter(room = room) presenter.test { val initialState = awaitItem() initialState.eventSink(RoomDetailsEvent.SetIsFavorite(true)) - setRoomIsFavoriteAction.assertCalled(1) + assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true)) + initialState.eventSink(RoomDetailsEvent.SetIsFavorite(false)) + assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false)) cancelAndIgnoreRemainingEvents() } } @Test - fun `present - changes in notable tags updates is favorite flag`() = runTest { + fun `present - changes in room info updates the is favorite flag`() = runTest { val room = aMatrixRoom() val presenter = createRoomDetailsPresenter(room = room) presenter.test { - room.updateNotableTags(RoomNotableTags(true)) + room.givenRoomInfo(aRoomInfo(isFavorite = true)) consumeItemsUntilPredicate { it.isFavorite }.last().let { state -> assertThat(state.isFavorite).isTrue() } - room.updateNotableTags(RoomNotableTags(false)) + room.givenRoomInfo(aRoomInfo(isFavorite = false)) consumeItemsUntilPredicate { !it.isFavorite }.last().let { state -> assertThat(state.isFavorite).isFalse() } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt index 76ed828fdf..1ab51a5219 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt @@ -26,8 +26,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 59936db7d4..ca81dafc28 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -55,10 +55,8 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import timber.log.Timber import javax.inject.Inject private const val EXTENDED_RANGE_SIZE = 40 diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenuTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenuTest.kt index 28177d6e0e..a1c98c6874 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenuTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenuTest.kt @@ -19,7 +19,6 @@ 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.architecture.AsyncData import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureCalledOnceWithParam From 05adf131170247dfb998f73b5a004b20b8e20811 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 15 Feb 2024 11:27:30 +0100 Subject: [PATCH 08/13] favorite : add changelog --- changelog.d/2208.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/2208.feature diff --git a/changelog.d/2208.feature b/changelog.d/2208.feature new file mode 100644 index 0000000000..63dc68b585 --- /dev/null +++ b/changelog.d/2208.feature @@ -0,0 +1 @@ +Mark a room or dm as favourite. From 977abcf4592e689e2cd17654e4146d1717e3ede8 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 15 Feb 2024 10:37:42 +0000 Subject: [PATCH 09/13] Update screenshots --- ...sDark_null_RoomDetailsDark--1_1_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...sDark_null_RoomDetailsDark--1_1_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...sDark_null_RoomDetailsDark--1_1_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...sDark_null_RoomDetailsDark--1_1_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...sDark_null_RoomDetailsDark--1_1_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...sDark_null_RoomDetailsDark--1_1_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...sDark_null_RoomDetailsDark--1_1_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...sDark_null_RoomDetailsDark--1_1_null_7,NEXUS_5,1.0,en].png | 4 ++-- ...sDark_null_RoomDetailsDark--1_1_null_8,NEXUS_5,1.0,en].png | 4 ++-- ...sDark_null_RoomDetailsDark--1_1_null_9,NEXUS_5,1.0,en].png | 3 +++ ...omDetails_null_RoomDetails--0_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...omDetails_null_RoomDetails--0_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...omDetails_null_RoomDetails--0_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...omDetails_null_RoomDetails--0_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...omDetails_null_RoomDetails--0_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...omDetails_null_RoomDetails--0_0_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...omDetails_null_RoomDetails--0_0_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...omDetails_null_RoomDetails--0_0_null_7,NEXUS_5,1.0,en].png | 4 ++-- ...omDetails_null_RoomDetails--0_0_null_8,NEXUS_5,1.0,en].png | 4 ++-- ...omDetails_null_RoomDetails--0_0_null_9,NEXUS_5,1.0,en].png | 3 +++ ...alBottomSheetContentForDm-Day-2_3_null,NEXUS_5,1.0,en].png | 4 ++-- ...BottomSheetContentForDm-Night-2_4_null,NEXUS_5,1.0,en].png | 4 ++-- ...stModalBottomSheetContent-Day-1_2_null,NEXUS_5,1.0,en].png | 4 ++-- ...ModalBottomSheetContent-Night-1_3_null,NEXUS_5,1.0,en].png | 4 ++-- ...View_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en].png | 4 ++-- ...View_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,en].png | 4 ++-- ...View_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,en].png | 4 ++-- ...View_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,en].png | 3 +++ ...tView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en].png | 4 ++-- ...ew_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en].png | 4 ++-- ...ew_null_RoomListView-Night-3_5_null_11,NEXUS_5,1.0,en].png | 4 ++-- ...ew_null_RoomListView-Night-3_5_null_12,NEXUS_5,1.0,en].png | 4 ++-- ...ew_null_RoomListView-Night-3_5_null_13,NEXUS_5,1.0,en].png | 3 +++ ...iew_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en].png | 4 ++-- 34 files changed, 72 insertions(+), 60 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_9,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_9,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_13,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_0,NEXUS_5,1.0,en].png index 2d80565669..2e0d2f52f4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2dd92e3cf76367608276fd10030d161ff370294b474c3e08fbf6463508fb1b0 -size 42064 +oid sha256:10c67ba7b16d81e030cb44719cc68a0a3fd57acba75e488a37c3bdf4f739cf95 +size 44537 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_1,NEXUS_5,1.0,en].png index dc6ad12b0d..0016e78cb8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb09fb2b24e1597e07898562b29cee3e62ad63040ab77895c234a13f4836ab08 -size 30442 +oid sha256:a17d073bf7c3e993c81e51e234c4f885d2264f6de8bf54a93306f14ee5a02a66 +size 33354 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_2,NEXUS_5,1.0,en].png index 3ff5018aec..c86faebfbc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8e482480e6103d81fa6e08857c3308bde87255ec2bed55ea616eca1cf3941cc -size 32698 +oid sha256:ea52227dcdf0806e292971e1b7332db28964eadd242882a80b6486d691e9c2c9 +size 35219 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_3,NEXUS_5,1.0,en].png index f22a3b63e1..a130ba4351 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63e7bf82b003a869d6ccfbc5057ddfe0f447bd7eb31801df828c410744d15223 -size 32008 +oid sha256:0163f939dfc3103eff7ded67b2fbd31dd050f6a4150245c9f0fbf3c7d13daf3e +size 34806 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_4,NEXUS_5,1.0,en].png index be699c15b7..5b02de2ffb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00ca19bd4146e4b7937627a9aadd900cbf928e809996d7e1c950c0ed03aa888f -size 39635 +oid sha256:ab7c7c6ae6951bf173f24fddb70e99feacd92286b088a6921900c6b7e881000a +size 42122 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_5,NEXUS_5,1.0,en].png index a5fc0f06cd..0d6bc21206 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ea77bd41442b6686fcbe53a4ea1bb29a6040a78ceb8f6e5453fddc0b4ae6b35 -size 39892 +oid sha256:4063c0c9f763304aea54a73aaf95efc636d029cab45369b24a3bfc0b3bf3731d +size 42774 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_6,NEXUS_5,1.0,en].png index a5fc0f06cd..0d6bc21206 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ea77bd41442b6686fcbe53a4ea1bb29a6040a78ceb8f6e5453fddc0b4ae6b35 -size 39892 +oid sha256:4063c0c9f763304aea54a73aaf95efc636d029cab45369b24a3bfc0b3bf3731d +size 42774 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_7,NEXUS_5,1.0,en].png index 622259fef7..9f0e81e3c0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:beeb97ce692ae6592dd61769cfdf87f3fc3518548b02443e9ef6134e3dec01bb -size 43479 +oid sha256:dabfea9a1b7604977444e8fedc977a61ab5576c6115aa97bffd77ad1d5c65038 +size 46198 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_8,NEXUS_5,1.0,en].png index 00bab0f541..351feab0c8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a5ef5d83bc9dd2e3bef092d6620baaf0f0f94657f41961bb1cf2b547ae10415 -size 41931 +oid sha256:d1dfdb2a8cca036992e06a4d2a626c32d40c899e85bd5d5cf3aaba4272d339d8 +size 44244 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_9,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..66ac910cef --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetailsDark_null_RoomDetailsDark--1_1_null_9,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd7974c00bce66a8a1715e956c64aa578db303ff287aefb9dc5bb7906e4e27d5 +size 44406 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_0,NEXUS_5,1.0,en].png index 6ab436dec6..947b00cb3d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a6e83b3b6880ee6eec8731e42e744c0fe6f806cbad6c1925cb297e19c1b811b -size 43222 +oid sha256:258605b56cbfb5ade4ce390b7e38a37b0602612d45a751b0032e4ba757d8d76f +size 45766 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_1,NEXUS_5,1.0,en].png index 3bdb76b2d7..5d76493733 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3d669d780e1b7a3c741d5f6c8f20e8ffe80b283291f133672221cddcca192dc -size 31530 +oid sha256:c984e6333a0db11f1e52f246f019b927d97910dd35f5b79aae6d7c4f3094f913 +size 34667 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_2,NEXUS_5,1.0,en].png index 5a19953067..a640c79a22 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee48e602a928b83bb547efe6352c419d1bf71ea8490226884f715f3266d493e5 -size 34080 +oid sha256:613b0bd1053c46b45fe6b58c754b03c69bab0a6cc2d90cd99278b4786f1253b0 +size 36681 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_3,NEXUS_5,1.0,en].png index ab41b3c954..400b589f07 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22e8f719df1249e31f26022815930577f9baf135454ff260664fa65045c1b690 -size 32687 +oid sha256:97416e171d87d7290924270c175b44a10c2b25fd3541156bf9e5258975f0c3a4 +size 35627 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_4,NEXUS_5,1.0,en].png index 5a2f5937f0..14f86e1167 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee5dde97c04f0ccdc21349158150e11ad1a1b91c97bfd3aaefacc4e0e1a17a8a -size 40828 +oid sha256:28a271552ea0e61954646c47bd8e87048c92cace7ca4176901c7b4d000bbc53f +size 43375 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_5,NEXUS_5,1.0,en].png index 148b6503b7..c4fddf0f15 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bfd34cc11af2c77b25e19d4493db998d8cad0efed379dce3a9537b2c49fda28 -size 41039 +oid sha256:7a4ddad1a6f2afad344e8e88fd297c91a5064206b2dbcb1130dd15b5fe22fbc9 +size 44086 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_6,NEXUS_5,1.0,en].png index 148b6503b7..c4fddf0f15 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bfd34cc11af2c77b25e19d4493db998d8cad0efed379dce3a9537b2c49fda28 -size 41039 +oid sha256:7a4ddad1a6f2afad344e8e88fd297c91a5064206b2dbcb1130dd15b5fe22fbc9 +size 44086 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_7,NEXUS_5,1.0,en].png index c329d038db..49af7727ee 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:567e1b09ae9383c8af71c7d66d2877091c0ce3d98eab1d92c7bc9d2f896875bc -size 44629 +oid sha256:36e579b569043783e8890a6cf5ffa1ae511c524c20f923765825ab9a9ccf9bb4 +size 47592 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_8,NEXUS_5,1.0,en].png index 253475f636..3571ab69cd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb8863bcb78be69e715ec9bbf3ef7ee51fd142b484a52a22b381373708d5049e -size 43101 +oid sha256:2b27ecb48f63242353d750c5fb2eb5cf2fcb3c11edd046cc9c03db57f3d26122 +size 45472 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_9,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..693457317c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_9,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fb2205cfbb68ae2e808fe62765311b10921a8bd579e23803484a492de36d028 +size 45645 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Day-2_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Day-2_3_null,NEXUS_5,1.0,en].png index 4eb32e5121..a6140d1dba 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Day-2_3_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Day-2_3_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d636ef74890668b0d2cc49af71b1e3734237fa1f9944afb89d8152cb9a839cd4 -size 17391 +oid sha256:404fc390042c6d7870e2fbef176eb497ac3fb474c53483c085cd04038d0192ef +size 21918 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Night-2_4_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Night-2_4_null,NEXUS_5,1.0,en].png index 676aa25413..698bd9c32b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Night-2_4_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Night-2_4_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c883b6e8c7ae3570f2d7370a929cc9afd2a1983bc6064f50090ad8e8c9295d4 -size 16289 +oid sha256:4791d9b6fa50a29c10b618d27786033b69983229a9ce2e10b83e35cbe4a6365a +size 20671 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Day-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Day-1_2_null,NEXUS_5,1.0,en].png index 115580282c..9ecd0d5c68 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Day-1_2_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Day-1_2_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6b6fe672b31db43d1aeed7d0835d638b729c09ac01b03fcdfd814b2e1d861d5 -size 15553 +oid sha256:d3b0fe4d5ca9f4b735ed43ea167c238d2086a2aa6f8760fe74841bc6f0a1be6c +size 20289 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Night-1_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Night-1_3_null,NEXUS_5,1.0,en].png index f4410234ee..87fa39e373 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Night-1_3_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Night-1_3_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:178e6f2f9aa771831eb3f672cf212d5b3a37532c02dcc5126446965b49be7bdd -size 14558 +oid sha256:1d80d0580828e0f657ff2f36915c2d0a7a9d56612007da87c3b9676f882131bc +size 19023 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en].png index cdfb465771..4e360c7666 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2523a28a889fd1739fe1e0c94edce5d86a5ebcbe6c3973a3c49fbb8fb8a19f79 -size 55617 +oid sha256:5f8802121f779f48dd556a9a3a08e6ac1a63e86d5695f6f8c133fbb346d76ef6 +size 89779 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,en].png index 06846f3231..cdfb465771 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13dadbd502163a9bfe81cb1e67f9aa0933c4b7f4fd3c8f731e930a28709485d2 -size 51948 +oid sha256:2523a28a889fd1739fe1e0c94edce5d86a5ebcbe6c3973a3c49fbb8fb8a19f79 +size 55617 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,en].png index f901e915e5..06846f3231 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64c4eb481f40871925405ae317cb80927caf31cb552a8aa9549bfb5658ca91e4 -size 137589 +oid sha256:13dadbd502163a9bfe81cb1e67f9aa0933c4b7f4fd3c8f731e930a28709485d2 +size 51948 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f901e915e5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64c4eb481f40871925405ae317cb80927caf31cb552a8aa9549bfb5658ca91e4 +size 137589 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en].png index 4e360c7666..2f2ac0e0c0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f8802121f779f48dd556a9a3a08e6ac1a63e86d5695f6f8c133fbb346d76ef6 -size 89779 +oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0 +size 4462 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en].png index a7fd32602e..a751592e9b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b25e023153dd094299a0c794d5e488bdc6fc0c478abbfbce7337e52b233715d -size 57463 +oid sha256:7cdd93c157565f7f8d3fa23a43c30e02531dab40578a315c5d0d10a99e74f259 +size 91394 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_11,NEXUS_5,1.0,en].png index 65b76802d2..a7fd32602e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a00391ef16a762a14dd7e348ce97b500ca1b42a32ee3ff926a10865f46cd06c -size 53590 +oid sha256:2b25e023153dd094299a0c794d5e488bdc6fc0c478abbfbce7337e52b233715d +size 57463 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_12,NEXUS_5,1.0,en].png index 56f7f133f3..65b76802d2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_12,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_12,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7bf47b0a25c455b108d7b3585a42849f03c6652af73a175fe2147cb1ad62a66 -size 161125 +oid sha256:0a00391ef16a762a14dd7e348ce97b500ca1b42a32ee3ff926a10865f46cd06c +size 53590 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_13,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..56f7f133f3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_13,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7bf47b0a25c455b108d7b3585a42849f03c6652af73a175fe2147cb1ad62a66 +size 161125 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en].png index a751592e9b..2f2ac0e0c0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7cdd93c157565f7f8d3fa23a43c30e02531dab40578a315c5d0d10a99e74f259 -size 91394 +oid sha256:3764d8bd7dc2783a8af43aad65a217d7e533ed17c4d4367b7994470bf35b62b0 +size 4462 From fc706bb9d63beb66c91999d047be3ac5e22a66b5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 15 Feb 2024 12:24:32 +0100 Subject: [PATCH 10/13] favorite : fix CI --- .../features/roomdetails/RoomDetailsPresenterTests.kt | 1 - .../android/features/roomlist/impl/RoomListPresenterTests.kt | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt index c8a3b17835..6840a09d1e 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -39,7 +39,6 @@ import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.StateEventType diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index 25e6d89b78..f106b72fb9 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -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 @@ -367,7 +368,9 @@ class RoomListPresenterTests { ) } - room.setIsFavorite(isFavorite = true) + room.givenRoomInfo( + aRoomInfo(isFavorite = true) + ) awaitItem().also { state -> assertThat(state.contextMenu) .isEqualTo( From 6ef50081bd22401e8a67f8122f41325987cfd0da Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 15 Feb 2024 13:22:07 +0100 Subject: [PATCH 11/13] favorite : apply pr review remarks --- .../roomdetails/impl/RoomDetailsEvent.kt | 2 +- .../roomdetails/impl/RoomDetailsPresenter.kt | 2 +- .../roomdetails/impl/RoomDetailsView.kt | 2 +- .../roomdetails/RoomDetailsPresenterTests.kt | 4 +-- .../roomlist/impl/RoomListPresenter.kt | 23 ++++++++++--- .../libraries/architecture/coroutine/Job.kt | 34 ------------------- 6 files changed, 23 insertions(+), 44 deletions(-) delete mode 100644 libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coroutine/Job.kt diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt index db3d3dfe2d..3e7fc96c68 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt @@ -20,5 +20,5 @@ sealed interface RoomDetailsEvent { data object LeaveRoom : RoomDetailsEvent data object MuteNotification : RoomDetailsEvent data object UnmuteNotification : RoomDetailsEvent - data class SetIsFavorite(val isFavorite: Boolean) : RoomDetailsEvent + data class SetFavorite(val isFavorite: Boolean) : RoomDetailsEvent } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 52cb7a9d05..2c663bfdde 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -124,7 +124,7 @@ class RoomDetailsPresenter @Inject constructor( client.notificationSettingsService().unmuteRoom(room.roomId, room.isEncrypted, room.isOneToOne) } } - is RoomDetailsEvent.SetIsFavorite -> { + is RoomDetailsEvent.SetFavorite -> { scope.launch { room.setIsFavorite(event.isFavorite) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index df6b969095..efa8e6e321 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -167,7 +167,7 @@ fun RoomDetailsView( FavoriteSection( isFavorite = state.isFavorite, onFavoriteChanges = { - state.eventSink(RoomDetailsEvent.SetIsFavorite(it)) + state.eventSink(RoomDetailsEvent.SetFavorite(it)) } ) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt index 6840a09d1e..1faccce70e 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -438,9 +438,9 @@ class RoomDetailsPresenterTests { val presenter = createRoomDetailsPresenter(room = room) presenter.test { val initialState = awaitItem() - initialState.eventSink(RoomDetailsEvent.SetIsFavorite(true)) + initialState.eventSink(RoomDetailsEvent.SetFavorite(true)) assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true)) - initialState.eventSink(RoomDetailsEvent.SetIsFavorite(false)) + initialState.eventSink(RoomDetailsEvent.SetFavorite(false)) assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false)) cancelAndIgnoreRemainingEvents() } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index ca81dafc28..69f43957bf 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -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 @@ -38,8 +39,6 @@ import io.element.android.features.roomlist.impl.datasource.RoomListDataSource import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.architecture.coroutine.cancel -import io.element.android.libraries.architecture.coroutine.rememberJob import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState import io.element.android.libraries.featureflag.api.FeatureFlagService @@ -53,9 +52,12 @@ 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.first +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch import javax.inject.Inject @@ -120,7 +122,6 @@ class RoomListPresenter @Inject constructor( var displaySearchResults by rememberSaveable { mutableStateOf(false) } val contextMenu = remember { mutableStateOf(RoomListState.ContextMenu.Hidden) } - val showContextMenuJob = rememberJob() fun handleEvents(event: RoomListEvents) { when (event) { @@ -135,10 +136,9 @@ class RoomListPresenter @Inject constructor( displaySearchResults = !displaySearchResults } is RoomListEvents.ShowContextMenu -> { - showContextMenuJob.value = coroutineScope.showContextMenu(event, contextMenu) + coroutineScope.showContextMenu(event, contextMenu) } is RoomListEvents.HideContextMenu -> { - showContextMenuJob.cancel() contextMenu.value = RoomListState.ContextMenu.Hidden } is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(LeaveRoomEvent.ShowConfirmation(event.roomId)) @@ -192,6 +192,7 @@ class RoomListPresenter @Inject constructor( matrixUser.value = client.getCurrentUser() } + @OptIn(ExperimentalCoroutinesApi::class) private fun CoroutineScope.showContextMenu(event: RoomListEvents.ShowContextMenu, contextMenuState: MutableState) = launch { val initialState = RoomListState.ContextMenu.Shown( roomId = event.roomListRoomSummary.roomId, @@ -202,13 +203,25 @@ class RoomListPresenter @Inject constructor( hasNewContent = event.roomListRoomSummary.hasNewContent ) contextMenuState.value = initialState + client.getRoom(event.roomListRoomSummary.roomId)?.use { room -> + + val isContextMenuShownFlow = snapshotFlow { + contextMenuState.value is RoomListState.ContextMenu.Shown + } + room.roomInfoFlow .onEach { roomInfo -> contextMenuState.value = initialState.copy( isFavorite = roomInfo.isFavorite, ) } + .flatMapLatest { + isContextMenuShownFlow + } + .takeWhile { isContextMenuShown -> + isContextMenuShown + } .collect() } } diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coroutine/Job.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coroutine/Job.kt deleted file mode 100644 index bf3d4f783e..0000000000 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coroutine/Job.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 - * - * http://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.architecture.coroutine - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import kotlinx.coroutines.Job -import kotlin.coroutines.cancellation.CancellationException - -@Composable -fun rememberJob(): MutableState = remember { - mutableStateOf(null) -} - -fun MutableState.cancel(cause: CancellationException? = null) { - value?.cancel(cause) - value = null -} From 25047f709340f2f6a4e6069110e55d5164bf501c Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 15 Feb 2024 15:54:53 +0100 Subject: [PATCH 12/13] favorite : use distinctUntilChanged --- .../roomlist/impl/RoomListPresenter.kt | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 69f43957bf..6f692621b3 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -54,8 +54,10 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS 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 @@ -206,23 +208,21 @@ class RoomListPresenter @Inject constructor( client.getRoom(event.roomListRoomSummary.roomId)?.use { room -> - val isContextMenuShownFlow = snapshotFlow { - contextMenuState.value is RoomListState.ContextMenu.Shown - } + val isShowingContextMenuFlow = snapshotFlow { contextMenuState.value is RoomListState.ContextMenu.Shown } + .distinctUntilChanged() - room.roomInfoFlow - .onEach { roomInfo -> - contextMenuState.value = initialState.copy( - isFavorite = roomInfo.isFavorite, - ) - } - .flatMapLatest { - isContextMenuShownFlow - } - .takeWhile { isContextMenuShown -> - isContextMenuShown + val isFavoriteFlow = room.roomInfoFlow + .map { it.isFavorite } + .distinctUntilChanged() + + isFavoriteFlow + .onEach { isFavorite -> + contextMenuState.value = initialState.copy(isFavorite = isFavorite) } + .flatMapLatest { isShowingContextMenuFlow } + .takeWhile { isShowingContextMenu -> isShowingContextMenu } .collect() + } } From 3893db3c21cf90aa3a436662cc298c7802c955aa Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 15 Feb 2024 16:23:05 +0100 Subject: [PATCH 13/13] favorites : fix formating --- .../element/android/features/roomlist/impl/RoomListPresenter.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 6f692621b3..68ef53703a 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -222,7 +222,6 @@ class RoomListPresenter @Inject constructor( .flatMapLatest { isShowingContextMenuFlow } .takeWhile { isShowingContextMenu -> isShowingContextMenu } .collect() - } }