diff options
Diffstat (limited to 'apps/mobile/hooks')
| -rw-r--r-- | apps/mobile/hooks/index.ts | 9 | ||||
| -rw-r--r-- | apps/mobile/hooks/useColorScheme.ts | 5 | ||||
| -rw-r--r-- | apps/mobile/hooks/useQuestions.ts | 44 | ||||
| -rw-r--r-- | apps/mobile/hooks/useTasks.ts | 204 | ||||
| -rw-r--r-- | apps/mobile/hooks/useThemeColor.ts | 47 |
5 files changed, 309 insertions, 0 deletions
diff --git a/apps/mobile/hooks/index.ts b/apps/mobile/hooks/index.ts new file mode 100644 index 0000000..2fa97fc --- /dev/null +++ b/apps/mobile/hooks/index.ts @@ -0,0 +1,9 @@ +/** + * Hooks exports + */ +export { + useThemeColors, + useThemeColor, + useThemeColorSet, + useIsDarkMode, +} from './useThemeColor'; diff --git a/apps/mobile/hooks/useColorScheme.ts b/apps/mobile/hooks/useColorScheme.ts new file mode 100644 index 0000000..97ef339 --- /dev/null +++ b/apps/mobile/hooks/useColorScheme.ts @@ -0,0 +1,5 @@ +import { useColorScheme as useRNColorScheme } from 'react-native'; + +export function useColorScheme() { + return useRNColorScheme() ?? 'light'; +} diff --git a/apps/mobile/hooks/useQuestions.ts b/apps/mobile/hooks/useQuestions.ts new file mode 100644 index 0000000..af77a51 --- /dev/null +++ b/apps/mobile/hooks/useQuestions.ts @@ -0,0 +1,44 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { listPendingQuestions, answerQuestion, type PendingQuestion } from '../lib/api'; + +// Query keys for questions +export const questionKeys = { + all: ['questions'] as const, + lists: () => [...questionKeys.all, 'list'] as const, + list: () => [...questionKeys.lists()] as const, +}; + +/** + * Hook to fetch pending questions + * Polls every 5 seconds for updates + */ +export function usePendingQuestions() { + return useQuery({ + queryKey: questionKeys.list(), + queryFn: listPendingQuestions, + refetchInterval: 5000, + }); +} + +/** + * Hook to answer a pending question + */ +export function useAnswerQuestion() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ questionId, response }: { questionId: string; response: string }) => + answerQuestion(questionId, response), + onSuccess: () => { + // Invalidate questions list to refetch + queryClient.invalidateQueries({ queryKey: questionKeys.lists() }); + }, + }); +} + +/** + * Get the count of pending questions + */ +export function getQuestionCount(questions: PendingQuestion[] | undefined): number { + return questions?.length ?? 0; +} diff --git a/apps/mobile/hooks/useTasks.ts b/apps/mobile/hooks/useTasks.ts new file mode 100644 index 0000000..4d56f63 --- /dev/null +++ b/apps/mobile/hooks/useTasks.ts @@ -0,0 +1,204 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { + listTasks, + getTask, + startTask, + stopTask, + getTaskOutput, + sendTaskMessage, + type TaskSummary, + type TaskWithSubtasks, + type TaskOutputResponse, +} from '../lib/api'; + +// Query keys for consistent cache management +export const taskKeys = { + all: ['tasks'] as const, + lists: () => [...taskKeys.all, 'list'] as const, + list: () => [...taskKeys.lists()] as const, + details: () => [...taskKeys.all, 'detail'] as const, + detail: (id: string) => [...taskKeys.details(), id] as const, + output: (id: string) => [...taskKeys.all, 'output', id] as const, +}; + +/** + * Hook to fetch the list of all tasks + * Automatically refetches every 5 seconds for live updates + */ +export function useTasks() { + return useQuery({ + queryKey: taskKeys.list(), + queryFn: async () => { + const response = await listTasks(); + return response.tasks; + }, + refetchInterval: 5000, // Poll every 5 seconds for updates + staleTime: 2000, // Consider data stale after 2 seconds + }); +} + +/** + * Hook to fetch a specific task with its subtasks + */ +export function useTask(taskId: string | null) { + return useQuery({ + queryKey: taskKeys.detail(taskId ?? ''), + queryFn: () => getTask(taskId!), + enabled: !!taskId, + refetchInterval: 5000, + }); +} + +/** + * Hook to fetch task output history + */ +export function useTaskOutput(taskId: string | null) { + return useQuery({ + queryKey: taskKeys.output(taskId ?? ''), + queryFn: () => getTaskOutput(taskId!), + enabled: !!taskId, + refetchInterval: 3000, // More frequent updates for output + }); +} + +/** + * Hook to start a task + */ +export function useStartTask() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: startTask, + onSuccess: (updatedTask) => { + // Invalidate task list to refetch + queryClient.invalidateQueries({ queryKey: taskKeys.lists() }); + // Update the specific task in cache + queryClient.setQueryData( + taskKeys.detail(updatedTask.id), + (old: TaskWithSubtasks | undefined) => { + if (old) { + return { ...old, ...updatedTask }; + } + return old; + } + ); + }, + }); +} + +/** + * Hook to stop a task + */ +export function useStopTask() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: stopTask, + onSuccess: (updatedTask) => { + // Invalidate task list to refetch + queryClient.invalidateQueries({ queryKey: taskKeys.lists() }); + // Update the specific task in cache + queryClient.setQueryData( + taskKeys.detail(updatedTask.id), + (old: TaskWithSubtasks | undefined) => { + if (old) { + return { ...old, ...updatedTask }; + } + return old; + } + ); + }, + }); +} + +/** + * Hook to send a message to a task + */ +export function useSendTaskMessage() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ taskId, message }: { taskId: string; message: string }) => + sendTaskMessage(taskId, message), + onSuccess: (_, { taskId }) => { + // Invalidate task output to refetch + queryClient.invalidateQueries({ queryKey: taskKeys.output(taskId) }); + }, + }); +} + +/** + * Helper to group tasks by status for display + */ +export function groupTasksByStatus(tasks: TaskSummary[]) { + const groups = { + running: [] as TaskSummary[], + pending: [] as TaskSummary[], + blocked: [] as TaskSummary[], + completed: [] as TaskSummary[], + }; + + for (const task of tasks) { + switch (task.status) { + case 'running': + case 'initializing': + case 'starting': + groups.running.push(task); + break; + case 'pending': + groups.pending.push(task); + break; + case 'blocked': + case 'paused': + groups.blocked.push(task); + break; + case 'done': + case 'failed': + case 'merged': + groups.completed.push(task); + break; + } + } + + return groups; +} + +/** + * Get counts for dashboard display + */ +export function getTaskCounts(tasks: TaskSummary[]) { + const counts = { + total: tasks.length, + running: 0, + pending: 0, + blocked: 0, + completed: 0, + failed: 0, + }; + + for (const task of tasks) { + switch (task.status) { + case 'running': + case 'initializing': + case 'starting': + counts.running++; + break; + case 'pending': + counts.pending++; + break; + case 'blocked': + case 'paused': + counts.blocked++; + break; + case 'done': + case 'merged': + counts.completed++; + break; + case 'failed': + counts.failed++; + break; + } + } + + return counts; +} diff --git a/apps/mobile/hooks/useThemeColor.ts b/apps/mobile/hooks/useThemeColor.ts new file mode 100644 index 0000000..170a6d6 --- /dev/null +++ b/apps/mobile/hooks/useThemeColor.ts @@ -0,0 +1,47 @@ +import { useColorScheme } from 'react-native'; +import { Colors, type ThemeColors } from '../constants/Colors'; + +/** + * Hook to get the current theme colors + * @returns The colors for the current color scheme + */ +export function useThemeColors(): ThemeColors { + const colorScheme = useColorScheme() ?? 'dark'; + return Colors[colorScheme]; +} + +/** + * Hook to get a specific color from the theme + * @param colorName - The name of the color to retrieve + * @returns The color value for the current theme + */ +export function useThemeColor<K extends keyof ThemeColors>( + colorName: K +): ThemeColors[K] { + const colors = useThemeColors(); + return colors[colorName]; +} + +/** + * Hook to get multiple colors from the theme + * @param colorNames - Array of color names to retrieve + * @returns Object with requested color values + */ +export function useThemeColorSet<K extends keyof ThemeColors>( + colorNames: K[] +): Pick<ThemeColors, K> { + const colors = useThemeColors(); + return colorNames.reduce((acc, name) => { + acc[name] = colors[name]; + return acc; + }, {} as Pick<ThemeColors, K>); +} + +/** + * Hook to check if the current theme is dark + * @returns boolean indicating if dark mode is active + */ +export function useIsDarkMode(): boolean { + const colorScheme = useColorScheme(); + return colorScheme === 'dark'; +} |
