import { useMemo } from 'react' import { KeyboardAvoidingView, Platform, Pressable, Text, TextInput, View } from 'react-native' import { Check, Copy, FileText, Plus, Send, Trash2, X } from 'lucide-react-native' import type { DiffComment } from '../../../src/shared/types' import { colors } from '../theme/mobile-theme' import type { ActionSheetAction } from './ActionSheetModal' import { ActionSheetModal } from './ActionSheetModal' import { BottomDrawer } from './BottomDrawer' import { ConfirmModal } from './ConfirmModal' import { mobileReviewCountLabel } from '../session/mobile-diff-review-screen-model' import type { useMobileDiffReviewController } from '../session/use-mobile-diff-review-controller' import { mobileDiffReviewStyles as styles } from './mobile-diff-review-screen-styles' type Props = { controller: ReturnType } export function MobileDiffReviewDrawers({ controller }: Props) { const sendActions = useSendActions(controller) const overflowActions = useOverflowActions(controller) return ( <> 0 ? `${controller.reviewedUnstagedCount} reviewed unstaged files can be staged` : undefined } actions={overflowActions} onClose={() => controller.setShowOverflow(false)} /> controller.setSendSheet(null)} /> { const target = controller.discardTarget controller.setDiscardTarget(null) if (target) { void controller.runGitMutation('git.discard', target) } }} onCancel={() => controller.setDiscardTarget(null)} /> ) } function useSendActions(controller: ReturnType) { return useMemo(() => { const comments = controller.unsentComments const terminalActions = controller.sendSheet?.kind === 'ready' || controller.sendSheet?.kind === 'error' ? controller.sendSheet.terminals.map((terminal) => ({ label: `${terminal.title || 'Terminal'} (${terminal.terminal.slice(0, 6)})`, icon: Send, disabled: comments.length === 0, skipAutoClose: true, onPress: () => void controller.sendPromptToTerminal(terminal.terminal, comments) })) : [] return [ ...terminalActions, { label: 'New Agent Session', icon: Plus, disabled: comments.length === 0, skipAutoClose: true, onPress: () => void controller.createTerminalAndSend(comments) }, { label: 'Copy Notes', icon: Copy, disabled: controller.screenState.kind !== 'ready' || controller.screenState.comments.length === 0, onPress: () => void controller.copyNotes() } ] }, [controller]) } function useOverflowActions(controller: ReturnType) { return useMemo( () => [ { label: 'Copy Notes', icon: Copy, disabled: controller.screenState.kind !== 'ready' || controller.screenState.comments.length === 0, onPress: () => void controller.copyNotes() }, { label: 'Send Unsent Notes', icon: Send, disabled: controller.unsentComments.length === 0, skipAutoClose: true, onPress: () => void controller.openSendSheet() }, { label: 'Clear Sent Notes', icon: Trash2, disabled: controller.screenState.kind !== 'ready' || controller.screenState.comments.every((comment) => comment.sentAt === undefined), skipAutoClose: true, onPress: () => void controller.clearSentNotes() }, { label: 'Stage Reviewed Files', icon: Check, disabled: controller.reviewedUnstagedCount === 0 || controller.busyAction !== null, skipAutoClose: true, onPress: () => void controller.stageReviewedFiles() }, { label: 'Mark Unreviewed', icon: X, disabled: controller.screenState.kind !== 'ready' || !controller.currentItem || !controller.currentItem.isReviewed, skipAutoClose: true, onPress: () => void controller.markUnreviewed() }, { label: 'Open in Session', icon: FileText, disabled: !controller.currentItem || controller.currentItem.scope === 'branch', onPress: () => void controller.openInSession() } ], [controller] ) } function sendSheetMessage( controller: ReturnType ): string | undefined { return controller.sendSheet?.kind === 'loading' ? 'Loading agent sessions...' : controller.sendSheet?.kind === 'error' ? controller.sendSheet.message : `${controller.unsentComments.length} unsent notes` } function NoteComposerDrawer({ controller }: Props) { const composer = controller.composer return ( {composer?.mode === 'edit' ? 'Edit Note' : 'Add Note'} {composer?.mode === 'create' && composer.lineNumber > 0 ? `Line ${composer.lineNumber}` : 'File note'} [styles.iconButton, pressed && styles.iconButtonPressed]} onPress={controller.closeComposer} accessibilityRole="button" accessibilityLabel="Cancel note" > {composer?.mode === 'edit' ? ( ) : null} ) } function composerLabel( composer: { mode: 'create'; lineNumber: number } | { mode: 'edit'; comment: DiffComment } | null ): string { return composer?.mode === 'create' && composer.lineNumber > 0 ? `Save note on line ${composer.lineNumber}` : 'Review note' } function DeleteNoteButton({ onPress }: { onPress: () => Promise }) { return ( [styles.secondaryButton, pressed && styles.buttonPressed]} onPress={() => void onPress()} accessibilityRole="button" accessibilityLabel="Delete note" > Delete ) } function SaveNoteButton({ controller, composer }: { controller: ReturnType composer: ReturnType['composer'] }) { const disabled = controller.composerBody.trim().length === 0 return ( [ styles.primaryButton, disabled && styles.buttonDisabled, pressed && styles.buttonPressed ]} disabled={disabled} onPress={() => void controller.saveComposer()} accessibilityRole="button" accessibilityLabel={composerLabel(composer)} > Save ) } function CompletionDrawer({ controller }: Props) { const noteCount = controller.screenState.kind === 'ready' ? controller.screenState.comments.length : 0 return ( controller.setShowCompletion(false)} > Review Complete {mobileReviewCountLabel(controller.queue.length, 'file', 'files')} reviewed,{' '} {mobileReviewCountLabel(noteCount, 'note', 'notes')} [styles.secondaryButton, pressed && styles.buttonPressed]} disabled={controller.reviewedUnstagedCount === 0} onPress={() => void controller.stageReviewedFiles()} accessibilityRole="button" accessibilityLabel="Stage reviewed files" > Stage Reviewed [styles.primaryButton, pressed && styles.buttonPressed]} disabled={controller.unsentComments.length === 0} onPress={() => void controller.openSendSheet()} accessibilityRole="button" accessibilityLabel="Send notes to agent" > Send Notes ) }