diff --git a/src/components/ChatView/ChatView.tsx b/src/components/ChatView/ChatView.tsx index 3e5bc68..b7ab7f7 100644 --- a/src/components/ChatView/ChatView.tsx +++ b/src/components/ChatView/ChatView.tsx @@ -33,7 +33,7 @@ import ImageView from './ImageView'; import {createStyles} from './styles'; import {chatSessionStore, modelStore} from '../../store'; - +import {SelectTextView} from '../SelectTextView'; import {l10n} from '../../utils/l10n'; import {MessageType, User} from '../../utils/types'; import { @@ -264,13 +264,22 @@ export const ChatView = observer( chatSessionStore.exitEditMode(); }, []); - const {handleCopy, handleEdit, handleTryAgain, handleTryAgainWith} = - useMessageActions({ - user, - messages, - handleSendPress: wrappedOnSendPress, - setInputText, - }); + const { + handleSelectView, + handleCopy, + handleEdit, + handleTryAgain, + handleTryAgainWith, + isSelectionModalVisible, + selectedMessageContent, + handleTextSelected, + setSelectionModalVisible, + } = useMessageActions({ + user, + messages, + handleSendPress: wrappedOnSendPress, + setInputText, + }); const l10nValue = React.useMemo( () => ({...l10n[locale], ...unwrap(l10nOverride)}), @@ -429,6 +438,15 @@ export const ChatView = observer( icon: 'content-copy', disabled: false, }, + { + label: 'Select Text', + onPress: () => { + handleSelectView(selectedMessage); + handleMenuDismiss(); + }, + icon: 'select-drag', + disabled: false, + }, ]; if (!isAuthor) { @@ -479,6 +497,7 @@ export const ChatView = observer( handleEdit, handleMenuDismiss, size.width, + handleSelectView, ]); const renderMenuItem = React.useCallback( @@ -756,6 +775,12 @@ export const ChatView = observer( onRequestClose={handleRequestClose} visible={isImageViewVisible} /> + setSelectionModalVisible(false)} + content={selectedMessageContent} + onTextSelected={handleTextSelected} + /> void; + content: string; + onTextSelected: (selectedText: string) => void; +} + +export const SelectTextView: React.FC = ({ + visible, + onClose, + content, + onTextSelected, +}) => { + const theme = useTheme(); + const textRef = useRef(null); + const styles = getStyles(theme); + + const handleTextSelection = (event: any) => { + const {selection} = event.nativeEvent; + if (selection && selection.start !== selection.end) { + const selectedText = content.substring(selection.start, selection.end); + onTextSelected(selectedText); + } + }; + + return ( + + + + + {({pressed}) => ( + + )} + + Select Text + + + + + {content} + + + + + ); +}; diff --git a/src/components/SelectTextView/index.ts b/src/components/SelectTextView/index.ts new file mode 100644 index 0000000..016a6f3 --- /dev/null +++ b/src/components/SelectTextView/index.ts @@ -0,0 +1 @@ +export * from './SelectTextView'; diff --git a/src/components/SelectTextView/styles.ts b/src/components/SelectTextView/styles.ts new file mode 100644 index 0000000..29e2273 --- /dev/null +++ b/src/components/SelectTextView/styles.ts @@ -0,0 +1,34 @@ +import {StyleSheet} from 'react-native'; +import {MD3Theme} from 'react-native-paper'; + +export const getStyles = (theme: MD3Theme) => + StyleSheet.create({ + modalContainer: { + flex: 1, + backgroundColor: theme.colors.background, + padding: 16, + }, + headerContainer: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 16, + }, + headerText: { + fontSize: 22, + fontWeight: 'bold', + color: theme.colors.primary, + marginLeft: 16, + }, + contentText: { + color: theme.colors.primary, + fontSize: 18, + lineHeight: 24, + marginTop: 5, + maxWidth: '95%', + alignSelf: 'center', + }, + scrollViewContent: { + flexGrow: 1, + padding: 5, + }, + }); diff --git a/src/hooks/useMessageActions.ts b/src/hooks/useMessageActions.ts index afad942..d0c9b58 100644 --- a/src/hooks/useMessageActions.ts +++ b/src/hooks/useMessageActions.ts @@ -1,9 +1,7 @@ -import {useCallback} from 'react'; - +import {useCallback, useState} from 'react'; import Clipboard from '@react-native-clipboard/clipboard'; import {chatSessionStore, modelStore} from '../store'; - import {MessageType, User} from '../utils/types'; interface UseMessageActionsProps { @@ -19,12 +17,29 @@ export const useMessageActions = ({ handleSendPress, setInputText, }: UseMessageActionsProps) => { + const [isSelectionModalVisible, setSelectionModalVisible] = useState(false); + const [selectedMessageContent, setSelectedMessageContent] = useState(''); + const [selectedText, setSelectedText] = useState(''); + const handleCopy = useCallback((message: MessageType.Text) => { if (message.type === 'text') { Clipboard.setString(message.text.trim()); } }, []); + const handleSelectView = useCallback((message: MessageType.Text) => { + if (message.type === 'text') { + setSelectedMessageContent(message.text); + setSelectionModalVisible(true); + } + }, []); + + const handleTextSelected = useCallback((text: string) => { + setSelectedText(text); + Clipboard.setString(text); + setSelectionModalVisible(false); + }, []); + const handleEdit = useCallback( async (message: MessageType.Text) => { if (message.type !== 'text' || message.author.id !== user.id) { @@ -46,7 +61,6 @@ export const useMessageActions = ({ // If it's the user's message, resubmit it if (message.author.id === user.id) { - // Remove all messages from this point (inclusive) const messageText = message.text; chatSessionStore.removeMessagesFromId(message.id, true); await handleSendPress({text: messageText, type: 'text'}); @@ -87,7 +101,13 @@ export const useMessageActions = ({ return { handleCopy, handleEdit, + handleSelectView, handleTryAgain, handleTryAgainWith, + isSelectionModalVisible, + selectedMessageContent, + handleTextSelected, + setSelectionModalVisible, + selectedText, }; };