From 067abfea0dad1c64129efca970b3ffb8fda011ec Mon Sep 17 00:00:00 2001 From: balzack Date: Wed, 28 Dec 2022 14:55:54 -0800 Subject: [PATCH] updated mobile app to stagger load conversations --- app/mobile/ios/Databag/Info.plist | 2 +- app/mobile/src/context/useCardContext.hook.js | 21 ++++- .../src/context/useChannelContext.hook.js | 17 ++++- .../context/useConversationContext.hook.js | 76 +++++++++++++------ .../src/context/useStoreContext.hook.js | 18 +++-- .../src/session/conversation/Conversation.jsx | 9 ++- .../conversation/useConversation.hook.js | 10 +++ 7 files changed, 117 insertions(+), 36 deletions(-) diff --git a/app/mobile/ios/Databag/Info.plist b/app/mobile/ios/Databag/Info.plist index 9447df13..c807082d 100644 --- a/app/mobile/ios/Databag/Info.plist +++ b/app/mobile/ios/Databag/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.8 + 1.9 CFBundleSignature ???? CFBundleVersion diff --git a/app/mobile/src/context/useCardContext.hook.js b/app/mobile/src/context/useCardContext.hook.js index 236a5356..4ffcd100 100644 --- a/app/mobile/src/context/useCardContext.hook.js +++ b/app/mobile/src/context/useCardContext.hook.js @@ -222,6 +222,17 @@ export function useCardContext() { } } } + const setCardChannelTopicMarker = (cardId, channelId, marker) => { + let card = cards.current.get(cardId); + if (card) { + let channel = card.channels.get(channelId); + if (channel) { + channel.topicMarker = marker; + card.channels.set(channelId, channel); + cards.current.set(cardId, card); + } + } + } const setCardChannelBlocked = (cardId, channelId, blocked) => { let card = cards.current.get(cardId); if (card) { @@ -512,6 +523,12 @@ export function useCardContext() { setCardChannelSyncRevision(cardId, channelId, revision); updateState({ cards: cards.current }); }, + setTopicMarker: async (cardId, channelId, marker) => { + const { guid } = session.current; + await store.actions.setCardChannelItemTopicMarker(guid, cardId, channelId, marker); + setCardChannelTopicMarker(cardId, channelId, marker); + updateState({ cards: cards.current }); + }, setChannelBlocked: async (cardId, channelId) => { const { guid } = session.current; await store.actions.setCardChannelItemBlocked(guid, cardId, channelId); @@ -556,9 +573,9 @@ export function useCardContext() { const { detail, profile } = getCardEntry(cardId); return await getContactChannelTopic(profile.node, `${profile.guid}.${detail.token}`, channelId, topicId); }, - getChannelTopics: async (cardId, channelId, revision) => { + getChannelTopics: async (cardId, channelId, revision, count, begin, end) => { const { detail, profile } = getCardEntry(cardId); - return await getContactChannelTopics(profile.node, `${profile.guid}.${detail.token}`, channelId, revision); + return await getContactChannelTopics(profile.node, `${profile.guid}.${detail.token}`, channelId, revision, count, begin, end); }, getChannelTopicAssetUrl: (cardId, channelId, topicId, assetId) => { const { detail, profile } = getCardEntry(cardId); diff --git a/app/mobile/src/context/useChannelContext.hook.js b/app/mobile/src/context/useChannelContext.hook.js index 1e0d3ae1..afd3f852 100644 --- a/app/mobile/src/context/useChannelContext.hook.js +++ b/app/mobile/src/context/useChannelContext.hook.js @@ -108,6 +108,13 @@ export function useChannelContext() { channels.current.set(channelId, channel); } } + const setChannelTopicMarker = (channelId, marker) => { + let channel = channels.current.get(channelId); + if (channel) { + channel.topicMarker = marker; + channels.current.set(channelId, channel); + } + } const setChannelBlocked = (channelId, blocked) => { let channel = channels.current.get(channelId); if (channel) { @@ -213,6 +220,12 @@ export function useChannelContext() { setChannelSyncRevision(channelId, revision); updateState({ channels: channels.current }); }, + setTopicMarker: async (channelId, marker) => { + const { guid } = session.current; + await store.actions.setChannelItemTopicMarker(guid, channelId, marker); + setChannelTopicMarker(channelId, marker); + updateState({ channels: channels.current }); + }, setBlocked: async (channelId) => { const { guid } = session.current; await store.actions.setChannelItemBlocked(guid, channelId); @@ -257,9 +270,9 @@ export function useChannelContext() { const { server, appToken } = session.current; return await getChannelTopic(server, appToken, channelId, topicId); }, - getTopics: async (channelId, revision) => { + getTopics: async (channelId, revision, count, begin, end) => { const { server, appToken } = session.current; - return await getChannelTopics(server, appToken, channelId, revision); + return await getChannelTopics(server, appToken, channelId, revision, count, begin, end); }, getTopicAssetUrl: (channelId, topicId, assetId) => { const { server, appToken } = session.current; diff --git a/app/mobile/src/context/useConversationContext.hook.js b/app/mobile/src/context/useConversationContext.hook.js index a6137a07..659f484b 100644 --- a/app/mobile/src/context/useConversationContext.hook.js +++ b/app/mobile/src/context/useConversationContext.hook.js @@ -32,8 +32,9 @@ export function useConversationContext() { const channel = useContext(ChannelContext); const profile = useContext(ProfileContext); const topics = useRef(null); - const revision = useRef(0); + const revision = useRef(null); const force = useRef(false); + const more = useRef(false); const detailRevision = useRef(0); const syncing = useRef(false); const conversationId = useRef(null); @@ -112,11 +113,11 @@ export function useConversationContext() { } return await channel.actions.getTopic(channelId, topicId); } - const getTopics = async (cardId, channelId, revision) => { + const getTopics = async (cardId, channelId, revision, begin, end) => { if (cardId) { - return await card.actions.getChannelTopics(cardId, channelId, revision); + return await card.actions.getChannelTopics(cardId, channelId, revision, 16, begin, end); } - return await channel.actions.getTopics(channelId, revision) + return await channel.actions.getTopics(channelId, revision, 16, begin, end) } const getTopicAssetUrl = (cardId, channelId, assetId) => { if (cardId) { @@ -166,6 +167,12 @@ export function useConversationContext() { } return await channel.actions.setSyncRevision(channelId, revision); } + const setTopicMarker = async (cardId, channelId, marker) => { + if (cardId) { + return await card.actions.setTopicMarker(cardId, channelId, marker); + } + return await channel.actions.setTopicMarker(channelId, marker); + } useEffect(() => { if (conversationId.current) { @@ -189,31 +196,46 @@ export function useConversationContext() { if (conversationId.current) { const { cardId, channelId } = conversationId.current; const channelItem = getChannel(cardId, channelId); - if (channelItem && (channelItem.revision !== revision.current || force.current)) { + + if (channelItem && (channelItem.revision !== revision.current || force.current || more.current)) { syncing.current = true; try { - // set channel details - if (detailRevision.current != channelItem.detailRevision) { - if (curView === setView.current) { - await setChannel(channelItem); - detailRevision.current = channelItem.detailRevision; - } - } - // initial load from store + // sync from server + let res; if (!topics.current) { topics.current = new Map(); const items = await getTopicItems(cardId, channelId); items.forEach(item => { topics.current.set(item.topicId, item); }); + await setChannel(channelItem); + detailRevision.current = channelItem.detailRevision; + } + else if (detailRevision.current != channelItem.detailRevision) { + await setChannel(channelItem); + detailRevision.current = channelItem.detailRevision; + } + else if (more.current) { + more.current = false; + res = await getTopics(cardId, channelId, null, null, channelItem.topicMarker) + } + else if (channelItem.topicRevision !== channelItem.syncRevision || force.current) { + force.current = false; + res = await getTopics(cardId, channelId, channelItem.syncRevision, channelItem.topicMarker) + } + else { + if (cardId) { + card.actions.setChannelReadRevision(cardId, channelId, revision.current); + } + else { + channel.actions.setReadRevision(channelId, channelItem.revision); + } + revision.current = channelItem.revision; } - // sync from server - if (channelItem.topicRevision != channelItem.syncRevision || force.current) { - force.current = false; - const res = await getTopics(cardId, channelId, channelItem.syncRevision) + if (res?.topics) { for (const topic of res.topics) { if (!topic.data) { topics.current.delete(topic.id); @@ -238,18 +260,16 @@ export function useConversationContext() { } } } + } + + if (res?.marker) { + await setTopicMarker(cardId, channelId, res.marker); + } + if (res?.revision) { await setSyncRevision(cardId, channelId, res.revision); } - // update revision - revision.current = channelItem.revision; if (curView == setView.current) { - if (cardId) { - card.actions.setChannelReadRevision(cardId, channelId, revision.current); - } - else { - channel.actions.setReadRevision(channelId, revision.current); - } updateState({ topics: topics.current, init: true, error: false }); } @@ -637,6 +657,12 @@ export function useConversationContext() { } } }, + loadMore: () => { + if (conversationId.current) { + more.current = true; + sync(); + } + }, resync: () => { if (conversationId.current) { force.current = true; diff --git a/app/mobile/src/context/useStoreContext.hook.js b/app/mobile/src/context/useStoreContext.hook.js index 8eb86e85..0ffacff6 100644 --- a/app/mobile/src/context/useStoreContext.hook.js +++ b/app/mobile/src/context/useStoreContext.hook.js @@ -1,7 +1,7 @@ import { useEffect, useState, useRef, useContext } from 'react'; import SQLite from "react-native-sqlite-storage"; -const DATABAG_DB = 'databag_v081.db'; +const DATABAG_DB = 'db_v090.db'; export function useStoreContext() { const [state, setState] = useState({}); @@ -12,10 +12,10 @@ export function useStoreContext() { } const initSession = async (guid) => { - await db.current.executeSql(`CREATE TABLE IF NOT EXISTS channel_${guid} (channel_id text, revision integer, detail_revision integer, topic_revision integer, blocked integer, sync_revision integer, detail text, unsealed_detail text, summary text, unsealed_summary text, offsync integer, read_revision integer, unique(channel_id))`); + await db.current.executeSql(`CREATE TABLE IF NOT EXISTS channel_${guid} (channel_id text, revision integer, detail_revision integer, topic_revision integer, topic_marker integer, blocked integer, sync_revision integer, detail text, unsealed_detail text, summary text, unsealed_summary text, offsync integer, read_revision integer, unique(channel_id))`); await db.current.executeSql(`CREATE TABLE IF NOT EXISTS channel_topic_${guid} (channel_id text, topic_id text, revision integer, detail_revision integer, blocked integer, detail text, unsealed_detail text, unique(channel_id, topic_id))`); await db.current.executeSql(`CREATE TABLE IF NOT EXISTS card_${guid} (card_id text, revision integer, detail_revision integer, profile_revision integer, detail text, profile text, notified_view integer, notified_article integer, notified_profile integer, notified_channel integer, offsync integer, blocked integer, unique(card_id))`); - await db.current.executeSql(`CREATE TABLE IF NOT EXISTS card_channel_${guid} (card_id text, channel_id text, revision integer, detail_revision integer, topic_revision integer, sync_revision integer, detail text, unsealed_detail text, summary text, unsealed_summary text, offsync integer, blocked integer, read_revision integer, unique(card_id, channel_id))`); + await db.current.executeSql(`CREATE TABLE IF NOT EXISTS card_channel_${guid} (card_id text, channel_id text, revision integer, detail_revision integer, topic_revision integer, topic_marker integer, sync_revision integer, detail text, unsealed_detail text, summary text, unsealed_summary text, offsync integer, blocked integer, read_revision integer, unique(card_id, channel_id))`); await db.current.executeSql(`CREATE TABLE IF NOT EXISTS card_channel_topic_${guid} (card_id text, channel_id text, topic_id text, revision integer, detail_revision integer, blocked integer, detail text, unsealed_detail text, unique(card_id, channel_id, topic_id))`); } @@ -212,6 +212,9 @@ export function useStoreContext() { setChannelItemSyncRevision: async (guid, channelId, revision) => { await db.current.executeSql(`UPDATE channel_${guid} set sync_revision=? where channel_id=?`, [revision, channelId]); }, + setChannelItemTopicMarker: async (guid, channelId, marker) => { + await db.current.executeSql(`UPDATE channel_${guid} set topic_marker=? where channel_id=?`, [marker, channelId]); + }, setChannelItemBlocked: async (guid, channelId) => { await db.current.executeSql(`UPDATE channel_${guid} set blocked=? where channel_id=?`, [1, channelId]); }, @@ -242,13 +245,14 @@ export function useStoreContext() { }; }, getChannelItems: async (guid) => { - const values = await getAppValues(db.current, `SELECT channel_id, read_revision, revision, sync_revision, blocked, detail_revision, topic_revision, detail, unsealed_detail, summary, unsealed_summary FROM channel_${guid}`, []); + const values = await getAppValues(db.current, `SELECT channel_id, read_revision, revision, sync_revision, blocked, detail_revision, topic_revision, topic_marker, detail, unsealed_detail, summary, unsealed_summary FROM channel_${guid}`, []); return values.map(channel => ({ channelId: channel.channel_id, revision: channel.revision, readRevision: channel.read_revision, detailRevision: channel.detail_revision, topicRevision: channel.topic_revision, + topicMarker: channel.topic_marker, syncRevision: channel.sync_revision, blocked: channel.blocked, detail: decodeObject(channel.detail), @@ -311,6 +315,9 @@ export function useStoreContext() { setCardChannelItemSyncRevision: async (guid, cardId, channelId, revision) => { await db.current.executeSql(`UPDATE card_channel_${guid} set sync_revision=? where card_id=? and channel_id=?`, [revision, cardId, channelId]); }, + setCardChannelItemTopicMarker: async (guid, cardId, channelId, marker) => { + await db.current.executeSql(`UPDATE card_channel_${guid} set topic_marker=? where card_id=? and channel_id=?`, [marker, cardId, channelId]); + }, setCardChannelItemDetail: async (guid, cardId, channelId, revision, detail) => { await db.current.executeSql(`UPDATE card_channel_${guid} set detail_revision=?, detail=?, unsealed_detail=null where card_id=? and channel_id=?`, [revision, encodeObject(detail), cardId, channelId]); }, @@ -335,7 +342,7 @@ export function useStoreContext() { }; }, getCardChannelItems: async (guid) => { - const values = await getAppValues(db.current, `SELECT card_id, channel_id, read_revision, sync_revision, revision, blocked, detail_revision, topic_revision, detail, unsealed_detail, summary, unsealed_summary FROM card_channel_${guid}`, []); + const values = await getAppValues(db.current, `SELECT card_id, channel_id, read_revision, sync_revision, revision, blocked, detail_revision, topic_revision, topic_marker, detail, unsealed_detail, summary, unsealed_summary FROM card_channel_${guid}`, []); return values.map(channel => ({ cardId: channel.card_id, channelId: channel.channel_id, @@ -343,6 +350,7 @@ export function useStoreContext() { readRevision: channel.read_revision, detailRevision: channel.detail_revision, topicRevision: channel.topic_revision, + topicMarker: channel.topic_marker, syncRevision: channel.sync_revision, detail: decodeObject(channel.detail), unsealedDetail: decodeObject(channel.unsealed_detail), diff --git a/app/mobile/src/session/conversation/Conversation.jsx b/app/mobile/src/session/conversation/Conversation.jsx index a610e037..6a195693 100644 --- a/app/mobile/src/session/conversation/Conversation.jsx +++ b/app/mobile/src/session/conversation/Conversation.jsx @@ -24,7 +24,12 @@ export function ConversationHeader({ closeConversation, openDetails, state, acti - { state.subject } + { state.more && !state.latched && ( + + )} + { (!state.more || state.latched) && ( + { state.subject } + )} { state.error && ( @@ -94,10 +99,12 @@ export function ConversationBody({ state, actions }) { contentContainerStyle={styles.topics} ref={ref} data={state.topics} + onEndReached={actions.loadMore} onMomentumScrollEnd={ Platform.OS === 'ios' ? noop : actions.unlatch } onScrollBeginDrag={ Platform.OS !== 'ios' ? noop : actions.unlatch } maintainVisibleContentPosition={ state.latched ? null : { minIndexForVisibile: 2, } } inverted={true} + initialNumToRender={16} renderItem={({item}) => actions.setFocus(item.topicId)} hosting={state.host == null} sealed={state.sealed} sealKey={state.sealKey} diff --git a/app/mobile/src/session/conversation/useConversation.hook.js b/app/mobile/src/session/conversation/useConversation.hook.js index 8cb4a32d..3df8f641 100644 --- a/app/mobile/src/session/conversation/useConversation.hook.js +++ b/app/mobile/src/session/conversation/useConversation.hook.js @@ -25,6 +25,7 @@ export function useConversation() { locked: false, sealKey: null, delayed: false, + more: false, }); const init = useRef(true); @@ -129,6 +130,15 @@ export function useConversation() { blockTopic: async (topicId) => { await conversation.actions.blockTopic(topicId); }, + loadMore: async () => { + if (!state.more) { + updateState({ more: true }); + setTimeout(() => { + updateState({ more: false }); + }, 1000); + await conversation.actions.loadMore(); + } + }, reportTopic: async (topicId) => { await conversation.actions.addTopicReport(topicId); },