Room List Filters implementation (#2423)

This commit is contained in:
Mauro
2024-02-08 16:50:44 +01:00
committed by GitHub
parent c84ba3bdf6
commit 306597e52d
18 changed files with 292 additions and 128 deletions

View File

@@ -303,6 +303,7 @@
4BB51476A29E7E27BC14EA22 /* UserDetailsEditScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E6BD64CB4610B9C95FC02 /* UserDetailsEditScreenViewModel.swift */; };
4C356F5CCB4CDC99BFA45185 /* AppLockSetupPINScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7884BD256C091EB511B2EDF /* AppLockSetupPINScreenViewModelProtocol.swift */; };
4C5A638DAA8AF64565BA4866 /* EncryptedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5351EBD7A0B9610548E4B7B2 /* EncryptedRoomTimelineItem.swift */; };
4C8C0C9FC10BA73AB7780534 /* RoomListFiltersStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE0C9653870803E4F91F474 /* RoomListFiltersStateTests.swift */; };
4E0D9E09B52CEC4C0E6211A8 /* MediaPickerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F49FB9EE2913234F06CE68 /* MediaPickerScreenCoordinator.swift */; };
4E8A2A2CFEB212F14E49E1A1 /* AppLockSetupSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5484457C81B325660901B161 /* AppLockSetupSettingsScreen.swift */; };
4E8F17EBA24FBBA6ABB62ECB /* MockBackgroundTaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3948D16F021DFDB2CD26EAA8 /* MockBackgroundTaskService.swift */; };
@@ -968,6 +969,7 @@
F421FD5979EF53C8204BDC77 /* SecureBackupLogoutConfirmationScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC09F30B0E1010951952BDC /* SecureBackupLogoutConfirmationScreenUITests.swift */; };
F4433EF57B4BB3C077F8B00E /* SessionVerificationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD9E0FFA29EAACFF3AB9732 /* SessionVerificationScreenViewModel.swift */; };
F4971845B5C4F270F6BC5745 /* ScaledFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D82F234B3576BD6268C7950 /* ScaledFrameModifier.swift */; };
F4996C82A4B3A5FF0C8EDD03 /* RoomListFilterModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */; };
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; };
F50A6FCE26714E27FE5495DD /* PollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F23B21CF15F9F4BAA0788B /* PollOptionView.swift */; };
F519DE17A3A0F760307B2E6D /* InviteUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D155E09BF961BBA8F85263 /* InviteUsersScreenViewModel.swift */; };
@@ -1565,6 +1567,7 @@
8977176AB534AA41630395BC /* LegalInformationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenViewModelProtocol.swift; sourceTree = "<group>"; };
897DF5E9A70CE05A632FC8AF /* UTType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTType.swift; sourceTree = "<group>"; };
8AB10FA6570DD08B3966C159 /* WelcomeScreenScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreenScreenUITests.swift; sourceTree = "<group>"; };
8AE0C9653870803E4F91F474 /* RoomListFiltersStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFiltersStateTests.swift; sourceTree = "<group>"; };
8AE78FA0011E07920AE83135 /* PlainMentionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlainMentionBuilder.swift; sourceTree = "<group>"; };
8AFCE895ECFFA53FEE64D62B /* MediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoader.swift; sourceTree = "<group>"; };
8BEBF0E59F25E842EDB6FD11 /* LocationSharingScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingScreenModels.swift; sourceTree = "<group>"; };
@@ -1883,6 +1886,7 @@
DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationViewModelTests.swift; sourceTree = "<group>"; };
DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineItem.swift; sourceTree = "<group>"; };
E062C1750EFC8627DE4CAB8E /* MapTilerAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerAuthorization.swift; sourceTree = "<group>"; };
E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFilterModels.swift; sourceTree = "<group>"; };
E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = "<group>"; };
E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileListRow.swift; sourceTree = "<group>"; };
E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenCoordinator.swift; sourceTree = "<group>"; };
@@ -2130,6 +2134,7 @@
037A5661B26EC6BE068188D7 /* Filters */ = {
isa = PBXGroup;
children = (
E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */,
E6372DD10DED30E7AD7BCE21 /* RoomListFiltersView.swift */,
24EC819497BB5F8C4998D760 /* RoomListFilterView.swift */,
);
@@ -3343,6 +3348,7 @@
00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */,
2EFE1922F39398ABFB36DF3F /* RoomDetailsViewModelTests.swift */,
4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */,
8AE0C9653870803E4F91F474 /* RoomListFiltersStateTests.swift */,
EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */,
69B63F817FE305548DB4B512 /* RoomMembersListViewModelTests.swift */,
58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */,
@@ -5328,6 +5334,7 @@
9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */,
EA974337FA7D040E7C74FE6E /* RoomDetailsViewModelTests.swift in Sources */,
095D3906CF2F940C2D2D17CC /* RoomFlowCoordinatorTests.swift in Sources */,
4C8C0C9FC10BA73AB7780534 /* RoomListFiltersStateTests.swift in Sources */,
6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */,
CAF8755E152204F55F8D6B5B /* RoomMembersListViewModelTests.swift in Sources */,
E49F74BD93230BDEFFE5EA51 /* RoomNotificationSettingsScreenViewModelTests.swift in Sources */,
@@ -5796,6 +5803,7 @@
42F1C8731166633E35A6D7E6 /* RoomEventStringBuilder.swift in Sources */,
D55AF9B5B55FEED04771A461 /* RoomFlowCoordinator.swift in Sources */,
04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */,
F4996C82A4B3A5FF0C8EDD03 /* RoomListFilterModels.swift in Sources */,
4A9CEEE612D6D8B3DDBD28BA /* RoomListFilterView.swift in Sources */,
33F1FB19F222BA9930AB1A00 /* RoomListFiltersView.swift in Sources */,
FA4296218444C48BC890F46B /* RoomMemberDetails.swift in Sources */,
@@ -6763,7 +6771,7 @@
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
requirement = {
kind = exactVersion;
version = 1.1.37;
version = 1.1.38;
};
};
821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = {

View File

@@ -129,8 +129,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-rust-components-swift",
"state" : {
"revision" : "4d0d75004f8361530d7424ab198e363027823718",
"version" : "1.1.37"
"revision" : "691d8b0f0994d9669fadbd2452bef7270f3713ad",
"version" : "1.1.38"
}
},
{

View File

@@ -45,7 +45,7 @@ class GlobalSearchScreenViewModel: GlobalSearchScreenViewModelType, GlobalSearch
.map(\.bindings.searchQuery)
.removeDuplicates()
.sink { [weak self] searchQuery in
self?.roomSummaryProvider.setFilter(.normalizedMatchRoomName(searchQuery))
self?.roomSummaryProvider.setFilter(.include(.init(query: searchQuery)))
}
.store(in: &cancellables)
@@ -60,7 +60,7 @@ class GlobalSearchScreenViewModel: GlobalSearchScreenViewModelType, GlobalSearch
switch viewAction {
case .dismiss:
actionsSubject.send(.dismiss)
roomSummaryProvider.setFilter(.all) // This is a shared provider
roomSummaryProvider.setFilter(.include(.all)) // This is a shared provider
case .select(let roomID):
actionsSubject.send(.select(roomID: roomID))
case .reachedTop:

View File

@@ -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)
}
}

