import {
ActivityIndicator,
Platform,
Pressable,
ScrollView,
SectionList,
Text,
TextInput,
View
} from 'react-native'
import { Minus, MoreHorizontal, Plus, Sparkles } from 'lucide-react-native'
import { imeGuardedSubmitProps } from '../ime/ime-submit-carry'
import { colors, spacing } from '../theme/mobile-theme'
import { MobileSourceControlCreatePrEntry } from './MobileSourceControlCreatePrEntry'
import { MobileCommitFailurePanel } from './MobileCommitFailurePanel'
import { KEYBOARD_COMMIT_BAR_CLEARANCE } from './mobile-source-control-screen-state'
import { makeRenderFileRow, BranchCompareFooter } from './MobileSourceControlFileRows'
import type { MobileSourceControlState } from './use-mobile-source-control-state'
import { styles } from './mobile-source-control-styles'
import { hubStyles } from './mobile-source-control-hub-styles'
type Props = {
state: MobileSourceControlState
}
// Changes tab: local file changes only — uncommitted (staged/unstaged) plus
// committed-on-branch vs base. PR conflicts and push status live elsewhere.
export function MobileSourceControlContent({ state }: Props) {
const {
insets,
connState,
busyAction,
commitMessage,
setCommitMessage,
generatingMessage,
setShowActionSheet,
setDiscardTarget,
actionError,
commitFailureRecovery,
commitFailureRecoveryAction,
keyboardLift,
openingPath,
openingBranchPath,
sections,
hasVisibleChanges,
stageablePaths,
unstageablePaths,
stagedCount,
primaryAction,
createPrAction,
stageAll,
unstageAll,
generateCommitMessage,
cancelGenerateCommitMessage,
openFile,
openBranchDiff,
runGitAction
} = state
const ioBusy = busyAction !== null || openingPath !== null || openingBranchPath !== null
const shouldShowGenerateButton = stagedCount > 0 || generatingMessage
const createPrHeroActive =
createPrAction.visible && !createPrAction.disabled && !createPrAction.pushFirst
const branchCompareFooter = (
)
return (
<>
{connState !== 'connected' ? (
// Why: once data has loaded the screen looks alive even when the
// desktop link is down, so taps appear to do nothing (STA-1511).
// Surface the reconnect state where the user is looking.
Reconnecting to desktop...
) : null}
{commitFailureRecovery ? (
) : actionError ? (
{actionError}
) : null}
[
styles.bulkButton,
(stageablePaths.length === 0 || ioBusy) && styles.bulkButtonDisabled,
pressed && styles.bulkButtonPressed
]}
onPress={() => void stageAll()}
disabled={ioBusy || stageablePaths.length === 0}
>
{busyAction === 'stage-all' ? (
) : (
)}
Stage All
[
styles.bulkButton,
(unstageablePaths.length === 0 || ioBusy) && styles.bulkButtonDisabled,
pressed && styles.bulkButtonPressed
]}
onPress={() => void unstageAll()}
disabled={ioBusy || unstageablePaths.length === 0}
>
{busyAction === 'unstage-all' ? (
) : (
)}
Unstage All
[
styles.bulkMenuButton,
pressed && styles.bulkButtonPressed,
ioBusy && styles.bulkButtonDisabled
]}
onPress={() => setShowActionSheet(true)}
disabled={ioBusy}
hitSlop={8}
accessibilityLabel="Open source control actions"
>
{!hasVisibleChanges ? (
No local changes
Working tree is clean.
) : sections.length === 0 ? (
// Why: RN SectionList with empty `sections` often skips ListFooterComponent,
// which hid "Committed on Branch" when only branch files remain.
{branchCompareFooter}
) : (
`${item.area}:${item.path}:${item.oldPath ?? ''}`}
renderSectionHeader={({ section }) => (
{section.title}
{section.data.length}
)}
ListFooterComponent={branchCompareFooter}
stickySectionHeadersEnabled={false}
contentContainerStyle={styles.listContent}
/>
)}
0 ? keyboardLift + KEYBOARD_COMMIT_BAR_CLEARANCE : keyboardLift,
paddingBottom: keyboardLift > 0 ? spacing.md : spacing.md + insets.bottom
}
]}
>
{stagedCount === 0 ? (
No staged files
) : (
)}
{shouldShowGenerateButton ? (
[
styles.generateButton,
busyAction !== null && styles.commitButtonDisabled,
pressed && styles.commitButtonPressed
]}
// Why: commit-message AI belongs to the commit path; hiding it
// during Stage All keeps the quick action visually unambiguous.
disabled={busyAction !== null}
onPress={() =>
generatingMessage ? cancelGenerateCommitMessage() : void generateCommitMessage()
}
accessibilityLabel={
generatingMessage
? 'Cancel commit message generation'
: 'Generate commit message with AI'
}
>
{generatingMessage ? (
) : (
)}
) : null}
[
styles.commitButton,
createPrHeroActive && styles.commitButtonSecondary,
primaryAction.disabled && styles.commitButtonDisabled,
pressed && styles.commitButtonPressed
]}
onPress={primaryAction.onPress}
disabled={primaryAction.disabled}
accessibilityLabel={primaryAction.accessibilityLabel}
accessibilityHint={primaryAction.accessibilityHint}
>
{primaryAction.loading ? (
) : (
{primaryAction.label}
)}
>
)
}