Merge pull request #6097 from element-hq/feature/bma/favoriteWording

Improve favorite wording and icon of room
This commit is contained in:
Benoit Marty
2026-01-28 17:16:53 +01:00
committed by GitHub
53 changed files with 305 additions and 382 deletions

View File

@@ -10,7 +10,7 @@ package io.element.android.features.home.impl
import io.element.android.libraries.matrix.api.core.SessionId
sealed interface HomeEvents {
data class SelectHomeNavigationBarItem(val item: HomeNavigationBarItem) : HomeEvents
data class SwitchToAccount(val sessionId: SessionId) : HomeEvents
sealed interface HomeEvent {
data class SelectHomeNavigationBarItem(val item: HomeNavigationBarItem) : HomeEvent
data class SwitchToAccount(val sessionId: SessionId) : HomeEvent
}

View File

@@ -34,7 +34,7 @@ import io.element.android.annotations.ContributesNode
import io.element.android.features.home.api.HomeEntryPoint
import io.element.android.features.home.impl.components.RoomListMenuAction
import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.roomlist.RoomListEvents
import io.element.android.features.home.impl.roomlist.RoomListEvent
import io.element.android.features.invite.api.InviteData
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteView
import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBlockEntryPoint
@@ -159,7 +159,7 @@ class HomeFlowNode(
}
private fun onNewOwnersSelected(roomId: RoomId) {
stateFlow.value.roomListState.eventSink(RoomListEvents.LeaveRoom(roomId, needsConfirmation = false))
stateFlow.value.roomListState.eventSink(RoomListEvent.LeaveRoom(roomId, needsConfirmation = false))
}
private fun rootNode(buildContext: BuildContext): Node {

View File

@@ -80,15 +80,15 @@ class HomePresenter(
val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator()
val directLogoutState = logoutPresenter.present()
fun handleEvent(event: HomeEvents) {
fun handleEvent(event: HomeEvent) {
when (event) {
is HomeEvents.SelectHomeNavigationBarItem -> coroutineState.launch {
is HomeEvent.SelectHomeNavigationBarItem -> coroutineState.launch {
if (event.item == HomeNavigationBarItem.Spaces) {
announcementService.showAnnouncement(Announcement.Space)
}
currentHomeNavigationBarItemOrdinal = event.item.ordinal
}
is HomeEvents.SwitchToAccount -> coroutineState.launch {
is HomeEvent.SwitchToAccount -> coroutineState.launch {
sessionStore.setLatestSession(event.sessionId.value)
}
}

View File

@@ -29,7 +29,7 @@ data class HomeState(
val snackbarMessage: SnackbarMessage?,
val canReportBug: Boolean,
val directLogoutState: DirectLogoutState,
val eventSink: (HomeEvents) -> Unit,
val eventSink: (HomeEvent) -> Unit,
) {
val displayActions = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats
val displayRoomListFilters = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats && roomListState.displayFilters

View File

@@ -59,7 +59,7 @@ internal fun aHomeState(
homeSpacesState: HomeSpacesState = aHomeSpacesState(),
canReportBug: Boolean = true,
directLogoutState: DirectLogoutState = aDirectLogoutState(),
eventSink: (HomeEvents) -> Unit = {}
eventSink: (HomeEvent) -> Unit = {}
) = HomeState(
currentUserAndNeighbors = currentUserAndNeighbors.toImmutableList(),
showAvatarIndicator = showAvatarIndicator,

View File

@@ -47,7 +47,7 @@ import io.element.android.features.home.impl.components.RoomListMenuAction
import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.roomlist.RoomListContextMenu
import io.element.android.features.home.impl.roomlist.RoomListDeclineInviteMenu
import io.element.android.features.home.impl.roomlist.RoomListEvents
import io.element.android.features.home.impl.roomlist.RoomListEvent
import io.element.android.features.home.impl.roomlist.RoomListState
import io.element.android.features.home.impl.search.RoomListSearchView
import io.element.android.features.home.impl.spaces.HomeSpacesView
@@ -156,7 +156,7 @@ private fun HomeScaffold(
BackHandler(
enabled = state.currentHomeNavigationBarItem != HomeNavigationBarItem.Chats,
) {
state.eventSink(HomeEvents.SelectHomeNavigationBarItem(HomeNavigationBarItem.Chats))
state.eventSink(HomeEvent.SelectHomeNavigationBarItem(HomeNavigationBarItem.Chats))
}
val hazeState = rememberHazeState()
@@ -172,11 +172,11 @@ private fun HomeScaffold(
currentUserAndNeighbors = state.currentUserAndNeighbors,
showAvatarIndicator = state.showAvatarIndicator,
areSearchResultsDisplayed = roomListState.searchState.isSearchActive,
onToggleSearch = { roomListState.eventSink(RoomListEvents.ToggleSearchResults) },
onToggleSearch = { roomListState.eventSink(RoomListEvent.ToggleSearchResults) },
onMenuActionClick = onMenuActionClick,
onOpenSettings = onOpenSettings,
onAccountSwitch = {
state.eventSink(HomeEvents.SwitchToAccount(it))
state.eventSink(HomeEvent.SwitchToAccount(it))
},
onCreateSpace = onCreateSpaceClick,
scrollBehavior = scrollBehavior,
@@ -211,7 +211,7 @@ private fun HomeScaffold(
lazyListStateTarget.animateScrollToItem(0)
}
} else {
state.eventSink(HomeEvents.SelectHomeNavigationBarItem(item))
state.eventSink(HomeEvent.SelectHomeNavigationBarItem(item))
}
},
modifier = Modifier.hazeEffect(

View File

@@ -47,7 +47,7 @@ import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.model.RoomSummaryDisplayType
import io.element.android.features.home.impl.roomlist.RoomListContentState
import io.element.android.features.home.impl.roomlist.RoomListContentStateProvider
import io.element.android.features.home.impl.roomlist.RoomListEvents
import io.element.android.features.home.impl.roomlist.RoomListEvent
import io.element.android.features.home.impl.roomlist.SecurityBannerState
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@@ -64,7 +64,7 @@ fun RoomListContentView(
filtersState: RoomListFiltersState,
lazyListState: LazyListState,
hideInvitesAvatars: Boolean,
eventSink: (RoomListEvents) -> Unit,
eventSink: (RoomListEvent) -> Unit,
onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit,
onRoomClick: (RoomListRoomSummary) -> Unit,
@@ -131,7 +131,7 @@ private fun SkeletonView(
@Composable
private fun EmptyView(
state: RoomListContentState.Empty,
eventSink: (RoomListEvents) -> Unit,
eventSink: (RoomListEvent) -> Unit,
onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit,
onCreateRoomClick: () -> Unit,
@@ -155,13 +155,13 @@ private fun EmptyView(
SecurityBannerState.SetUpRecovery -> {
SetUpRecoveryKeyBanner(
onContinueClick = onSetUpRecoveryClick,
onDismissClick = { eventSink(RoomListEvents.DismissBanner) },
onDismissClick = { eventSink(RoomListEvent.DismissBanner) },
)
}
SecurityBannerState.RecoveryKeyConfirmation -> {
ConfirmRecoveryKeyBanner(
onContinueClick = onConfirmRecoveryKeyClick,
onDismissClick = { eventSink(RoomListEvents.DismissBanner) },
onDismissClick = { eventSink(RoomListEvent.DismissBanner) },
)
}
SecurityBannerState.None -> Unit
@@ -175,7 +175,7 @@ private fun RoomsView(
state: RoomListContentState.Rooms,
hideInvitesAvatars: Boolean,
filtersState: RoomListFiltersState,
eventSink: (RoomListEvents) -> Unit,
eventSink: (RoomListEvent) -> Unit,
onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit,
onRoomClick: (RoomListRoomSummary) -> Unit,
@@ -207,7 +207,7 @@ private fun RoomsView(
private fun RoomsViewList(
state: RoomListContentState.Rooms,
hideInvitesAvatars: Boolean,
eventSink: (RoomListEvents) -> Unit,
eventSink: (RoomListEvent) -> Unit,
onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit,
onRoomClick: (RoomListRoomSummary) -> Unit,
@@ -225,7 +225,7 @@ private fun RoomsViewList(
}
val updatedEventSink by rememberUpdatedState(newValue = eventSink)
LaunchedEffect(visibleRange) {
updatedEventSink(RoomListEvents.UpdateVisibleRange(visibleRange))
updatedEventSink(RoomListEvent.UpdateVisibleRange(visibleRange))
}
LazyColumn(
state = lazyListState,
@@ -237,7 +237,7 @@ private fun RoomsViewList(
item {
SetUpRecoveryKeyBanner(
onContinueClick = onSetUpRecoveryClick,
onDismissClick = { updatedEventSink(RoomListEvents.DismissBanner) },
onDismissClick = { updatedEventSink(RoomListEvent.DismissBanner) },
)
}
}
@@ -245,7 +245,7 @@ private fun RoomsViewList(
item {
ConfirmRecoveryKeyBanner(
onContinueClick = onConfirmRecoveryKeyClick,
onDismissClick = { updatedEventSink(RoomListEvents.DismissBanner) },
onDismissClick = { updatedEventSink(RoomListEvent.DismissBanner) },
)
}
}
@@ -260,7 +260,7 @@ private fun RoomsViewList(
} else if (state.showNewNotificationSoundBanner) {
item {
NewNotificationSoundBanner(
onDismissClick = { updatedEventSink(RoomListEvents.DismissNewNotificationSoundBanner) },
onDismissClick = { updatedEventSink(RoomListEvent.DismissNewNotificationSoundBanner) },
)
}
}

View File

@@ -44,7 +44,7 @@ import io.element.android.features.home.impl.model.LatestEvent
import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.model.RoomListRoomSummaryProvider
import io.element.android.features.home.impl.model.RoomSummaryDisplayType
import io.element.android.features.home.impl.roomlist.RoomListEvents
import io.element.android.features.home.impl.roomlist.RoomListEvent
import io.element.android.libraries.core.extensions.orEmpty
import io.element.android.libraries.core.extensions.toSafeLength
import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom
@@ -74,7 +74,7 @@ internal fun RoomSummaryRow(
hideInviteAvatars: Boolean,
isInviteSeen: Boolean,
onClick: (RoomListRoomSummary) -> Unit,
eventSink: (RoomListEvents) -> Unit,
eventSink: (RoomListEvent) -> Unit,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier) {
@@ -104,10 +104,10 @@ internal fun RoomSummaryRow(
Spacer(modifier = Modifier.height(12.dp))
InviteButtonsRowMolecule(
onAcceptClick = {
eventSink(RoomListEvents.AcceptInvite(room))
eventSink(RoomListEvent.AcceptInvite(room))
},
onDeclineClick = {
eventSink(RoomListEvents.ShowDeclineInviteMenu(room))
eventSink(RoomListEvent.ShowDeclineInviteMenu(room))
}
)
}
@@ -117,7 +117,7 @@ internal fun RoomSummaryRow(
room = room,
onClick = onClick,
onLongClick = {
eventSink(RoomListEvents.ShowContextMenu(room))
eventSink(RoomListEvent.ShowContextMenu(room))
},
) {
NameAndTimestampRow(

View File

@@ -8,7 +8,7 @@
package io.element.android.features.home.impl.filters
sealed interface RoomListFiltersEvents {
data class ToggleFilter(val filter: RoomListFilter) : RoomListFiltersEvents
data object ClearSelectedFilters : RoomListFiltersEvents
sealed interface RoomListFiltersEvent {
data class ToggleFilter(val filter: RoomListFilter) : RoomListFiltersEvent
data object ClearSelectedFilters : RoomListFiltersEvent
}

View File

@@ -28,12 +28,12 @@ class RoomListFiltersPresenter(
@Composable
override fun present(): RoomListFiltersState {
fun handleEvent(event: RoomListFiltersEvents) {
fun handleEvent(event: RoomListFiltersEvent) {
when (event) {
RoomListFiltersEvents.ClearSelectedFilters -> {
RoomListFiltersEvent.ClearSelectedFilters -> {
filterSelectionStrategy.clear()
}
is RoomListFiltersEvents.ToggleFilter -> {
is RoomListFiltersEvent.ToggleFilter -> {
filterSelectionStrategy.toggle(event.filter)
}
}

View File

@@ -14,7 +14,7 @@ import kotlinx.collections.immutable.toImmutableList
data class RoomListFiltersState(
val filterSelectionStates: ImmutableList<FilterSelectionState>,
val eventSink: (RoomListFiltersEvents) -> Unit,
val eventSink: (RoomListFiltersEvent) -> Unit,
) {
val hasAnyFilterSelected = filterSelectionStates.any { it.isSelected }

View File

@@ -24,7 +24,7 @@ class RoomListFiltersStateProvider : PreviewParameterProvider<RoomListFiltersSta
fun aRoomListFiltersState(
filterSelectionStates: List<FilterSelectionState> = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = false) },
eventSink: (RoomListFiltersEvents) -> Unit = {},
eventSink: (RoomListFiltersEvent) -> Unit = {},
) = RoomListFiltersState(
filterSelectionStates = filterSelectionStates.toImmutableList(),
eventSink = eventSink,

View File

@@ -60,11 +60,11 @@ fun RoomListFiltersView(
modifier: Modifier = Modifier
) {
fun onClearFiltersClick() {
state.eventSink(RoomListFiltersEvents.ClearSelectedFilters)
state.eventSink(RoomListFiltersEvent.ClearSelectedFilters)
}
fun onToggleFilter(filter: RoomListFilter) {
state.eventSink(RoomListFiltersEvents.ToggleFilter(filter))
state.eventSink(RoomListFiltersEvent.ToggleFilter(filter))
}
var scrollToStart by remember { mutableIntStateOf(0) }

View File

@@ -37,41 +37,41 @@ import io.element.android.libraries.ui.strings.CommonStrings
fun RoomListContextMenu(
contextMenu: RoomListState.ContextMenu.Shown,
canReportRoom: Boolean,
eventSink: (RoomListEvents.ContextMenuEvents) -> Unit,
eventSink: (RoomListEvent.ContextMenuEvent) -> Unit,
onRoomSettingsClick: (roomId: RoomId) -> Unit,
onReportRoomClick: (roomId: RoomId) -> Unit
) {
ModalBottomSheet(
onDismissRequest = { eventSink(RoomListEvents.HideContextMenu) },
onDismissRequest = { eventSink(RoomListEvent.HideContextMenu) },
) {
RoomListModalBottomSheetContent(
contextMenu = contextMenu,
canReportRoom = canReportRoom,
onRoomMarkReadClick = {
eventSink(RoomListEvents.HideContextMenu)
eventSink(RoomListEvents.MarkAsRead(contextMenu.roomId))
eventSink(RoomListEvent.HideContextMenu)
eventSink(RoomListEvent.MarkAsRead(contextMenu.roomId))
},
onRoomMarkUnreadClick = {
eventSink(RoomListEvents.HideContextMenu)
eventSink(RoomListEvents.MarkAsUnread(contextMenu.roomId))
eventSink(RoomListEvent.HideContextMenu)
eventSink(RoomListEvent.MarkAsUnread(contextMenu.roomId))
},
onRoomSettingsClick = {
eventSink(RoomListEvents.HideContextMenu)
eventSink(RoomListEvent.HideContextMenu)
onRoomSettingsClick(contextMenu.roomId)
},
onLeaveRoomClick = {
eventSink(RoomListEvents.HideContextMenu)
eventSink(RoomListEvents.LeaveRoom(contextMenu.roomId, needsConfirmation = true))
eventSink(RoomListEvent.HideContextMenu)
eventSink(RoomListEvent.LeaveRoom(contextMenu.roomId, needsConfirmation = true))
},
onFavoriteChange = { isFavorite ->
eventSink(RoomListEvents.SetRoomIsFavorite(contextMenu.roomId, isFavorite))
eventSink(RoomListEvent.SetRoomIsFavorite(contextMenu.roomId, isFavorite))
},
onClearCacheRoomClick = {
eventSink(RoomListEvents.HideContextMenu)
eventSink(RoomListEvents.ClearCacheOfRoom(contextMenu.roomId))
eventSink(RoomListEvent.HideContextMenu)
eventSink(RoomListEvent.ClearCacheOfRoom(contextMenu.roomId))
},
onReportRoomClick = {
eventSink(RoomListEvents.HideContextMenu)
eventSink(RoomListEvent.HideContextMenu)
onReportRoomClick(contextMenu.roomId)
},
)
@@ -131,16 +131,21 @@ private fun RoomListModalBottomSheetContent(
style = ListItemStyle.Primary,
)
}
val (textResId, icon) = if (contextMenu.isFavorite) {
CommonStrings.common_favourited to CompoundIcons.FavouriteSolid()
} else {
CommonStrings.common_favourite to CompoundIcons.Favourite()
}
ListItem(
headlineContent = {
Text(
text = stringResource(id = CommonStrings.common_favourite),
text = stringResource(id = textResId),
style = MaterialTheme.typography.bodyLarge,
)
},
leadingContent = ListItemContent.Icon(
iconSource = IconSource.Vector(
CompoundIcons.Favourite(),
icon,
)
),
trailingContent = ListItemContent.Switch(

View File

@@ -38,27 +38,27 @@ fun RoomListDeclineInviteMenu(
menu: RoomListState.DeclineInviteMenu.Shown,
canReportRoom: Boolean,
onDeclineAndBlockClick: (RoomListRoomSummary) -> Unit,
eventSink: (RoomListEvents) -> Unit,
eventSink: (RoomListEvent) -> Unit,
) {
ModalBottomSheet(
onDismissRequest = { eventSink(RoomListEvents.HideDeclineInviteMenu) },
onDismissRequest = { eventSink(RoomListEvent.HideDeclineInviteMenu) },
) {
RoomListDeclineInviteMenuContent(
roomName = menu.roomSummary.name ?: menu.roomSummary.roomId.value,
onDeclineClick = {
eventSink(RoomListEvents.HideDeclineInviteMenu)
eventSink(RoomListEvents.DeclineInvite(menu.roomSummary, false))
eventSink(RoomListEvent.HideDeclineInviteMenu)
eventSink(RoomListEvent.DeclineInvite(menu.roomSummary, false))
},
onDeclineAndBlockClick = {
eventSink(RoomListEvents.HideDeclineInviteMenu)
eventSink(RoomListEvent.HideDeclineInviteMenu)
if (canReportRoom) {
onDeclineAndBlockClick(menu.roomSummary)
} else {
eventSink(RoomListEvents.DeclineInvite(menu.roomSummary, true))
eventSink(RoomListEvent.DeclineInvite(menu.roomSummary, true))
}
},
onCancelClick = {
eventSink(RoomListEvents.HideDeclineInviteMenu)
eventSink(RoomListEvent.HideDeclineInviteMenu)
}
)
}

View File

@@ -11,24 +11,24 @@ package io.element.android.features.home.impl.roomlist
import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.libraries.matrix.api.core.RoomId
sealed interface RoomListEvents {
data class UpdateVisibleRange(val range: IntRange) : RoomListEvents
data object DismissRequestVerificationPrompt : RoomListEvents
data object DismissBanner : RoomListEvents
data object DismissNewNotificationSoundBanner : RoomListEvents
data object ToggleSearchResults : RoomListEvents
data class ShowContextMenu(val roomSummary: RoomListRoomSummary) : RoomListEvents
sealed interface RoomListEvent {
data class UpdateVisibleRange(val range: IntRange) : RoomListEvent
data object DismissRequestVerificationPrompt : RoomListEvent
data object DismissBanner : RoomListEvent
data object DismissNewNotificationSoundBanner : RoomListEvent
data object ToggleSearchResults : RoomListEvent
data class ShowContextMenu(val roomSummary: RoomListRoomSummary) : RoomListEvent
data class AcceptInvite(val roomSummary: RoomListRoomSummary) : RoomListEvents
data class DeclineInvite(val roomSummary: RoomListRoomSummary, val blockUser: Boolean) : RoomListEvents
data class ShowDeclineInviteMenu(val roomSummary: RoomListRoomSummary) : RoomListEvents
data object HideDeclineInviteMenu : RoomListEvents
data class AcceptInvite(val roomSummary: RoomListRoomSummary) : RoomListEvent
data class DeclineInvite(val roomSummary: RoomListRoomSummary, val blockUser: Boolean) : RoomListEvent
data class ShowDeclineInviteMenu(val roomSummary: RoomListRoomSummary) : RoomListEvent
data object HideDeclineInviteMenu : RoomListEvent
sealed interface ContextMenuEvents : RoomListEvents
data object HideContextMenu : ContextMenuEvents
data class LeaveRoom(val roomId: RoomId, val needsConfirmation: Boolean) : 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
data class ClearCacheOfRoom(val roomId: RoomId) : ContextMenuEvents
sealed interface ContextMenuEvent : RoomListEvent
data object HideContextMenu : ContextMenuEvent
data class LeaveRoom(val roomId: RoomId, val needsConfirmation: Boolean) : ContextMenuEvent
data class MarkAsRead(val roomId: RoomId) : ContextMenuEvent
data class MarkAsUnread(val roomId: RoomId) : ContextMenuEvent
data class SetRoomIsFavorite(val roomId: RoomId, val isFavorite: Boolean) : ContextMenuEvent
data class ClearCacheOfRoom(val roomId: RoomId) : ContextMenuEvent
}

View File

@@ -29,7 +29,7 @@ import io.element.android.features.announcement.api.Announcement
import io.element.android.features.announcement.api.AnnouncementService
import io.element.android.features.home.impl.datasource.RoomListDataSource
import io.element.android.features.home.impl.filters.RoomListFiltersState
import io.element.android.features.home.impl.search.RoomListSearchEvents
import io.element.android.features.home.impl.search.RoomListSearchEvent
import io.element.android.features.home.impl.search.RoomListSearchState
import io.element.android.features.invite.api.SeenInvitesStore
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents.AcceptInvite
@@ -116,42 +116,42 @@ class RoomListPresenter(
val contextMenu = remember { mutableStateOf<RoomListState.ContextMenu>(RoomListState.ContextMenu.Hidden) }
val declineInviteMenu = remember { mutableStateOf<RoomListState.DeclineInviteMenu>(RoomListState.DeclineInviteMenu.Hidden) }
fun handleEvent(event: RoomListEvents) {
fun handleEvent(event: RoomListEvent) {
when (event) {
is RoomListEvents.UpdateVisibleRange -> coroutineScope.launch {
is RoomListEvent.UpdateVisibleRange -> coroutineScope.launch {
updateVisibleRange(event.range)
}
RoomListEvents.DismissRequestVerificationPrompt -> securityBannerDismissed = true
RoomListEvents.DismissBanner -> securityBannerDismissed = true
RoomListEvents.DismissNewNotificationSoundBanner -> coroutineScope.launch {
RoomListEvent.DismissRequestVerificationPrompt -> securityBannerDismissed = true
RoomListEvent.DismissBanner -> securityBannerDismissed = true
RoomListEvent.DismissNewNotificationSoundBanner -> coroutineScope.launch {
announcementService.onAnnouncementDismissed(Announcement.NewNotificationSound)
}
RoomListEvents.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
is RoomListEvents.ShowContextMenu -> {
RoomListEvent.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvent.ToggleSearchVisibility)
is RoomListEvent.ShowContextMenu -> {
coroutineScope.showContextMenu(event, contextMenu)
}
is RoomListEvents.HideContextMenu -> {
is RoomListEvent.HideContextMenu -> {
contextMenu.value = RoomListState.ContextMenu.Hidden
}
is RoomListEvents.LeaveRoom -> {
is RoomListEvent.LeaveRoom -> {
leaveRoomState.eventSink(LeaveRoomEvent.LeaveRoom(event.roomId, needsConfirmation = event.needsConfirmation))
}
is RoomListEvents.SetRoomIsFavorite -> coroutineScope.setRoomIsFavorite(event.roomId, event.isFavorite)
is RoomListEvents.MarkAsRead -> coroutineScope.markAsRead(event.roomId)
is RoomListEvents.MarkAsUnread -> coroutineScope.markAsUnread(event.roomId)
is RoomListEvents.AcceptInvite -> {
is RoomListEvent.SetRoomIsFavorite -> coroutineScope.setRoomIsFavorite(event.roomId, event.isFavorite)
is RoomListEvent.MarkAsRead -> coroutineScope.markAsRead(event.roomId)
is RoomListEvent.MarkAsUnread -> coroutineScope.markAsUnread(event.roomId)
is RoomListEvent.AcceptInvite -> {
acceptDeclineInviteState.eventSink(
AcceptInvite(event.roomSummary.toInviteData())
)
}
is RoomListEvents.DeclineInvite -> {
is RoomListEvent.DeclineInvite -> {
acceptDeclineInviteState.eventSink(
DeclineInvite(event.roomSummary.toInviteData(), blockUser = event.blockUser, shouldConfirm = false)
)
}
is RoomListEvents.ShowDeclineInviteMenu -> declineInviteMenu.value = RoomListState.DeclineInviteMenu.Shown(event.roomSummary)
RoomListEvents.HideDeclineInviteMenu -> declineInviteMenu.value = RoomListState.DeclineInviteMenu.Hidden
is RoomListEvents.ClearCacheOfRoom -> coroutineScope.clearCacheOfRoom(event.roomId)
is RoomListEvent.ShowDeclineInviteMenu -> declineInviteMenu.value = RoomListState.DeclineInviteMenu.Shown(event.roomSummary)
RoomListEvent.HideDeclineInviteMenu -> declineInviteMenu.value = RoomListState.DeclineInviteMenu.Hidden
is RoomListEvent.ClearCacheOfRoom -> coroutineScope.clearCacheOfRoom(event.roomId)
}
}
@@ -253,7 +253,7 @@ class RoomListPresenter(
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun CoroutineScope.showContextMenu(event: RoomListEvents.ShowContextMenu, contextMenuState: MutableState<RoomListState.ContextMenu>) = launch {
private fun CoroutineScope.showContextMenu(event: RoomListEvent.ShowContextMenu, contextMenuState: MutableState<RoomListState.ContextMenu>) = launch {
val initialState = RoomListState.ContextMenu.Shown(
roomId = event.roomSummary.roomId,
roomName = event.roomSummary.name,

View File

@@ -30,7 +30,7 @@ data class RoomListState(
val acceptDeclineInviteState: AcceptDeclineInviteState,
val hideInvitesAvatars: Boolean,
val canReportRoom: Boolean,
val eventSink: (RoomListEvents) -> Unit,
val eventSink: (RoomListEvent) -> Unit,
) {
val displayFilters = contentState is RoomListContentState.Rooms

View File

@@ -15,7 +15,7 @@ open class RoomListStateContextMenuShownProvider : PreviewParameterProvider<Room
override val values: Sequence<RoomListState.ContextMenu.Shown>
get() = sequenceOf(
aContextMenuShown(hasNewContent = true),
aContextMenuShown(isDm = true),
aContextMenuShown(isDm = true, isFavorite = true),
aContextMenuShown(roomName = null)
)
}

View File

@@ -56,7 +56,7 @@ internal fun aRoomListState(
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
hideInvitesAvatars: Boolean = false,
canReportRoom: Boolean = true,
eventSink: (RoomListEvents) -> Unit = {}
eventSink: (RoomListEvent) -> Unit = {}
) = RoomListState(
contextMenu = contextMenu,
declineInviteMenu = declineInviteMenu,

View File

@@ -8,7 +8,7 @@
package io.element.android.features.home.impl.search
sealed interface RoomListSearchEvents {
data object ToggleSearchVisibility : RoomListSearchEvents
data object ClearQuery : RoomListSearchEvents
sealed interface RoomListSearchEvent {
data object ToggleSearchVisibility : RoomListSearchEvent
data object ClearQuery : RoomListSearchEvent
}

View File

@@ -45,12 +45,12 @@ class RoomListSearchPresenter(
dataSource.setSearchQuery(searchQuery.text.toString())
}
fun handleEvent(event: RoomListSearchEvents) {
fun handleEvent(event: RoomListSearchEvent) {
when (event) {
RoomListSearchEvents.ClearQuery -> {
RoomListSearchEvent.ClearQuery -> {
searchQuery.clearText()
}
RoomListSearchEvents.ToggleSearchVisibility -> {
RoomListSearchEvent.ToggleSearchVisibility -> {
isSearchActive = !isSearchActive
searchQuery.clearText()
}

View File

@@ -16,5 +16,5 @@ data class RoomListSearchState(
val isSearchActive: Boolean,
val query: TextFieldState,
val results: ImmutableList<RoomListRoomSummary>,
val eventSink: (RoomListSearchEvents) -> Unit
val eventSink: (RoomListSearchEvent) -> Unit
)

View File

@@ -31,7 +31,7 @@ fun aRoomListSearchState(
isSearchActive: Boolean = false,
query: String = "",
results: ImmutableList<RoomListRoomSummary> = persistentListOf(),
eventSink: (RoomListSearchEvents) -> Unit = { },
eventSink: (RoomListSearchEvent) -> Unit = { },
) = RoomListSearchState(
isSearchActive = isSearchActive,
query = TextFieldState(initialText = query),

View File

@@ -38,7 +38,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.home.impl.components.RoomSummaryRow
import io.element.android.features.home.impl.contentType
import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.roomlist.RoomListEvents
import io.element.android.features.home.impl.roomlist.RoomListEvent
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@@ -54,12 +54,12 @@ import io.element.android.libraries.ui.strings.CommonStrings
internal fun RoomListSearchView(
state: RoomListSearchState,
hideInvitesAvatars: Boolean,
eventSink: (RoomListEvents) -> Unit,
eventSink: (RoomListEvent) -> Unit,
onRoomClick: (RoomId) -> Unit,
modifier: Modifier = Modifier,
) {
BackHandler(enabled = state.isSearchActive) {
state.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
state.eventSink(RoomListSearchEvent.ToggleSearchVisibility)
}
AnimatedVisibility(
@@ -83,13 +83,13 @@ internal fun RoomListSearchView(
private fun RoomListSearchContent(
state: RoomListSearchState,
hideInvitesAvatars: Boolean,
eventSink: (RoomListEvents) -> Unit,
eventSink: (RoomListEvent) -> Unit,
onRoomClick: (RoomId) -> Unit,
) {
val borderColor = MaterialTheme.colorScheme.tertiary
val strokeWidth = 1.dp
fun onBackButtonClick() {
state.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
state.eventSink(RoomListSearchEvent.ToggleSearchVisibility)
}
fun onRoomClick(room: RoomListRoomSummary) {
@@ -127,7 +127,7 @@ private fun RoomListSearchContent(
),
trailingIcon = if (state.query.text.isNotEmpty()) {
@Composable {
IconButton(onClick = { state.eventSink(RoomListSearchEvents.ClearQuery) }) {
IconButton(onClick = { state.eventSink(RoomListSearchEvent.ClearQuery) }) {
Icon(
imageVector = CompoundIcons.Close(),
contentDescription = stringResource(CommonStrings.action_cancel)

View File

@@ -8,9 +8,6 @@
package io.element.android.features.home.impl
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.announcement.api.Announcement
import io.element.android.features.announcement.api.AnnouncementService
@@ -70,9 +67,7 @@ class HomePresenterTest {
),
),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
assertThat(initialState.currentUserAndNeighbors.first()).isEqualTo(
MatrixUser(A_USER_ID, null, null)
@@ -96,9 +91,7 @@ class HomePresenterTest {
updateUserProfileResult = { _, _, _ -> },
),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
assertThat(initialState.canReportBug).isFalse()
val finalState = awaitItem()
@@ -115,9 +108,7 @@ class HomePresenterTest {
updateUserProfileResult = { _, _, _ -> },
),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
assertThat(initialState.showAvatarIndicator).isFalse()
indicatorService.setShowRoomListTopBarIndicator(true)
@@ -139,9 +130,7 @@ class HomePresenterTest {
updateUserProfileResult = { _, _, _ -> },
),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
assertThat(initialState.currentUserAndNeighbors.first()).isEqualTo(MatrixUser(matrixClient.sessionId))
// No new state is coming
@@ -159,12 +148,10 @@ class HomePresenterTest {
showAnnouncementResult = showAnnouncementResult,
)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
assertThat(initialState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Chats)
initialState.eventSink(HomeEvents.SelectHomeNavigationBarItem(HomeNavigationBarItem.Spaces))
initialState.eventSink(HomeEvent.SelectHomeNavigationBarItem(HomeNavigationBarItem.Spaces))
val finalState = awaitItem()
assertThat(finalState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Spaces)
showAnnouncementResult.assertions().isCalledOnce()
@@ -189,7 +176,7 @@ class HomePresenterTest {
assertThat(initialState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Chats)
assertThat(initialState.showNavigationBar).isTrue()
// User navigate to Spaces
initialState.eventSink(HomeEvents.SelectHomeNavigationBarItem(HomeNavigationBarItem.Spaces))
initialState.eventSink(HomeEvent.SelectHomeNavigationBarItem(HomeNavigationBarItem.Spaces))
val spaceState = awaitItem()
assertThat(spaceState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Spaces)
// The last space is left

View File

@@ -8,15 +8,13 @@
package io.element.android.features.home.impl.filters
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.home.impl.filters.selection.DefaultFilterSelectionStrategy
import io.element.android.features.home.impl.filters.selection.FilterSelectionState
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.tests.testutils.awaitLastSequentialItem
import io.element.android.tests.testutils.test
import kotlinx.coroutines.test.runTest
import org.junit.Test
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as MatrixRoomListFilter
@@ -25,9 +23,7 @@ class RoomListFiltersPresenterTest {
@Test
fun `present - initial state`() = runTest {
val presenter = createRoomListFiltersPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
awaitItem().let { state ->
assertThat(state.hasAnyFilterSelected).isFalse()
assertThat(state.filterSelectionStates).containsExactly(
@@ -46,10 +42,8 @@ class RoomListFiltersPresenterTest {
fun `present - toggle rooms filter`() = runTest {
val roomListService = FakeRoomListService()
val presenter = createRoomListFiltersPresenter(roomListService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
awaitItem().eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms))
presenter.test {
awaitItem().eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
awaitLastSequentialItem().let { state ->
assertThat(state.hasAnyFilterSelected).isTrue()
@@ -66,7 +60,7 @@ class RoomListFiltersPresenterTest {
assertThat(roomListCurrentFilter.filters).containsExactly(
MatrixRoomListFilter.Category.Group,
)
state.eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms))
state.eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
}
awaitLastSequentialItem().let { state ->
assertThat(state.hasAnyFilterSelected).isFalse()
@@ -88,13 +82,11 @@ class RoomListFiltersPresenterTest {
fun `present - clear filters event`() = runTest {
val roomListService = FakeRoomListService()
val presenter = createRoomListFiltersPresenter(roomListService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
awaitItem().eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms))
presenter.test {
awaitItem().eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
awaitLastSequentialItem().let { state ->
assertThat(state.hasAnyFilterSelected).isTrue()
state.eventSink.invoke(RoomListFiltersEvents.ClearSelectedFilters)
state.eventSink.invoke(RoomListFiltersEvent.ClearSelectedFilters)
}
awaitLastSequentialItem().let { state ->
assertThat(state.hasAnyFilterSelected).isFalse()

View File

@@ -27,7 +27,7 @@ class RoomListFiltersViewTest {
@Test
fun `clicking on filters generates expected Event`() {
val eventsRecorder = EventsRecorder<RoomListFiltersEvents>()
val eventsRecorder = EventsRecorder<RoomListFiltersEvent>()
rule.setContent {
RoomListFiltersView(
state = aRoomListFiltersState(eventSink = eventsRecorder),
@@ -36,14 +36,14 @@ class RoomListFiltersViewTest {
rule.clickOn(R.string.screen_roomlist_filter_rooms)
eventsRecorder.assertList(
listOf(
RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms),
RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms),
)
)
}
@Test
fun `clicking on clear filters generates expected Event`() {
val eventsRecorder = EventsRecorder<RoomListFiltersEvents>()
val eventsRecorder = EventsRecorder<RoomListFiltersEvent>()
rule.setContent {
RoomListFiltersView(
state = aRoomListFiltersState(
@@ -55,7 +55,7 @@ class RoomListFiltersViewTest {
rule.pressTag(TestTags.homeScreenClearFilters.value)
eventsRecorder.assertList(
listOf(
RoomListFiltersEvents.ClearSelectedFilters,
RoomListFiltersEvent.ClearSelectedFilters,
)
)
}

View File

@@ -30,7 +30,7 @@ class RoomListContextMenuTest {
@Test
fun `clicking on Mark as read generates expected Events`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val contextMenu = aContextMenuShown(hasNewContent = true)
rule.setRoomListContextMenu(
contextMenu = contextMenu,
@@ -39,15 +39,15 @@ class RoomListContextMenuTest {
rule.clickOn(R.string.screen_roomlist_mark_as_read)
eventsRecorder.assertList(
listOf(
RoomListEvents.HideContextMenu,
RoomListEvents.MarkAsRead(contextMenu.roomId),
RoomListEvent.HideContextMenu,
RoomListEvent.MarkAsRead(contextMenu.roomId),
)
)
}
@Test
fun `clicking on Mark as unread generates expected Events`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val contextMenu = aContextMenuShown(hasNewContent = false)
rule.setRoomListContextMenu(
contextMenu = contextMenu,
@@ -56,15 +56,15 @@ class RoomListContextMenuTest {
rule.clickOn(R.string.screen_roomlist_mark_as_unread)
eventsRecorder.assertList(
listOf(
RoomListEvents.HideContextMenu,
RoomListEvents.MarkAsUnread(contextMenu.roomId),
RoomListEvent.HideContextMenu,
RoomListEvent.MarkAsUnread(contextMenu.roomId),
)
)
}
@Test
fun `clicking on Leave room generates expected Events`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val contextMenu = aContextMenuShown(isDm = false)
rule.setRoomListContextMenu(
contextMenu = contextMenu,
@@ -73,15 +73,15 @@ class RoomListContextMenuTest {
rule.clickOn(CommonStrings.action_leave_room)
eventsRecorder.assertList(
listOf(
RoomListEvents.HideContextMenu,
RoomListEvents.LeaveRoom(contextMenu.roomId, needsConfirmation = true),
RoomListEvent.HideContextMenu,
RoomListEvent.LeaveRoom(contextMenu.roomId, needsConfirmation = true),
)
)
}
@Test
fun `clicking on Report room invokes the expected callback and generates expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val contextMenu = aContextMenuShown()
val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit)
rule.setRoomListContextMenu(
@@ -92,13 +92,13 @@ class RoomListContextMenuTest {
onReportRoomClick = callback,
)
rule.clickOn(CommonStrings.action_report_room)
eventsRecorder.assertSingle(RoomListEvents.HideContextMenu)
eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
callback.assertSuccess()
}
@Test
fun `clicking on Settings invokes the expected callback and generates expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val contextMenu = aContextMenuShown()
val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit)
rule.setRoomListContextMenu(
@@ -107,13 +107,13 @@ class RoomListContextMenuTest {
onRoomSettingsClick = callback,
)
rule.clickOn(CommonStrings.common_settings)
eventsRecorder.assertSingle(RoomListEvents.HideContextMenu)
eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
callback.assertSuccess()
}
@Test
fun `clicking on Favourites generates expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val contextMenu = aContextMenuShown(isDm = false, isFavorite = false)
val callback = EnsureNeverCalledWithParam<RoomId>()
rule.setRoomListContextMenu(
@@ -124,7 +124,7 @@ class RoomListContextMenuTest {
rule.clickOn(CommonStrings.common_favourite)
eventsRecorder.assertList(
listOf(
RoomListEvents.SetRoomIsFavorite(contextMenu.roomId, true),
RoomListEvent.SetRoomIsFavorite(contextMenu.roomId, true),
)
)
}
@@ -132,7 +132,7 @@ class RoomListContextMenuTest {
private fun AndroidComposeTestRule<*, *>.setRoomListContextMenu(
contextMenu: RoomListState.ContextMenu.Shown,
canReportRoom: Boolean = false,
eventSink: (RoomListEvents) -> Unit,
eventSink: (RoomListEvent) -> Unit,
onRoomSettingsClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
onReportRoomClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
) {

View File

@@ -28,7 +28,7 @@ class RoomListDeclineInviteMenuTest {
@Test
fun `clicking on decline emits the expected Events`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
rule.setSafeContent {
RoomListDeclineInviteMenu(
@@ -41,15 +41,15 @@ class RoomListDeclineInviteMenuTest {
rule.clickOn(CommonStrings.action_decline)
eventsRecorder.assertList(
listOf(
RoomListEvents.HideDeclineInviteMenu,
RoomListEvents.DeclineInvite(menu.roomSummary, blockUser = false),
RoomListEvent.HideDeclineInviteMenu,
RoomListEvent.DeclineInvite(menu.roomSummary, blockUser = false),
)
)
}
@Test
fun `clicking on decline and block when canReportRoom=true, it emits the expected Events and callback`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
rule.setSafeContent {
RoomListDeclineInviteMenu(
@@ -60,13 +60,13 @@ class RoomListDeclineInviteMenuTest {
)
}
rule.clickOn(CommonStrings.action_decline_and_block)
val expectedEvents = listOf(RoomListEvents.HideDeclineInviteMenu)
val expectedEvents = listOf(RoomListEvent.HideDeclineInviteMenu)
eventsRecorder.assertList(expectedEvents)
}
@Test
fun `clicking on decline and block when canReportRoom=false, it emits the expected Events`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
rule.setSafeContent {
RoomListDeclineInviteMenu(
@@ -78,15 +78,15 @@ class RoomListDeclineInviteMenuTest {
}
rule.clickOn(CommonStrings.action_decline_and_block)
val expectedEvents = listOf(
RoomListEvents.HideDeclineInviteMenu,
RoomListEvents.DeclineInvite(menu.roomSummary, blockUser = true),
RoomListEvent.HideDeclineInviteMenu,
RoomListEvent.DeclineInvite(menu.roomSummary, blockUser = true),
)
eventsRecorder.assertList(expectedEvents)
}
@Test
fun `clicking on cancel emits the expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
rule.setSafeContent {
RoomListDeclineInviteMenu(
@@ -97,6 +97,6 @@ class RoomListDeclineInviteMenuTest {
)
}
rule.clickOn(CommonStrings.action_cancel)
eventsRecorder.assertList(listOf(RoomListEvents.HideDeclineInviteMenu))
eventsRecorder.assertList(listOf(RoomListEvent.HideDeclineInviteMenu))
}
}

View File

@@ -8,9 +8,6 @@
package io.element.android.features.home.impl.roomlist
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.Interaction
import io.element.android.features.announcement.api.Announcement
@@ -21,7 +18,7 @@ import io.element.android.features.home.impl.datasource.aRoomListRoomSummaryFact
import io.element.android.features.home.impl.filters.RoomListFiltersState
import io.element.android.features.home.impl.filters.aRoomListFiltersState
import io.element.android.features.home.impl.model.createRoomListRoomSummary
import io.element.android.features.home.impl.search.RoomListSearchEvents
import io.element.android.features.home.impl.search.RoomListSearchEvent
import io.element.android.features.home.impl.search.RoomListSearchState
import io.element.android.features.home.impl.search.aRoomListSearchState
import io.element.android.features.invite.api.SeenInvitesStore
@@ -141,15 +138,13 @@ class RoomListPresenterTest {
val presenter = createRoomListPresenter(
client = FakeMatrixClient(roomListService = roomListService, encryptionService = encryptionService, syncService = syncService),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val eventWithContentAsRooms = consumeItemsUntilPredicate {
it.contentState is RoomListContentState.Rooms
}.last()
val eventSink = eventWithContentAsRooms.eventSink
assertThat(eventWithContentAsRooms.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.RecoveryKeyConfirmation)
eventSink(RoomListEvents.DismissRequestVerificationPrompt)
eventSink(RoomListEvent.DismissRequestVerificationPrompt)
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
}
}
@@ -173,9 +168,7 @@ class RoomListPresenterTest {
val presenter = createRoomListPresenter(
client = matrixClient,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = consumeItemsUntilPredicate {
it.contentState is RoomListContentState.Rooms
}.last()
@@ -194,7 +187,7 @@ class RoomListPresenterTest {
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
encryptionService.emitRecoveryState(RecoveryState.DISABLED)
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.SetUpRecovery)
nextState.eventSink(RoomListEvents.DismissBanner)
nextState.eventSink(RoomListEvent.DismissBanner)
val finalState = awaitItem()
assertThat(finalState.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
}
@@ -207,12 +200,10 @@ class RoomListPresenterTest {
givenGetRoomResult(A_ROOM_ID, room)
}
val presenter = createRoomListPresenter(client = client)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
val summary = createRoomListRoomSummary()
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
initialState.eventSink(RoomListEvent.ShowContextMenu(summary))
awaitItem().also { state ->
assertThat(state.contextMenu)
@@ -257,7 +248,7 @@ class RoomListPresenterTest {
presenter.test {
val initialState = awaitItem()
val summary = createRoomListRoomSummary()
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
initialState.eventSink(RoomListEvent.ShowContextMenu(summary))
awaitItem().also { state ->
assertThat(state.contextMenu)
.isEqualTo(
@@ -282,12 +273,10 @@ class RoomListPresenterTest {
givenGetRoomResult(A_ROOM_ID, room)
}
val presenter = createRoomListPresenter(client = client)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
val summary = createRoomListRoomSummary()
initialState.eventSink(RoomListEvents.ShowContextMenu(summary))
initialState.eventSink(RoomListEvent.ShowContextMenu(summary))
val shownState = awaitItem()
assertThat(shownState.contextMenu)
@@ -302,7 +291,7 @@ class RoomListPresenterTest {
)
)
shownState.eventSink(RoomListEvents.HideContextMenu)
shownState.eventSink(RoomListEvent.HideContextMenu)
val hiddenState = awaitItem()
assertThat(hiddenState.contextMenu).isEqualTo(RoomListState.ContextMenu.Hidden)
@@ -315,11 +304,9 @@ class RoomListPresenterTest {
val presenter = createRoomListPresenter(
leaveRoomState = aLeaveRoomState(eventSink = leaveRoomEventsRecorder),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
initialState.eventSink(RoomListEvents.LeaveRoom(A_ROOM_ID, needsConfirmation = true))
initialState.eventSink(RoomListEvent.LeaveRoom(A_ROOM_ID, needsConfirmation = true))
leaveRoomEventsRecorder.assertSingle(LeaveRoomEvent.LeaveRoom(A_ROOM_ID, needsConfirmation = true))
cancelAndIgnoreRemainingEvents()
}
@@ -327,7 +314,7 @@ class RoomListPresenterTest {
@Test
fun `present - toggle search menu`() = runTest {
val eventRecorder = EventsRecorder<RoomListSearchEvents>()
val eventRecorder = EventsRecorder<RoomListSearchEvent>()
val searchPresenter: Presenter<RoomListSearchState> = Presenter {
aRoomListSearchState(
eventSink = eventRecorder
@@ -336,20 +323,18 @@ class RoomListPresenterTest {
val presenter = createRoomListPresenter(
searchPresenter = searchPresenter,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
eventRecorder.assertEmpty()
initialState.eventSink(RoomListEvents.ToggleSearchResults)
initialState.eventSink(RoomListEvent.ToggleSearchResults)
eventRecorder.assertSingle(
RoomListSearchEvents.ToggleSearchVisibility
RoomListSearchEvent.ToggleSearchVisibility
)
initialState.eventSink(RoomListEvents.ToggleSearchResults)
initialState.eventSink(RoomListEvent.ToggleSearchResults)
eventRecorder.assertList(
listOf(
RoomListSearchEvents.ToggleSearchVisibility,
RoomListSearchEvents.ToggleSearchVisibility
RoomListSearchEvent.ToggleSearchVisibility,
RoomListSearchEvent.ToggleSearchVisibility
)
)
}
@@ -367,9 +352,7 @@ class RoomListPresenterTest {
notificationSettingsService = notificationSettingsService
)
val presenter = createRoomListPresenter(client = matrixClient)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, userDefinedMode)
val updatedState = consumeItemsUntilPredicate { state ->
(state.contentState as? RoomListContentState.Rooms)?.summaries.orEmpty().any { summary ->
@@ -394,13 +377,11 @@ class RoomListPresenterTest {
givenGetRoomResult(A_ROOM_ID, room)
}
val presenter = createRoomListPresenter(client = client, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, true))
initialState.eventSink(RoomListEvent.SetRoomIsFavorite(A_ROOM_ID, true))
setIsFavoriteResult.assertions().isCalledOnce().with(value(true))
initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, false))
initialState.eventSink(RoomListEvent.SetRoomIsFavorite(A_ROOM_ID, false))
setIsFavoriteResult.assertions().isCalledExactly(2)
.withSequence(
listOf(value(true)),
@@ -424,9 +405,7 @@ class RoomListPresenterTest {
val presenter = createRoomListPresenter(
client = matrixClient,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
assertThat(awaitItem().contentState).isInstanceOf(RoomListContentState.Empty::class.java)
}
}
@@ -463,23 +442,21 @@ class RoomListPresenterTest {
analyticsService = analyticsService,
notificationCleaner = notificationCleaner,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
allRooms.forEach {
assertThat(it.setUnreadFlagCalls).isEmpty()
}
initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID))
initialState.eventSink.invoke(RoomListEvent.MarkAsRead(A_ROOM_ID))
markAsReadResult.assertions().isCalledOnce().with(value(ReceiptType.READ))
assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false))
clearMessagesForRoomLambda.assertions().isCalledOnce()
.with(value(A_SESSION_ID), value(A_ROOM_ID))
initialState.eventSink.invoke(RoomListEvents.MarkAsUnread(A_ROOM_ID_2))
initialState.eventSink.invoke(RoomListEvent.MarkAsUnread(A_ROOM_ID_2))
assertThat(room2.setUnreadFlagCalls).isEqualTo(listOf(true))
// Test again with private read receipts
sessionPreferencesStore.setSendPublicReadReceipts(false)
initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID_3))
initialState.eventSink.invoke(RoomListEvent.MarkAsRead(A_ROOM_ID_3))
markAsReadResult3.assertions().isCalledOnce().with(value(ReceiptType.READ_PRIVATE))
assertThat(room3.setUnreadFlagCalls).isEqualTo(listOf(false))
clearMessagesForRoomLambda.assertions().isCalledExactly(2)
@@ -525,8 +502,8 @@ class RoomListPresenterTest {
it.id == roomSummary.roomId.value
}
state.eventSink(RoomListEvents.AcceptInvite(roomListRoomSummary))
state.eventSink(RoomListEvents.DeclineInvite(roomListRoomSummary, blockUser = false))
state.eventSink(RoomListEvent.AcceptInvite(roomListRoomSummary))
state.eventSink(RoomListEvent.DeclineInvite(roomListRoomSummary, blockUser = false))
val inviteData = roomListRoomSummary.toInviteData()
assert(eventSinkRecorder)
@@ -559,9 +536,9 @@ class RoomListPresenterTest {
it.contentState is RoomListContentState.Rooms
}.last()
state.eventSink(RoomListEvents.UpdateVisibleRange(IntRange(0, 10)))
state.eventSink(RoomListEvent.UpdateVisibleRange(IntRange(0, 10)))
// If called again, it will cancel the current one, which should not result in a test failure
state.eventSink(RoomListEvents.UpdateVisibleRange(IntRange(0, 11)))
state.eventSink(RoomListEvent.UpdateVisibleRange(IntRange(0, 11)))
advanceTimeBy(1.seconds)
subscribeToVisibleRoomsLambda.assertions().isCalledOnce()
}
@@ -588,12 +565,12 @@ class RoomListPresenterTest {
it.contentState is RoomListContentState.Rooms
}.last()
state.eventSink(RoomListEvents.UpdateVisibleRange(IntRange(0, 10)))
state.eventSink(RoomListEvent.UpdateVisibleRange(IntRange(0, 10)))
advanceTimeBy(1.seconds)
subscribeToVisibleRoomsLambda.assertions().isCalledOnce()
// If called again, it will subscribe to the next items
state.eventSink(RoomListEvents.UpdateVisibleRange(IntRange(0, 11)))
state.eventSink(RoomListEvent.UpdateVisibleRange(IntRange(0, 11)))
advanceTimeBy(1.seconds)
subscribeToVisibleRoomsLambda.assertions().isCalledExactly(2)
}
@@ -626,7 +603,7 @@ class RoomListPresenterTest {
assertThat(state.contentAsRooms().showNewNotificationSoundBanner).isFalse()
announcementService.emitAnnouncementsToShow(listOf(Announcement.NewNotificationSound))
assertThat(awaitItem().contentAsRooms().showNewNotificationSoundBanner).isTrue()
state.eventSink(RoomListEvents.DismissNewNotificationSoundBanner)
state.eventSink(RoomListEvent.DismissNewNotificationSoundBanner)
onAnnouncementDismissedResult.assertions().isCalledOnce()
.with(value(Announcement.NewNotificationSound))
// Simulate service updating the value

View File

@@ -46,7 +46,7 @@ class RoomListViewTest {
@Config(qualifiers = "h1024dp")
@Test
fun `displaying the view automatically sends a couple of UpdateVisibleRangeEvents`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
rule.setRoomListView(
state = aRoomListState(
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
@@ -56,15 +56,15 @@ class RoomListViewTest {
eventsRecorder.assertList(
listOf(
RoomListEvents.UpdateVisibleRange(IntRange.EMPTY),
RoomListEvents.UpdateVisibleRange(0..5),
RoomListEvent.UpdateVisibleRange(IntRange.EMPTY),
RoomListEvent.UpdateVisibleRange(0..5),
)
)
}
@Test
fun `clicking on close recovery key banner emits the expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
rule.setRoomListView(
state = aRoomListState(
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
@@ -77,12 +77,12 @@ class RoomListViewTest {
val close = rule.activity.getString(CommonStrings.action_close)
rule.onNodeWithContentDescription(close).performClick()
eventsRecorder.assertSingle(RoomListEvents.DismissBanner)
eventsRecorder.assertSingle(RoomListEvent.DismissBanner)
}
@Test
fun `clicking on close setup key banner emits the expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
rule.setRoomListView(
state = aRoomListState(
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery),
@@ -95,12 +95,12 @@ class RoomListViewTest {
val close = rule.activity.getString(CommonStrings.action_close)
rule.onNodeWithContentDescription(close).performClick()
eventsRecorder.assertSingle(RoomListEvents.DismissBanner)
eventsRecorder.assertSingle(RoomListEvent.DismissBanner)
}
@Test
fun `clicking on continue recovery key banner invokes the expected callback`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
ensureCalledOnce { callback ->
rule.setRoomListView(
state = aRoomListState(
@@ -121,7 +121,7 @@ class RoomListViewTest {
@Test
fun `clicking on continue setup key banner invokes the expected callback`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
ensureCalledOnce { callback ->
rule.setRoomListView(
state = aRoomListState(
@@ -139,7 +139,7 @@ class RoomListViewTest {
@Test
fun `clicking on start chat when the session has no room invokes the expected callback`() {
val eventsRecorder = EventsRecorder<RoomListEvents>(expectEvents = false)
val eventsRecorder = EventsRecorder<RoomListEvent>(expectEvents = false)
ensureCalledOnce { callback ->
rule.setRoomListView(
state = aRoomListState(
@@ -154,7 +154,7 @@ class RoomListViewTest {
@Test
fun `clicking on a room invokes the expected callback`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val state = aRoomListState(
eventSink = eventsRecorder,
)
@@ -178,7 +178,7 @@ class RoomListViewTest {
@Test
fun `clicking on a room twice invokes the expected callback only once`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val state = aRoomListState(
eventSink = eventsRecorder,
)
@@ -201,7 +201,7 @@ class RoomListViewTest {
@Test
fun `long clicking on a room emits the expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val state = aRoomListState(
eventSink = eventsRecorder,
)
@@ -215,12 +215,12 @@ class RoomListViewTest {
eventsRecorder.clear()
rule.onNodeWithText(room0.latestEvent.content().toString()).performTouchInput { longClick() }
eventsRecorder.assertSingle(RoomListEvents.ShowContextMenu(room0))
eventsRecorder.assertSingle(RoomListEvent.ShowContextMenu(room0))
}
@Test
fun `clicking on a room setting invokes the expected callback and emits expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val state = aRoomListState(
contextMenu = aContextMenuShown(),
eventSink = eventsRecorder,
@@ -238,12 +238,12 @@ class RoomListViewTest {
rule.clickOn(CommonStrings.common_settings)
}
eventsRecorder.assertSingle(RoomListEvents.HideContextMenu)
eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
}
@Test
fun `clicking on accept and decline invite emits the expected Events`() {
val eventsRecorder = EventsRecorder<RoomListEvents>()
val eventsRecorder = EventsRecorder<RoomListEvent>()
val state = aRoomListState(
eventSink = eventsRecorder,
)
@@ -259,8 +259,8 @@ class RoomListViewTest {
rule.clickOn(CommonStrings.action_decline)
eventsRecorder.assertList(
listOf(
RoomListEvents.AcceptInvite(invitedRoom),
RoomListEvents.ShowDeclineInviteMenu(invitedRoom),
RoomListEvent.AcceptInvite(invitedRoom),
RoomListEvent.ShowDeclineInviteMenu(invitedRoom),
)
)
}

View File

@@ -8,9 +8,6 @@
package io.element.android.features.home.impl.search
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.home.impl.datasource.aRoomListRoomSummaryFactory
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
@@ -19,6 +16,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.test.room.aRoomSummary
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.tests.testutils.test
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestScope
@@ -29,9 +27,7 @@ class RoomListSearchPresenterTest {
@Test
fun `present - initial state`() = runTest {
val presenter = createRoomListSearchPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
awaitItem().let { state ->
assertThat(state.isSearchActive).isFalse()
assertThat(state.query.text.toString()).isEmpty()
@@ -43,16 +39,14 @@ class RoomListSearchPresenterTest {
@Test
fun `present - toggle search visibility`() = runTest {
val presenter = createRoomListSearchPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
awaitItem().let { state ->
assertThat(state.isSearchActive).isFalse()
state.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
state.eventSink(RoomListSearchEvent.ToggleSearchVisibility)
}
awaitItem().let { state ->
assertThat(state.isSearchActive).isTrue()
state.eventSink(RoomListSearchEvents.ToggleSearchVisibility)
state.eventSink(RoomListSearchEvent.ToggleSearchVisibility)
}
awaitItem().let { state ->
assertThat(state.isSearchActive).isFalse()
@@ -64,9 +58,7 @@ class RoomListSearchPresenterTest {
fun `present - query search changes`() = runTest {
val roomListService = FakeRoomListService()
val presenter = createRoomListSearchPresenter(roomListService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
awaitItem().let { state ->
assertThat(
roomListService.allRooms.currentFilter.value
@@ -82,7 +74,7 @@ class RoomListSearchPresenterTest {
).isEqualTo(
RoomListFilter.NormalizedMatchRoomName("Search")
)
state.eventSink(RoomListSearchEvents.ClearQuery)
state.eventSink(RoomListSearchEvent.ClearQuery)
}
awaitItem().let { state ->
assertThat(state.query.text.toString()).isEmpty()
@@ -99,9 +91,7 @@ class RoomListSearchPresenterTest {
fun `present - room list changes`() = runTest {
val roomListService = FakeRoomListService()
val presenter = createRoomListSearchPresenter(roomListService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
awaitItem().let { state ->
assertThat(state.results).isEmpty()
}

View File

@@ -589,9 +589,14 @@ private fun FavoriteItem(
isFavorite: Boolean,
onFavoriteChanges: (Boolean) -> Unit,
) {
val (textResId, icon) = if (isFavorite) {
CommonStrings.common_favourited to CompoundIcons.FavouriteSolid()
} else {
CommonStrings.common_favourite to CompoundIcons.Favourite()
}
PreferenceSwitch(
icon = CompoundIcons.Favourite(),
title = stringResource(id = CommonStrings.common_favourite),
icon = icon,
title = stringResource(id = textResId),
isChecked = isFavorite,
onCheckedChange = onFavoriteChanges
)

View File

@@ -10,7 +10,7 @@ package io.element.android.features.roomdetails.impl.members
import io.element.android.libraries.matrix.api.room.RoomMember
sealed interface RoomMemberListEvents {
data class ChangeSelectedSection(val section: SelectedSection) : RoomMemberListEvents
data class RoomMemberSelected(val roomMember: RoomMember) : RoomMemberListEvents
sealed interface RoomMemberListEvent {
data class ChangeSelectedSection(val section: SelectedSection) : RoomMemberListEvent
data class RoomMemberSelected(val roomMember: RoomMember) : RoomMemberListEvent
}

View File

@@ -125,11 +125,11 @@ class RoomMemberListPresenter(
}
}
fun handleEvent(event: RoomMemberListEvents) {
fun handleEvent(event: RoomMemberListEvent) {
when (event) {
is RoomMemberListEvents.RoomMemberSelected ->
is RoomMemberListEvent.RoomMemberSelected ->
roomModerationState.eventSink(ShowActionsForUser(event.roomMember.toMatrixUser()))
is RoomMemberListEvents.ChangeSelectedSection -> selectedSection = event.section
is RoomMemberListEvent.ChangeSelectedSection -> selectedSection = event.section
}
}

View File

@@ -25,7 +25,7 @@ data class RoomMemberListState(
val canInvite: Boolean,
val selectedSection: SelectedSection,
val moderationState: RoomMemberModerationState,
val eventSink: (RoomMemberListEvents) -> Unit,
val eventSink: (RoomMemberListEvent) -> Unit,
) {
val showBannedSection: Boolean = moderationState.permissions.canBan && roomMembers.dataOrNull()?.banned?.isNotEmpty() == true
}

View File

@@ -85,7 +85,7 @@ internal fun aRoomMemberListState(
selectedSection: SelectedSection = SelectedSection.MEMBERS,
searchQuery: String = "",
canInvite: Boolean = false,
eventSink: (RoomMemberListEvents) -> Unit = {},
eventSink: (RoomMemberListEvent) -> Unit = {},
) = RoomMemberListState(
roomMembers = roomMembers,
filteredRoomMembers = roomMembers.map { it.filter(searchQuery) },

View File

@@ -25,8 +25,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource
@@ -68,7 +66,7 @@ fun RoomMemberListView(
modifier: Modifier = Modifier,
) {
fun onSelectUser(roomMember: RoomMember) {
state.eventSink(RoomMemberListEvents.RoomMemberSelected(roomMember))
state.eventSink(RoomMemberListEvent.RoomMemberSelected(roomMember))
}
Scaffold(
@@ -100,7 +98,7 @@ fun RoomMemberListView(
selectedSection = state.selectedSection,
showBannedSection = state.showBannedSection,
searchQuery = state.searchQuery.text.toString(),
onSelectedSectionChange = { state.eventSink(RoomMemberListEvents.ChangeSelectedSection(it)) },
onSelectedSectionChange = { state.eventSink(RoomMemberListEvent.ChangeSelectedSection(it)) },
onSelectUser = ::onSelectUser,
)
}

View File

@@ -10,10 +10,10 @@ package io.element.android.features.roomdetails.impl.notificationsettings
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
sealed interface RoomNotificationSettingsEvents {
data class ChangeRoomNotificationMode(val mode: RoomNotificationMode) : RoomNotificationSettingsEvents
data class SetNotificationMode(val isDefault: Boolean) : RoomNotificationSettingsEvents
data object DeleteCustomNotification : RoomNotificationSettingsEvents
data object ClearSetNotificationError : RoomNotificationSettingsEvents
data object ClearRestoreDefaultError : RoomNotificationSettingsEvents
sealed interface RoomNotificationSettingsEvent {
data class ChangeRoomNotificationMode(val mode: RoomNotificationMode) : RoomNotificationSettingsEvent
data class SetNotificationMode(val isDefault: Boolean) : RoomNotificationSettingsEvent
data object DeleteCustomNotification : RoomNotificationSettingsEvent
data object ClearSetNotificationError : RoomNotificationSettingsEvent
data object ClearRestoreDefaultError : RoomNotificationSettingsEvent
}

View File

@@ -100,12 +100,12 @@ class RoomNotificationSettingsPresenter(
!notificationSettingsService.canHomeServerPushEncryptedEventsToDevice().getOrDefault(true)
}
fun handleEvent(event: RoomNotificationSettingsEvents) {
fun handleEvent(event: RoomNotificationSettingsEvent) {
when (event) {
is RoomNotificationSettingsEvents.ChangeRoomNotificationMode -> {
is RoomNotificationSettingsEvent.ChangeRoomNotificationMode -> {
localCoroutineScope.setRoomNotificationMode(event.mode, pendingRoomNotificationMode, pendingSetDefault, setNotificationSettingAction)
}
is RoomNotificationSettingsEvents.SetNotificationMode -> {
is RoomNotificationSettingsEvent.SetNotificationMode -> {
if (event.isDefault) {
localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault)
} else {
@@ -114,13 +114,13 @@ class RoomNotificationSettingsPresenter(
}
}
}
is RoomNotificationSettingsEvents.DeleteCustomNotification -> {
is RoomNotificationSettingsEvent.DeleteCustomNotification -> {
localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault)
}
RoomNotificationSettingsEvents.ClearSetNotificationError -> {
RoomNotificationSettingsEvent.ClearSetNotificationError -> {
setNotificationSettingAction.value = AsyncAction.Uninitialized
}
RoomNotificationSettingsEvents.ClearRestoreDefaultError -> {
RoomNotificationSettingsEvent.ClearRestoreDefaultError -> {
restoreDefaultAction.value = AsyncAction.Uninitialized
}
}

View File

@@ -23,7 +23,7 @@ data class RoomNotificationSettingsState(
val setNotificationSettingAction: AsyncAction<Unit>,
val restoreDefaultAction: AsyncAction<Unit>,
val displayMentionsOnlyDisclaimer: Boolean,
val eventSink: (RoomNotificationSettingsEvents) -> Unit
val eventSink: (RoomNotificationSettingsEvent) -> Unit
)
val RoomNotificationSettingsState.displayNotificationMode: RoomNotificationMode? get() {

View File

@@ -85,7 +85,7 @@ private fun RoomSpecificNotificationSettingsView(
PreferenceSwitch(
isChecked = !state.displayIsDefault.orTrue(),
onCheckedChange = {
state.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(!it))
state.eventSink(RoomNotificationSettingsEvent.SetNotificationMode(!it))
},
title = stringResource(id = R.string.screen_room_notification_settings_allow_custom),
subtitle = stringResource(id = R.string.screen_room_notification_settings_allow_custom_footnote),
@@ -138,7 +138,7 @@ private fun RoomSpecificNotificationSettingsView(
enabled = !state.displayIsDefault.orTrue(),
displayMentionsOnlyDisclaimer = state.displayMentionsOnlyDisclaimer,
onSelectOption = {
state.eventSink(RoomNotificationSettingsEvents.ChangeRoomNotificationMode(it.mode))
state.eventSink(RoomNotificationSettingsEvent.ChangeRoomNotificationMode(it.mode))
},
)
}
@@ -148,14 +148,14 @@ private fun RoomSpecificNotificationSettingsView(
async = state.setNotificationSettingAction,
onSuccess = {},
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) },
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvent.ClearSetNotificationError) },
)
AsyncActionView(
async = state.restoreDefaultAction,
onSuccess = {},
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearRestoreDefaultError) },
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvent.ClearRestoreDefaultError) },
)
}
}

View File

@@ -60,7 +60,7 @@ fun UserDefinedRoomNotificationSettingsView(
enabled = !state.displayIsDefault.orTrue(),
displayMentionsOnlyDisclaimer = state.displayMentionsOnlyDisclaimer,
onSelectOption = {
state.eventSink(RoomNotificationSettingsEvents.ChangeRoomNotificationMode(it.mode))
state.eventSink(RoomNotificationSettingsEvent.ChangeRoomNotificationMode(it.mode))
},
)
}
@@ -69,7 +69,7 @@ fun UserDefinedRoomNotificationSettingsView(
headlineContent = { Text(stringResource(R.string.screen_room_notification_settings_edit_remove_setting)) },
style = ListItemStyle.Destructive,
onClick = {
state.eventSink(RoomNotificationSettingsEvents.DeleteCustomNotification)
state.eventSink(RoomNotificationSettingsEvent.DeleteCustomNotification)
}
)
@@ -77,14 +77,14 @@ fun UserDefinedRoomNotificationSettingsView(
async = state.setNotificationSettingAction,
onSuccess = {},
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) },
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvent.ClearSetNotificationError) },
)
AsyncActionView(
async = state.restoreDefaultAction,
onSuccess = { onBackClick() },
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearRestoreDefaultError) },
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvent.ClearRestoreDefaultError) },
)
}
}

View File

@@ -66,7 +66,7 @@ class RoomMemberListPresenterTest {
skipItems(1)
val loadedState = awaitItem()
assertThat(loadedState.showBannedSection).isTrue()
loadedState.eventSink(RoomMemberListEvents.ChangeSelectedSection(SelectedSection.BANNED))
loadedState.eventSink(RoomMemberListEvent.ChangeSelectedSection(SelectedSection.BANNED))
val bannedSectionState = awaitItem()
assertThat(bannedSectionState.selectedSection).isEqualTo(SelectedSection.BANNED)
// Now update the room members to have no banned users
@@ -188,7 +188,7 @@ class RoomMemberListPresenterTest {
)
presenter.test {
skipItems(1)
awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(anInvitedVictor()))
awaitItem().eventSink(RoomMemberListEvent.RoomMemberSelected(anInvitedVictor()))
}
}
}

View File

@@ -8,9 +8,6 @@
package io.element.android.features.roomdetails.impl.members.details
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.roomdetails.impl.aJoinedRoom
import io.element.android.features.roomdetails.impl.members.aRoomMember
@@ -89,9 +86,7 @@ class RoomMemberDetailsPresenterTest {
val presenter = createRoomMemberDetailsPresenter(
room = room,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
assertThat(initialState.userName).isEqualTo("Alice")
assertThat(initialState.avatarUrl).isEqualTo("Alice Avatar url")
@@ -111,9 +106,7 @@ class RoomMemberDetailsPresenterTest {
val presenter = createRoomMemberDetailsPresenter(
room = room,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
assertThat(initialState.userName).isEqualTo("Alice")
assertThat(initialState.avatarUrl).isEqualTo("Profile avatar url")
@@ -130,9 +123,7 @@ class RoomMemberDetailsPresenterTest {
val presenter = createRoomMemberDetailsPresenter(
room = room,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
assertThat(initialState.userName).isEqualTo("Profile user name")
assertThat(initialState.avatarUrl).isEqualTo("Profile avatar url")
@@ -161,9 +152,7 @@ class RoomMemberDetailsPresenterTest {
}
},
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
assertThat(initialState.userName).isNull()
assertThat(initialState.avatarUrl).isNull()

View File

@@ -8,9 +8,6 @@
package io.element.android.features.roomdetails.impl.notificationsettings
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.roomdetails.impl.aJoinedRoom
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
@@ -20,6 +17,7 @@ import io.element.android.libraries.matrix.test.notificationsettings.FakeNotific
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.tests.testutils.awaitLastSequentialItem
import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.test
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -27,9 +25,7 @@ class RoomNotificationSettingsPresenterTest {
@Test
fun `present - initial state is created from room info`() = runTest {
val presenter = createRoomNotificationSettingsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
assertThat(initialState.roomNotificationSettings.dataOrNull()).isNull()
assertThat(initialState.defaultRoomNotificationMode).isNull()
@@ -42,10 +38,8 @@ class RoomNotificationSettingsPresenterTest {
@Test
fun `present - notification mode changed`() = runTest {
val presenter = createRoomNotificationSettingsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
awaitItem().eventSink(RoomNotificationSettingsEvents.ChangeRoomNotificationMode(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY))
presenter.test {
awaitItem().eventSink(RoomNotificationSettingsEvent.ChangeRoomNotificationMode(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY))
val updatedState = consumeItemsUntilPredicate {
it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
}.last()
@@ -58,9 +52,7 @@ class RoomNotificationSettingsPresenterTest {
fun `present - observe notification mode changed`() = runTest {
val notificationSettingsService = FakeNotificationSettingsService()
val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
val updatedState = consumeItemsUntilPredicate {
it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
@@ -74,11 +66,9 @@ class RoomNotificationSettingsPresenterTest {
val notificationSettingsService = FakeNotificationSettingsService()
notificationSettingsService.givenSetNotificationModeError(AN_EXCEPTION)
val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(false))
initialState.eventSink(RoomNotificationSettingsEvent.SetNotificationMode(false))
val failedState = consumeItemsUntilPredicate {
it.setNotificationSettingAction.isFailure()
}.last()
@@ -87,7 +77,7 @@ class RoomNotificationSettingsPresenterTest {
assertThat(failedState.pendingSetDefault).isNull()
assertThat(failedState.setNotificationSettingAction.isFailure()).isTrue()
failedState.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError)
failedState.eventSink(RoomNotificationSettingsEvent.ClearSetNotificationError)
val errorClearedState = consumeItemsUntilPredicate {
it.setNotificationSettingAction.isUninitialized()
@@ -100,11 +90,9 @@ class RoomNotificationSettingsPresenterTest {
fun `present - notification settings set custom`() = runTest {
val notificationSettingsService = FakeNotificationSettingsService()
val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(false))
initialState.eventSink(RoomNotificationSettingsEvent.SetNotificationMode(false))
skipItems(3)
val defaultState = awaitItem()
assertThat(defaultState.roomNotificationSettings.dataOrNull()?.isDefault).isFalse()
@@ -115,12 +103,10 @@ class RoomNotificationSettingsPresenterTest {
@Test
fun `present - notification settings restore default`() = runTest {
val presenter = createRoomNotificationSettingsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
initialState.eventSink(RoomNotificationSettingsEvents.ChangeRoomNotificationMode(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY))
initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(true))
initialState.eventSink(RoomNotificationSettingsEvent.ChangeRoomNotificationMode(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY))
initialState.eventSink(RoomNotificationSettingsEvent.SetNotificationMode(true))
val defaultState = consumeItemsUntilPredicate {
it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
}.last()
@@ -134,17 +120,15 @@ class RoomNotificationSettingsPresenterTest {
val notificationSettingsService = FakeNotificationSettingsService()
notificationSettingsService.givenRestoreDefaultNotificationModeError(AN_EXCEPTION)
val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
val initialState = awaitItem()
initialState.eventSink(RoomNotificationSettingsEvents.ChangeRoomNotificationMode(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY))
initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(true))
initialState.eventSink(RoomNotificationSettingsEvent.ChangeRoomNotificationMode(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY))
initialState.eventSink(RoomNotificationSettingsEvent.SetNotificationMode(true))
val failedState = consumeItemsUntilPredicate {
it.restoreDefaultAction.isFailure()
}.last()
assertThat(failedState.restoreDefaultAction.isFailure()).isTrue()
failedState.eventSink(RoomNotificationSettingsEvents.ClearRestoreDefaultError)
failedState.eventSink(RoomNotificationSettingsEvent.ClearRestoreDefaultError)
val errorClearedState = consumeItemsUntilPredicate {
it.restoreDefaultAction.isUninitialized()
@@ -161,9 +145,7 @@ class RoomNotificationSettingsPresenterTest {
}
val room = aJoinedRoom(notificationSettingsService = notificationService, isEncrypted = true)
val presenter = createRoomNotificationSettingsPresenter(notificationService, room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
assertThat(awaitLastSequentialItem().displayMentionsOnlyDisclaimer).isTrue()
}
}
@@ -175,9 +157,7 @@ class RoomNotificationSettingsPresenterTest {
}
val room = aJoinedRoom(notificationSettingsService = notificationService, isEncrypted = false)
val presenter = createRoomNotificationSettingsPresenter(notificationService, room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
presenter.test {
assertThat(awaitLastSequentialItem().displayMentionsOnlyDisclaimer).isFalse()
}
}