View File

@@ -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)))
}
}
}

View File

@@ -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)
}
}

View File

@@ -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]))
}
}

View File

@@ -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]))
}
}

View File

@@ -49,7 +49,7 @@ class MessageForwardingScreenViewModel: MessageForwardingScreenViewModelType, Me
.removeDuplicates()
.sink { [weak self] searchQuery in
guard let self else { return }
self.roomSummaryProvider?.setFilter(.normalizedMatchRoomName(searchQuery))
self.roomSummaryProvider?.setFilter(.include(.init(query: searchQuery)))
}
.store(in: &cancellables)
@@ -60,7 +60,7 @@ class MessageForwardingScreenViewModel: MessageForwardingScreenViewModelType, Me
switch viewAction {
case .cancel:
actionsSubject.send(.dismiss)
roomSummaryProvider?.setFilter(.all)
roomSummaryProvider?.setFilter(.include(.all))
case .send:
guard let roomID = state.selectedRoomID else {
fatalError()

View File

@@ -49,14 +49,13 @@ class RoomProxy: RoomProxyProtocol {
var ownUserID: String {
room.ownUserId()
}
init?(roomListItem: RoomListItemProtocol,
room: RoomProtocol,
backgroundTaskService: BackgroundTaskServiceProtocol) async {
self.roomListItem = roomListItem
self.room = room
self.backgroundTaskService = backgroundTaskService
do {
timeline = try await TimelineProxy(timeline: room.timeline(), backgroundTaskService: backgroundTaskService)
} catch {
@@ -130,7 +129,7 @@ class RoomProxy: RoomProxyProtocol {
var canonicalAlias: String? {
room.canonicalAlias()
}
var avatarURL: URL? {
roomListItem.avatarUrl().flatMap(URL.init(string:))
}

View File

@@ -25,6 +25,7 @@ enum MockRoomSummaryProviderState {
class MockRoomSummaryProvider: RoomSummaryProviderProtocol {
private let initialRooms: [RoomSummary]
private(set) var currentFilter: RoomSummaryProviderFilter?
private let roomListSubject: CurrentValueSubject<[RoomSummary], Never>
var roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> {
@@ -60,17 +61,16 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol {
func updateVisibleRange(_ range: Range<Int>) { }
func setFilter(_ filter: RoomSummaryProviderFilter) {
currentFilter = filter
switch filter {
case .all:
roomListSubject.send(initialRooms)
case .none:
roomListSubject.send([])
case .normalizedMatchRoomName(let filter):
if filter.isEmpty {
roomListSubject.send(initialRooms)
case let .include(predicate):
if let query = predicate.query, !query.isEmpty {
roomListSubject.send(initialRooms.filter { $0.name?.localizedCaseInsensitiveContains(query) ?? false })
} else {
roomListSubject.send(initialRooms.filter { $0.name?.localizedCaseInsensitiveContains(filter) ?? false })
roomListSubject.send(initialRooms)
}
case .excludeAll:
roomListSubject.send([])
}
}
}

View File

@@ -102,7 +102,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
})
// Forces the listener above to be called with the current state
setFilter(.all)
setFilter(.include(.all))
listUpdatesTaskHandle = listUpdatesSubscriptionResult?.entriesStream
@@ -151,12 +151,16 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
func setFilter(_ filter: RoomSummaryProviderFilter) {
switch filter {
case .none:
case .excludeAll:
_ = listUpdatesSubscriptionResult?.controller.setFilter(kind: .none)
case .all:
_ = listUpdatesSubscriptionResult?.controller.setFilter(kind: .allNonLeft)
case .normalizedMatchRoomName(let query):
_ = listUpdatesSubscriptionResult?.controller.setFilter(kind: .normalizedMatchRoomName(pattern: query.lowercased()))
case let .include(predicate):
var filters = predicate.filters.compactMap(\.rustFilter)
if let query = predicate.query {
filters.append(.normalizedMatchRoomName(pattern: query.lowercased()))
}
// We never want to show left rooms.
filters.append(.nonLeft)
_ = listUpdatesSubscriptionResult?.controller.setFilter(kind: .all(filters: filters))
}
}

View File

@@ -87,10 +87,28 @@ enum RoomSummary: CustomStringConvertible, Equatable {
}
}
enum RoomSummaryProviderFilter {
case none
case all
case normalizedMatchRoomName(String)
enum RoomSummaryProviderFilter: Equatable {
struct Predicate: Equatable {
let query: String?
let filters: Set<RoomListFilter>
static var all: Predicate {
Predicate()
}
/// - Parameters:
/// - query: If provided the filter will do a normalized search, default is nil
/// - filters: Additional filters that can be provided for further filtering the room list, default is empty which means no additional filtering is done
init(query: String? = nil, filters: Set<RoomListFilter> = []) {
self.query = query
self.filters = filters
}
}
/// Filters out everything
case excludeAll
/// Includes only the items that satisfy the predicate logic
case include(Predicate)
}
protocol RoomSummaryProviderProtocol {

View File

@@ -92,8 +92,9 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
}
func sendReadReceipt(for itemID: TimelineItemIdentifier) async -> Result<Void, RoomTimelineControllerError> {
guard let eventID = itemID.eventID
else { return .success(()) }
guard let eventID = itemID.eventID else {
return .failure(.generic)
}
switch await roomProxy.timeline.sendReadReceipt(for: eventID,
type: appSettings.sendReadReceiptsEnabled ? .read : .readPrivate) {

View File

@@ -25,10 +25,13 @@ class HomeScreenViewModelTests: XCTestCase {
var clientProxy: MockClientProxy!
var context: HomeScreenViewModelType.Context! { viewModel.context }
var cancellables = Set<AnyCancellable>()
var roomSummaryProvider: MockRoomSummaryProvider!
override func setUpWithError() throws {
ServiceLocator.shared.settings.roomListFiltersEnabled = true
cancellables.removeAll()
clientProxy = MockClientProxy(userID: "@mock:client.com")
roomSummaryProvider = MockRoomSummaryProvider(state: .loaded(.mockRooms))
clientProxy = MockClientProxy(userID: "@mock:client.com", roomSummaryProvider: roomSummaryProvider)
viewModel = HomeScreenViewModel(userSession: MockUserSession(clientProxy: clientProxy,
mediaProvider: MockMediaProvider(),
voiceMessageMediaManager: VoiceMessageMediaManagerMock()),
@@ -37,6 +40,10 @@ class HomeScreenViewModelTests: XCTestCase {
userIndicatorController: ServiceLocator.shared.userIndicatorController)
}
override func tearDown() {
AppSettings.reset()
}
func testSelectRoom() async throws {
let mockRoomId = "mock_room_id"
var correctResult = false
@@ -153,4 +160,14 @@ class HomeScreenViewModelTests: XCTestCase {
XCTAssertNil(context.alertInfo)
XCTAssertTrue(correctResult)
}
func testFilters() async throws {
context.viewState.filtersState.activateFilter(.people)
try await Task.sleep(for: .milliseconds(100))
XCTAssertEqual(roomSummaryProvider.currentFilter, RoomSummaryProviderFilter.include(.init(filters: [.people])))
context.isSearchFieldFocused = true
context.searchQuery = "Test"
try await Task.sleep(for: .milliseconds(100))
XCTAssertEqual(roomSummaryProvider.currentFilter, RoomSummaryProviderFilter.include(.init(query: "Test", filters: [.people])))
}
}

View File

@@ -0,0 +1,70 @@
//
// 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 XCTest
@testable import ElementX
final class RoomListFiltersStateTests: XCTestCase {
var state: RoomListFiltersState!
override func setUp() {
state = RoomListFiltersState()
}
func testInitialState() {
XCTAssertFalse(state.isFiltering)
XCTAssertEqual(state.activeFilters, [])
XCTAssertEqual(state.availableFilters, RoomListFilter.allCases)
}
func testSetAndUnsetFilters() {
state.activateFilter(.unreads)
XCTAssertTrue(state.isFiltering)
XCTAssertEqual(state.activeFilters, [.unreads])
XCTAssertEqual(state.availableFilters, [.people, .rooms, .favourites])
state.deactivateFilter(.unreads)
XCTAssertFalse(state.isFiltering)
XCTAssertEqual(state.activeFilters, [])
XCTAssertEqual(state.availableFilters, RoomListFilter.allCases)
}
func testMutuallyExclusiveFilters() {
state.activateFilter(.people)
XCTAssertTrue(state.isFiltering)
XCTAssertEqual(state.activeFilters, [.people])
XCTAssertEqual(state.availableFilters, [.unreads, .favourites])
state.deactivateFilter(.people)
state.activateFilter(.rooms)
state.activateFilter(.unreads)
XCTAssertTrue(state.isFiltering)
XCTAssertEqual(state.activeFilters, [.rooms, .unreads])
XCTAssertEqual(state.availableFilters, [.favourites])
}
func testClearFilters() {
state.activateFilter(.people)
state.activateFilter(.unreads)
state.activateFilter(.favourites)
XCTAssertTrue(state.isFiltering)
XCTAssertEqual(state.activeFilters, [.people, .unreads, .favourites])
XCTAssertEqual(state.availableFilters, [])
state.clearFilters()
XCTAssertFalse(state.isFiltering)
XCTAssertEqual(state.activeFilters, [])
XCTAssertEqual(state.availableFilters, RoomListFilter.allCases)
}
}

1
changelog.d/pr-2423.wip Normal file
View File

@@ -0,0 +1 @@
All Filters have been implemented, except for the Favourites one.

View File

@@ -47,7 +47,7 @@ packages:
# Element/Matrix dependencies
MatrixRustSDK:
url: https://github.com/matrix-org/matrix-rust-components-swift
exactVersion: 1.1.37
exactVersion: 1.1.38
# path: ../matrix-rust-sdk
Compound:
url: https://github.com/element-hq/compound-ios