Skip to content

Commit

Permalink
feat: add session renaming functionality and context menu in sidebar (#…
Browse files Browse the repository at this point in the history
…185)

* feat: add chat session renaming functionality and context menu in sidebar
* feat: set new title for chat session during renaming in sidebar
* refactor: replace slide-to-delete with long-press menu
---------

Co-authored-by: sohailbaig <[email protected]>
Co-authored-by: a-ghorbani <[email protected]>
  • Loading branch information
3 people authored Jan 30, 2025
1 parent 3507e81 commit 86703a5
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 15 deletions.
129 changes: 114 additions & 15 deletions src/components/SidebarContent/SidebarContent.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, {useEffect, useState} from 'react';
import {TouchableOpacity, View} from 'react-native';
import {SafeAreaView} from 'react-native-safe-area-context';
import {TouchableOpacity, View, TextInput, Modal} from 'react-native';

import {observer} from 'mobx-react';
import {Drawer, Text} from 'react-native-paper';
import DeviceInfo from 'react-native-device-info';
import Clipboard from '@react-native-clipboard/clipboard';
import {SafeAreaView} from 'react-native-safe-area-context';
import {GestureHandlerRootView} from 'react-native-gesture-handler';
import {
DrawerContentScrollView,
Expand All @@ -18,7 +18,7 @@ import {createStyles} from './styles';

import {chatSessionStore} from '../../store';

import {AppleStyleSwipeableRow} from '..';
import {Menu} from '..';

export const SidebarContent: React.FC<DrawerContentComponentProps> = observer(
props => {
Expand All @@ -27,6 +27,12 @@ export const SidebarContent: React.FC<DrawerContentComponentProps> = observer(
build: '',
});

const [menuVisible, setMenuVisible] = useState<string | null>(null); // Track which menu is visible
const [menuPosition, setMenuPosition] = useState({x: 0, y: 0}); // Track menu position
const [renameModalVisible, setRenameModalVisible] = useState(false);
const [newTitle, setNewTitle] = useState('');
const [sessionToRename, setSessionToRename] = useState<string | null>(null);

useEffect(() => {
chatSessionStore.loadSessionList();

Expand All @@ -47,6 +53,26 @@ export const SidebarContent: React.FC<DrawerContentComponentProps> = observer(
Clipboard.setString(versionString);
};

const openMenu = (sessionId: string, event: any) => {
const {nativeEvent} = event;
setMenuPosition({x: nativeEvent.pageX, y: nativeEvent.pageY});
setMenuVisible(sessionId);
};

const closeMenu = () => setMenuVisible(null);

const handleRename = () => {
if (sessionToRename && newTitle.trim()) {
chatSessionStore.updateSessionTitleBySessionId(
sessionToRename,
newTitle,
);
setRenameModalVisible(false);
setNewTitle('');
setSessionToRename(null);
}
};

return (
<GestureHandlerRootView style={styles.sidebarContainer}>
<View style={styles.contentWrapper}>
Expand Down Expand Up @@ -85,22 +111,47 @@ export const SidebarContent: React.FC<DrawerContentComponentProps> = observer(
const isActive =
chatSessionStore.activeSessionId === session.id;
return (
<AppleStyleSwipeableRow
key={session.id} // Ensure each swipeable row has a unique key
onDelete={() =>
chatSessionStore.deleteSession(session.id)
}>
<Drawer.Item
active={isActive}
key={session.id}
label={session.title}
<View key={session.id} style={styles.sessionItem}>
<TouchableOpacity
onPress={() => {
chatSessionStore.setActiveSession(session.id);
props.navigation.navigate('Chat');
console.log(`Navigating to session: ${session.id}`);
}}
/>
</AppleStyleSwipeableRow>
onLongPress={event => openMenu(session.id, event)} // Open menu on long press
style={styles.sessionTouchable}>
<Drawer.Item
active={isActive}
label={session.title}
/>
</TouchableOpacity>

{/* Menu for the session item */}
<Menu
visible={menuVisible === session.id}
onDismiss={closeMenu}
anchor={menuPosition}>
<Menu.Item
style={styles.menu}
onPress={() => {
setSessionToRename(session.id);
setNewTitle(session.title);
setRenameModalVisible(true);
closeMenu();
}}
leadingIcon="pencil"
label="Rename"
/>
<Menu.Item
style={styles.menu}
onPress={() => {
chatSessionStore.deleteSession(session.id);
closeMenu();
}}
leadingIcon="trash-can-outline"
label="Delete"
/>
</Menu>
</View>
);
})}
</Drawer.Section>
Expand All @@ -121,6 +172,54 @@ export const SidebarContent: React.FC<DrawerContentComponentProps> = observer(
</TouchableOpacity>
</SafeAreaView>
</View>

{/* Rename Modal */}
<Modal
transparent={true}
visible={renameModalVisible}
onRequestClose={() => {
setRenameModalVisible(false);
setNewTitle('');
setSessionToRename(null);
}}
animationType="fade">
<View style={styles.modalOverlay}>
<View style={styles.modalContent}>
<Text style={styles.modalTitle}>Rename Chat</Text>
<TextInput
style={styles.textInput}
placeholder="New Title"
placeholderTextColor={theme.colors.onSurfaceVariant}
value={newTitle}
maxLength={40}
onChangeText={setNewTitle}
autoFocus={true}
onSubmitEditing={handleRename}
returnKeyType="done"
/>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={styles.cancelButton}
onPress={() => {
setRenameModalVisible(false);
setNewTitle('');
setSessionToRename(null);
}}>
<Text style={styles.cancelText}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.confirmButton,
!newTitle.trim() && styles.disabledButton,
]}
onPress={handleRename}
disabled={!newTitle.trim()}>
<Text style={styles.confirmText}>Done</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
</GestureHandlerRootView>
);
},
Expand Down
86 changes: 86 additions & 0 deletions src/components/SidebarContent/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,90 @@ export const createStyles = (theme: MD3Theme) =>
versionSafeArea: {
backgroundColor: theme.colors.surface,
},
menu: {
width: 170,
},
sessionItem: {
position: 'relative',
},
sessionTouchable: {
flex: 1,
},
modalOverlay: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
modalContent: {
width: '80%',
backgroundColor: theme.colors.surface,
borderRadius: 14,
overflow: 'hidden',
borderWidth: StyleSheet.hairlineWidth,
borderColor: theme.dark
? theme.colors.outline + '50'
: theme.colors.outline + '30',
},
modalTitle: {
fontSize: 17,
fontWeight: '600',
color: theme.colors.onSurface,
textAlign: 'center',
paddingVertical: 16,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: theme.dark
? theme.colors.outline + '50'
: theme.colors.outline + '30',
},
textInput: {
margin: 16,
padding: 12,
backgroundColor: theme.dark
? theme.colors.surfaceVariant + '80'
: theme.colors.surface + '90',
color: theme.colors.onSurface,
fontSize: 17,
borderWidth: StyleSheet.hairlineWidth * 2,
borderColor: theme.dark
? theme.colors.outline + '50'
: theme.colors.outline + '30',
borderRadius: 10,
},
buttonContainer: {
flexDirection: 'row',
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: theme.dark
? theme.colors.outline + '50'
: theme.colors.outline + '30',
},
cancelButton: {
flex: 1,
paddingVertical: 12,
borderRightWidth: StyleSheet.hairlineWidth,
borderRightColor: theme.dark
? theme.colors.outline + '50'
: theme.colors.outline + '30',
alignItems: 'center',
justifyContent: 'center',
},
cancelText: {
color: theme.colors.onSurfaceVariant,
fontSize: 17,
fontWeight: '400',
},
confirmButton: {
flex: 1,
paddingVertical: 12,
alignItems: 'center',
justifyContent: 'center',
},
confirmText: {
color: theme.colors.primary,
fontSize: 17,
fontWeight: '600',
},
disabledButton: {
opacity: 0.4,
},
});
11 changes: 11 additions & 0 deletions src/store/ChatSessionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ class ChatSessionStore {
});
}

// Update session title by session ID
updateSessionTitleBySessionId(sessionId: string, newTitle: string): void {
const session = this.sessions.find(s => s.id === sessionId);
if (session) {
runInAction(() => {
session.title = newTitle;
});
this.saveSessionsMetadata();
}
}

updateSessionTitle(session: SessionMetaData) {
if (session.messages.length > 0) {
const message = session.messages[session.messages.length - 1];
Expand Down

0 comments on commit 86703a5

Please sign in to comment.