From f040ec7902ec8ee67672a7df7c8c3a34aefa74de Mon Sep 17 00:00:00 2001 From: le king fu Date: Thu, 12 Mar 2026 20:04:35 -0400 Subject: [PATCH] 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',