mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 04:18:31 +00:00
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:
@@ -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,18 +20,14 @@ 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 {
|
|
||||||
EmptyView()
|
|
||||||
} else {
|
|
||||||
ForEach(context.viewState.visibleRooms) { room in
|
ForEach(context.viewState.visibleRooms) { room in
|
||||||
if room.isPlaceholder {
|
if room.isPlaceholder {
|
||||||
HomeScreenRoomCell(room: room, context: context, isSelected: false)
|
HomeScreenRoomCell(room: room, context: context, isSelected: false)
|
||||||
@@ -55,5 +51,4 @@ struct HomeScreenRoomList: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user