feat: show subtask progress in Android widget (#9)
- Add subtaskCount/subtaskDoneCount to WidgetTask with correlated SQL subqueries - Display subtask indicator (✓ done/total) below task title in widget rows - Update type guard for new fields in headless handler Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
72eafbd9d9
commit
f61ce64b50
3 changed files with 22 additions and 3 deletions
|
|
@ -3,7 +3,7 @@ import { requestWidgetUpdate } from 'react-native-android-widget';
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { db } from '../db/client';
|
||||
import { tasks, lists } from '../db/schema';
|
||||
import { eq, and, isNull, gte, lte, lt, asc } from 'drizzle-orm';
|
||||
import { eq, and, isNull, gte, lte, lt, asc, sql } from 'drizzle-orm';
|
||||
import { startOfDay, endOfDay, addWeeks } from 'date-fns';
|
||||
import { TaskListWidget } from '../widgets/TaskListWidget';
|
||||
|
||||
|
|
@ -17,6 +17,8 @@ export interface WidgetTask {
|
|||
dueDate: string | null;
|
||||
completed: boolean;
|
||||
listColor: string | null;
|
||||
subtaskCount: number;
|
||||
subtaskDoneCount: number;
|
||||
}
|
||||
|
||||
export async function syncWidgetData(): Promise<void> {
|
||||
|
|
@ -35,6 +37,8 @@ export async function syncWidgetData(): Promise<void> {
|
|||
completed: tasks.completed,
|
||||
position: tasks.position,
|
||||
listColor: lists.color,
|
||||
subtaskCount: sql<number>`(SELECT COUNT(*) FROM tasks AS sub WHERE sub.parent_id = ${tasks.id})`.as('subtask_count'),
|
||||
subtaskDoneCount: sql<number>`(SELECT COUNT(*) FROM tasks AS sub WHERE sub.parent_id = ${tasks.id} AND sub.completed = 1)`.as('subtask_done_count'),
|
||||
};
|
||||
|
||||
// Fetch tasks with due date in the next 2 weeks
|
||||
|
|
@ -87,6 +91,8 @@ export async function syncWidgetData(): Promise<void> {
|
|||
dueDate: t.dueDate ? new Date(t.dueDate).toISOString() : null,
|
||||
completed: t.completed,
|
||||
listColor: t.listColor,
|
||||
subtaskCount: t.subtaskCount ?? 0,
|
||||
subtaskDoneCount: t.subtaskDoneCount ?? 0,
|
||||
});
|
||||
|
||||
// Combine: overdue first, then upcoming, then no date
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ function TaskItemRow({ task, isDark }: { task: WidgetTask; isDark: boolean }) {
|
|||
}}
|
||||
/>
|
||||
) : null}
|
||||
<FlexWidget style={{ flex: 1 }}>
|
||||
<FlexWidget style={{ flex: 1, flexDirection: 'column' }}>
|
||||
<TextWidget
|
||||
text={task.title}
|
||||
maxLines={1}
|
||||
|
|
@ -159,6 +159,17 @@ function TaskItemRow({ task, isDark }: { task: WidgetTask; isDark: boolean }) {
|
|||
color: c.text,
|
||||
}}
|
||||
/>
|
||||
{(task.subtaskCount ?? 0) > 0 ? (
|
||||
<TextWidget
|
||||
text={`✓ ${task.subtaskDoneCount ?? 0}/${task.subtaskCount}`}
|
||||
style={{
|
||||
fontSize: 11,
|
||||
fontFamily: FONT_REGULAR,
|
||||
color: task.subtaskDoneCount === task.subtaskCount ? TODAY_COLOR : c.textSecondary,
|
||||
marginTop: 1,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</FlexWidget>
|
||||
</FlexWidget>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ function isWidgetTask(item: unknown): item is WidgetTask {
|
|||
typeof obj.priority === 'number' &&
|
||||
typeof obj.completed === 'boolean' &&
|
||||
(obj.dueDate === null || typeof obj.dueDate === 'string') &&
|
||||
(obj.listColor === null || obj.listColor === undefined || typeof obj.listColor === 'string')
|
||||
(obj.listColor === null || obj.listColor === undefined || typeof obj.listColor === 'string') &&
|
||||
(obj.subtaskCount === undefined || typeof obj.subtaskCount === 'number') &&
|
||||
(obj.subtaskDoneCount === undefined || typeof obj.subtaskDoneCount === 'number')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue