diff --git a/RadixWallet/Features/AccountHistory/TransactionHistory+Reducer.swift b/RadixWallet/Features/AccountHistory/TransactionHistory+Reducer.swift index 3e67ddf46c..db6ac9ff22 100644 --- a/RadixWallet/Features/AccountHistory/TransactionHistory+Reducer.swift +++ b/RadixWallet/Features/AccountHistory/TransactionHistory+Reducer.swift @@ -29,8 +29,6 @@ public struct TransactionHistory: Sendable, FeatureReducer { /// Workaround, TCA sends the sectionDisappeared after we dismiss, causing a run-time warning var didDismiss: Bool = false - var transactionToScrollTo: TXID? = nil - struct Loading: Hashable, Sendable { let parameters: TransactionHistoryParameters var isLoading: Bool = false diff --git a/RadixWallet/Features/AccountHistory/TransactionHistory+View.swift b/RadixWallet/Features/AccountHistory/TransactionHistory+View.swift index 63b7fd716d..afbfcf6cc4 100644 --- a/RadixWallet/Features/AccountHistory/TransactionHistory+View.swift +++ b/RadixWallet/Features/AccountHistory/TransactionHistory+View.swift @@ -587,8 +587,32 @@ extension TransactionHistory { } public func updateUIView(_ uiView: UITableView, context: Context) { - context.coordinator.sections = sections - uiView.reloadData() + guard sections != context.coordinator.sections else { return } + let oldTransactions = context.coordinator.sections.allTransactions + let newTransactions = sections.allTransactions + + if !oldTransactions.isEmpty, newTransactions.hasSuffix(oldTransactions) { + print(" •• updateUIView: inserted \(newTransactions.count - oldTransactions.count) above") + let oldContentHeight = uiView.contentSize.height + let oldContentOffset = uiView.contentOffset.y + context.coordinator.sections = sections + uiView.reloadData() + let newContentHeight = uiView.contentSize.height + + let new = oldContentOffset + newContentHeight - oldContentHeight + let inserted = newContentHeight - oldContentHeight + print(" •• updateUIView: (height : offset): \(oldContentHeight) : \(oldContentOffset) -> \(newContentHeight) : \(new) [\(inserted)]") + + uiView.contentOffset.y = oldContentOffset + newContentHeight - oldContentHeight + } else if !oldTransactions.isEmpty, newTransactions.hasPrefix(oldTransactions) { + print(" •• updateUIView: inserted \(newTransactions.count - oldTransactions.count) below") + context.coordinator.sections = sections + uiView.reloadData() + } else { + print(" •• updateUIView: everything changed") + context.coordinator.sections = sections + uiView.reloadData() + } } public func makeCoordinator() -> Coordinator { @@ -607,8 +631,6 @@ extension TransactionHistory { private var scrolling: (direction: ScrollDirection, count: Int) = (.down, 0) - var scrolledToTransaction: TXID? = nil - public init( sections: IdentifiedArrayOf, action: @escaping (Action) -> Void @@ -668,11 +690,13 @@ extension TransactionHistory { } previousCell = indexPath + // We only want to pre-emptively load if they have been scrolling for a while in the same direction if scrolling.count > 8 { - if scrolling.direction == .down, bottomTransactions.contains(txID) { + let transactions = sections.allTransactions + if scrolling.direction == .down, transactions.suffix(15).contains(txID) { action(.nearingBottom) scrolling.count = 0 - } else if scrollDirection == .up, topTransactions.contains(txID) { + } else if scrollDirection == .up, transactions.prefix(7).contains(txID) { action(.nearingTop) scrolling.count = 0 } @@ -707,14 +731,71 @@ extension TransactionHistory { action(.monthChanged(newMonth)) month = newMonth } + } + } +} - private var topTransactions: some Collection { - sections.prefix(7).flatMap(\.transactions.ids).prefix(7) - } +extension Collection where Element: Equatable { + func hasPrefix(_ elements: some Collection) -> Bool { + prefix(elements.count).elementsEqual(elements) + } - private var bottomTransactions: [TXID] { - sections.suffix(15).flatMap(\.transactions.ids).suffix(15) + func hasSuffix(_ elements: some Collection) -> Bool { + suffix(elements.count).elementsEqual(elements) + } + + func prefix(sharedWith other: some Collection) -> [Element] { + zip(self, other).prefix(while: ==).map(\.0) + } + + func suffix(sharedWith other: some Collection) -> some Collection { + zip(self, other).map(Pair.init).suffix(while: \.equal).map(\.left) + } +} + +extension IdentifiedArrayOf { + var allTransactions: [TXID] { + flatMap(\.transactions.ids) + } + + var firstTransaction: TXID? { + first?.transactions.first?.id + } + + func index(of transaction: TXID) -> IndexPath? { + for (index, section) in enumerated() { + if let row = section.transactions.ids.firstIndex(of: transaction) { + return .init(row: row, section: index) } } + + return nil + } +} + +// MARK: - Pair +public struct Pair { + public let left: L + public let right: R + + public init(_ left: L, _ right: R) { + self.left = left + self.right = right + } +} + +// MARK: Sendable +extension Pair: Sendable where L: Sendable, R: Sendable {} + +// MARK: Equatable +extension Pair: Equatable where L: Equatable, R: Equatable {} + +// MARK: Hashable +extension Pair: Hashable where L: Hashable, R: Hashable {} + +extension Pair where L == R, L: Equatable, R: Equatable { + /// The two components are equal + public var equal: Bool { + left == right } }