import { useNavigation, type StaticScreenProps } from "@react-navigation/native"; import { useCallback, useState } from "react"; import { Platform, Pressable, ScrollView, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { AndroidSheetHeader } from "../../../components/AndroidScreenHeader"; import { AppText as Text, AppTextInput as TextInput } from "../../../components/AppText"; import { cn } from "../../../lib/cn"; import { useEnvironmentQuery } from "../../../state/query"; import { useThreadSelection } from "../../../state/use-thread-selection"; import { useSelectedThreadGitActions } from "../../../state/use-selected-thread-git-actions"; import { useSelectedThreadGitState } from "../../../state/use-selected-thread-git-state"; import { useSelectedThreadWorktree } from "../../../state/use-selected-thread-worktree"; import { vcsEnvironment } from "../../../state/vcs"; import { SheetActionButton } from "./gitSheetComponents"; type GitCommitSheetProps = StaticScreenProps<{ readonly environmentId: string; readonly threadId: string; }>; export function GitCommitSheet(_props: GitCommitSheetProps) { const navigation = useNavigation(); const insets = useSafeAreaInsets(); const { selectedThread } = useThreadSelection(); const { selectedThreadCwd } = useSelectedThreadWorktree(); const gitState = useSelectedThreadGitState(); const gitActions = useSelectedThreadGitActions(); const gitStatus = useEnvironmentQuery( selectedThread !== null && selectedThreadCwd !== null ? vcsEnvironment.status({ environmentId: selectedThread.environmentId, input: { cwd: selectedThreadCwd }, }) : null, ); const busy = gitState.gitOperationLabel !== null; const isDefaultRef = gitStatus.data?.isDefaultRef ?? false; const allFiles = gitStatus.data?.workingTree?.files ?? []; const [dialogCommitMessage, setDialogCommitMessage] = useState(""); const [excludedFiles, setExcludedFiles] = useState>(new Set()); const [isEditingFiles, setIsEditingFiles] = useState(false); const selectedFiles = allFiles.filter((file) => !excludedFiles.has(file.path)); const allSelected = excludedFiles.size === 0; const noneSelected = selectedFiles.length === 0; const selectedInsertions = selectedFiles.reduce((sum, file) => sum + file.insertions, 0); const selectedDeletions = selectedFiles.reduce((sum, file) => sum + file.deletions, 0); const selectedFilePreview = selectedFiles.slice(0, 3); const runCommitAction = useCallback( async (featureBranch: boolean) => { const commitMessage = dialogCommitMessage.trim(); navigation.goBack(); await gitActions.onRunSelectedThreadGitAction({ action: "commit", featureBranch, ...(commitMessage ? { commitMessage } : {}), ...(!allSelected ? { filePaths: selectedFiles.map((file) => file.path) } : {}), }); }, [allSelected, dialogCommitMessage, gitActions, navigation, selectedFiles], ); return ( {Platform.OS === "android" ? ( navigation.goBack()} /> ) : null} Branch {gitStatus.data?.refName ?? "(detached HEAD)"} {isDefaultRef ? ( Warning: this is the default branch. ) : null} Files {selectedFiles.length} selected ยท +{selectedInsertions} / -{selectedDeletions} {!allSelected && isEditingFiles ? ( setExcludedFiles(new Set())} > Reset ) : null} setIsEditingFiles((current) => !current)} > {isEditingFiles ? "Done" : "Edit"} {allFiles.length === 0 ? ( No changed files are available to commit. ) : !isEditingFiles ? ( {selectedFilePreview.map((file) => ( {file.path} +{file.insertions} -{file.deletions} ))} {selectedFiles.length > selectedFilePreview.length ? ( +{selectedFiles.length - selectedFilePreview.length} more files ) : null} ) : ( {allFiles.map((file) => { const included = !excludedFiles.has(file.path); return ( { setExcludedFiles((current) => { const next = new Set(current); if (next.has(file.path)) { next.delete(file.path); } else { next.add(file.path); } return next; }); }} > {file.path} {!included ? ( Excluded from this commit ) : null} +{file.insertions} -{file.deletions} ); })} )} Commit message void runCommitAction(true)} /> void runCommitAction(false)} /> ); }