This commit is contained in:
Avently
2024-12-30 10:34:31 -08:00
parent e2b9e162eb
commit 6b308693f2
6 changed files with 144 additions and 54 deletions
@@ -17,7 +17,7 @@ func apiLoadMessages(
_ pagination: ChatPagination,
_ chatState: ActiveChatState,
_ search: String = "",
_ visibleItemIndexesNonReversed: () -> ClosedRange<Int> = { 0 ... 0 }
_ visibleItemIndexesNonReversed: @MainActor () -> ClosedRange<Int> = { 0 ... 0 }
) async {
let chat: Chat
let navInfo: NavigationInfo
@@ -67,8 +67,9 @@ func apiLoadMessages(
guard let indexInCurrentItems else { return }
let (newIds, _) = mapItemsToIds(chat.chatItems)
let wasSize = newItems.count
let visibleItemIndexes = await MainActor.run { visibleItemIndexesNonReversed() }
let modifiedSplits = removeDuplicatesAndModifySplitsOnBeforePagination(
unreadAfterItemId, &newItems, newIds, chatState.splits, visibleItemIndexesNonReversed
unreadAfterItemId, &newItems, newIds, chatState.splits, visibleItemIndexes
)
let insertAt = max((indexInCurrentItems - (wasSize - newItems.count) + modifiedSplits.trimmedIds.count), 0)
newItems.insert(contentsOf: chat.chatItems, at: insertAt)
@@ -151,18 +152,21 @@ private func removeDuplicatesAndModifySplitsOnBeforePagination(
_ newItems: inout [ChatItem],
_ newIds: Set<Int64>,
_ splits: [Int64],
_ visibleItemIndexesNonReversed: () -> ClosedRange<Int>
_ visibleItemIndexes: ClosedRange<Int>
) -> ModifiedSplits {
var oldUnreadSplitIndex: Int = -1
var newUnreadSplitIndex: Int = -1
let visibleItemIndexes = visibleItemIndexesNonReversed()
var lastSplitIndexTrimmed: Int? = nil
var allowedTrimming = true
var index = 0
/** keep the newest [TRIM_KEEP_COUNT] items (bottom area) and oldest [TRIM_KEEP_COUNT] items, trim others */
let trimRange = visibleItemIndexes.upperBound + TRIM_KEEP_COUNT ... newItems.count - TRIM_KEEP_COUNT
let trimLowerBound = visibleItemIndexes.upperBound + TRIM_KEEP_COUNT
let trimUpperBound = newItems.count - TRIM_KEEP_COUNT
let trimRange = trimUpperBound >= trimLowerBound ? trimLowerBound ... trimUpperBound : -1 ... -1
var trimmedIds = Set<Int64>()
let prevItemTrimRange = visibleItemIndexes.upperBound + TRIM_KEEP_COUNT + 1 ... newItems.count - TRIM_KEEP_COUNT
let prevTrimLowerBound = visibleItemIndexes.upperBound + TRIM_KEEP_COUNT + 1
let prevTrimUpperBound = newItems.count - TRIM_KEEP_COUNT
let prevItemTrimRange = prevTrimUpperBound >= prevTrimLowerBound ? prevTrimLowerBound ... prevTrimUpperBound : -1 ... -1
var newSplits = splits
newItems.removeAll(where: {
@@ -245,8 +245,9 @@ class ActiveChatState {
let currentIndex = nonReversedItems.firstIndex(where: { $0.id == unreadAfterItemId })
let newIndex = nonReversedItems.firstIndex(where: { $0.id == toItemId })
guard let currentIndex, let newIndex else {
return unreadAfterItemId = toItemId
return
}
unreadAfterItemId = toItemId
let unreadDiff = newIndex > currentIndex
? -nonReversedItems[currentIndex + 1..<newIndex + 1].filter { $0.isRcvNew }.count
: nonReversedItems[newIndex + 1..<currentIndex + 1].filter { $0.isRcvNew }.count
@@ -255,8 +256,9 @@ class ActiveChatState {
func moveUnreadAfterItem(_ fromIndex: Int, _ toIndex: Int, _ nonReversedItems: [ChatItem]) {
if fromIndex == -1 || toIndex == -1 {
return unreadAfterItemId = nonReversedItems[toIndex].id
return
}
unreadAfterItemId = nonReversedItems[toIndex].id
let unreadDiff = toIndex > fromIndex
? -nonReversedItems[fromIndex + 1..<toIndex + 1].filter { $0.isRcvNew }.count
: nonReversedItems[toIndex + 1..<fromIndex + 1].filter { $0.isRcvNew }.count
@@ -308,6 +310,7 @@ class BoxedValue<T: Hashable>: Hashable {
}
extension ReverseList.Controller {
@MainActor
func visibleItemIndexesNonReversed(_ mergedItems: Binding<MergedItems>) -> ClosedRange<Int> {
let zero = 0 ... 0
if itemCount == 0 {
@@ -30,26 +30,78 @@ func loadLastItems(_ loadingMoreItems: Binding<Bool>, _ chatInfo: ChatInfo) {
}
}
func preloadItems(_ mergedItems: MergedItems, _ state: ListState, _ ignoreLoadingRequests: Binding<[Int64]>, _ loadItems: @escaping (ChatPagination) -> Void) {
func preloadItems(_ mergedItems: MergedItems, _ allowLoadMoreItems: Bool, _ listState: ListState, _ ignoreLoadingRequests: Binding<[Int64]>, _ loadItems: @escaping (ChatPagination) async -> Bool) async {
let allowLoad = allowLoadMoreItems || mergedItems.items.count == listState.lastVisibleItemIndex + 1
let remaining = ChatPagination.UNTIL_PRELOAD_COUNT
let firstVisibleIndex = state.firstVisibleItemIndex
let firstVisibleIndex = listState.firstVisibleItemIndex
preloadItemsAfter()
await preloadItemsBefore()
await preloadItemsAfter()
func preloadItemsAfter() {
func preloadItemsBefore() async {
let splits = mergedItems.splits
let lastVisibleIndex = listState.lastVisibleItemIndex
var lastIndexToLoadFrom: Int? = findLastIndexToLoadFromInSplits(firstVisibleIndex, lastVisibleIndex, remaining, splits)
logger.debug("LALAL LASTINDEX TO LOAD FROM \(lastIndexToLoadFrom ?? -1)")
let items: [ChatItem] = ItemsModel.shared.reversedChatItems.reversed()
if splits.isEmpty && !items.isEmpty && lastVisibleIndex > mergedItems.items.count - remaining && items.count >= ChatPagination.INITIAL_COUNT {
lastIndexToLoadFrom = items.count - 1
}
logger.debug("LALAL LASTINDEX TO LOAD FROM after \(lastIndexToLoadFrom ?? -1), count \(items.count)")
let loadFromItemId: Int64?
if allowLoad, let lastIndexToLoadFrom {
let index = items.count - 1 - lastIndexToLoadFrom
loadFromItemId = index >= 0 ? items[index].id : nil
logger.debug("LALAL LASTINDEX TO LOAD FROM inside \(loadFromItemId ?? -1)")
} else {
logger.debug("LALAL LOADFROMNIL")
loadFromItemId = nil
}
guard let loadFromItemId, !ignoreLoadingRequests.wrappedValue.contains(loadFromItemId) else {
return
}
let sizeWas = items.count
let firstItemIdWas = items.first?.id
let triedToLoad = await loadItems(ChatPagination.before(chatItemId: loadFromItemId, count: ChatPagination.PRELOAD_COUNT))
logger.debug("LALAL PRELOAD BEFORE \(String(describing: splits)) \(firstVisibleIndex) \(sizeWas) \(triedToLoad) \(lastIndexToLoadFrom ?? -1)")
if triedToLoad && sizeWas == ItemsModel.shared.reversedChatItems.count && firstItemIdWas == ItemsModel.shared.reversedChatItems.last?.id {
ignoreLoadingRequests.wrappedValue.append(loadFromItemId)
}
}
func preloadItemsAfter() async {
let items: [ChatItem] = ItemsModel.shared.reversedChatItems.reversed()
let splits = mergedItems.splits
let split = splits.last(where: { $0.indexRangeInParentItems.contains(firstVisibleIndex) })
// we're inside a splitRange (top --- [end of the splitRange --- we're here --- start of the splitRange] --- bottom)
logger.debug("LALAL PRELOAD AFTER \(String(describing: split)) \(firstVisibleIndex) \(items.count - 1 - (split?.indexRangeInReversed.lowerBound ?? -1))")
logger.debug("LALAL PRELOAD AFTER \(String(describing: splits)) \(firstVisibleIndex) \((split?.indexRangeInParentItems.lowerBound ?? 0) + remaining) \(items.count - 1 - (split?.indexRangeInParentItems.lowerBound ?? -1))")
if let split, split.indexRangeInParentItems.lowerBound + remaining > firstVisibleIndex {
let index = items.count - 1 - split.indexRangeInReversed.lowerBound
if index >= 0 {
let loadFromItemId = items[index].id
loadItems(.after(chatItemId: loadFromItemId, count: ChatPagination.PRELOAD_COUNT))
_ = await loadItems(ChatPagination.after(chatItemId: loadFromItemId, count: ChatPagination.PRELOAD_COUNT))
}
}
}
}
private func findLastIndexToLoadFromInSplits(_ firstVisibleIndex: Int, _ lastVisibleIndex: Int, _ remaining: Int, _ splits: [SplitRange]) -> Int? {
for split in splits {
// before any split
if split.indexRangeInParentItems.lowerBound > firstVisibleIndex {
if lastVisibleIndex > (split.indexRangeInParentItems.lowerBound - remaining) {
return split.indexRangeInReversed.lowerBound - 1
}
break
}
let containsInRange = split.indexRangeInParentItems.contains(firstVisibleIndex)
if containsInRange {
if lastVisibleIndex > (split.indexRangeInParentItems.upperBound - remaining) {
return split.indexRangeInReversed.upperBound
}
break
}
}
return nil
}
+37 -31
View File
@@ -33,7 +33,6 @@ struct ChatView: View {
@State private var customUserProfile: Profile?
@State private var connectionCode: String?
@State private var loadingItems = false
@State private var firstPage = false
@State private var searchMode = false
@State private var searchText: String = ""
@FocusState private var searchFocussed
@@ -47,6 +46,7 @@ struct ChatView: View {
@State private var selectedChatItems: Set<Int64>? = nil
@State private var showDeleteSelectedMessages: Bool = false
@State private var allowToDeleteSelectedMessagesForAll: Bool = false
@State private var allowLoadMoreItems: Bool = false
@State private var ignoreLoadingRequests: [Int64] = []
@AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
@@ -202,7 +202,9 @@ struct ChatView: View {
} else {
.last(count: loadItemsPerPage)
}
loadChatItems(chat.chatInfo, pagination)
Task {
_ = await loadChatItems(chat.chatInfo, pagination)
}
}
}
.environmentObject(scrollModel)
@@ -442,7 +444,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, ignoreLoadingRequests: searchValueIsEmpty ? $ignoreLoadingRequests : Binding.constant([])) { index, mergedItem in
ReverseList(items: im.reversedChatItems, mergedItems: $mergedItems, revealedItems: $revealedItems, unreadCount: Binding.constant(chat.chatStats.unreadCount), scrollState: $scrollModel.state, 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
@@ -476,11 +478,15 @@ struct ChatView: View {
.environmentObject(theme) // crashes without this line when scrolling to the first unread in ReverseList
.id(ci.id) // Required to trigger `onAppear` on iOS15
} loadItems: { pagination, visibleItemIndexesNonReversed in
loadChatItems(cInfo, pagination, visibleItemIndexesNonReversed)
await loadChatItems(cInfo, pagination, visibleItemIndexesNonReversed)
}
.onAppear {
mergedItems = MergedItems.create(im.reversedChatItems, chat.chatStats.unreadCount, revealedItems, ItemsModel.shared.chatState)
loadLastItems($loadingItems, chat.chatInfo)
allowLoadMoreItems = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
allowLoadMoreItems = true
}
}
.onChange(of: im.reversedChatItems) { items in
mergedItems = MergedItems.create(items, chat.chatStats.unreadCount, revealedItems, ItemsModel.shared.chatState)
@@ -493,6 +499,10 @@ struct ChatView: View {
}
.onChange(of: chat.id) { _ in
loadLastItems($loadingItems, chat.chatInfo)
allowLoadMoreItems = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
allowLoadMoreItems = true
}
}
.opacity(ItemsModel.shared.isLoading ? 0 : 1)
.padding(.vertical, -InvertedTableView.inset)
@@ -893,34 +903,30 @@ struct ChatView: View {
}
}
private func loadChatItems(_ cInfo: ChatInfo, _ pagination: ChatPagination, _ visibleItemIndexesNonReversed: @escaping () -> ClosedRange<Int> = { 0 ... 0 }) {
Task {
if loadingItems || firstPage { return }
loadingItems = true
var chatItemsAvailable = true
var itemsCountChanged = false
// Load additional items until the page is +50 large after merging
//while chatItemsAvailable && filtered(im.reversedChatItems).count < loadItemsPerPage {
let oldCount = im.reversedChatItems.count
await apiLoadMessages(
cInfo.chatType,
cInfo.apiId,
pagination,
im.chatState,
searchText,
visibleItemIndexesNonReversed
)
itemsCountChanged = im.reversedChatItems.count != oldCount
chatItemsAvailable = itemsCountChanged
//}
logger.debug("LALAL ITEMCOUNTCHANGED \(itemsCountChanged) \(oldCount) \(im.reversedChatItems.count)")
await MainActor.run {
if !itemsCountChanged {
firstPage = true
}
loadingItems = false
}
private func loadChatItems(_ cInfo: ChatInfo, _ pagination: ChatPagination, _ visibleItemIndexesNonReversed: @escaping () -> ClosedRange<Int> = { 0 ... 0 }) async -> Bool {
if loadingItems { return false }
loadingItems = true
var chatItemsAvailable = true
var itemsCountChanged = false
// Load additional items until the page is +50 large after merging
//while chatItemsAvailable && filtered(im.reversedChatItems).count < loadItemsPerPage {
let oldCount = im.reversedChatItems.count
await apiLoadMessages(
cInfo.chatType,
cInfo.apiId,
pagination,
im.chatState,
searchText,
visibleItemIndexesNonReversed
)
itemsCountChanged = im.reversedChatItems.count != oldCount
chatItemsAvailable = itemsCountChanged
//}
logger.debug("LALAL ITEMCOUNTCHANGED \(itemsCountChanged) \(oldCount) \(im.reversedChatItems.count)")
await MainActor.run {
loadingItems = false
}
return true
}
func stopAudioPlayer() {
+31 -6
View File
@@ -18,6 +18,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
@Binding var unreadCount: Int
@Binding var scrollState: ReverseListScrollModel.State
@Binding var allowLoadMoreItems: Bool
@Binding var ignoreLoadingRequests: [Int64]
/// Closure, that returns user interface for a given item
@@ -25,7 +26,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
let content: (Int, MergedItem) -> Content
// pagination, visibleItemIndexesNonReversed
let loadItems: (ChatPagination, @escaping () -> ClosedRange<Int>) -> Void
let loadItems: (ChatPagination, @escaping @MainActor () -> ClosedRange<Int>) async -> Bool
func makeUIViewController(context: Context) -> Controller {
Controller(representer: self)
@@ -78,6 +79,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
var runBlockOnEndDecelerating: (() -> Void)? = nil
private var scrollToRowOnAppear = 0
private var translationToApply: CGPoint? = nil
init(representer: ReverseList) {
self.representer = representer
@@ -112,12 +114,24 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
logger.debug("LALAL ITEM \(indexPath.item)")
Task {
// do not put it outside Task because it will be stackoverflow error
if let state = self.getListState(), prevFirstVisible != state.firstVisibleItemIndex {
let state = await MainActor.run {
self.getListState()
}
if let state, prevFirstVisible != state.firstVisibleItemIndex {
prevFirstVisible = state.firstVisibleItemIndex
logger.debug("LALAL LOADING before")
preloadItems(self.representer.mergedItems, state, self.representer.$ignoreLoadingRequests) { pagination in
await preloadItems(self.representer.mergedItems, self.representer.allowLoadMoreItems, state, self.representer.$ignoreLoadingRequests) { pagination in
logger.debug("LALAL LOADING INSIDE")
self.representer.loadItems(pagination, { self.visibleItemIndexesNonReversed(Binding.constant(self.representer.mergedItems)) })
let triedToLoad = await self.representer.loadItems(pagination, { self.visibleItemIndexesNonReversed(Binding.constant(self.representer.mergedItems)) })
if triedToLoad {
let t = self.tableView.panGestureRecognizer.translation(in: self.tableView.superview!)
if t.y != 0 {
self.translationToApply = t
}
// stop scrolling
self.tableView.setContentOffset(self.tableView.contentOffset, animated: false)
}
return triedToLoad
}
}
}
@@ -240,7 +254,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
if #available(iOS 16.0, *) {
animated = true
}
if let index, tableView.numberOfRows(inSection: 0) != 0 {
if let index, tableView.numberOfRows(inSection: 0) > index {
tableView.scrollToRow(
at: IndexPath(row: index, section: 0),
at: position,
@@ -283,7 +297,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
}))
itemsInCurrentSnapshot = new
if (wasCount == 101 && self.representer.mergedItems.items.count == 101) || snap.itemIdentifiers == prevSnapshot.itemIdentifiers {
if snap.itemIdentifiers == prevSnapshot.itemIdentifiers {
logger.debug("LALAL SAME ITEMS, not rebuilding the tableview")
return
}
@@ -350,6 +364,17 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
)
let state = getListState()!
logger.debug("LALAL NOW FIRST VISIBLE \(state.firstVisibleItemIndex) \(state.firstVisibleItemOffset)")
if let t = translationToApply {
// self.tableView.panGestureRecognizer.setTranslation(CGPointMake(t.x, t.y), in: self.tableView.superview!)
let o = self.tableView.contentOffset
// UIView.animate(withDuration: 0.5) {
// self.tableView.setContentOffset(CGPointMake(o.x + t.x, o.y + t.y), animated: false)
// self.tableView.layoutIfNeeded()
// }
self.tableView.setContentOffset(CGPointMake(o.x + t.x, o.y + t.y), animated: true)
translationToApply = nil
}
//}
}
}
+1 -1
View File
@@ -1185,7 +1185,7 @@ struct NewUser: Encodable {
public enum ChatPagination {
public static let INITIAL_COUNT = 50
public static let PRELOAD_COUNT = 50
public static let UNTIL_PRELOAD_COUNT = 50
public static let UNTIL_PRELOAD_COUNT = 20
case last(count: Int)
case after(chatItemId: Int64, count: Int)