optimized item identifiers to use merged item directly

This commit is contained in:
Avently
2025-01-16 04:55:24 -08:00
parent 890e35b902
commit 31ee89c3c7
4 changed files with 37 additions and 99 deletions
+8 -3
View File
@@ -952,12 +952,17 @@ final class ChatModel: ObservableObject {
// returns the previous member in the same merge group and the count of members in this group
func getPrevHiddenMember(_ member: GroupMember, _ range: ClosedRange<Int>) -> (GroupMember?, Int) {
let items = im.reversedChatItems
var prevMember: GroupMember? = nil
var memberIds: Set<Int64> = []
for i in range {
if case let .groupRcv(m) = im.reversedChatItems[i].chatDir {
if prevMember == nil && m.groupMemberId != member.groupMemberId { prevMember = m }
memberIds.insert(m.groupMemberId)
if i < items.count {
if case let .groupRcv(m) = items[i].chatDir {
if prevMember == nil && m.groupMemberId != member.groupMemberId { prevMember = m }
memberIds.insert(m.groupMemberId)
}
} else {
logger.error("getPrevHiddenMember: index >= count of reversed items: \(i) vs \(items.count)")
}
}
return (prevMember, memberIds.count)
@@ -114,15 +114,11 @@ struct MergedItems {
}
enum MergedItem: /*Identifiable, */Hashable {
// var id: Int64 {
// get {
// switch self {
// case let .single(item, _): item.item.id
// case let .grouped(items, _, _, _, _, _, _): items.boxedValue.last!.item.id
// }
// }
// }
enum MergedItem: Hashable, Equatable {
// equatable and hashable implementations allows to NSDiffableDataSourceSnapshot to see the difference and correcrly scroll items we want. Without any of it, the scroll position will be random in ReverseList
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.hashValue == rhs.hashValue
}
var hashValue: Int { self.newest().item.hashValue }
@@ -275,26 +271,7 @@ class ActiveChatState {
}
}
struct BoxedValue2<T: Hashable>: /*Identifiable, */Hashable {
// var id: Int64 { (boxedValue as! MergedItem).id }
static func == (lhs: BoxedValue2<T>, rhs: BoxedValue2<T>) -> Bool {
lhs.boxedValue == rhs.boxedValue
}
var hashValue: Int { (boxedValue as! MergedItem).newest().hashValue }
func hash(into hasher: inout Hasher) {
hasher.combine("\((boxedValue as! MergedItem).newest())")
}
var boxedValue : T
init(_ value: T) {
self.boxedValue = value
}
}
class BoxedValue<T: Hashable>: Hashable {
class BoxedValue<T: Hashable>: Equatable, Hashable {
static func == (lhs: BoxedValue<T>, rhs: BoxedValue<T>) -> Bool {
lhs.boxedValue == rhs.boxedValue
}
@@ -313,7 +290,7 @@ extension ReverseList.Controller {
@MainActor
func visibleItemIndexesNonReversed(_ mergedItems: Binding<MergedItems>) -> ClosedRange<Int> {
let zero = 0 ... 0
if itemCount == 0 {
if mergedItems.wrappedValue.items.count == 0 {
return zero
}
let listState = getListState() ?? ListState()
+1 -1
View File
@@ -445,7 +445,7 @@ struct ChatView: View {
return GeometryReader { g in
let _ = logger.debug("LALAL RELOAD \(im.reversedChatItems.count)")
// LALAL CAN I CHANGE BINDING LIKE THIS IN ignoreLoadingRequests?
ReverseList(items: im.reversedChatItems, mergedItems: $mergedItems, revealedItems: $revealedItems, unreadCount: Binding.constant(chat.chatStats.unreadCount), scrollState: $scrollModel.state, loadingMoreItems: $loadingMoreItems, allowLoadMoreItems: $allowLoadMoreItems, ignoreLoadingRequests: searchValueIsEmpty ? $ignoreLoadingRequests : Binding.constant([])) { index, mergedItem in
ReverseList(mergedItems: mergedItems, revealedItems: $revealedItems, unreadCount: Binding.constant(chat.chatStats.unreadCount), scrollState: $scrollModel.state, loadingMoreItems: $loadingMoreItems, allowLoadMoreItems: $allowLoadMoreItems, ignoreLoadingRequests: searchValueIsEmpty ? $ignoreLoadingRequests : Binding.constant([])) { index, mergedItem in
let ci = switch mergedItem {
case let .single(item, _): item.item
case let .grouped(items, _, _, _, _, _, _): items.boxedValue.last!.item
+21 -65
View File
@@ -12,8 +12,7 @@ import SimpleXChat
/// A List, which displays it's items in reverse order - from bottom to top
struct ReverseList<Content: View>: UIViewControllerRepresentable {
let items: Array<ChatItem>
@Binding var mergedItems: MergedItems
var mergedItems: MergedItems
@Binding var revealedItems: Set<Int64>
@Binding var unreadCount: Int
@@ -37,11 +36,11 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
func updateUIViewController(_ controller: Controller, context: Context) {
controller.representer = self
if case let .scrollingTo(destination) = scrollState, !items.isEmpty, !controller.scrollToItemInProgress {
if case let .scrollingTo(destination) = scrollState, !mergedItems.items.isEmpty, !controller.scrollToItemInProgress {
controller.view.layer.removeAllAnimations()
switch destination {
case let .item(id):
let row = items.firstIndex(where: { $0.id == id })
let row = mergedItems.indexInParentItems[id]
if let row {
logger.debug("LALAL SCROLLING TO \(row)")
controller.scroll(to: row, position: .bottom)
@@ -64,11 +63,11 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
// so it's better to just wait until dragging ends
if waitOnEndedScrolling && controller.tableView.isDragging/* && !controller.tableView.isDecelerating*/ {
controller.runBlockOnEndDecelerating = {
controller.update(items: items)
controller.update()
}
} else {
controller.runBlockOnEndDecelerating = nil
controller.update(items: items)
controller.update()
}
}
}
@@ -77,17 +76,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
public class Controller: UITableViewController {
private enum Section { case main }
var representer: ReverseList
// Here Int means hash of the ChatItem that is inside MergedItem.newest().item.hashValue.
// Putting MergedItem here directly prevents UITableViewDiffableDataSource to make partial updates
// which looks like UITableView scrolls to bottom on insert values to bottom instead of
// remains in the same scroll position
private var dataSource: UITableViewDiffableDataSource<Section, Int>!
var itemCount: Int {
get {
representer.mergedItems.items.count
}
}
private var itemsInPrevSnapshot: Dictionary<Int, MergedItem> = [:]
private var dataSource: UITableViewDiffableDataSource<Section, MergedItem>!
private let updateFloatingButtons = PassthroughSubject<Void, Never>()
private var bag = Set<AnyCancellable>()
@@ -128,17 +117,17 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
}
// 3. Configure data source
self.dataSource = UITableViewDiffableDataSource<Section, Int>(
self.dataSource = UITableViewDiffableDataSource<Section, MergedItem>(
tableView: tableView
) { (tableView, indexPath, item) -> UITableViewCell? in
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath)
if #available(iOS 16.0, *) {
cell.contentConfiguration = UIHostingConfiguration { self.representer.content(indexPath.item, self.itemsInPrevSnapshot[item]!) }
cell.contentConfiguration = UIHostingConfiguration { self.representer.content(indexPath.item, item) }
.margins(.all, 0)
.minSize(height: 1) // Passing zero will result in system default of 44 points being used
} else {
if let cell = cell as? HostingCell<Content> {
cell.set(content: self.representer.content(indexPath.item, self.itemsInPrevSnapshot[item]!), parent: self)
cell.set(content: self.representer.content(indexPath.item, item), parent: self)
} else {
fatalError("Unexpected Cell Type for: \(item)")
}
@@ -269,46 +258,26 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
}
}
func update(items: [ChatItem]) {
var prevSnapshot = dataSource.snapshot()
func update() {
let prevSnapshot = dataSource.snapshot()
let wasCount = prevSnapshot.numberOfItems
let willBeCount = representer.mergedItems.items.count
let items = representer.mergedItems.items
let willBeCount = items.count
let c = 1
let insertedSeveralNewestItems = wasCount != 0 && willBeCount - wasCount == c && prevSnapshot.itemIdentifiers.first!.hashValue == self.representer.mergedItems.items[c].hashValue
logger.debug("LALAL WAS \(wasCount) will be \(self.representer.mergedItems.items.count)")
//self.representer.mergedItems = MergedItems.create(items, representer.unreadCount, representer.revealedItems, ItemsModel.shared.chatState)
let snapshot: NSDiffableDataSourceSnapshot<Section, Int>
let itemsInCurrentSnapshot: Dictionary<Int, MergedItem>
if insertedSeveralNewestItems {
var new = itemsInPrevSnapshot
for i in 0 ..< c {
prevSnapshot.insertItems([representer.mergedItems.items[c - i].hashValue], beforeItem: prevSnapshot.itemIdentifiers.first!)
new[representer.mergedItems.items[c - i].hashValue] = representer.mergedItems.items[c - i]
}
itemsInCurrentSnapshot = new
snapshot = prevSnapshot
} else {
logger.debug("LALAL BEFORE snap")
var new: Dictionary<Int, MergedItem> = [:]
let identifiers = representer.mergedItems.items.map({ merged in
new[merged.hashValue] = merged
return merged.hashValue
})
logger.debug("LALAL AFTER snap")
if identifiers == prevSnapshot.itemIdentifiers {
if items == prevSnapshot.itemIdentifiers {
logger.debug("LALAL SAME ITEMS, not rebuilding the tableview")
// update counters because they are static, unbound to specific chat and will become outdated if a new empty chat was open after non-empty one with unread messages
updateFloatingButtons.send()
return
}
var snap = NSDiffableDataSourceSnapshot<Section, Int>()
snap.appendSections([.main])
snap.appendItems(identifiers)
itemsInCurrentSnapshot = new
snapshot = snap
}
var snapshot = NSDiffableDataSourceSnapshot<Section, MergedItem>()
snapshot.appendSections([.main])
snapshot.appendItems(items)
dataSource.defaultRowAnimation = .none
let wasContentHeight = tableView.contentSize.height
let wasOffset = tableView.contentOffset.y
let listState = getListState()
@@ -317,12 +286,11 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
let countDiff = max(0, snapshot.numberOfItems - prevSnapshot.numberOfItems)
// Sets content offset on initial load
if wasCount == 0 {
itemsInPrevSnapshot = itemsInCurrentSnapshot
dataSource.apply(
snapshot,
animatingDifferences: insertedSeveralNewestItems
)
if let firstUnreadItem = snapshot.itemIdentifiers.lastIndex(where: { hash in itemsInPrevSnapshot[hash]!.hasUnread() }) {
if let firstUnreadItem = snapshot.itemIdentifiers.lastIndex(where: { $0.hasUnread() }) {
scrollToRowWhenKnowSize(firstUnreadItem)
} else {
tableView.setContentOffset(
@@ -332,18 +300,14 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
}
} else if wasCount != snapshot.numberOfItems {
logger.debug("LALAL drag \(self.tableView.isDragging), decel \(self.tableView.isDecelerating)")
// tableView.panGestureRecognizer.isEnabled = false
// logger.debug("LALAL drag2 \(self.tableView.isDragging), decel \(self.tableView.isDecelerating)")
if useSmoothScrolling && tableView.isDecelerating {
// CATransaction.begin()
itemsInPrevSnapshot = itemsInCurrentSnapshot
tableView.panGestureRecognizer.isEnabled = false
tableView.beginUpdates()
dataSource.apply(
snapshot,
animatingDifferences: false
)
tableView.endUpdates()
// CATransaction.commit()
tableView.panGestureRecognizer.isEnabled = true
} else {
// remember current translation
@@ -354,7 +318,6 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
translationToApply = t
}
}
itemsInPrevSnapshot = itemsInCurrentSnapshot
dataSource.apply(
snapshot,
animatingDifferences: false
@@ -516,13 +479,6 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
await preloadItems(self.representer.mergedItems, self.representer.allowLoadMoreItems, state, self.representer.$ignoreLoadingRequests) { pagination in
logger.debug("LALAL LOADING INSIDE")
let triedToLoad = await self.representer.loadItems(false, pagination, { self.visibleItemIndexesNonReversed(Binding.constant(self.representer.mergedItems)) })
let superview = self.tableView.superview
if triedToLoad, let superview {
// let t = self.tableView.panGestureRecognizer.translation(in: superview)
// if t.y != 0 {
// self.translationToApply = t
// }
}
return triedToLoad
}
}