diff --git a/native/chat/chat-input-bar.react.js b/native/chat/chat-input-bar.react.js --- a/native/chat/chat-input-bar.react.js +++ b/native/chat/chat-input-bar.react.js @@ -18,6 +18,10 @@ import Icon from 'react-native-vector-icons/Ionicons'; import { useDispatch } from 'react-redux'; +import { + moveDraftActionType, + updateDraftActionType, +} from 'lib/actions/draft-actions'; import { joinThreadActionTypes, joinThread, @@ -55,7 +59,6 @@ import Button from '../components/button.react'; import ClearableTextInput from '../components/clearable-text-input.react'; import SWMansionIcon from '../components/swmansion-icon.react'; -import { type UpdateDraft, type MoveDraft, useDrafts } from '../data/core-data'; import { type InputState, InputStateContext } from '../input/input-state'; import { getKeyboardHeight } from '../keyboard/keyboard'; import KeyboardInputHost from '../keyboard/keyboard-input-host.react'; @@ -112,8 +115,6 @@ // Redux state +viewerID: ?string, +draft: string, - +updateDraft: UpdateDraft, - +moveDraft: MoveDraft, +joinThreadLoadingStatus: LoadingStatus, +threadCreationInProgress: boolean, +calendarQuery: () => CalendarQuery, @@ -312,10 +313,13 @@ this.state.text && this.props.threadInfo.id !== prevProps.threadInfo.id ) { - this.props.moveDraft( - draftKeyFromThreadID(prevProps.threadInfo.id), - draftKeyFromThreadID(this.props.threadInfo.id), - ); + this.props.dispatch({ + type: moveDraftActionType, + payload: { + oldKey: draftKeyFromThreadID(prevProps.threadInfo.id), + newKey: draftKeyFromThreadID(this.props.threadInfo.id), + }, + }); } else if (!this.state.textEdited && this.props.draft !== prevProps.draft) { this.setState({ text: this.props.draft }); } @@ -598,9 +602,12 @@ }; saveDraft = _throttle(text => { - this.props.updateDraft({ - key: draftKeyFromThreadID(this.props.threadInfo.id), - text, + this.props.dispatch({ + type: updateDraftActionType, + payload: { + key: draftKeyFromThreadID(this.props.threadInfo.id), + text, + }, }); }, 400); @@ -830,7 +837,10 @@ const viewerID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); - const { draft, updateDraft, moveDraft } = useDrafts(props.threadInfo.id); + const draft = useSelector( + state => + state.draftStore.drafts[draftKeyFromThreadID(props.threadInfo.id)] ?? '', + ); const joinThreadLoadingStatus = useSelector(joinThreadLoadingStatusSelector); const createThreadLoadingStatus = useSelector( createThreadLoadingStatusSelector, @@ -862,8 +872,6 @@ {...props} viewerID={viewerID} draft={draft} - updateDraft={updateDraft} - moveDraft={moveDraft} joinThreadLoadingStatus={joinThreadLoadingStatus} threadCreationInProgress={threadCreationInProgress} calendarQuery={calendarQuery} diff --git a/native/chat/thread-draft-updater.react.js b/native/chat/thread-draft-updater.react.js --- a/native/chat/thread-draft-updater.react.js +++ b/native/chat/thread-draft-updater.react.js @@ -2,11 +2,12 @@ import invariant from 'invariant'; import * as React from 'react'; +import { useDispatch } from 'react-redux'; +import { moveDraftActionType } from 'lib/actions/draft-actions'; import { pendingToRealizedThreadIDsSelector } from 'lib/selectors/thread-selectors'; import { draftKeyFromThreadID } from 'lib/shared/thread-utils'; -import { useDrafts } from '../data/core-data'; import { useSelector } from '../redux/redux-utils'; import type { AppState } from '../redux/state-types'; @@ -15,7 +16,7 @@ const pendingToRealizedThreadIDs = useSelector((state: AppState) => pendingToRealizedThreadIDsSelector(state.threadStore.threadInfos), ); - const drafts = useDrafts(); + const dispatch = useDispatch(); const cachedThreadIDsRef = React.useRef(); if (!cachedThreadIDsRef.current) { @@ -26,7 +27,6 @@ cachedThreadIDsRef.current = newCachedThreadIDs; } - const { moveDraft } = drafts; React.useEffect(() => { for (const [pendingThreadID, threadID] of pendingToRealizedThreadIDs) { const cachedThreadIDs = cachedThreadIDsRef.current; @@ -34,13 +34,16 @@ if (cachedThreadIDs.has(threadID)) { continue; } - moveDraft( - draftKeyFromThreadID(pendingThreadID), - draftKeyFromThreadID(threadID), - ); + dispatch({ + type: moveDraftActionType, + payload: { + oldKey: draftKeyFromThreadID(pendingThreadID), + newKey: draftKeyFromThreadID(threadID), + }, + }); cachedThreadIDs.add(threadID); } - }, [pendingToRealizedThreadIDs, moveDraft]); + }, [pendingToRealizedThreadIDs, dispatch]); return null; }, ); diff --git a/native/data/core-data-provider.react.js b/native/data/core-data-provider.react.js deleted file mode 100644 --- a/native/data/core-data-provider.react.js +++ /dev/null @@ -1,157 +0,0 @@ -// @flow - -import * as React from 'react'; -import { useSelector } from 'react-redux'; - -import { commCoreModule } from '../native-modules'; -import { isTaskCancelledError } from '../utils/error-handling'; -import { type CoreData, defaultCoreData, CoreDataContext } from './core-data'; - -type Props = { - +children: React.Node, -}; -function CoreDataProvider(props: Props): React.Node { - const [draftCache, setDraftCache] = React.useState< - $PropertyType<$PropertyType, 'data'>, - >(defaultCoreData.drafts.data); - - React.useEffect(() => { - (async () => { - try { - const fetchedDrafts = await commCoreModule.getAllDrafts(); - setDraftCache(prevDrafts => { - const mergedDrafts = {}; - for (const draftObj of fetchedDrafts) { - mergedDrafts[draftObj.key] = draftObj.text; - } - for (const key in prevDrafts) { - const value = prevDrafts[key]; - if (!value) { - continue; - } - mergedDrafts[key] = value; - } - return mergedDrafts; - }); - } catch (e) { - if (!isTaskCancelledError(e)) { - throw e; - } - } - })(); - }, []); - - const removeAllDrafts = React.useCallback(async () => { - const oldDrafts = draftCache; - setDraftCache({}); - try { - return await commCoreModule.removeAllDrafts(); - } catch (e) { - setDraftCache(oldDrafts); - if (!isTaskCancelledError(e)) { - throw e; - } - } - }, [draftCache]); - - const viewerID = useSelector( - state => state.currentUserInfo && state.currentUserInfo.id, - ); - const prevViewerIDRef = React.useRef(); - React.useEffect(() => { - if (!viewerID) { - return; - } - if (prevViewerIDRef.current === viewerID) { - return; - } - if (prevViewerIDRef.current) { - removeAllDrafts(); - } - prevViewerIDRef.current = viewerID; - }, [viewerID, removeAllDrafts]); - - /** - * wrapper for updating the draft state receiving an array of drafts - * if you want to add/update the draft, pass the draft with non-empty text - * if you pass a draft with !!text == false - * it will remove this entry from the cache - */ - const setDrafts = React.useCallback( - (newDrafts: $ReadOnlyArray<{ +key: string, +text: ?string }>) => { - setDraftCache(prevDrafts => { - const result = { ...prevDrafts }; - newDrafts.forEach(draft => { - if (draft.text) { - result[draft.key] = draft.text; - } else { - delete result[draft.key]; - } - }); - return result; - }); - }, - [], - ); - const updateDraft = React.useCallback( - async (draft: { +key: string, +text: string }) => { - const prevDraftText = draftCache[draft.key]; - setDrafts([draft]); - try { - return await commCoreModule.updateDraft(draft); - } catch (e) { - setDrafts([{ key: draft.key, text: prevDraftText }]); - if (isTaskCancelledError(e)) { - return false; - } - throw e; - } - }, - [draftCache, setDrafts], - ); - - const moveDraft = React.useCallback( - async (prevKey: string, newKey: string) => { - const value = draftCache[prevKey]; - if (!value) { - return false; - } - setDrafts([ - { key: newKey, text: value }, - { key: prevKey, text: null }, - ]); - try { - return await commCoreModule.moveDraft(prevKey, newKey); - } catch (e) { - setDrafts([ - { key: newKey, text: null }, - { key: prevKey, text: value }, - ]); - if (isTaskCancelledError(e)) { - return false; - } - throw e; - } - }, - [draftCache, setDrafts], - ); - - const coreData = React.useMemo( - () => ({ - drafts: { - data: draftCache, - updateDraft, - moveDraft, - }, - }), - [draftCache, updateDraft, moveDraft], - ); - - return ( - - {props.children} - - ); -} - -export default CoreDataProvider; diff --git a/native/data/core-data.js b/native/data/core-data.js deleted file mode 100644 --- a/native/data/core-data.js +++ /dev/null @@ -1,75 +0,0 @@ -// @flow - -import * as React from 'react'; - -import { draftKeyFromThreadID } from 'lib/shared/thread-utils'; - -import { commCoreModule } from '../native-modules'; -import { isTaskCancelledError } from '../utils/error-handling'; - -type DraftType = { - +key: string, - +text: string, -}; - -export type UpdateDraft = (draft: DraftType) => Promise; -export type MoveDraft = (prevKey: string, nextKey: string) => Promise; - -export type CoreData = { - +drafts: { - +data: { +[key: string]: string }, - +updateDraft: UpdateDraft, - +moveDraft: MoveDraft, - }, -}; - -const defaultCoreData = Object.freeze({ - drafts: { - data: ({}: { +[key: string]: string }), - updateDraft: async (draft: DraftType): Promise => { - try { - return commCoreModule.updateDraft(draft); - } catch (e) { - if (!isTaskCancelledError(e)) { - throw e; - } - } - return false; - }, - moveDraft: async (prevKey: string, nextKey: string): Promise => { - try { - return commCoreModule.moveDraft(prevKey, nextKey); - } catch (e) { - if (!isTaskCancelledError(e)) { - throw e; - } - } - return false; - }, - }, -}); - -const CoreDataContext: React.Context = React.createContext( - defaultCoreData, -); - -type ThreadDrafts = { - +draft: string, - +moveDraft: MoveDraft, - +updateDraft: UpdateDraft, -}; -const useDrafts = (threadID: ?string): ThreadDrafts => { - const coreData = React.useContext(CoreDataContext); - return React.useMemo( - () => ({ - draft: threadID - ? coreData.drafts.data[draftKeyFromThreadID(threadID)] ?? '' - : '', - updateDraft: coreData.drafts.updateDraft, - moveDraft: coreData.drafts.moveDraft, - }), - [coreData, threadID], - ); -}; - -export { defaultCoreData, CoreDataContext, useDrafts }; diff --git a/native/data/sqlite-context-provider.js b/native/data/sqlite-context-provider.js --- a/native/data/sqlite-context-provider.js +++ b/native/data/sqlite-context-provider.js @@ -5,6 +5,7 @@ import ExitApp from 'react-native-exit-app'; import { useDispatch } from 'react-redux'; +import { setDraftStoreDrafts } from 'lib/actions/draft-actions'; import { setMessageStoreMessages } from 'lib/actions/message-actions.js'; import { setThreadStoreActionType } from 'lib/actions/thread-actions'; import { isLoggedIn } from 'lib/selectors/user-selectors'; @@ -93,9 +94,10 @@ (async () => { await sensitiveDataHandled; try { - const [threads, messages] = await Promise.all([ + const [threads, messages, drafts] = await Promise.all([ commCoreModule.getAllThreads(), commCoreModule.getAllMessages(), + commCoreModule.getAllDrafts(), ]); const threadInfosFromDB = convertClientDBThreadInfosToRawThreadInfos( threads, @@ -108,6 +110,10 @@ type: setMessageStoreMessages, payload: messages, }); + dispatch({ + type: setDraftStoreDrafts, + payload: drafts, + }); setStoreLoaded(true); } catch (setStoreException) { if (isTaskCancelledError(setStoreException)) { diff --git a/native/root.react.js b/native/root.react.js --- a/native/root.react.js +++ b/native/root.react.js @@ -22,7 +22,6 @@ import ChatContextProvider from './chat/chat-context-provider.react'; import PersistedStateGate from './components/persisted-state-gate'; import ConnectedStatusBar from './connected-status-bar.react'; -import CoreDataProvider from './data/core-data-provider.react'; import { SQLiteContextProvider } from './data/sqlite-context-provider'; import ErrorBoundary from './error-boundary.react'; import InputStateContainer from './input/input-state-container.react'; @@ -242,35 +241,33 @@ return ( - - - - - - - - - - - {gated} - - - - - {navigation} - - - - - - - - - + + + + + + + + + + {gated} + + + + + {navigation} + + + + + + + + );