Room list searching tweaks (#1800)

* Prevent the room list from flashing previous rooms when searching

* Remove reset diff chunking now that the whole list is limited

* Bump the Rust SDK to v1.1.19 and fix breaking API changes
This commit is contained in:
Stefan Ceriu
2023-09-26 14:33:15 +03:00
committed by GitHub
parent 68ea9acb36
commit dad6560ed4
13 changed files with 75 additions and 69 deletions
+1 -1
View File
@@ -5765,7 +5765,7 @@
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
requirement = { requirement = {
kind = exactVersion; kind = exactVersion;
version = 1.1.18; version = 1.1.19;
}; };
}; };
821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = { 821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = {
@@ -129,8 +129,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-rust-components-swift", "location" : "https://github.com/matrix-org/matrix-rust-components-swift",
"state" : { "state" : {
"revision" : "f00c834ec4f80c9eea43282b79b368c93ad6bd82", "revision" : "8f03dc0cdfc8aa7a4110fb3d6561090befc4d0c3",
"version" : "1.1.18" "version" : "1.1.19"
} }
}, },
{ {
@@ -98,6 +98,7 @@ struct HomeScreenViewState: BindableState {
struct HomeScreenViewStateBindings { struct HomeScreenViewStateBindings {
var searchQuery = "" var searchQuery = ""
var isSearchFieldFocused = false
var alertInfo: AlertInfo<UUID>? var alertInfo: AlertInfo<UUID>?
var leaveRoomAlertItem: LeaveRoomAlertItem? var leaveRoomAlertItem: LeaveRoomAlertItem?
@@ -88,7 +88,17 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
.map(\.bindings.searchQuery) .map(\.bindings.searchQuery)
.removeDuplicates() .removeDuplicates()
.sink { [weak self] searchQuery in .sink { [weak self] searchQuery in
self?.roomSummaryProvider?.updateFilterPattern(searchQuery) guard let self else { return }
updateFilter(isSearchFieldFocused: state.bindings.isSearchFieldFocused, searchQuery: searchQuery)
}
.store(in: &cancellables)
context.$viewState
.map(\.bindings.isSearchFieldFocused)
.removeDuplicates()
.sink { [weak self] isSearchFieldFocused in
guard let self else { return }
updateFilter(isSearchFieldFocused: isSearchFieldFocused, searchQuery: state.bindings.searchQuery)
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -142,6 +152,16 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
// MARK: - Private // MARK: - Private
private func updateFilter(isSearchFieldFocused: Bool, searchQuery: String) {
if !isSearchFieldFocused {
roomSummaryProvider?.setFilter(.all)
} else if searchQuery.isEmpty {
roomSummaryProvider?.setFilter(.none)
} else {
roomSummaryProvider?.setFilter(.normalizedMatchRoomName(searchQuery))
}
}
private func setupRoomSummaryProviderSubscriptions() { private func setupRoomSummaryProviderSubscriptions() {
guard let roomSummaryProvider, let inviteSummaryProvider else { guard let roomSummaryProvider, let inviteSummaryProvider else {
MXLog.error("Room summary provider unavailable") MXLog.error("Room summary provider unavailable")
@@ -23,7 +23,6 @@ struct HomeScreen: View {
@ObservedObject var context: HomeScreenViewModel.Context @ObservedObject var context: HomeScreenViewModel.Context
@State private var scrollViewAdapter = ScrollViewAdapter() @State private var scrollViewAdapter = ScrollViewAdapter()
@State private var isSearching = false
// Bloom components // Bloom components
@State private var bloomView: UIView? @State private var bloomView: UIView?
@@ -56,7 +55,7 @@ struct HomeScreen: View {
topSection topSection
LazyVStack(spacing: 0) { LazyVStack(spacing: 0) {
HomeScreenRoomList(context: context, isSearching: $isSearching) HomeScreenRoomList(context: context)
} }
.searchable(text: $context.searchQuery) .searchable(text: $context.searchQuery)
.compoundSearchField() .compoundSearchField()
@@ -100,7 +99,7 @@ struct HomeScreen: View {
} }
} }
let isTopController = controller.navigationController?.topViewController != controller let isTopController = controller.navigationController?.topViewController != controller
let isHidden = isTopController || isSearching let isHidden = isTopController || context.isSearchFieldFocused
if let bloomView { if let bloomView {
bloomView.isHidden = isHidden bloomView.isHidden = isHidden
UIView.transition(with: bloomView, duration: 1.75, options: .curveEaseInOut) { UIView.transition(with: bloomView, duration: 1.75, options: .curveEaseInOut) {
@@ -196,7 +195,7 @@ struct HomeScreen: View {
sessionVerificationBanner sessionVerificationBanner
} }
if context.viewState.hasPendingInvitations, !isSearching { if context.viewState.hasPendingInvitations, !context.isSearchFieldFocused {
HomeScreenInvitesButton(title: L10n.actionInvitesList, hasBadge: context.viewState.hasUnreadPendingInvitations) { HomeScreenInvitesButton(title: L10n.actionInvitesList, hasBadge: context.viewState.hasUnreadPendingInvitations) {
context.send(viewAction: .selectInvites) context.send(viewAction: .selectInvites)
} }
@@ -20,39 +20,34 @@ struct HomeScreenRoomList: View {
@Environment(\.isSearching) var isSearchFieldFocused @Environment(\.isSearching) var isSearchFieldFocused
@ObservedObject var context: HomeScreenViewModel.Context @ObservedObject var context: HomeScreenViewModel.Context
@Binding var isSearching: Bool
var body: some View { var body: some View {
content content
.onChange(of: isSearchFieldFocused) { isSearching = $0 } .onChange(of: isSearchFieldFocused) { context.isSearchFieldFocused = $0 }
} }
@ViewBuilder @ViewBuilder
private var content: some View { private var content: some View {
if isSearchFieldFocused, context.searchQuery.count == 0 { ForEach(context.viewState.visibleRooms) { room in
EmptyView() if room.isPlaceholder {
} else { HomeScreenRoomCell(room: room, context: context, isSelected: false)
ForEach(context.viewState.visibleRooms) { room in .redacted(reason: .placeholder)
if room.isPlaceholder { } else {
HomeScreenRoomCell(room: room, context: context, isSelected: false) let isSelected = context.viewState.selectedRoomID == room.id
.redacted(reason: .placeholder) HomeScreenRoomCell(room: room, context: context, isSelected: isSelected)
} else { .contextMenu {
let isSelected = context.viewState.selectedRoomID == room.id Button {
HomeScreenRoomCell(room: room, context: context, isSelected: isSelected) context.send(viewAction: .showRoomDetails(roomIdentifier: room.id))
.contextMenu { } label: {
Button { Label(L10n.commonSettings, systemImage: "gearshape")
context.send(viewAction: .showRoomDetails(roomIdentifier: room.id))
} label: {
Label(L10n.commonSettings, systemImage: "gearshape")
}
Button(role: .destructive) {
context.send(viewAction: .leaveRoom(roomIdentifier: room.id))
} label: {
Label(L10n.actionLeaveRoom, systemImage: "rectangle.portrait.and.arrow.right")
}
} }
}
Button(role: .destructive) {
context.send(viewAction: .leaveRoom(roomIdentifier: room.id))
} label: {
Label(L10n.actionLeaveRoom, systemImage: "rectangle.portrait.and.arrow.right")
}
}
} }
} }
} }
@@ -241,9 +241,7 @@ class ClientProxy: ClientProxyProtocol {
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? { func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
// Try fetching the room from the cold cache (if available) first // Try fetching the room from the cold cache (if available) first
var (roomListItem, room) = await Task.dispatch(on: clientQueue) { var (roomListItem, room) = await roomTupleForIdentifier(identifier)
self.roomTupleForIdentifier(identifier)
}
if let roomListItem, let room { if let roomListItem, let room {
return await RoomProxy(roomListItem: roomListItem, return await RoomProxy(roomListItem: roomListItem,
@@ -262,9 +260,7 @@ class ClientProxy: ClientProxyProtocol {
_ = await roomSummaryProvider.statePublisher.values.first(where: { $0.isLoaded }) _ = await roomSummaryProvider.statePublisher.values.first(where: { $0.isLoaded })
} }
(roomListItem, room) = await Task.dispatch(on: clientQueue) { (roomListItem, room) = await roomTupleForIdentifier(identifier)
self.roomTupleForIdentifier(identifier)
}
guard let roomListItem else { guard let roomListItem else {
MXLog.error("Invalid roomListItem for identifier \(identifier)") MXLog.error("Invalid roomListItem for identifier \(identifier)")
@@ -519,10 +515,10 @@ class ClientProxy: ClientProxyProtocol {
}) })
} }
private func roomTupleForIdentifier(_ identifier: String) -> (RoomListItem?, Room?) { private func roomTupleForIdentifier(_ identifier: String) async -> (RoomListItem?, Room?) {
do { do {
let roomListItem = try roomListService?.room(roomId: identifier) let roomListItem = try roomListService?.room(roomId: identifier)
let fullRoom = roomListItem?.fullRoom() let fullRoom = await roomListItem?.fullRoom()
return (roomListItem, fullRoom) return (roomListItem, fullRoom)
} catch { } catch {
@@ -182,7 +182,7 @@ struct MediaUploadingPreprocessor {
case .success(let result): case .success(let result):
switch await generateThumbnailForVideoAt(result.url) { switch await generateThumbnailForVideoAt(result.url) {
case .success(let thumbnailResult): case .success(let thumbnailResult):
let videoSize = ((try? UInt64(FileManager.default.sizeForItem(at: result.url))) ?? 0) ?? 0 let videoSize = (try? UInt64(FileManager.default.sizeForItem(at: result.url))) ?? 0
let thumbnailSize = (try? UInt64(FileManager.default.sizeForItem(at: thumbnailResult.url))) ?? 0 let thumbnailSize = (try? UInt64(FileManager.default.sizeForItem(at: thumbnailResult.url))) ?? 0
let thumbnailInfo = ThumbnailInfo(height: UInt64(thumbnailResult.height), let thumbnailInfo = ThumbnailInfo(height: UInt64(thumbnailResult.height),
@@ -271,7 +271,8 @@ class RoomProxy: RoomProxyProtocol {
return await Task.dispatch(on: messageSendingDispatchQueue) { return await Task.dispatch(on: messageSendingDispatchQueue) {
do { do {
if let eventID { if let eventID {
try self.room.sendReply(msg: messageContent, inReplyToEventId: eventID, txnId: transactionId) let replyItem = try self.room.getEventTimelineItemByEventId(eventId: eventID)
try self.room.sendReply(msg: messageContent, replyItem: replyItem, txnId: transactionId)
} else { } else {
self.room.send(msg: messageContent, txnId: transactionId) self.room.send(msg: messageContent, txnId: transactionId)
} }
@@ -662,7 +663,7 @@ class RoomProxy: RoomProxyProtocol {
do { do {
let data = try Data(contentsOf: imageURL) let data = try Data(contentsOf: imageURL)
return try .success(self.room.uploadAvatar(mimeType: mimeType, data: [UInt8](data))) return try .success(self.room.uploadAvatar(mimeType: mimeType, data: [UInt8](data), mediaInfo: nil))
} catch { } catch {
return .failure(.failedUploadingAvatar) return .failure(.failedUploadingAvatar)
} }
@@ -46,7 +46,7 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol {
func updateVisibleRange(_ range: Range<Int>) { } func updateVisibleRange(_ range: Range<Int>) { }
func updateFilterPattern(_ pattern: String?) { } func setFilter(_ filter: RoomSummaryProviderFilter) { }
} }
extension Array where Element == RoomSummary { extension Array where Element == RoomSummary {
@@ -91,7 +91,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
}) })
// Forces the listener above to be called with the current state // Forces the listener above to be called with the current state
updateFilterPattern(nil) setFilter(.all)
listUpdatesTaskHandle = listUpdatesSubscriptionResult?.entriesStream listUpdatesTaskHandle = listUpdatesSubscriptionResult?.entriesStream
@@ -134,13 +134,15 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
} }
} }
func updateFilterPattern(_ pattern: String?) { func setFilter(_ filter: RoomSummaryProviderFilter) {
guard let pattern, !pattern.isEmpty else { switch filter {
case .none:
_ = listUpdatesSubscriptionResult?.controller.setFilter(kind: .none)
case .all:
_ = listUpdatesSubscriptionResult?.controller.setFilter(kind: .all) _ = listUpdatesSubscriptionResult?.controller.setFilter(kind: .all)
return case .normalizedMatchRoomName(let query):
_ = listUpdatesSubscriptionResult?.controller.setFilter(kind: .normalizedMatchRoomName(pattern: query.lowercased()))
} }
_ = listUpdatesSubscriptionResult?.controller.setFilter(kind: .normalizedMatchRoomName(pattern: pattern.lowercased()))
} }
// MARK: - Private // MARK: - Private
@@ -156,23 +158,9 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
MXLog.verbose("\(name): Received \(diffs.count) diffs, current room list \(rooms.compactMap { $0.id ?? "Empty" })") MXLog.verbose("\(name): Received \(diffs.count) diffs, current room list \(rooms.compactMap { $0.id ?? "Empty" })")
var updatedItems = rooms rooms = diffs.reduce(rooms) { currentItems, diff in
for diff in diffs { processDiff(diff, on: currentItems)
let resetDiffChunkingThreshhold = 50
if case .reset(let values) = diff, values.count > resetDiffChunkingThreshhold {
// Special case resets in order to prevent large updates from blocking the UI
// Render the first resetDiffChunkingThreshhold as a reset and then append the rest to give the UI time to update
updatedItems = processDiff(.reset(values: Array(values[..<resetDiffChunkingThreshhold])), on: updatedItems)
// Once a reset is chunked dispatch the first part to the UI for rendering
rooms = updatedItems
updatedItems = processDiff(.append(values: Array(values.dropFirst(resetDiffChunkingThreshhold))), on: updatedItems)
} else {
updatedItems = processDiff(diff, on: updatedItems)
}
} }
rooms = updatedItems
MXLog.verbose("\(name): Finished applying \(diffs.count) diffs, new room list \(rooms.compactMap { $0.id ?? "Empty" })") MXLog.verbose("\(name): Finished applying \(diffs.count) diffs, new room list \(rooms.compactMap { $0.id ?? "Empty" })")
@@ -87,6 +87,12 @@ enum RoomSummary: CustomStringConvertible, Equatable {
} }
} }
enum RoomSummaryProviderFilter {
case none
case all
case normalizedMatchRoomName(String)
}
protocol RoomSummaryProviderProtocol { protocol RoomSummaryProviderProtocol {
/// Publishes the currently available room summaries /// Publishes the currently available room summaries
var roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> { get } var roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> { get }
@@ -100,5 +106,5 @@ protocol RoomSummaryProviderProtocol {
func updateVisibleRange(_ range: Range<Int>) func updateVisibleRange(_ range: Range<Int>)
func updateFilterPattern(_ pattern: String?) func setFilter(_ filter: RoomSummaryProviderFilter)
} }
+1 -1
View File
@@ -45,7 +45,7 @@ packages:
# Element/Matrix dependencies # Element/Matrix dependencies
MatrixRustSDK: MatrixRustSDK:
url: https://github.com/matrix-org/matrix-rust-components-swift url: https://github.com/matrix-org/matrix-rust-components-swift
exactVersion: 1.1.18 exactVersion: 1.1.19
# path: ../matrix-rust-sdk # path: ../matrix-rust-sdk
DesignKit: DesignKit:
path: DesignKit path: DesignKit