mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 19:23:36 +00:00
changes
This commit is contained in:
1 parent
bf54e22bee
commit
7a1cf8300d
5 files changed
+222
-82
No files matched your search
@@ -62,6 +62,10 @@ struct FramedItemView: View {
|
||||
withAnimation {
|
||||
scrollModel.scrollToItem(id: ci.id)
|
||||
}
|
||||
} else if let id = qi.itemId {
|
||||
scrollModel.scrollToItem(id: id)
|
||||
} else {
|
||||
showQuotedItemDoesNotExistAlert()
|
||||
}
|
||||
}
|
||||
} else if let itemForwarded = chatItem.meta.itemForwarded {
|
||||
@@ -336,6 +340,13 @@ struct FramedItemView: View {
|
||||
return videoWidth
|
||||
}
|
||||
}
|
||||
|
||||
private func showQuotedItemDoesNotExistAlert() {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title: "No message",
|
||||
message: "This message was deleted or not received yet."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func toggleSecrets<V: View>(_ ft: [FormattedText]?, _ showSecrets: Binding<Bool>, _ v: V) -> some View {
|
||||
|
||||
@@ -105,7 +105,7 @@ func apiLoadMessages(
|
||||
}
|
||||
case .around:
|
||||
newItems.append(contentsOf: oldItems)
|
||||
let newSplits = removeDuplicatesAndUpperSplits(&newItems, chat, chatState.splits, visibleItemIndexesNonReversed)
|
||||
let newSplits = await removeDuplicatesAndUpperSplits(&newItems, chat, chatState.splits, visibleItemIndexesNonReversed)
|
||||
// currently, items will always be added on top, which is index 0
|
||||
newItems.insert(contentsOf: chat.chatItems, at: 0)
|
||||
let newReversed: [ChatItem] = newItems.reversed()
|
||||
@@ -270,15 +270,15 @@ private func removeDuplicatesAndUpperSplits(
|
||||
_ newItems: inout [ChatItem],
|
||||
_ chat: Chat,
|
||||
_ splits: [Int64],
|
||||
_ visibleItemIndexesNonReversed: () -> ClosedRange<Int>
|
||||
) -> [Int64] {
|
||||
_ visibleItemIndexesNonReversed: @MainActor () -> ClosedRange<Int>
|
||||
) async -> [Int64] {
|
||||
if splits.isEmpty {
|
||||
removeDuplicates(&newItems, chat)
|
||||
return splits
|
||||
}
|
||||
|
||||
var newSplits = splits
|
||||
let visibleItemIndexes = visibleItemIndexesNonReversed()
|
||||
let visibleItemIndexes = await MainActor.run { visibleItemIndexesNonReversed() }
|
||||
let (newIds, _) = mapItemsToIds(chat.chatItems)
|
||||
var idsToTrim: [BoxedValue<Set<Int64>>] = []
|
||||
idsToTrim.append(BoxedValue(Set()))
|
||||
|
||||
@@ -85,6 +85,22 @@ func preloadItems(_ mergedItems: MergedItems, _ allowLoadMoreItems: Bool, _ list
|
||||
}
|
||||
}
|
||||
|
||||
func oldestPartiallyVisibleListItemInListStateOrNull(_ mergedItems: MergedItems, _ listState: ListState) -> ListItem? {
|
||||
if listState.lastVisibleItemIndex < mergedItems.items.count {
|
||||
return mergedItems.items[listState.lastVisibleItemIndex].oldest()
|
||||
} else {
|
||||
return mergedItems.items.last?.oldest()
|
||||
}
|
||||
}
|
||||
|
||||
private func lastFullyVisibleIemInListState(_ mergedItems: MergedItems, _ listState: ListState) -> ChatItem? {
|
||||
if listState.lastVisibleItemIndex < mergedItems.items.count {
|
||||
return mergedItems.items[listState.lastVisibleItemIndex].newest().item
|
||||
} else {
|
||||
return mergedItems.items.last?.newest().item
|
||||
}
|
||||
}
|
||||
|
||||
private func findLastIndexToLoadFromInSplits(_ firstVisibleIndex: Int, _ lastVisibleIndex: Int, _ remaining: Int, _ splits: [SplitRange]) -> Int? {
|
||||
for split in splits {
|
||||
// before any split
|
||||
@@ -105,3 +121,16 @@ private func findLastIndexToLoadFromInSplits(_ firstVisibleIndex: Int, _ lastVis
|
||||
return nil
|
||||
}
|
||||
|
||||
func tryBlockAndSetLoadingMore(_ loadingMoreItems: Binding<Bool>, _ block: @escaping () async throws -> Void) async {
|
||||
do {
|
||||
await MainActor.run {
|
||||
loadingMoreItems.wrappedValue = true
|
||||
}
|
||||
try await block()
|
||||
} catch {
|
||||
logger.error("Error loading more items: \(error)")
|
||||
}
|
||||
await MainActor.run {
|
||||
loadingMoreItems.wrappedValue = false
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ struct ChatView: View {
|
||||
@State private var connectionStats: ConnectionStats?
|
||||
@State private var customUserProfile: Profile?
|
||||
@State private var connectionCode: String?
|
||||
@State private var loadingItems = false
|
||||
@State private var loadingMoreItems = false
|
||||
@State private var searchMode = false
|
||||
@State private var searchText: String = ""
|
||||
@FocusState private var searchFocussed
|
||||
@@ -78,7 +78,7 @@ struct ChatView: View {
|
||||
VStack(spacing: 0) {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
chatItemsList()
|
||||
FloatingButtons(theme: theme, scrollModel: scrollModel, chat: chat)
|
||||
FloatingButtons(theme: theme, scrollModel: scrollModel, chat: chat, loadingMoreItems: $loadingMoreItems)
|
||||
}
|
||||
connectingText()
|
||||
if selectedChatItems == nil {
|
||||
@@ -177,6 +177,7 @@ struct ChatView: View {
|
||||
showChatInfoSheet = false
|
||||
selectedChatItems = nil
|
||||
revealedItems = Set()
|
||||
// LALAL
|
||||
scrollModel.scrollToBottom()
|
||||
stopAudioPlayer()
|
||||
if let cId {
|
||||
@@ -444,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, allowLoadMoreItems: $allowLoadMoreItems, 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, 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
|
||||
@@ -477,12 +478,16 @@ 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
|
||||
await loadChatItems(cInfo, pagination, visibleItemIndexesNonReversed)
|
||||
} loadItems: { unchecked, pagination, visibleItemIndexesNonReversed in
|
||||
if unchecked {
|
||||
await loadChatItemsUnchecked(cInfo, pagination, visibleItemIndexesNonReversed)
|
||||
} else {
|
||||
await loadChatItems(cInfo, pagination, visibleItemIndexesNonReversed)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
mergedItems = MergedItems.create(im.reversedChatItems, chat.chatStats.unreadCount, revealedItems, ItemsModel.shared.chatState)
|
||||
loadLastItems($loadingItems, chat.chatInfo)
|
||||
loadLastItems($loadingMoreItems, chat.chatInfo)
|
||||
allowLoadMoreItems = false
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
allowLoadMoreItems = true
|
||||
@@ -498,7 +503,7 @@ struct ChatView: View {
|
||||
mergedItems = MergedItems.create(im.reversedChatItems, unreadCount, revealedItems, ItemsModel.shared.chatState)
|
||||
}
|
||||
.onChange(of: chat.id) { _ in
|
||||
loadLastItems($loadingItems, chat.chatInfo)
|
||||
loadLastItems($loadingMoreItems, chat.chatInfo)
|
||||
allowLoadMoreItems = false
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
allowLoadMoreItems = true
|
||||
@@ -541,22 +546,29 @@ struct ChatView: View {
|
||||
@Published var isNearBottom: Bool = true
|
||||
@Published var date: Date?
|
||||
@Published var isDateVisible: Bool = false
|
||||
var mergedItems: MergedItems = MergedItems.init(items: [], splits: [], indexInParentItems: [:])
|
||||
var totalUnread: Int = 0
|
||||
var isReallyNearBottom: Bool = true
|
||||
var hideDateWorkItem: DispatchWorkItem?
|
||||
|
||||
func updateOnListChange(_ listState: ListState) {
|
||||
func updateOnListChange(_ mergedItems: MergedItems, _ listState: ListState) {
|
||||
let im = ItemsModel.shared
|
||||
let unreadBelow =
|
||||
if let id = listState.bottomItemId,
|
||||
let index = im.reversedChatItems.firstIndex(where: { $0.id == id })
|
||||
{
|
||||
im.reversedChatItems[..<index].reduce(into: 0) { unread, chatItem in
|
||||
if chatItem.isRcvNew { unread += 1 }
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
// let unreadBelow =
|
||||
// if let id = listState.bottomItemId,
|
||||
// let index = im.reversedChatItems.firstIndex(where: { $0.id == id })
|
||||
// {
|
||||
// im.reversedChatItems[..<index].reduce(into: 0) { unread, chatItem in
|
||||
// if chatItem.isRcvNew { unread += 1 }
|
||||
// }
|
||||
// } else {
|
||||
// 0
|
||||
// }
|
||||
let lastVisibleItem = oldestPartiallyVisibleListItemInListStateOrNull(mergedItems, listState)
|
||||
let unreadBelow = if let lastVisibleItem {
|
||||
max(0, totalUnread - lastVisibleItem.unreadBefore)
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
let date: Date? =
|
||||
if let topItemDate = listState.topItemDate {
|
||||
Calendar.current.startOfDay(for: topItemDate)
|
||||
@@ -571,6 +583,7 @@ struct ChatView: View {
|
||||
it.unreadBelow = unreadBelow
|
||||
it.date = date
|
||||
it.isReallyNearBottom = listState.scrollOffset > 0 && listState.scrollOffset < 500
|
||||
it.mergedItems = mergedItems
|
||||
}
|
||||
|
||||
// set floating button indication mode
|
||||
@@ -617,6 +630,7 @@ struct ChatView: View {
|
||||
let theme: AppTheme
|
||||
let scrollModel: ReverseListScrollModel
|
||||
let chat: Chat
|
||||
@Binding var loadingMoreItems: Bool
|
||||
@ObservedObject var model = FloatingButtonModel.shared
|
||||
|
||||
var body: some View {
|
||||
@@ -638,7 +652,12 @@ struct ChatView: View {
|
||||
.foregroundColor(theme.colors.primary)
|
||||
}
|
||||
.onTapGesture {
|
||||
scrollModel.scrollToNextPage()
|
||||
if let index = model.mergedItems.items.lastIndex(where: { $0.hasUnread() }) {
|
||||
// scroll to the top unread item
|
||||
loadingMoreItems = true
|
||||
scrollModel.scrollToRow(row: index)
|
||||
loadingMoreItems = false
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
Button {
|
||||
@@ -904,8 +923,8 @@ struct ChatView: View {
|
||||
}
|
||||
|
||||
private func loadChatItems(_ cInfo: ChatInfo, _ pagination: ChatPagination, _ visibleItemIndexesNonReversed: @escaping () -> ClosedRange<Int> = { 0 ... 0 }) async -> Bool {
|
||||
if loadingItems { return false }
|
||||
loadingItems = true
|
||||
if loadingMoreItems { return false }
|
||||
loadingMoreItems = true
|
||||
var chatItemsAvailable = true
|
||||
var itemsCountChanged = false
|
||||
// Load additional items until the page is +50 large after merging
|
||||
@@ -923,12 +942,34 @@ struct ChatView: View {
|
||||
chatItemsAvailable = itemsCountChanged
|
||||
//}
|
||||
logger.debug("LALAL ITEMCOUNTCHANGED \(itemsCountChanged) \(oldCount) \(im.reversedChatItems.count)")
|
||||
|
||||
await MainActor.run {
|
||||
loadingItems = false
|
||||
loadingMoreItems = false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func loadChatItemsUnchecked(_ cInfo: ChatInfo, _ pagination: ChatPagination, _ visibleItemIndexesNonReversed: @escaping () -> ClosedRange<Int> = { 0 ... 0 }) async -> Bool {
|
||||
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)")
|
||||
return true
|
||||
}
|
||||
|
||||
func stopAudioPlayer() {
|
||||
VoiceItemState.chatView.values.forEach { $0.audioPlayer?.stop() }
|
||||
VoiceItemState.chatView = [:]
|
||||
|
||||
@@ -18,6 +18,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
@Binding var unreadCount: Int
|
||||
|
||||
@Binding var scrollState: ReverseListScrollModel.State
|
||||
@Binding var loadingMoreItems: Bool
|
||||
@Binding var allowLoadMoreItems: Bool
|
||||
@Binding var ignoreLoadingRequests: [Int64]
|
||||
|
||||
@@ -25,8 +26,8 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
/// Index, merged item
|
||||
let content: (Int, MergedItem) -> Content
|
||||
|
||||
// pagination, visibleItemIndexesNonReversed
|
||||
let loadItems: (ChatPagination, @escaping @MainActor () -> ClosedRange<Int>) async -> Bool
|
||||
// unchecked, pagination, visibleItemIndexesNonReversed
|
||||
let loadItems: (Bool, ChatPagination, @escaping @MainActor () -> ClosedRange<Int>) async -> Bool
|
||||
|
||||
func makeUIViewController(context: Context) -> Controller {
|
||||
Controller(representer: self)
|
||||
@@ -40,7 +41,14 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
case .nextPage:
|
||||
controller.scrollToNextPage()
|
||||
case let .item(id):
|
||||
controller.scroll(to: items.firstIndex(where: { $0.id == id }), position: .bottom)
|
||||
let row = items.firstIndex(where: { $0.id == id })
|
||||
if let row {
|
||||
controller.scroll(to: row, position: .bottom)
|
||||
} else {
|
||||
controller.scrollToItem(id)
|
||||
}
|
||||
case let .row(row):
|
||||
controller.scroll(to: row, position: .bottom)
|
||||
case .bottom:
|
||||
controller.scroll(to: 0, position: .top)
|
||||
}
|
||||
@@ -111,30 +119,35 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
tableView: tableView
|
||||
) { (tableView, indexPath, item) -> UITableViewCell? in
|
||||
//if indexPath.item > self.itemCount - 8 {
|
||||
logger.debug("LALAL ITEM \(indexPath.item)")
|
||||
Task {
|
||||
// do not put it outside Task because it will be stackoverflow error
|
||||
let state = await MainActor.run {
|
||||
self.getListState()
|
||||
logger.debug("LALAL ITEM \(indexPath.item)")
|
||||
Task {
|
||||
// do not put it outside Task because it will be stackoverflow error
|
||||
let state = await MainActor.run {
|
||||
self.getListState()
|
||||
}
|
||||
if let state, prevFirstVisible != state.firstVisibleItemIndex {
|
||||
logger.debug("LALAL LOADING before")
|
||||
if representer.scrollState != .atDestination {
|
||||
// do not try to load anything in order to not interupt scrolling
|
||||
return
|
||||
}
|
||||
if let state, prevFirstVisible != state.firstVisibleItemIndex {
|
||||
prevFirstVisible = state.firstVisibleItemIndex
|
||||
logger.debug("LALAL LOADING before")
|
||||
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(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)
|
||||
prevFirstVisible = state.firstVisibleItemIndex
|
||||
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
|
||||
// stop scrolling
|
||||
self.tableView.setContentOffset(self.tableView.contentOffset, animated: false)
|
||||
}
|
||||
return triedToLoad
|
||||
}
|
||||
}
|
||||
}
|
||||
//}
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath)
|
||||
if #available(iOS 16.0, *) {
|
||||
@@ -167,7 +180,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
.throttle(for: 0.2, scheduler: DispatchQueue.global(qos: .background), latest: true)
|
||||
.sink {
|
||||
if let listState = DispatchQueue.main.sync(execute: { [weak self] in self?.getListState() }) {
|
||||
ChatView.FloatingButtonModel.shared.updateOnListChange(listState)
|
||||
ChatView.FloatingButtonModel.shared.updateOnListChange(representer.mergedItems, listState)
|
||||
}
|
||||
}
|
||||
.store(in: &bag)
|
||||
@@ -328,10 +341,10 @@ 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)")
|
||||
// tableView.panGestureRecognizer.isEnabled = false
|
||||
// logger.debug("LALAL drag2 \(self.tableView.isDragging), decel \(self.tableView.isDecelerating)")
|
||||
if tableView.isDecelerating {
|
||||
// CATransaction.begin()
|
||||
// CATransaction.begin()
|
||||
itemsInPrevSnapshot = itemsInCurrentSnapshot
|
||||
tableView.beginUpdates()
|
||||
dataSource.apply(
|
||||
@@ -339,7 +352,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
animatingDifferences: false
|
||||
)
|
||||
tableView.endUpdates()
|
||||
// CATransaction.commit()
|
||||
// CATransaction.commit()
|
||||
tableView.panGestureRecognizer.isEnabled = true
|
||||
} else {
|
||||
itemsInPrevSnapshot = itemsInCurrentSnapshot
|
||||
@@ -348,30 +361,30 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
animatingDifferences: false
|
||||
)
|
||||
//if snapshot.itemIdentifiers[0] == prevSnapshot.itemIdentifiers[0] {
|
||||
// added new items to top
|
||||
// added new items to top
|
||||
|
||||
//} else {
|
||||
// added new items to bottom
|
||||
// logger.debug("LALAL WAS HEIGHT \(wasContentHeight) now \(self.tableView.contentSize.height), offset was \(wasOffset), now \(self.tableView.contentOffset.y), will be \(self.tableView.contentOffset.y + (self.tableView.contentSize.height - wasContentHeight)), countDiff \(countDiff), wasVisibleRow \(wasFirstVisibleRow), wasFirstVisibleOffset \(wasFirstVisibleOffset)")
|
||||
self.tableView.scrollToRow(
|
||||
at: IndexPath(row: max(0, min(snapshot.numberOfItems - 1, countDiff + wasFirstVisibleRow)), section: 0),
|
||||
at: .top,
|
||||
animated: false
|
||||
)
|
||||
self.tableView.setContentOffset(
|
||||
CGPoint(x: 0, y: self.tableView.contentOffset.y - wasFirstVisibleOffset),
|
||||
animated: false
|
||||
)
|
||||
let state = getListState()!
|
||||
logger.debug("LALAL NOW FIRST VISIBLE \(state.firstVisibleItemIndex) \(state.firstVisibleItemOffset)")
|
||||
// added new items to bottom
|
||||
// logger.debug("LALAL WAS HEIGHT \(wasContentHeight) now \(self.tableView.contentSize.height), offset was \(wasOffset), now \(self.tableView.contentOffset.y), will be \(self.tableView.contentOffset.y + (self.tableView.contentSize.height - wasContentHeight)), countDiff \(countDiff), wasVisibleRow \(wasFirstVisibleRow), wasFirstVisibleOffset \(wasFirstVisibleOffset)")
|
||||
self.tableView.scrollToRow(
|
||||
at: IndexPath(row: max(0, min(snapshot.numberOfItems - 1, countDiff + wasFirstVisibleRow)), section: 0),
|
||||
at: .top,
|
||||
animated: false
|
||||
)
|
||||
self.tableView.setContentOffset(
|
||||
CGPoint(x: 0, y: self.tableView.contentOffset.y - wasFirstVisibleOffset),
|
||||
animated: false
|
||||
)
|
||||
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!)
|
||||
// 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()
|
||||
// }
|
||||
// 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
|
||||
}
|
||||
@@ -392,22 +405,23 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
visibleRows.last?.row ?? 0 < representer.mergedItems.items.count {
|
||||
let scrollOffset: Double = tableView.contentOffset.y + InvertedTableView.inset
|
||||
let topItemDate: Date? =
|
||||
if let lastVisible = visibleRows.last(where: { isVisible(indexPath: $0) }) {
|
||||
representer.mergedItems.items[lastVisible.item].oldest().item.meta.itemTs
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
if let lastVisible = visibleRows.last(where: { isVisible(indexPath: $0) }) {
|
||||
representer.mergedItems.items[lastVisible.item].oldest().item.meta.itemTs
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
let firstVisible = visibleRows.first(where: { isVisible(indexPath: $0) })
|
||||
let firstVisibleOffset: CGFloat? = if let row = firstVisible?.row {
|
||||
offsetForRow(row)
|
||||
} else { nil }
|
||||
let lastVisible = visibleRows.last(where: { isVisible(indexPath: $0) })
|
||||
logger.debug("LALAL LAST \(lastVisible?.item ?? -2)")
|
||||
let bottomItemId: ChatItem.ID? =
|
||||
if let firstVisible {
|
||||
representer.mergedItems.items[firstVisible.row].newest().item.id
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
if let firstVisible {
|
||||
representer.mergedItems.items[firstVisible.row].newest().item.id
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
return ListState(scrollOffset: scrollOffset, topItemDate: topItemDate, bottomItemId: bottomItemId, firstVisibleItemIndex: firstVisible?.row ?? 0, lastVisibleItemIndex: lastVisible?.row ?? 0, firstVisibleItemOffset: firstVisibleOffset ?? 0)
|
||||
}
|
||||
return nil
|
||||
@@ -435,6 +449,46 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
relativeFrame.minY < tableView.frame.height - InvertedTableView.inset
|
||||
} else { false }
|
||||
}
|
||||
|
||||
func scrollToItem(_ itemId: Int64) {
|
||||
Task {
|
||||
logger.debug("LALAL SCROLL TO ITEM \(itemId) \(ItemsModel.shared.reversedChatItems.count)")
|
||||
do {
|
||||
var index = representer.mergedItems.indexInParentItems[itemId]
|
||||
// setting it to 'loading' even if the item is loaded because in rare cases when the resulting item is near the top, scrolling to
|
||||
// it will trigger loading more items and will scroll to incorrect position (because of trimming)
|
||||
await MainActor.run {
|
||||
representer.loadingMoreItems = true
|
||||
}
|
||||
logger.debug("LALAL SCROLL TO ITEM \(index ?? -2)")
|
||||
if index == nil {
|
||||
let pagination = ChatPagination.around(chatItemId: itemId, count: ChatPagination.PRELOAD_COUNT * 2)
|
||||
let oldSize = ItemsModel.shared.reversedChatItems.count
|
||||
_ = await self.representer.loadItems(true, pagination, { self.visibleItemIndexesNonReversed(Binding.constant(self.representer.mergedItems))})
|
||||
var repeatsLeft = 50
|
||||
while oldSize == ItemsModel.shared.reversedChatItems.count && repeatsLeft > 0 {
|
||||
try await Task.sleep(nanoseconds: 20_000000)
|
||||
repeatsLeft -= 1
|
||||
logger.debug("LALAL SCROLL REPEATS \(repeatsLeft)")
|
||||
}
|
||||
index = representer.mergedItems.indexInParentItems[itemId]
|
||||
}
|
||||
logger.debug("LALAL SCROLL TO ITEM2 \(index ?? -3) \(ItemsModel.shared.reversedChatItems.count)")
|
||||
if let index {
|
||||
scroll(to: min(ItemsModel.shared.reversedChatItems.count - 1, index), position: .bottom)
|
||||
} else {
|
||||
await MainActor.run {
|
||||
representer.scrollState = .atDestination
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.error("Error scrolling to item: \(error)")
|
||||
}
|
||||
await MainActor.run {
|
||||
representer.loadingMoreItems = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `UIHostingConfiguration` back-port for iOS14 and iOS15
|
||||
@@ -502,6 +556,7 @@ class ReverseListScrollModel: ObservableObject {
|
||||
enum Destination: Equatable {
|
||||
case nextPage
|
||||
case item(ChatItem.ID)
|
||||
case row(Int)
|
||||
case bottom
|
||||
}
|
||||
|
||||
@@ -522,6 +577,10 @@ class ReverseListScrollModel: ObservableObject {
|
||||
func scrollToItem(id: ChatItem.ID) {
|
||||
state = .scrollingTo(.item(id))
|
||||
}
|
||||
|
||||
func scrollToRow(row: Int) {
|
||||
state = .scrollingTo(.row(row))
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let cellReuseId = "hostingCell"
|
||||
|
||||
Reference in new issue
Block a user