From dde33acdf2033ba31b2e3444870f067d547860ff Mon Sep 17 00:00:00 2001 From: medic-bot Date: Thu, 12 Mar 2026 19:48:14 -0400 Subject: [PATCH 1/4] fix: show all tasks in widget without date or count limits Remove the 2-week date filter from widget task query so tasks with distant due dates are included. Remove the maxItems truncation from the scrollable ListWidget so the displayed list matches the counter. Co-Authored-By: Claude Opus 4.6 --- src/services/widgetSync.ts | 8 +++----- src/widgets/TaskListWidget.tsx | 9 ++------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/services/widgetSync.ts b/src/services/widgetSync.ts index e60c623..9d0efdc 100644 --- a/src/services/widgetSync.ts +++ b/src/services/widgetSync.ts @@ -4,7 +4,7 @@ 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, sql } from 'drizzle-orm'; -import { startOfDay, endOfDay, addWeeks } from 'date-fns'; +import { startOfDay } from 'date-fns'; import { TaskListWidget } from '../widgets/TaskListWidget'; export const WIDGET_DATA_KEY = 'widget:tasks'; @@ -34,7 +34,6 @@ export async function syncWidgetData(): Promise { try { const now = new Date(); const todayStart = startOfDay(now); - const twoWeeksEnd = endOfDay(addWeeks(now, 2)); const selectFields = { id: tasks.id, @@ -48,7 +47,7 @@ export async function syncWidgetData(): Promise { subtaskDoneCount: sql`(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 + // Fetch all upcoming tasks (today and future) const upcomingTasks = await db .select(selectFields) .from(tasks) @@ -57,8 +56,7 @@ export async function syncWidgetData(): Promise { and( eq(tasks.completed, false), isNull(tasks.parentId), - gte(tasks.dueDate, todayStart), - lte(tasks.dueDate, twoWeeksEnd) + gte(tasks.dueDate, todayStart) ) ) .orderBy(asc(tasks.dueDate)); diff --git a/src/widgets/TaskListWidget.tsx b/src/widgets/TaskListWidget.tsx index ccfe348..ab9360f 100644 --- a/src/widgets/TaskListWidget.tsx +++ b/src/widgets/TaskListWidget.tsx @@ -380,17 +380,14 @@ function SmallWidget({ tasks, isDark }: { tasks: WidgetTask[]; isDark: boolean } function ListWidgetContent({ tasks, - maxItems, isDark, expandedTaskIds, }: { tasks: WidgetTask[]; - maxItems: number; isDark: boolean; expandedTaskIds: Set; }) { const c = getColors(isDark); - const displayTasks = tasks.slice(0, maxItems); return ( {/* Task list */} - {displayTasks.length > 0 ? ( + {tasks.length > 0 ? ( - {displayTasks.map((task) => ( + {tasks.map((task) => ( ; } - const maxItems = widgetName === 'SimplListeLarge' ? 8 : 4; return ( From f040ec7902ec8ee67672a7df7c8c3a34aefa74de Mon Sep 17 00:00:00 2001 From: le king fu Date: Thu, 12 Mar 2026 20:04:35 -0400 Subject: [PATCH 2/4] feat: add configurable widget display period setting (#23) Instead of removing the time filter entirely, let users choose the widget display period (1 week, 2 weeks, 4 weeks, or all tasks) from Settings. Default remains 2 weeks for backward compatibility. Co-Authored-By: Claude Opus 4.6 --- app/(tabs)/settings.tsx | 54 +++++++++++++++++++++++++++++++++- src/i18n/en.json | 6 +++- src/i18n/fr.json | 6 +++- src/services/widgetSync.ts | 33 +++++++++++++++------ src/stores/useSettingsStore.ts | 4 +++ 5 files changed, 91 insertions(+), 12 deletions(-) diff --git a/app/(tabs)/settings.tsx b/app/(tabs)/settings.tsx index beca90c..d9f63af 100644 --- a/app/(tabs)/settings.tsx +++ b/app/(tabs)/settings.tsx @@ -2,13 +2,14 @@ import { useState, useEffect, useCallback } from 'react'; import { View, Text, Pressable, useColorScheme, TextInput, ScrollView, Alert, Modal, Platform, Switch, Linking, ActivityIndicator } from 'react-native'; import { KeyboardAvoidingView } from 'react-native-keyboard-controller'; import { useTranslation } from 'react-i18next'; -import { Sun, Moon, Smartphone, Plus, Trash2, Pencil, Bell, CalendarDays, Mail, RefreshCw } from 'lucide-react-native'; +import { Sun, Moon, Smartphone, Plus, Trash2, Pencil, Bell, CalendarDays, LayoutGrid, Mail, RefreshCw } from 'lucide-react-native'; import Constants from 'expo-constants'; import { colors } from '@/src/theme/colors'; import { useSettingsStore } from '@/src/stores/useSettingsStore'; import { getAllTags, createTag, updateTag, deleteTag } from '@/src/db/repository/tags'; import { initCalendar } from '@/src/services/calendar'; +import { syncWidgetData } from '@/src/services/widgetSync'; import i18n from '@/src/i18n'; type ThemeMode = 'light' | 'dark' | 'system'; @@ -23,6 +24,7 @@ export default function SettingsScreen() { notificationsEnabled, setNotificationsEnabled, reminderOffset, setReminderOffset, calendarSyncEnabled, setCalendarSyncEnabled, + widgetPeriodWeeks, setWidgetPeriodWeeks, } = useSettingsStore(); const isDark = (theme === 'system' ? systemScheme : theme) === 'dark'; @@ -299,6 +301,56 @@ export default function SettingsScreen() { + {/* Widget Section */} + + + {t('widget.title')} + + + + + + + {t('widget.period')} + + + + {[ + { value: 1, label: t('widget.periodWeek', { count: 1 }) }, + { value: 2, label: t('widget.periodWeek', { count: 2 }) }, + { value: 4, label: t('widget.periodWeek', { count: 4 }) }, + { value: 0, label: t('widget.periodAll') }, + ].map((opt) => { + const isActive = widgetPeriodWeeks === opt.value; + return ( + { + setWidgetPeriodWeeks(opt.value); + syncWidgetData(); + }} + className={`rounded-full px-3 py-1.5 ${isActive ? 'bg-bleu' : isDark ? 'bg-[#3A3A3A]' : 'bg-[#E5E7EB]'}`} + > + + {opt.label} + + + ); + })} + + + + + {/* Tags Section */} diff --git a/src/i18n/en.json b/src/i18n/en.json index 5ecf688..b8626be 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -138,6 +138,10 @@ "overdue": "Overdue", "today": "Today", "tomorrow": "Tomorrow", - "noDate": "No date" + "noDate": "No date", + "period": "Display period", + "periodWeek_one": "{{count}} week", + "periodWeek_other": "{{count}} weeks", + "periodAll": "All" } } diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 738d9e3..f029c31 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -138,6 +138,10 @@ "overdue": "En retard", "today": "Aujourd'hui", "tomorrow": "Demain", - "noDate": "Sans date" + "noDate": "Sans date", + "period": "Période affichée", + "periodWeek_one": "{{count}} semaine", + "periodWeek_other": "{{count}} semaines", + "periodAll": "Toutes" } } diff --git a/src/services/widgetSync.ts b/src/services/widgetSync.ts index 9d0efdc..509ddd0 100644 --- a/src/services/widgetSync.ts +++ b/src/services/widgetSync.ts @@ -4,7 +4,7 @@ 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, sql } from 'drizzle-orm'; -import { startOfDay } from 'date-fns'; +import { startOfDay, endOfDay, addWeeks } from 'date-fns'; import { TaskListWidget } from '../widgets/TaskListWidget'; export const WIDGET_DATA_KEY = 'widget:tasks'; @@ -35,6 +35,19 @@ export async function syncWidgetData(): Promise { const now = new Date(); const todayStart = startOfDay(now); + // Read widget period setting from AsyncStorage (0 = all, N = N weeks ahead) + let widgetPeriodWeeks = 2; + try { + const settingsRaw = await AsyncStorage.getItem('simpl-liste-settings'); + if (settingsRaw) { + const settings = JSON.parse(settingsRaw); + const stored = settings?.state?.widgetPeriodWeeks; + if (typeof stored === 'number') widgetPeriodWeeks = stored; + } + } catch { + // Default to 2 weeks + } + const selectFields = { id: tasks.id, title: tasks.title, @@ -47,18 +60,20 @@ export async function syncWidgetData(): Promise { subtaskDoneCount: sql`(SELECT COUNT(*) FROM tasks AS sub WHERE sub.parent_id = ${tasks.id} AND sub.completed = 1)`.as('subtask_done_count'), }; - // Fetch all upcoming tasks (today and future) + // Fetch upcoming tasks (filtered by period setting, 0 = all future tasks) + const upcomingConditions = [ + eq(tasks.completed, false), + isNull(tasks.parentId), + gte(tasks.dueDate, todayStart), + ]; + if (widgetPeriodWeeks > 0) { + upcomingConditions.push(lte(tasks.dueDate, endOfDay(addWeeks(now, widgetPeriodWeeks)))); + } const upcomingTasks = await db .select(selectFields) .from(tasks) .leftJoin(lists, eq(tasks.listId, lists.id)) - .where( - and( - eq(tasks.completed, false), - isNull(tasks.parentId), - gte(tasks.dueDate, todayStart) - ) - ) + .where(and(...upcomingConditions)) .orderBy(asc(tasks.dueDate)); // Fetch overdue tasks diff --git a/src/stores/useSettingsStore.ts b/src/stores/useSettingsStore.ts index ee5963d..f22d0a2 100644 --- a/src/stores/useSettingsStore.ts +++ b/src/stores/useSettingsStore.ts @@ -10,11 +10,13 @@ interface SettingsState { notificationsEnabled: boolean; reminderOffset: number; // hours before due date (0 = at time) calendarSyncEnabled: boolean; + widgetPeriodWeeks: number; // 0 = all tasks, otherwise number of weeks ahead setTheme: (theme: ThemeMode) => void; setLocale: (locale: 'fr' | 'en') => void; setNotificationsEnabled: (enabled: boolean) => void; setReminderOffset: (offset: number) => void; setCalendarSyncEnabled: (enabled: boolean) => void; + setWidgetPeriodWeeks: (weeks: number) => void; } export const useSettingsStore = create()( @@ -25,11 +27,13 @@ export const useSettingsStore = create()( notificationsEnabled: true, reminderOffset: 0, calendarSyncEnabled: false, + widgetPeriodWeeks: 2, setTheme: (theme) => set({ theme }), setLocale: (locale) => set({ locale }), setNotificationsEnabled: (notificationsEnabled) => set({ notificationsEnabled }), setReminderOffset: (reminderOffset) => set({ reminderOffset }), setCalendarSyncEnabled: (calendarSyncEnabled) => set({ calendarSyncEnabled }), + setWidgetPeriodWeeks: (widgetPeriodWeeks) => set({ widgetPeriodWeeks }), }), { name: 'simpl-liste-settings', From 2e13528c6bacc6baf0d3bcd11282355a78d75a3f Mon Sep 17 00:00:00 2001 From: le king fu Date: Thu, 12 Mar 2026 20:10:45 -0400 Subject: [PATCH 3/4] fix: default widget period to all tasks (#23) Change default widgetPeriodWeeks from 2 to 0 (all tasks) so that the issue is resolved out of the box without requiring the user to discover the new setting. Co-Authored-By: Claude Opus 4.6 --- src/services/widgetSync.ts | 4 ++-- src/stores/useSettingsStore.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/widgetSync.ts b/src/services/widgetSync.ts index 509ddd0..c5cf4c1 100644 --- a/src/services/widgetSync.ts +++ b/src/services/widgetSync.ts @@ -36,7 +36,7 @@ export async function syncWidgetData(): Promise { const todayStart = startOfDay(now); // Read widget period setting from AsyncStorage (0 = all, N = N weeks ahead) - let widgetPeriodWeeks = 2; + let widgetPeriodWeeks = 0; try { const settingsRaw = await AsyncStorage.getItem('simpl-liste-settings'); if (settingsRaw) { @@ -45,7 +45,7 @@ export async function syncWidgetData(): Promise { if (typeof stored === 'number') widgetPeriodWeeks = stored; } } catch { - // Default to 2 weeks + // Default to all tasks } const selectFields = { diff --git a/src/stores/useSettingsStore.ts b/src/stores/useSettingsStore.ts index f22d0a2..c9998df 100644 --- a/src/stores/useSettingsStore.ts +++ b/src/stores/useSettingsStore.ts @@ -27,7 +27,7 @@ export const useSettingsStore = create()( notificationsEnabled: true, reminderOffset: 0, calendarSyncEnabled: false, - widgetPeriodWeeks: 2, + widgetPeriodWeeks: 0, setTheme: (theme) => set({ theme }), setLocale: (locale) => set({ locale }), setNotificationsEnabled: (notificationsEnabled) => set({ notificationsEnabled }), From 9a8bb13e972612636e5b27a073bf9ff39d1bbb77 Mon Sep 17 00:00:00 2001 From: le king fu Date: Thu, 12 Mar 2026 20:20:47 -0400 Subject: [PATCH 4/4] fix: cap widget task list at 30 items to prevent memory issues Add safety limit of 30 rendered tasks in the widget to avoid Android memory constraints. Also add cross-reference comment for the implicit AsyncStorage coupling with useSettingsStore. Co-Authored-By: Claude Opus 4.6 --- src/services/widgetSync.ts | 1 + src/widgets/TaskListWidget.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/services/widgetSync.ts b/src/services/widgetSync.ts index c5cf4c1..38755dd 100644 --- a/src/services/widgetSync.ts +++ b/src/services/widgetSync.ts @@ -36,6 +36,7 @@ export async function syncWidgetData(): Promise { const todayStart = startOfDay(now); // Read widget period setting from AsyncStorage (0 = all, N = N weeks ahead) + // Coupled with useSettingsStore.ts — key 'simpl-liste-settings', path state.widgetPeriodWeeks let widgetPeriodWeeks = 0; try { const settingsRaw = await AsyncStorage.getItem('simpl-liste-settings'); diff --git a/src/widgets/TaskListWidget.tsx b/src/widgets/TaskListWidget.tsx index ab9360f..c084192 100644 --- a/src/widgets/TaskListWidget.tsx +++ b/src/widgets/TaskListWidget.tsx @@ -473,7 +473,7 @@ function ListWidgetContent({ - {/* Task list */} + {/* Task list — cap at 30 items to avoid Android widget memory limits */} {tasks.length > 0 ? ( - {tasks.map((task) => ( + {tasks.slice(0, 30).map((task) => (