mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-09 09:35:55 +00:00
changes
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user