diff --git a/docs/rfcs/2024-10-07-chat-infinite-scrolling.md b/docs/rfcs/2024-10-07-chat-infinite-scrolling.md index a3ce59870f..920a9920f1 100644 --- a/docs/rfcs/2024-10-07-chat-infinite-scrolling.md +++ b/docs/rfcs/2024-10-07-chat-infinite-scrolling.md @@ -20,11 +20,150 @@ At the same time, this system will: - Contain database fetch operations. - Maintain smooth scrolling. +### Pagination integration: + +``` +fetch(scrollingUp, chatItemId) + if scrollingUp + fetchBefore(chatItemId) + else + fetchAfter(chatItemId) + +-- Another option would be if the api exposed a way to do before and after, possibly already supported since they are just separate query strings +fetchNearItem(chatItemId) + fetchBefore(chatItemId) + fetchAfter(chatItemId) + +fetchLatest() + fetchLast() +``` + ### Option 1 -Keep single "Fragment" of chat items +Keep single instance of `chatItems` representing the current scrollable chat area. The entire array is cleared and replaced during long-distance navigation, and items are appended or prepended as needed during scroll. + +#### Data structures + +``` +chatItems = Array +``` + +#### Behaviour + +##### Scroll data fetching +``` +if scrollingUp + newItems = fetch(ScrollingUp, chatItems.first().id) + chatItems.addToBeginning(newItems) +else + newItems = fetch(ScrollingDown, chatItems.last().id) + chatItems.addToEnd(newItems) +``` + +##### Long Distance Navigation +``` +newItems = + if item + fetchNearItem(item) + else + fetchLatest +chatItems = newItems -- Totally clears chat items +``` + +##### Requires fetch +Remains mainly unchanged, only needs to take into account direction and either use first or last item instead of always using first. + +**Pros:** +- Simplest solution as it maintains only one instance of chat data, adding new data to the beginning or end of the array is straightforward, making this approach easy to implement + +**Cons:** +- When jumping to the latest messages, especially for large databases, there will be a delay caused by the database fetch. For users with bigger DBs, this could degrade user experience significantly. +- Not reusing any of the previously loaded data means that the system performs extra database accesses when fetching items that have already been loaded. ### Option 2 -Keep 2 "Fragements" of chat items +2 sections within `chatItems` representing the current scrollable chat area and the bottom area. The bottom section is never cleared, while long distance navigation creates/rebuilds the anchor and resets items previously added on this section. Anchor is also destroyed in the cases where both sections intersect + +#### Data structures + +``` +chatItems = Array + +data ScrollAnchor { + chatItemId: Long + chatItemIndex: Int +} + +// Optional Set for bottom section IDs +// Will take o(n) space where n is the number of items in the bottom section, It will make dedup and intersection operations o(1), that is probably a acceptable tradeoff as we want to minimize execution speed while scrolling. + +Set bottomSectionIds +``` + +#### Behaviour + +##### Scroll data fetching +``` +if scrollingUp + newItems = fetch(ScrollingUp, chatItems.first().id) + if anchor + if newItems intercepts items + dedup newItems + anchor = null + else + anchor.chatItemIndex += newItems.size + anchor.chatItemId = newItems.last() + + chatItems.addAtIndex(0, newItems) +else + if anchor + newItems = fetch(ScrollingDown, anchor.chatItemId) + if newItems intercepts items + dedup newItems + chatItems.insertAfter(anchor.chatItemIndex, newItems) + anchor = null + else + chatItems.insertAfter(anchor.chatItemIndex, newItems) + anchor.chatItemIndex += newItems.size + anchor.chatItemId = newItems.last() + else + Noop -- never happens +``` + +##### Long Distance Navigation +``` +if item + newItems = fetchNearItem(item) + if newItems intercepts items + dedup newItems + chatItems.insertAtStart(newItems) + anchor = null + else + anchor = { chatItemId: newItems.last(), chatItemIndex: newItems.lastIndex } + chatItems.insertAtStart(newItems) +else + if anchor + chatItems.remove(from: 0, to: anchor.chatItemIndex) + anchor = null +``` + +##### Requires fetch +``` +if scrollingUp + -- Unchanged +else + if anchor + is diff between the index of bottom visible item and anchor chatItemIndex < PRELOAD value + else + false +``` + +**Pros:** +- Keeping two sections makes it simple for users to scroll through messages and jump to recent ones and doesn't add extreme complexity in code design. +- Most common action for users (going back to the bottom of the chat) are totally addressed and instant without exponetially increasing the ammount of data stored in device runtime memory. + +**Cons:** +- Managing two sections can make the code harder to maintain and could introduce bugs if not handled carefully. +- Not reusing anchors means the app might have to access the database more often. +- Deduplication of items as they are fetched becomes of extreme importance as not doing it effectively can lead to duplicated messages being displayed. ### Option 3 Keep N "Fragments" of chat items \ No newline at end of file