import React, { useCallback, useEffect, useRef, useState } from "react"; import { Animated, Keyboard, LayoutAnimation, Modal, Platform, Pressable, ScrollView, Text, TextInput, UIManager, View, } from "react-native"; import { GestureHandlerRootView, PanGestureHandler, PanGestureHandlerGestureEvent, State, Swipeable, } from "react-native-gesture-handler"; import * as Haptics from "expo-haptics"; import { WsSession } from "../types"; import { useChat } from "../contexts/ChatContext"; if ( Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental ) { UIManager.setLayoutAnimationEnabledExperimental(true); } interface SessionPickerProps { visible: boolean; onClose: () => void; } /* ── Swipeable row with delete action ── */ function SessionRow({ session, onSwitch, onLongPress, onDelete, }: { session: WsSession; onSwitch: () => void; onLongPress: () => void; onDelete: () => void; }) { const swipeRef = useRef(null); const renderRightActions = ( _progress: Animated.AnimatedInterpolation, dragX: Animated.AnimatedInterpolation, ) => { const scale = dragX.interpolate({ inputRange: [-100, -50, 0], outputRange: [1, 0.8, 0], extrapolate: "clamp", }); return ( { swipeRef.current?.close(); onDelete(); }} style={{ backgroundColor: "#FF3B30", justifyContent: "center", alignItems: "center", width: 80, borderRadius: 16, marginLeft: 8, }} > Remove ); }; return ( ({ width: "100%", backgroundColor: pressed ? "#252538" : "#1E1E2E", borderRadius: 16, padding: 14, flexDirection: "row", alignItems: "center", borderWidth: session.isActive ? 2 : 1, borderColor: session.isActive ? "#4A9EFF" : "#2E2E45", })} > {/* Number badge */} {session.index} {/* Session info */} {session.name} {session.kind === "api" ? "Headless" : session.kind === "visual" ? "Visual" : session.type === "terminal" ? "Terminal" : "Claude"} {session.isActive ? " — active" : ""} {/* Active indicator */} {session.isActive && ( )} ); } /* ── Inline rename editor ── */ function RenameEditor({ name, onConfirm, onCancel, }: { name: string; onConfirm: (newName: string) => void; onCancel: () => void; }) { const [editName, setEditName] = useState(name); return ( onConfirm(editName.trim())} onBlur={onCancel} returnKeyType="done" style={{ color: "#E8E8F0", fontSize: 16, fontWeight: "600", padding: 0, marginBottom: 10, }} placeholderTextColor="#5A5A78" placeholder="Session name..." /> onConfirm(editName.trim())} style={{ flex: 1, backgroundColor: "#4A9EFF", borderRadius: 10, paddingVertical: 8, alignItems: "center", }} > Save Cancel ); } /* ── Main SessionPicker ── */ export function SessionPicker({ visible, onClose }: SessionPickerProps) { const { sessions, requestSessions, switchSession, renameSession, removeSession, } = useChat(); const [editingId, setEditingId] = useState(null); const [keyboardHeight, setKeyboardHeight] = useState(0); // Sort: active first, then by index const sortedSessions = [...sessions].sort((a, b) => { if (a.isActive && !b.isActive) return -1; if (!a.isActive && b.isActive) return 1; return a.index - b.index; }); useEffect(() => { const showSub = Keyboard.addListener("keyboardWillShow", (e) => setKeyboardHeight(e.endCoordinates.height), ); const hideSub = Keyboard.addListener("keyboardWillHide", () => setKeyboardHeight(0), ); return () => { showSub.remove(); hideSub.remove(); }; }, []); useEffect(() => { if (!visible) { setEditingId(null); } else { requestSessions(); } }, [visible, requestSessions]); const handleClose = useCallback(() => { setEditingId(null); Keyboard.dismiss(); onClose(); }, [onClose]); const handleSwitch = useCallback( (session: WsSession) => { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); switchSession(session.id); handleClose(); }, [switchSession, handleClose], ); const handleStartRename = useCallback((session: WsSession) => { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); setEditingId(session.id); }, []); const handleConfirmRename = useCallback( (sessionId: string, newName: string) => { if (newName) { renameSession(sessionId, newName); } setEditingId(null); }, [renameSession], ); const handleRemove = useCallback( (session: WsSession) => { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning); LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); removeSession(session.id); }, [removeSession], ); return ( {/* Handle bar */} {/* Header */} Sessions requestSessions()} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }} style={({ pressed }) => ({ paddingHorizontal: 12, paddingVertical: 6, borderRadius: 12, backgroundColor: pressed ? "#252538" : "#1E1E2E", })} > Refresh {/* Session list */} {sortedSessions.length === 0 ? ( No sessions found ) : ( sortedSessions.map((session) => ( {editingId === session.id ? ( handleConfirmRename(session.id, name) } onCancel={() => setEditingId(null)} /> ) : ( handleSwitch(session)} onLongPress={() => handleStartRename(session)} onDelete={() => handleRemove(session)} /> )} )) )} Tap to switch — Long press to rename — Swipe left to remove ); }