feat: add multi-platform downloads and improve homepage animations
This commit is contained in:
@@ -597,7 +597,7 @@ export function AgentInputArea({
|
||||
const isVoiceModeForAgent = voice?.isVoiceModeForAgent(serverId, agentId) ?? false
|
||||
|
||||
const handleToggleRealtimeVoice = useCallback(() => {
|
||||
if (!voice || !isConnected) {
|
||||
if (!voice || !isConnected || !agent) {
|
||||
return
|
||||
}
|
||||
if (voice.isVoiceSwitching) {
|
||||
@@ -614,7 +614,7 @@ export function AgentInputArea({
|
||||
toast.error(message)
|
||||
}
|
||||
})
|
||||
}, [agentId, isConnected, serverId, toast, voice])
|
||||
}, [agent, agentId, isConnected, serverId, toast, voice])
|
||||
|
||||
function handleEditQueuedMessage(id: string) {
|
||||
const item = queuedMessages.find((q) => q.id === id)
|
||||
@@ -704,7 +704,7 @@ export function AgentInputArea({
|
||||
|
||||
const rightContent = (
|
||||
<View style={styles.rightControls}>
|
||||
{!isVoiceModeForAgent ? (
|
||||
{!isVoiceModeForAgent && agent ? (
|
||||
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||
<TooltipTrigger
|
||||
onPress={handleToggleRealtimeVoice}
|
||||
|
||||
@@ -27,6 +27,7 @@ interface AgentListProps {
|
||||
selectedAgentId?: string
|
||||
onAgentSelect?: () => void
|
||||
listFooterComponent?: ReactElement | null
|
||||
showAttentionIndicator?: boolean
|
||||
}
|
||||
|
||||
interface AgentListSection {
|
||||
@@ -112,12 +113,14 @@ function SessionRow({
|
||||
agent,
|
||||
isMobile,
|
||||
selectedAgentId,
|
||||
showAttentionIndicator,
|
||||
onPress,
|
||||
onLongPress,
|
||||
}: {
|
||||
agent: AggregatedAgent
|
||||
isMobile: boolean
|
||||
selectedAgentId?: string
|
||||
showAttentionIndicator: boolean
|
||||
onPress: (agent: AggregatedAgent) => void
|
||||
onLongPress: (agent: AggregatedAgent) => void
|
||||
}) {
|
||||
@@ -154,7 +157,7 @@ function SessionRow({
|
||||
{(agent.pendingPermissionCount ?? 0) > 0 ? (
|
||||
<SessionBadge label={`${agent.pendingPermissionCount} pending`} tone="warning" />
|
||||
) : null}
|
||||
{!isMobile && agent.requiresAttention ? (
|
||||
{!isMobile && showAttentionIndicator && agent.requiresAttention ? (
|
||||
<SessionBadge label="Attention" tone="danger" />
|
||||
) : null}
|
||||
</View>
|
||||
@@ -187,7 +190,7 @@ function SessionRow({
|
||||
<Text style={styles.columnMetaFixed}>{timeAgo}</Text>
|
||||
</>
|
||||
)}
|
||||
{isMobile && agent.requiresAttention ? (
|
||||
{isMobile && showAttentionIndicator && agent.requiresAttention ? (
|
||||
<View style={styles.rowTrailing}>
|
||||
<SessionBadge label="Attention" tone="danger" />
|
||||
</View>
|
||||
@@ -200,12 +203,14 @@ function SessionTableSection({
|
||||
section,
|
||||
isMobile,
|
||||
selectedAgentId,
|
||||
showAttentionIndicator,
|
||||
onAgentPress,
|
||||
onAgentLongPress,
|
||||
}: {
|
||||
section: AgentListSection
|
||||
isMobile: boolean
|
||||
selectedAgentId?: string
|
||||
showAttentionIndicator: boolean
|
||||
onAgentPress: (agent: AggregatedAgent) => void
|
||||
onAgentLongPress: (agent: AggregatedAgent) => void
|
||||
}) {
|
||||
@@ -225,6 +230,7 @@ function SessionTableSection({
|
||||
agent={agent}
|
||||
isMobile={isMobile}
|
||||
selectedAgentId={selectedAgentId}
|
||||
showAttentionIndicator={showAttentionIndicator}
|
||||
onPress={onAgentPress}
|
||||
onLongPress={onAgentLongPress}
|
||||
/>
|
||||
@@ -242,6 +248,7 @@ export function AgentList({
|
||||
selectedAgentId,
|
||||
onAgentSelect,
|
||||
listFooterComponent,
|
||||
showAttentionIndicator = true,
|
||||
}: AgentListProps) {
|
||||
const { theme } = useUnistyles()
|
||||
const pathname = usePathname()
|
||||
@@ -325,11 +332,12 @@ export function AgentList({
|
||||
section={section}
|
||||
isMobile={isMobile}
|
||||
selectedAgentId={selectedAgentId}
|
||||
showAttentionIndicator={showAttentionIndicator}
|
||||
onAgentPress={handleAgentPress}
|
||||
onAgentLongPress={handleAgentLongPress}
|
||||
/>
|
||||
),
|
||||
[handleAgentLongPress, handleAgentPress, isMobile, selectedAgentId]
|
||||
[handleAgentLongPress, handleAgentPress, isMobile, selectedAgentId, showAttentionIndicator]
|
||||
)
|
||||
|
||||
const keyExtractor = useCallback((section: AgentListSection) => section.key, [])
|
||||
|
||||
@@ -24,7 +24,7 @@ import { router, usePathname } from 'expo-router'
|
||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from 'react-native-unistyles'
|
||||
import { type GestureType } from 'react-native-gesture-handler'
|
||||
import * as Clipboard from 'expo-clipboard'
|
||||
import { ChevronDown, ChevronRight, MoreVertical, Plus } from 'lucide-react-native'
|
||||
import { Archive, ChevronDown, ChevronRight, Copy, MoreVertical, Plus } from 'lucide-react-native'
|
||||
import { NestableScrollContainer } from 'react-native-draggable-flatlist'
|
||||
import { DraggableList, type DraggableRenderItemInfo } from './draggable-list'
|
||||
import type { DraggableListDragHandleProps } from './draggable-list.types'
|
||||
@@ -43,7 +43,6 @@ import {
|
||||
} from '@/hooks/use-sidebar-workspaces-list'
|
||||
import { useSidebarOrderStore } from '@/stores/sidebar-order-store'
|
||||
import { useKeyboardShortcutsStore } from '@/stores/keyboard-shortcuts-store'
|
||||
import { formatTimeAgo } from '@/utils/time'
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
@@ -126,12 +125,6 @@ interface WorkspaceRowInnerProps {
|
||||
onCopyPath?: () => void
|
||||
}
|
||||
|
||||
function resolveWorkspaceCreatedAtLabel(workspace: SidebarWorkspaceEntry): string | null {
|
||||
if (!workspace.activityAt) {
|
||||
return null
|
||||
}
|
||||
return formatTimeAgo(workspace.activityAt)
|
||||
}
|
||||
|
||||
function resolveStatusDotColor(input: {
|
||||
theme: ReturnType<typeof useUnistyles>['theme']
|
||||
@@ -620,7 +613,8 @@ function WorkspaceRowInner({
|
||||
onCopyPath,
|
||||
}: WorkspaceRowInnerProps) {
|
||||
const { theme } = useUnistyles()
|
||||
const createdAtLabel = resolveWorkspaceCreatedAtLabel(workspace)
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
const isMobile = Platform.OS !== 'web'
|
||||
const interaction = useLongPressDragInteraction({
|
||||
drag,
|
||||
menuController,
|
||||
@@ -636,14 +630,18 @@ function WorkspaceRowInner({
|
||||
}, [interaction.didLongPressRef, onPress])
|
||||
|
||||
return (
|
||||
<View style={styles.workspaceRowContainer}>
|
||||
<View
|
||||
style={styles.workspaceRowContainer}
|
||||
onPointerEnter={() => setIsHovered(true)}
|
||||
onPointerLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<Pressable
|
||||
disabled={isArchiving}
|
||||
style={({ pressed, hovered = false }) => [
|
||||
style={({ pressed }) => [
|
||||
styles.workspaceRow,
|
||||
isDragging && styles.workspaceRowDragging,
|
||||
selected && styles.sidebarRowSelected,
|
||||
hovered && styles.workspaceRowHovered,
|
||||
isHovered && styles.workspaceRowHovered,
|
||||
pressed && styles.workspaceRowPressed,
|
||||
]}
|
||||
onPressIn={interaction.handlePressIn}
|
||||
@@ -659,41 +657,43 @@ function WorkspaceRowInner({
|
||||
style={styles.workspaceRowLeft}
|
||||
>
|
||||
<WorkspaceStatusIndicator bucket={workspace.statusBucket} loading={isArchiving} />
|
||||
<Text style={styles.workspaceBranchText} numberOfLines={1}>
|
||||
<Text
|
||||
style={[styles.workspaceBranchText, isHovered && styles.workspaceBranchTextHovered]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{workspace.name}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.workspaceRowRight}>
|
||||
{workspace.diffStat ? (
|
||||
<View style={styles.diffStatRow}>
|
||||
<Text style={styles.diffStatAdditions}>+{workspace.diffStat.additions}</Text>
|
||||
<Text style={styles.diffStatDeletions}>-{workspace.diffStat.deletions}</Text>
|
||||
</View>
|
||||
) : createdAtLabel ? (
|
||||
<Text style={styles.workspaceCreatedAtText} numberOfLines={1}>
|
||||
{createdAtLabel}
|
||||
</Text>
|
||||
) : null}
|
||||
{showShortcutBadge && shortcutNumber !== null ? (
|
||||
<View style={styles.shortcutBadge}>
|
||||
<Text style={styles.shortcutBadgeText}>{shortcutNumber}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
{onArchive ? (
|
||||
{onArchive && (isHovered || isMobile) ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
hitSlop={8}
|
||||
style={styles.kebabButton}
|
||||
style={({ hovered = false }) => [
|
||||
styles.kebabButton,
|
||||
hovered && styles.kebabButtonHovered,
|
||||
]}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Workspace actions"
|
||||
testID={`sidebar-workspace-kebab-${workspace.workspaceKey}`}
|
||||
>
|
||||
<MoreVertical size={14} color={theme.colors.foregroundMuted} />
|
||||
{({ hovered }) => (
|
||||
<MoreVertical
|
||||
size={14}
|
||||
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" width={200}>
|
||||
{onCopyPath ? (
|
||||
<DropdownMenuItem
|
||||
testID={`sidebar-workspace-menu-copy-path-${workspace.workspaceKey}`}
|
||||
leading={<Copy size={14} color={theme.colors.foregroundMuted} />}
|
||||
onSelect={onCopyPath}
|
||||
>
|
||||
Copy path
|
||||
@@ -701,15 +701,20 @@ function WorkspaceRowInner({
|
||||
) : null}
|
||||
<DropdownMenuItem
|
||||
testID={`sidebar-workspace-menu-archive-${workspace.workspaceKey}`}
|
||||
leading={<Archive size={14} color={theme.colors.foregroundMuted} />}
|
||||
status={archiveStatus}
|
||||
pendingLabel={archivePendingLabel}
|
||||
destructive
|
||||
onSelect={onArchive}
|
||||
>
|
||||
{archiveLabel ?? 'Archive'}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : workspace.diffStat ? (
|
||||
<View style={styles.diffStatRow}>
|
||||
<Text style={styles.diffStatAdditions}>+{workspace.diffStat.additions}</Text>
|
||||
<Text style={styles.diffStatDeletions}>-{workspace.diffStat.deletions}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
</Pressable>
|
||||
@@ -1726,10 +1731,8 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
workspaceCreatedAtText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
flexShrink: 0,
|
||||
workspaceBranchTextHovered: {
|
||||
opacity: 1,
|
||||
},
|
||||
diffStatRow: {
|
||||
flexDirection: 'row',
|
||||
@@ -1752,6 +1755,9 @@ const styles = StyleSheet.create((theme) => ({
|
||||
borderRadius: 4,
|
||||
marginLeft: 2,
|
||||
},
|
||||
kebabButtonHovered: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
shortcutBadge: {
|
||||
minWidth: 18,
|
||||
height: 18,
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
useState,
|
||||
type PropsWithChildren,
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
@@ -184,8 +185,9 @@ type TriggerStyleProp =
|
||||
| StyleProp<ViewStyle>
|
||||
| ((state: TriggerState) => StyleProp<ViewStyle>);
|
||||
|
||||
interface DropdownMenuTriggerProps extends Omit<PressableProps, "style"> {
|
||||
interface DropdownMenuTriggerProps extends Omit<PressableProps, "style" | "children"> {
|
||||
style?: TriggerStyleProp;
|
||||
children: ReactNode | ((state: TriggerState) => ReactNode);
|
||||
}
|
||||
|
||||
export function DropdownMenuTrigger({
|
||||
@@ -193,7 +195,7 @@ export function DropdownMenuTrigger({
|
||||
disabled,
|
||||
style,
|
||||
...props
|
||||
}: PropsWithChildren<DropdownMenuTriggerProps>): ReactElement {
|
||||
}: DropdownMenuTriggerProps): ReactElement {
|
||||
const ctx = useDropdownMenuContext("DropdownMenuTrigger");
|
||||
|
||||
const handlePress = useCallback(() => {
|
||||
@@ -215,7 +217,10 @@ export function DropdownMenuTrigger({
|
||||
return style;
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{({ pressed, hovered = false }) => {
|
||||
const state: TriggerState = { pressed, hovered: Boolean(hovered), open: ctx.open };
|
||||
return typeof children === "function" ? children(state) : children;
|
||||
}}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ export function AgentsScreen({ serverId }: { serverId: string }) {
|
||||
showCheckoutInfo={false}
|
||||
isRefreshing={isManualRefresh && isRevalidating}
|
||||
onRefresh={handleRefresh}
|
||||
showAttentionIndicator={false}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user