diff --git a/src/screens/Messages/Conversation/MessageInput.tsx b/src/screens/Messages/Conversation/MessageInput.tsx index 926d66e7..d05d6109 100644 --- a/src/screens/Messages/Conversation/MessageInput.tsx +++ b/src/screens/Messages/Conversation/MessageInput.tsx @@ -15,6 +15,10 @@ import Graphemer from 'graphemer' import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants' import {useHaptics} from '#/lib/haptics' +import { + useMessageDraft, + useSaveMessageDraft, +} from '#/state/messages/message-drafts' import * as Toast from '#/view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' @@ -29,7 +33,8 @@ export function MessageInput({ const {_} = useLingui() const t = useTheme() const playHaptic = useHaptics() - const [message, setMessage] = React.useState('') + const {getDraft, clearDraft} = useMessageDraft() + const [message, setMessage] = React.useState(getDraft) const [maxHeight, setMaxHeight] = React.useState() const [isInputScrollable, setIsInputScrollable] = React.useState(false) @@ -45,13 +50,14 @@ export function MessageInput({ Toast.show(_(msg`Message is too long`)) return } + clearDraft() onSendMessage(message.trimEnd()) playHaptic() setMessage('') setTimeout(() => { inputRef.current?.focus() }, 100) - }, [message, onSendMessage, playHaptic, _]) + }, [message, onSendMessage, playHaptic, _, clearDraft]) const onInputLayout = React.useCallback( (e: NativeSyntheticEvent) => { @@ -69,6 +75,8 @@ export function MessageInput({ [scrollToEnd, topInset], ) + useSaveMessageDraft(message) + return ( { if (message.trim() === '') { @@ -28,9 +33,10 @@ export function MessageInput({ Toast.show(_(msg`Message is too long`)) return } + clearDraft() onSendMessage(message.trimEnd()) setMessage('') - }, [message, onSendMessage, _]) + }, [message, onSendMessage, _, clearDraft]) const onKeyDown = React.useCallback( (e: React.KeyboardEvent) => { @@ -50,6 +56,8 @@ export function MessageInput({ [], ) + useSaveMessageDraft(message) + return ( - {children} + + {children} + ) } diff --git a/src/state/messages/message-drafts.tsx b/src/state/messages/message-drafts.tsx new file mode 100644 index 00000000..132e8596 --- /dev/null +++ b/src/state/messages/message-drafts.tsx @@ -0,0 +1,83 @@ +import React, {useEffect, useMemo, useReducer, useRef} from 'react' + +import {useCurrentConvoId} from './current-convo-id' + +const MessageDraftsContext = React.createContext<{ + state: State + dispatch: React.Dispatch +} | null>(null) + +function useMessageDraftsContext() { + const ctx = React.useContext(MessageDraftsContext) + if (!ctx) { + throw new Error( + 'useMessageDrafts must be used within a MessageDraftsContext', + ) + } + return ctx +} + +export function useMessageDraft() { + const {currentConvoId} = useCurrentConvoId() + const {state, dispatch} = useMessageDraftsContext() + return useMemo( + () => ({ + getDraft: () => (currentConvoId && state[currentConvoId]) || '', + clearDraft: () => { + if (currentConvoId) { + dispatch({type: 'clear', convoId: currentConvoId}) + } + }, + }), + [state, dispatch, currentConvoId], + ) +} + +export function useSaveMessageDraft(message: string) { + const {currentConvoId} = useCurrentConvoId() + const {dispatch} = useMessageDraftsContext() + const messageRef = useRef(message) + messageRef.current = message + + useEffect(() => { + return () => { + if (currentConvoId) { + dispatch({ + type: 'set', + convoId: currentConvoId, + draft: messageRef.current, + }) + } + } + }, [currentConvoId, dispatch]) +} + +type State = {[convoId: string]: string} +type Actions = + | {type: 'set'; convoId: string; draft: string} + | {type: 'clear'; convoId: string} + +function reducer(state: State, action: Actions): State { + switch (action.type) { + case 'set': + return {...state, [action.convoId]: action.draft} + case 'clear': + return {...state, [action.convoId]: ''} + default: + return state + } +} + +export function MessageDraftsProvider({children}: {children: React.ReactNode}) { + const [state, dispatch] = useReducer(reducer, {}) + + const ctx = useMemo(() => { + return {state, dispatch} + }, [state]) + + return ( + + {children} + + ) +}