Room List Filters implementation (#2423)

This commit is contained in:
Mauro
2024-02-08 16:50:44 +01:00
committed by GitHub
parent e1d1e99ad4
commit 224e4e4881
18 changed files with 292 additions and 128 deletions
@@ -212,88 +212,3 @@ extension HomeScreenRoom {
avatarURL: details.avatarURL)
}
}
enum RoomListFilter: Int, CaseIterable, Identifiable {
var id: Int {
rawValue
}
case people
case rooms
case unreads
case favourites
case lowPriority
var localizedName: String {
switch self {
case .people:
return L10n.screenRoomlistFilterPeople
case .rooms:
return L10n.screenRoomlistFilterRooms
case .unreads:
return L10n.screenRoomlistFilterUnreads
case .favourites:
return L10n.screenRoomlistFilterFavourites
case .lowPriority:
return L10n.screenRoomlistFilterLowPriority
}
}
var complementaryFilter: RoomListFilter? {
switch self {
case .people:
return .rooms
case .rooms:
return .people
case .unreads:
return nil
case .favourites:
return .lowPriority
case .lowPriority:
return .favourites
}
}
}
final class RoomListFiltersState: ObservableObject {
@Published private var enabledFilters: Set<RoomListFilter>
init(enabledFilters: Set<RoomListFilter> = []) {
self.enabledFilters = enabledFilters
}
var sortedEnabledFilters: [RoomListFilter] {
enabledFilters.sorted(by: { $0.rawValue < $1.rawValue })
}
var sortedAvailableFilters: [RoomListFilter] {
var availableFilters = Set(RoomListFilter.allCases)
for filter in enabledFilters {
availableFilters.remove(filter)
if let complementaryFilter = filter.complementaryFilter {
availableFilters.remove(complementaryFilter)
}
}
return availableFilters.sorted(by: { $0.rawValue < $1.rawValue })
}
var isFiltering: Bool {
!enabledFilters.isEmpty
}
func set(_ filter: RoomListFilter, isEnabled: Bool) {
if isEnabled {
enabledFilters.insert(filter)
} else {
enabledFilters.remove(filter)
}
}
func clearFilters() {
enabledFilters.removeAll()
}
func isEnabled(_ filter: RoomListFilter) -> Bool {
enabledFilters.contains(filter)
}
}
@@ -83,7 +83,17 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
.store(in: &cancellables)
appSettings.$roomListFiltersEnabled
.weakAssign(to: \.state.shouldShowFilters, on: self)
.sink { [weak self] value in
guard let self else {
return
}
if !value {
state.shouldShowFilters = false
state.filtersState.clearFilters()
} else {
state.shouldShowFilters = true
}
}
.store(in: &cancellables)
appSettings.$markAsUnreadEnabled
@@ -96,8 +106,9 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
let isSearchFieldFocused = context.$viewState.map(\.bindings.isSearchFieldFocused)
let searchQuery = context.$viewState.map(\.bindings.searchQuery)
let enabledFilters = context.viewState.filtersState.$activeFilters
isSearchFieldFocused
.combineLatest(searchQuery)
.combineLatest(searchQuery, enabledFilters)
.removeDuplicates { $0 == $1 }
.map { _ in () }
.sink { [weak self] in
@@ -196,12 +207,13 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
private func updateFilter() {
if state.shouldHideRoomList {
roomSummaryProvider?.setFilter(.none)
roomSummaryProvider?.setFilter(.excludeAll)
} else {
if state.bindings.isSearchFieldFocused {
roomSummaryProvider?.setFilter(.normalizedMatchRoomName(state.bindings.searchQuery))
roomSummaryProvider?.setFilter(.include(.init(query: state.bindings.searchQuery,
filters: state.filtersState.activeFilters)))
} else {
roomSummaryProvider?.setFilter(.all)
roomSummaryProvider?.setFilter(.include(.init(filters: state.filtersState.activeFilters)))
}
}
}
@@ -0,0 +1,119 @@
//
// Copyright 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.
//
import Combine
import Foundation
import MatrixRustSDK
enum RoomListFilter: Int, CaseIterable, Identifiable {
var id: Int {
rawValue
}
case people
case rooms
case unreads
case favourites
var localizedName: String {
switch self {
case .people:
return L10n.screenRoomlistFilterPeople
case .rooms:
return L10n.screenRoomlistFilterRooms
case .unreads:
return L10n.screenRoomlistFilterUnreads
case .favourites:
return L10n.screenRoomlistFilterFavourites
}
}
var incompatibleFilter: RoomListFilter? {
switch self {
case .people:
return .rooms
case .rooms:
return .people
case .unreads:
return nil
case .favourites:
// When we will have Low Priority we may need to return it here
return nil
}
}
var rustFilter: RoomListEntriesDynamicFilterKind? {
switch self {
case .people:
return .category(expect: .people)
case .rooms:
return .category(expect: .group)
case .unreads:
return .unread
case .favourites:
// Not implemented yet
return nil
}
}
}
final class RoomListFiltersState: ObservableObject {
@Published private(set) var activeFilters: Set<RoomListFilter>
init(activeFilters: Set<RoomListFilter> = []) {
self.activeFilters = activeFilters
}
var sortedActiveFilters: [RoomListFilter] {
activeFilters.sorted(by: { $0.rawValue < $1.rawValue })
}
var availableFilters: [RoomListFilter] {
var availableFilters = Set(RoomListFilter.allCases)
for filter in activeFilters {
availableFilters.remove(filter)
if let complementaryFilter = filter.incompatibleFilter {
availableFilters.remove(complementaryFilter)
}
}
return availableFilters.sorted(by: { $0.rawValue < $1.rawValue })
}
var isFiltering: Bool {
!activeFilters.isEmpty
}
func activateFilter(_ filter: RoomListFilter) {
if let incompatibleFilter = filter.incompatibleFilter,
activeFilters.contains(incompatibleFilter) {
fatalError("[RoomListFiltersState] adding mutually exclusive filters is not allowed")
}
activeFilters.insert(filter)
}
func deactivateFilter(_ filter: RoomListFilter) {
activeFilters.remove(filter)
}
func clearFilters() {
activeFilters.removeAll()
}
func isFilterActive(_ filter: RoomListFilter) -> Bool {
activeFilters.contains(filter)
}
}
@@ -22,9 +22,9 @@ struct RoomListFilterView: View {
var body: some View {
let binding = Binding<Bool>(get: {
state.isEnabled(filter)
state.isFilterActive(filter)
}, set: { isEnabled, _ in
state.set(filter, isEnabled: isEnabled)
isEnabled ? state.activateFilter(filter) : state.deactivateFilter(filter)
})
Toggle(isOn: binding) {
Text(filter.localizedName)
@@ -36,7 +36,7 @@ struct RoomListFilterView: View {
struct RoomListFilterView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
RoomListFilterView(filter: .people, state: .init())
RoomListFilterView(filter: .people, state: .init(enabledFilters: [.people]))
RoomListFilterView(filter: .people, state: .init(activeFilters: [.people]))
}
}
@@ -31,10 +31,10 @@ struct RoomListFiltersView: View {
.hidden()
.frame(width: 0)
}
ForEach(state.sortedEnabledFilters) { filter in
ForEach(state.sortedActiveFilters) { filter in
RoomListFilterView(filter: filter, state: state)
}
ForEach(state.sortedAvailableFilters) { filter in
ForEach(state.availableFilters) { filter in
RoomListFilterView(filter: filter, state: state)
}
}
@@ -62,6 +62,6 @@ struct RoomListFiltersView: View {
struct RoomListFiltersView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
RoomListFiltersView(state: .init())
RoomListFiltersView(state: .init(enabledFilters: [.rooms, .favourites]))
RoomListFiltersView(state: .init(activeFilters: [.rooms, .favourites]))
}
}