mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 19:44:05 +00:00
Enable fuzzy search behind a feature flag. (#1490)
This commit is contained in:
@@ -29,6 +29,8 @@ final class AppSettings {
|
|||||||
case enableNotifications
|
case enableNotifications
|
||||||
case enableInAppNotifications
|
case enableInAppNotifications
|
||||||
case pusherProfileTag
|
case pusherProfileTag
|
||||||
|
|
||||||
|
// Feature flags
|
||||||
case shouldCollapseRoomStateEvents
|
case shouldCollapseRoomStateEvents
|
||||||
case userSuggestionsEnabled
|
case userSuggestionsEnabled
|
||||||
case readReceiptsEnabled
|
case readReceiptsEnabled
|
||||||
@@ -36,6 +38,7 @@ final class AppSettings {
|
|||||||
case notificationSettingsEnabled
|
case notificationSettingsEnabled
|
||||||
case swiftUITimelineEnabled
|
case swiftUITimelineEnabled
|
||||||
case pollsInTimeline
|
case pollsInTimeline
|
||||||
|
case fuzzySearchEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
|
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
|
||||||
@@ -225,4 +228,7 @@ final class AppSettings {
|
|||||||
|
|
||||||
@UserPreference(key: UserDefaultsKeys.pollsInTimeline, defaultValue: false, storageType: .userDefaults(store))
|
@UserPreference(key: UserDefaultsKeys.pollsInTimeline, defaultValue: false, storageType: .userDefaults(store))
|
||||||
var pollsInTimelineEnabled
|
var pollsInTimelineEnabled
|
||||||
|
|
||||||
|
@UserPreference(key: UserDefaultsKeys.fuzzySearchEnabled, defaultValue: true, storageType: .userDefaults(store))
|
||||||
|
var fuzzySearchEnabled
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ enum HomeScreenViewAction {
|
|||||||
case skipSessionVerification
|
case skipSessionVerification
|
||||||
case updateVisibleItemRange(range: Range<Int>, isScrolling: Bool)
|
case updateVisibleItemRange(range: Range<Int>, isScrolling: Bool)
|
||||||
case selectInvites
|
case selectInvites
|
||||||
|
case updatedSearchQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
enum HomeScreenRoomListMode: CustomStringConvertible {
|
enum HomeScreenRoomListMode: CustomStringConvertible {
|
||||||
@@ -79,12 +80,14 @@ struct HomeScreenViewState: BindableState {
|
|||||||
|
|
||||||
var selectedRoomID: String?
|
var selectedRoomID: String?
|
||||||
|
|
||||||
|
var fuzzySearchEnabled: Bool
|
||||||
|
|
||||||
var visibleRooms: [HomeScreenRoom] {
|
var visibleRooms: [HomeScreenRoom] {
|
||||||
if roomListMode == .skeletons {
|
if roomListMode == .skeletons {
|
||||||
return placeholderRooms
|
return placeholderRooms
|
||||||
}
|
}
|
||||||
|
|
||||||
if bindings.searchQuery.isEmpty {
|
if fuzzySearchEnabled || bindings.searchQuery.isEmpty {
|
||||||
return rooms
|
return rooms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
roomSummaryProvider = userSession.clientProxy.roomSummaryProvider
|
roomSummaryProvider = userSession.clientProxy.roomSummaryProvider
|
||||||
inviteSummaryProvider = userSession.clientProxy.inviteSummaryProvider
|
inviteSummaryProvider = userSession.clientProxy.inviteSummaryProvider
|
||||||
|
|
||||||
super.init(initialViewState: HomeScreenViewState(userID: userSession.userID),
|
super.init(initialViewState: HomeScreenViewState(userID: userSession.userID, fuzzySearchEnabled: appSettings.fuzzySearchEnabled),
|
||||||
imageProvider: userSession.mediaProvider)
|
imageProvider: userSession.mediaProvider)
|
||||||
|
|
||||||
userSession.callbacks
|
userSession.callbacks
|
||||||
@@ -74,6 +74,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
.weakAssign(to: \.state.selectedRoomID, on: self)
|
.weakAssign(to: \.state.selectedRoomID, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
appSettings.$fuzzySearchEnabled
|
||||||
|
.weakAssign(to: \.state.fuzzySearchEnabled, on: self)
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
guard let roomSummaryProvider, let inviteSummaryProvider else {
|
guard let roomSummaryProvider, let inviteSummaryProvider else {
|
||||||
MXLog.error("Room summary provider unavailable")
|
MXLog.error("Room summary provider unavailable")
|
||||||
return
|
return
|
||||||
@@ -187,6 +191,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
callback?(.presentStartChatScreen)
|
callback?(.presentStartChatScreen)
|
||||||
case .selectInvites:
|
case .selectInvites:
|
||||||
callback?(.presentInvitesScreen)
|
callback?(.presentInvitesScreen)
|
||||||
|
case .updatedSearchQuery:
|
||||||
|
roomSummaryProvider?.updateFilterPattern(state.bindings.searchQuery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,9 +80,13 @@ struct HomeScreen: View {
|
|||||||
updateVisibleRange()
|
updateVisibleRange()
|
||||||
}
|
}
|
||||||
.onChange(of: context.searchQuery) { searchQuery in
|
.onChange(of: context.searchQuery) { searchQuery in
|
||||||
guard searchQuery.isEmpty else { return }
|
if context.viewState.fuzzySearchEnabled {
|
||||||
// Dispatch allows the view to update after changing the query
|
context.send(viewAction: .updatedSearchQuery)
|
||||||
DispatchQueue.main.async { updateVisibleRange() }
|
} else {
|
||||||
|
guard searchQuery.isEmpty else { return }
|
||||||
|
// Dispatch allows the view to update after changing the query
|
||||||
|
DispatchQueue.main.async { updateVisibleRange() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onReceive(scrollViewAdapter.scrollDirection) { direction in
|
.onReceive(scrollViewAdapter.scrollDirection) { direction in
|
||||||
withAnimation(.elementDefault) { lastScrollDirection = direction }
|
withAnimation(.elementDefault) { lastScrollDirection = direction }
|
||||||
|
|||||||
+1
@@ -49,6 +49,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
|
|||||||
var notificationSettingsEnabled: Bool { get set }
|
var notificationSettingsEnabled: Bool { get set }
|
||||||
var swiftUITimelineEnabled: Bool { get set }
|
var swiftUITimelineEnabled: Bool { get set }
|
||||||
var pollsInTimelineEnabled: Bool { get set }
|
var pollsInTimelineEnabled: Bool { get set }
|
||||||
|
var fuzzySearchEnabled: Bool { get set }
|
||||||
var filterNotificationsByPushRulesEnabled: Bool { get set }
|
var filterNotificationsByPushRulesEnabled: Bool { get set }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
@@ -37,6 +37,13 @@ struct DeveloperOptionsScreen: View {
|
|||||||
Text("Resets on reboot")
|
Text("Resets on reboot")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Section("Room list") {
|
||||||
|
Toggle(isOn: $context.fuzzySearchEnabled) {
|
||||||
|
Text("Fuzzy search")
|
||||||
|
Text("Requires app reboot")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Section("Notifications") {
|
Section("Notifications") {
|
||||||
Toggle(isOn: $context.notificationSettingsEnabled) {
|
Toggle(isOn: $context.notificationSettingsEnabled) {
|
||||||
|
|||||||
@@ -414,11 +414,13 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
let eventStringBuilder = RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
|
let eventStringBuilder = RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
|
||||||
roomSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
|
roomSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
|
||||||
eventStringBuilder: eventStringBuilder,
|
eventStringBuilder: eventStringBuilder,
|
||||||
name: "AllRooms")
|
name: "AllRooms",
|
||||||
|
appSettings: appSettings)
|
||||||
try await roomSummaryProvider?.setRoomList(roomListService.allRooms())
|
try await roomSummaryProvider?.setRoomList(roomListService.allRooms())
|
||||||
inviteSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
|
inviteSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
|
||||||
eventStringBuilder: eventStringBuilder,
|
eventStringBuilder: eventStringBuilder,
|
||||||
name: "Invites")
|
name: "Invites",
|
||||||
|
appSettings: appSettings)
|
||||||
|
|
||||||
self.syncService = syncService
|
self.syncService = syncService
|
||||||
self.roomListService = roomListService
|
self.roomListService = roomListService
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
func setRoomList(_ roomList: RoomList) { }
|
func setRoomList(_ roomList: RoomList) { }
|
||||||
|
|
||||||
func updateVisibleRange(_ range: Range<Int>) { }
|
func updateVisibleRange(_ range: Range<Int>) { }
|
||||||
|
|
||||||
|
func updateFilterPattern(_ pattern: String?) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Array where Element == RoomSummary {
|
extension Array where Element == RoomSummary {
|
||||||
|
|||||||
@@ -22,12 +22,14 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
private let roomListService: RoomListServiceProtocol
|
private let roomListService: RoomListServiceProtocol
|
||||||
private let eventStringBuilder: RoomEventStringBuilder
|
private let eventStringBuilder: RoomEventStringBuilder
|
||||||
private let name: String
|
private let name: String
|
||||||
|
private var appSettings: AppSettings
|
||||||
|
|
||||||
private let serialDispatchQueue: DispatchQueue
|
private let serialDispatchQueue: DispatchQueue
|
||||||
|
|
||||||
private var roomList: RoomListProtocol?
|
private var roomList: RoomListProtocol?
|
||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
private var listUpdatesSubscriptionResult: RoomListEntriesWithDynamicFilterResult?
|
||||||
private var listUpdatesTaskHandle: TaskHandle?
|
private var listUpdatesTaskHandle: TaskHandle?
|
||||||
private var stateUpdatesTaskHandle: TaskHandle?
|
private var stateUpdatesTaskHandle: TaskHandle?
|
||||||
|
|
||||||
@@ -53,11 +55,13 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
|
|
||||||
init(roomListService: RoomListServiceProtocol,
|
init(roomListService: RoomListServiceProtocol,
|
||||||
eventStringBuilder: RoomEventStringBuilder,
|
eventStringBuilder: RoomEventStringBuilder,
|
||||||
name: String) {
|
name: String,
|
||||||
|
appSettings: AppSettings) {
|
||||||
self.roomListService = roomListService
|
self.roomListService = roomListService
|
||||||
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomsummaryprovider", qos: .utility)
|
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomsummaryprovider", qos: .utility)
|
||||||
self.eventStringBuilder = eventStringBuilder
|
self.eventStringBuilder = eventStringBuilder
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.appSettings = appSettings
|
||||||
|
|
||||||
diffsPublisher
|
diffsPublisher
|
||||||
.receive(on: serialDispatchQueue)
|
.receive(on: serialDispatchQueue)
|
||||||
@@ -73,21 +77,17 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
self.roomList = roomList
|
self.roomList = roomList
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let listUpdatesSubscriptionResult = roomList.entries(listener: RoomListEntriesListenerProxy { [weak self] updates in
|
listUpdatesSubscriptionResult = roomList.entriesWithDynamicFilter(listener: RoomListEntriesListenerProxy { [weak self] updates in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
MXLog.info("\(name): Received list update")
|
MXLog.info("\(name): Received list update")
|
||||||
diffsPublisher.send(updates)
|
diffsPublisher.send(updates)
|
||||||
})
|
})
|
||||||
|
|
||||||
listUpdatesTaskHandle = listUpdatesSubscriptionResult.entriesStream
|
|
||||||
|
|
||||||
rooms = listUpdatesSubscriptionResult.entries.map { roomListEntry in
|
|
||||||
buildSummaryForRoomListEntry(roomListEntry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manually call it here as the didSet doesn't work from constructors
|
listUpdatesTaskHandle = listUpdatesSubscriptionResult?.entriesStream
|
||||||
roomListSubject.send(rooms)
|
|
||||||
|
|
||||||
|
// Forces the listener above to be called with the current state
|
||||||
|
updateFilterPattern(nil)
|
||||||
|
|
||||||
let stateUpdatesSubscriptionResult = try roomList.loadingState(listener: RoomListStateObserver { [weak self] state in
|
let stateUpdatesSubscriptionResult = try roomList.loadingState(listener: RoomListStateObserver { [weak self] state in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
MXLog.info("\(name): Received state update: \(state)")
|
MXLog.info("\(name): Received state update: \(state)")
|
||||||
@@ -113,6 +113,17 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateFilterPattern(_ pattern: String?) {
|
||||||
|
guard let pattern, !pattern.isEmpty else {
|
||||||
|
_ = listUpdatesSubscriptionResult?.dynamicFilter.set(kind: .all)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard appSettings.fuzzySearchEnabled else { return }
|
||||||
|
|
||||||
|
_ = listUpdatesSubscriptionResult?.dynamicFilter.set(kind: .fuzzyMatchRoomName(pattern: pattern.lowercased()))
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
fileprivate func updateRoomsWithDiffs(_ diffs: [RoomListEntriesUpdate]) {
|
fileprivate func updateRoomsWithDiffs(_ diffs: [RoomListEntriesUpdate]) {
|
||||||
|
|||||||
@@ -99,4 +99,6 @@ protocol RoomSummaryProviderProtocol {
|
|||||||
func setRoomList(_ roomList: RoomList)
|
func setRoomList(_ roomList: RoomList)
|
||||||
|
|
||||||
func updateVisibleRange(_ range: Range<Int>)
|
func updateVisibleRange(_ range: Range<Int>)
|
||||||
|
|
||||||
|
func updateFilterPattern(_ pattern: String?)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Allow fuzzy searching for room list rooms
|
||||||
Reference in New Issue
Block a user