Browse Source

RoomListFilters : first iteration on the design

jme/room_list_filters_embedded
ganfra 8 months ago
parent
commit
5dcc9fba29
  1. 4
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt
  2. 2
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt
  3. 2
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt
  4. 24
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt
  5. 40
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFilter.kt
  6. 22
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersEvents.kt
  7. 69
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenter.kt
  8. 27
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersState.kt
  9. 42
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersStateProvider.kt
  10. 144
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersView.kt

4
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt

@ -33,6 +33,7 @@ import io.element.android.features.networkmonitor.api.NetworkMonitor @@ -33,6 +33,7 @@ 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.features.roomlist.impl.filters.RoomListFiltersPresenter
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
@ -63,6 +64,7 @@ class RoomListPresenter @Inject constructor( @@ -63,6 +64,7 @@ class RoomListPresenter @Inject constructor(
private val encryptionService: EncryptionService,
private val featureFlagService: FeatureFlagService,
private val indicatorService: IndicatorService,
private val filtersPresenter: RoomListFiltersPresenter,
) : Presenter<RoomListState> {
@Composable
override fun present(): RoomListState {
@ -76,6 +78,7 @@ class RoomListPresenter @Inject constructor( @@ -76,6 +78,7 @@ class RoomListPresenter @Inject constructor(
val filteredRoomList by roomListDataSource.filteredRooms.collectAsState()
val filter by roomListDataSource.filter.collectAsState()
val networkConnectionStatus by networkMonitor.connectivity.collectAsState()
val filtersState = filtersPresenter.present()
LaunchedEffect(Unit) {
roomListDataSource.launchIn(this)
@ -148,6 +151,7 @@ class RoomListPresenter @Inject constructor( @@ -148,6 +151,7 @@ class RoomListPresenter @Inject constructor(
displaySearchResults = displaySearchResults,
contextMenu = contextMenu,
leaveRoomState = leaveRoomState,
filtersState = filtersState,
eventSink = ::handleEvents
)
}

2
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt

@ -18,6 +18,7 @@ package io.element.android.features.roomlist.impl @@ -18,6 +18,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.filters.RoomListFiltersState
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
@ -40,6 +41,7 @@ data class RoomListState( @@ -40,6 +41,7 @@ data class RoomListState(
val displaySearchResults: Boolean,
val contextMenu: ContextMenu,
val leaveRoomState: LeaveRoomState,
val filtersState: RoomListFiltersState,
val eventSink: (RoomListEvents) -> Unit,
) {
sealed interface ContextMenu {

2
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt

@ -19,6 +19,7 @@ package io.element.android.features.roomlist.impl @@ -19,6 +19,7 @@ package io.element.android.features.roomlist.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.leaveroom.api.aLeaveRoomState
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState
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
@ -70,6 +71,7 @@ internal fun aRoomListState() = RoomListState( @@ -70,6 +71,7 @@ internal fun aRoomListState() = RoomListState(
displaySearchResults = false,
contextMenu = RoomListState.ContextMenu.Hidden,
leaveRoomState = aLeaveRoomState(),
filtersState = aRoomListFiltersState(),
eventSink = {}
)

24
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt

@ -54,6 +54,7 @@ import io.element.android.features.roomlist.impl.components.RequestVerificationH @@ -54,6 +54,7 @@ import io.element.android.features.roomlist.impl.components.RequestVerificationH
import io.element.android.features.roomlist.impl.components.RoomListMenuAction
import io.element.android.features.roomlist.impl.components.RoomListTopBar
import io.element.android.features.roomlist.impl.components.RoomSummaryRow
import io.element.android.features.roomlist.impl.filters.RoomListFiltersView
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.features.roomlist.impl.search.RoomListSearchResultView
import io.element.android.libraries.architecture.AsyncData
@ -209,16 +210,19 @@ private fun RoomListContent( @@ -209,16 +210,19 @@ private fun RoomListContent(
Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
RoomListTopBar(
matrixUser = state.matrixUser,
showAvatarIndicator = state.showAvatarIndicator,
areSearchResultsDisplayed = state.displaySearchResults,
onFilterChanged = { state.eventSink(RoomListEvents.UpdateFilter(it)) },
onToggleSearch = { state.eventSink(RoomListEvents.ToggleSearchResults) },
onMenuActionClicked = onMenuActionClicked,
onOpenSettings = onOpenSettings,
scrollBehavior = scrollBehavior,
)
Column {
RoomListTopBar(
matrixUser = state.matrixUser,
showAvatarIndicator = state.showAvatarIndicator,
areSearchResultsDisplayed = state.displaySearchResults,
onFilterChanged = { state.eventSink(RoomListEvents.UpdateFilter(it)) },
onToggleSearch = { state.eventSink(RoomListEvents.ToggleSearchResults) },
onMenuActionClicked = onMenuActionClicked,
onOpenSettings = onOpenSettings,
scrollBehavior = scrollBehavior,
)
RoomListFiltersView(state = state.filtersState)
}
},
content = { padding ->
if (state.roomList is AsyncData.Success && state.roomList.data.isEmpty()) {

40
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFilter.kt

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
/*
* 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.roomlist.impl.filters
import io.element.android.features.roomlist.impl.R
/**
* Enum class representing the different filters that can be applied to the room list.
* Order is important.
*/
enum class RoomListFilter(val stringResource: Int){
Rooms(R.string.screen_roomlist_filter_rooms),
People(R.string.screen_roomlist_filter_people),
Unread(R.string.screen_roomlist_filter_unreads),
Favourites(R.string.screen_roomlist_filter_favourites),
LowPriority(R.string.screen_roomlist_filter_low_priority);
val oppositeFilter: RoomListFilter?
get() = when (this) {
Rooms -> People
People -> Rooms
Unread -> null
Favourites -> LowPriority
LowPriority -> Favourites
}
}

22
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersEvents.kt

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
/*
* 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.roomlist.impl.filters
sealed interface RoomListFiltersEvents {
data object ClearSelectedFilters : RoomListFiltersEvents
data class ToggleFilter(val filter: RoomListFilter) : RoomListFiltersEvents
}

69
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersPresenter.kt

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
/*
* 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.roomlist.impl.filters
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import io.element.android.libraries.architecture.Presenter
import kotlinx.collections.immutable.toPersistentList
import javax.inject.Inject
class RoomListFiltersPresenter @Inject constructor() : Presenter<RoomListFiltersState> {
@Composable
override fun present(): RoomListFiltersState {
var unselectedFilters: Set<RoomListFilter> by rememberSaveable {
mutableStateOf(RoomListFilter.entries.toSet())
}
var selectedFilters: Set<RoomListFilter> by rememberSaveable {
mutableStateOf(emptySet())
}
fun updateFilters(newSelectedFilters: Set<RoomListFilter>) {
selectedFilters = newSelectedFilters
unselectedFilters = RoomListFilter.entries.toSet() -
selectedFilters -
selectedFilters.mapNotNull { it.oppositeFilter }.toSet()
}
fun handleEvents(event: RoomListFiltersEvents) {
when (event) {
is RoomListFiltersEvents.ToggleFilter -> {
val newSelectedFilters = if (selectedFilters.contains(event.filter)) {
selectedFilters - event.filter
} else {
selectedFilters + event.filter
}
updateFilters(newSelectedFilters)
}
RoomListFiltersEvents.ClearSelectedFilters -> {
updateFilters(newSelectedFilters = emptySet())
}
}
}
return RoomListFiltersState(
unselectedFilters = unselectedFilters.toPersistentList(),
selectedFilters = selectedFilters.toPersistentList(),
eventSink = ::handleEvents
)
}
}

27
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersState.kt

@ -0,0 +1,27 @@ @@ -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.
*/
package io.element.android.features.roomlist.impl.filters
import kotlinx.collections.immutable.ImmutableList
data class RoomListFiltersState(
val unselectedFilters: ImmutableList<RoomListFilter>,
val selectedFilters: ImmutableList<RoomListFilter>,
val eventSink: (RoomListFiltersEvents) -> Unit,
) {
val showClearFilterButton = selectedFilters.isNotEmpty()
}

42
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersStateProvider.kt

@ -0,0 +1,42 @@ @@ -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.roomlist.impl.filters
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
class RoomListFiltersStateProvider : PreviewParameterProvider<RoomListFiltersState> {
override val values: Sequence<RoomListFiltersState>
get() = sequenceOf(
aRoomListFiltersState(),
aRoomListFiltersState(
selectedFilters = persistentListOf(RoomListFilter.Rooms, RoomListFilter.Favourites),
unselectedFilters = persistentListOf(RoomListFilter.Unread),
)
)
}
fun aRoomListFiltersState(
unselectedFilters: ImmutableList<RoomListFilter> = RoomListFilter.entries.toImmutableList(),
selectedFilters: ImmutableList<RoomListFilter> = persistentListOf(),
) = RoomListFiltersState(
unselectedFilters = unselectedFilters,
selectedFilters = selectedFilters,
) {}

144
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/filters/RoomListFiltersView.kt

@ -0,0 +1,144 @@ @@ -0,0 +1,144 @@
/*
* 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.roomlist.impl.filters
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Text
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun RoomListFiltersView(
state: RoomListFiltersState,
modifier: Modifier = Modifier
) {
fun onClearFiltersClicked() {
state.eventSink(RoomListFiltersEvents.ClearSelectedFilters)
}
fun onFilterClicked(filter: RoomListFilter) {
state.eventSink(RoomListFiltersEvents.ToggleFilter(filter))
}
val horizontalPadding = if(state.showClearFilterButton) 4.dp else 16.dp
Row(modifier.padding(horizontal = horizontalPadding)) {
AnimatedVisibility(visible = state.showClearFilterButton) {
RoomListClearFiltersButton(onClick = ::onClearFiltersClicked)
}
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(state.selectedFilters) { filter ->
RoomListFilterView(
roomListFilter = filter,
selected = true,
onClick = ::onFilterClicked,
modifier = Modifier.animateItemPlacement(),
)
}
items(state.unselectedFilters) { filter ->
RoomListFilterView(
roomListFilter = filter,
selected = false,
onClick = ::onFilterClicked,
modifier = Modifier.animateItemPlacement(),
)
}
}
}
}
@Composable
private fun RoomListClearFiltersButton(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
IconButton(
modifier = modifier,
onClick = onClick,
) {
Box(
modifier = Modifier
.clip(CircleShape)
.background(ElementTheme.colors.bgActionPrimaryRest)
) {
Icon(
modifier = Modifier.align(Alignment.Center),
imageVector = CompoundIcons.Close,
tint = ElementTheme.colors.iconOnSolidPrimary,
contentDescription = null,
)
}
}
}
@Composable
private fun RoomListFilterView(
roomListFilter: RoomListFilter,
selected: Boolean,
onClick: (RoomListFilter) -> Unit,
modifier: Modifier = Modifier
) {
FilterChip(
selected = selected,
onClick = { onClick(roomListFilter) },
modifier = modifier,
shape = CircleShape,
colors = FilterChipDefaults.filterChipColors(
containerColor = ElementTheme.colors.bgCanvasDefault,
selectedContainerColor = ElementTheme.colors.bgActionPrimaryRest,
labelColor = ElementTheme.colors.textPrimary,
selectedLabelColor = ElementTheme.colors.textOnSolidPrimary,
),
label = {
Text(text = stringResource(id = roomListFilter.stringResource))
}
)
}
@PreviewsDayNight
@Composable
internal fun RoomListFiltersViewPreview(@PreviewParameter(RoomListFiltersStateProvider::class) state: RoomListFiltersState) = ElementPreview {
RoomListFiltersView(
state = state,
)
}
Loading…
Cancel
Save