diff options
author | Samuel Newman <mozzius@protonmail.com> | 2024-05-14 18:55:43 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-14 18:55:43 +0100 |
commit | 9861494e341b482a522d0ecc6a2194bb12a769fb (patch) | |
tree | ea57b047639058f2b28ee9347cc2b811ff407095 | |
parent | f147256fdca45d6b460f53aacc777c7a0957e4c6 (diff) | |
download | voidsky-9861494e341b482a522d0ecc6a2194bb12a769fb.tar.zst |
[🐴] Message drafts (#3993)
* drafts * don't throw if no convo ID * Remove labs package --------- Co-authored-by: Eric Bailey <git@esb.lol>
-rw-r--r-- | src/screens/Messages/Conversation/MessageInput.tsx | 12 | ||||
-rw-r--r-- | src/screens/Messages/Conversation/MessageInput.web.tsx | 12 | ||||
-rw-r--r-- | src/state/messages/index.tsx | 5 | ||||
-rw-r--r-- | src/state/messages/message-drafts.tsx | 83 |
4 files changed, 107 insertions, 5 deletions
diff --git a/src/screens/Messages/Conversation/MessageInput.tsx b/src/screens/Messages/Conversation/MessageInput.tsx index 926d66e7d..d05d6109e 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<number | undefined>() 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<TextInputContentSizeChangeEventData>) => { @@ -69,6 +75,8 @@ export function MessageInput({ [scrollToEnd, topInset], ) + useSaveMessageDraft(message) + return ( <View style={a.p_sm}> <View diff --git a/src/screens/Messages/Conversation/MessageInput.web.tsx b/src/screens/Messages/Conversation/MessageInput.web.tsx index 2ee03bb31..b9506b5bb 100644 --- a/src/screens/Messages/Conversation/MessageInput.web.tsx +++ b/src/screens/Messages/Conversation/MessageInput.web.tsx @@ -6,6 +6,10 @@ import Graphemer from 'graphemer' import TextareaAutosize from 'react-textarea-autosize' import {MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants' +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' @@ -18,7 +22,8 @@ export function MessageInput({ }) { const {_} = useLingui() const t = useTheme() - const [message, setMessage] = React.useState('') + const {getDraft, clearDraft} = useMessageDraft() + const [message, setMessage] = React.useState(getDraft) const onSubmit = React.useCallback(() => { 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<HTMLTextAreaElement>) => { @@ -50,6 +56,8 @@ export function MessageInput({ [], ) + useSaveMessageDraft(message) + return ( <View style={a.p_sm}> <View diff --git a/src/state/messages/index.tsx b/src/state/messages/index.tsx index 205d17e8c..04ace8d60 100644 --- a/src/state/messages/index.tsx +++ b/src/state/messages/index.tsx @@ -2,11 +2,14 @@ import React from 'react' import {CurrentConvoIdProvider} from '#/state/messages/current-convo-id' import {MessagesEventBusProvider} from '#/state/messages/events' +import {MessageDraftsProvider} from './message-drafts' export function MessagesProvider({children}: {children: React.ReactNode}) { return ( <CurrentConvoIdProvider> - <MessagesEventBusProvider>{children}</MessagesEventBusProvider> + <MessageDraftsProvider> + <MessagesEventBusProvider>{children}</MessagesEventBusProvider> + </MessageDraftsProvider> </CurrentConvoIdProvider> ) } diff --git a/src/state/messages/message-drafts.tsx b/src/state/messages/message-drafts.tsx new file mode 100644 index 000000000..132e85967 --- /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<Actions> +} | 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 ( + <MessageDraftsContext.Provider value={ctx}> + {children} + </MessageDraftsContext.Provider> + ) +} |