diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index bcdab40..53a6bd8 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useState, useCallback, useRef } from 'react'; -import { View, Text, Pressable, useColorScheme, Alert } from 'react-native'; +import { View, Text, Pressable, TextInput, useColorScheme, Alert } from 'react-native'; import { useRouter } from 'expo-router'; -import { Plus, ArrowUpDown, Filter, Download } from 'lucide-react-native'; +import { Plus, ArrowUpDown, Filter, Download, Search, X } from 'lucide-react-native'; import { useTranslation } from 'react-i18next'; import * as Haptics from 'expo-haptics'; import DraggableFlatList, { RenderItemParams } from 'react-native-draggable-flatlist'; @@ -38,6 +38,8 @@ export default function InboxScreen() { const [tasks, setTasks] = useState([]); const [showSort, setShowSort] = useState(false); const [showFilter, setShowFilter] = useState(false); + const [showSearch, setShowSearch] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); const systemScheme = useColorScheme(); const theme = useSettingsStore((s) => s.theme); const isDark = (theme === 'system' ? systemScheme : theme) === 'dark'; @@ -110,7 +112,15 @@ export default function InboxScreen() { }; const filtersActive = hasActiveFilters(); - const canDrag = sortBy === 'position'; + const isSearching = searchQuery.length > 0; + const canDrag = sortBy === 'position' && !isSearching; + + const filteredTasks = isSearching + ? tasks.filter((task) => { + const q = searchQuery.toLowerCase(); + return task.title.toLowerCase().includes(q) || (task.notes?.toLowerCase().includes(q) ?? false); + }) + : tasks; const renderItem = ({ item, drag }: RenderItemParams) => ( {/* Toolbar */} - - - - - setShowSort(true)} className="mr-3 p-1"> - - - setShowFilter(true)} className="relative p-1"> - - {filtersActive && ( - - )} - - + {showSearch ? ( + + + + { setShowSearch(false); setSearchQuery(''); }} className="p-1"> + + + + ) : ( + + setShowSearch(true)} className="mr-3 p-1"> + + + + + + setShowSort(true)} className="mr-3 p-1"> + + + setShowFilter(true)} className="relative p-1"> + + {filtersActive && ( + + )} + + + )} - {tasks.length === 0 ? ( + {filteredTasks.length === 0 ? ( - {filtersActive ? t('empty.list') : t('empty.inbox')} + {isSearching ? t('search.noResults', { query: searchQuery }) : filtersActive ? t('empty.list') : t('empty.inbox')} ) : ( item.id} contentContainerStyle={{ paddingBottom: 100 }} renderItem={renderItem} diff --git a/app/list/[id].tsx b/app/list/[id].tsx index 602d70f..6f13c70 100644 --- a/app/list/[id].tsx +++ b/app/list/[id].tsx @@ -1,8 +1,8 @@ import { useEffect, useState, useCallback, useRef } from 'react'; -import { View, Text, Pressable, useColorScheme, Alert } from 'react-native'; +import { View, Text, Pressable, TextInput, useColorScheme, Alert } from 'react-native'; import { useRouter, useLocalSearchParams } from 'expo-router'; import { - ArrowLeft, Plus, ArrowUpDown, Filter, Download, + ArrowLeft, Plus, ArrowUpDown, Filter, Download, Search, X, List, ShoppingCart, Briefcase, Home, Heart, Star, BookOpen, GraduationCap, Dumbbell, Utensils, Plane, Music, Code, Wrench, Gift, Camera, Palette, Dog, Leaf, Zap, @@ -54,6 +54,8 @@ export default function ListDetailScreen() { const [listIcon, setListIcon] = useState(null); const [showSort, setShowSort] = useState(false); const [showFilter, setShowFilter] = useState(false); + const [showSearch, setShowSearch] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); const systemScheme = useColorScheme(); const theme = useSettingsStore((s) => s.theme); const isDark = (theme === 'system' ? systemScheme : theme) === 'dark'; @@ -134,7 +136,15 @@ export default function ListDetailScreen() { }; const filtersActive = hasActiveFilters(); - const canDrag = sortBy === 'position'; + const isSearching = searchQuery.length > 0; + const canDrag = sortBy === 'position' && !isSearching; + + const filteredTasks = isSearching + ? tasks.filter((task) => { + const q = searchQuery.toLowerCase(); + return task.title.toLowerCase().includes(q) || (task.notes?.toLowerCase().includes(q) ?? false); + }) + : tasks; const renderItem = ({ item, drag }: RenderItemParams) => ( + setShowSearch(true)} className="mr-3 p-1"> + + @@ -196,19 +209,39 @@ export default function ListDetailScreen() { - {tasks.length === 0 ? ( + {/* Search bar */} + {showSearch && ( + + + + { setShowSearch(false); setSearchQuery(''); }} className="p-1"> + + + + )} + + {filteredTasks.length === 0 ? ( - {t('empty.list')} + {isSearching ? t('search.noResults', { query: searchQuery }) : t('empty.list')} ) : ( item.id} contentContainerStyle={{ paddingBottom: 100 }} renderItem={renderItem} diff --git a/src/i18n/en.json b/src/i18n/en.json index ed0baff..97df818 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -116,6 +116,10 @@ "success": "File exported", "noTasks": "No tasks to export" }, + "search": { + "placeholder": "Search tasks...", + "noResults": "No results for \"{{query}}\"" + }, "empty": { "inbox": "No tasks yet.\nTap + to get started.", "list": "This list is empty." diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 68ddf54..2d2e675 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -116,6 +116,10 @@ "success": "Fichier exporté", "noTasks": "Aucune tâche à exporter" }, + "search": { + "placeholder": "Rechercher des tâches...", + "noResults": "Aucun résultat pour « {{query}} »" + }, "empty": { "inbox": "Aucune tâche.\nAppuyez sur + pour commencer.", "list": "Cette liste est vide."