Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add text selection functionality in chat view with modal support #195

Draft
wants to merge 26 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b3d5e3d
feat: add text selection functionality in chat view with modal support
Jan 31, 2025
6aacea4
fix(styles): add missing newline at end of file in MessageSelectionVi…
Jan 31, 2025
9971957
fix(styles): add missing newline at end of file in MessageSelectionVi…
Jan 31, 2025
ae9f147
fix(styles): format import statement in ChatView component
Jan 31, 2025
79587e6
fix(styles): format code for consistency in ChatView component
Jan 31, 2025
b79d978
fix: format code for consistency in ChatView component
Jan 31, 2025
00fbd3a
fix(styles): adjust indentation for MessageSelectionView in ChatView …
Jan 31, 2025
60024a5
fix(styles): format import statements in MessageSelectionView component
Jan 31, 2025
37a026e
fix(styles): format code for consistency in MessageSelectionView comp…
Jan 31, 2025
6ed84a7
fix: update icon name in MessageSelectionView component for consistency
Jan 31, 2025
37730bb
fix(styles): adjust formatting for consistency in MessageSelectionVie…
Jan 31, 2025
6a10d6a
fix(styles): improve formatting and indentation in MessageSelectionVi…
Jan 31, 2025
f3b1569
fix(styles): correct formatting in MessageSelectionView component
Jan 31, 2025
786b07d
fix(styles): update theme type and adjust color usage in MessageSelec…
Jan 31, 2025
9acff70
fix: correct JSX formatting in MessageSelectionView component
Jan 31, 2025
076eaa2
fix(styles): improve formatting and indentation in MessageSelectionVi…
Jan 31, 2025
073c9dd
fix(styles): adjust formatting in getStyles function in MessageSelect…
Jan 31, 2025
82fa8a6
fix(styles): improve formatting in getStyles function in MessageSelec…
Jan 31, 2025
3cf8829
fix: add handleSelectView to dependencies in ChatView component
Jan 31, 2025
695acf8
fix: update icon and improve styles in MessageSelectionView component
Feb 1, 2025
d2c8d86
fix: add ScrollView to MessageSelectionView for improved text selecti…
Feb 1, 2025
164ccb3
fix: adjust formatting in MessageSelectionView component for improved…
Feb 1, 2025
9c7213e
fix: adjust text formatting in MessageSelectionView for consistency
Feb 1, 2025
59106d4
fix: improve code formatting in MessageSelectionView for consistency
Feb 1, 2025
54deece
fix: improve code formatting in MessageSelectionView for consistency
Feb 1, 2025
f5d2fec
chore: replace MessageSelectionView with SelectTextView component
Feb 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions src/components/ChatView/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)}),
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -479,6 +497,7 @@ export const ChatView = observer(
handleEdit,
handleMenuDismiss,
size.width,
handleSelectView,
]);

const renderMenuItem = React.useCallback(
Expand Down Expand Up @@ -756,6 +775,12 @@ export const ChatView = observer(
onRequestClose={handleRequestClose}
visible={isImageViewVisible}
/>
<SelectTextView
visible={isSelectionModalVisible}
onClose={() => setSelectionModalVisible(false)}
content={selectedMessageContent}
onTextSelected={handleTextSelected}
/>
<Menu
visible={menuVisible}
onDismiss={handleMenuDismiss}
Expand Down
67 changes: 67 additions & 0 deletions src/components/SelectTextView/SelectTextView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, {useRef} from 'react';
import {Modal, Pressable, Text, View, ScrollView} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import {useTheme} from '../../hooks/useTheme';
import {getStyles} from './styles';

interface SelectTextViewProps {
visible: boolean;
onClose: () => void;
content: string;
onTextSelected: (selectedText: string) => void;
}

export const SelectTextView: React.FC<SelectTextViewProps> = ({
visible,
onClose,
content,
onTextSelected,
}) => {
const theme = useTheme();
const textRef = useRef<Text>(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 (
<Modal
visible={visible}
transparent
animationType="slide"
onRequestClose={onClose}>
<View style={styles.modalContainer}>
<View style={styles.headerContainer}>
<Pressable onPress={onClose}>
{({pressed}) => (
<Icon
name="arrow-left"
size={26}
color={
pressed ? theme.colors.surfaceVariant : theme.colors.text
}
/>
)}
</Pressable>
<Text style={styles.headerText}>Select Text</Text>
</View>

<ScrollView contentContainerStyle={styles.scrollViewContent}>
<Text
ref={textRef}
selectable
selectionColor={theme.colors.surfaceVariant}
onTextLayout={handleTextSelection}
style={styles.contentText}>
{content}
</Text>
</ScrollView>
</View>
</Modal>
);
};
1 change: 1 addition & 0 deletions src/components/SelectTextView/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './SelectTextView';
34 changes: 34 additions & 0 deletions src/components/SelectTextView/styles.ts
Original file line number Diff line number Diff line change
@@ -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,
},
});
28 changes: 24 additions & 4 deletions src/hooks/useMessageActions.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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) {
Expand All @@ -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'});
Expand Down Expand Up @@ -87,7 +101,13 @@ export const useMessageActions = ({
return {
handleCopy,
handleEdit,
handleSelectView,
handleTryAgain,
handleTryAgainWith,
isSelectionModalVisible,
selectedMessageContent,
handleTextSelected,
setSelectionModalVisible,
selectedText,
};
};
Loading