diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index d2c4a12..76b6fe7 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, TextInput, useColorScheme, Alert, RefreshControl } from 'react-native'; +import { View, Text, Pressable, TextInput, useColorScheme, Alert, Animated, Easing } from 'react-native'; import { useRouter } from 'expo-router'; -import { Plus, ArrowUpDown, Filter, Download, Search, X } from 'lucide-react-native'; +import { Plus, ArrowUpDown, Filter, Download, Search, X, RefreshCw } from 'lucide-react-native'; import { useTranslation } from 'react-i18next'; import * as Haptics from 'expo-haptics'; import DraggableFlatList, { RenderItemParams } from 'react-native-draggable-flatlist'; @@ -45,6 +45,7 @@ export default function InboxScreen() { const isDark = (theme === 'system' ? systemScheme : theme) === 'dark'; const isDraggingRef = useRef(false); const [refreshing, setRefreshing] = useState(false); + const spinAnim = useRef(new Animated.Value(0)).current; const { sortBy, sortOrder, filterPriority, filterTag, filterCompleted, filterDueDate, hasActiveFilters } = useTaskStore(); @@ -72,10 +73,29 @@ export default function InboxScreen() { }, [loadTasks]); const handleRefresh = useCallback(async () => { + if (refreshing) return; setRefreshing(true); - await loadTasks(); - setRefreshing(false); - }, [loadTasks]); + spinAnim.setValue(0); + Animated.loop( + Animated.timing(spinAnim, { + toValue: 1, + duration: 800, + easing: Easing.linear, + useNativeDriver: true, + }) + ).start(); + try { + await loadTasks(); + } finally { + setRefreshing(false); + spinAnim.stopAnimation(); + } + }, [loadTasks, refreshing, spinAnim]); + + const spin = spinAnim.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '360deg'], + }); const handleToggle = async (id: string) => { await toggleComplete(id); @@ -171,6 +191,11 @@ export default function InboxScreen() { ) : ( + + + + + setShowSearch(true)} className="mr-3 p-1"> @@ -208,14 +233,6 @@ export default function InboxScreen() { onDragBegin={() => { isDraggingRef.current = true; }} onDragEnd={handleDragEnd} activationDistance={canDrag ? 0 : 10000} - refreshControl={ - - } /> )} diff --git a/app/list/[id].tsx b/app/list/[id].tsx index e653816..2d06432 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, TextInput, useColorScheme, Alert, RefreshControl } from 'react-native'; +import { View, Text, Pressable, TextInput, useColorScheme, Alert, Animated, Easing } from 'react-native'; import { useRouter, useLocalSearchParams } from 'expo-router'; import { - ArrowLeft, Plus, ArrowUpDown, Filter, Download, Search, X, + ArrowLeft, Plus, ArrowUpDown, Filter, Download, Search, X, RefreshCw, List, ShoppingCart, Briefcase, Home, Heart, Star, BookOpen, GraduationCap, Dumbbell, Utensils, Plane, Music, Code, Wrench, Gift, Camera, Palette, Dog, Leaf, Zap, @@ -62,6 +62,7 @@ export default function ListDetailScreen() { const isDark = (theme === 'system' ? systemScheme : theme) === 'dark'; const isDraggingRef = useRef(false); const [refreshing, setRefreshing] = useState(false); + const spinAnim = useRef(new Animated.Value(0)).current; const { sortBy, sortOrder, filterPriority, filterTag, filterCompleted, filterDueDate, hasActiveFilters } = useTaskStore(); @@ -97,10 +98,29 @@ export default function ListDetailScreen() { }, [loadData]); const handleRefresh = useCallback(async () => { + if (refreshing) return; setRefreshing(true); - await loadData(); - setRefreshing(false); - }, [loadData]); + spinAnim.setValue(0); + Animated.loop( + Animated.timing(spinAnim, { + toValue: 1, + duration: 800, + easing: Easing.linear, + useNativeDriver: true, + }) + ).start(); + try { + await loadData(); + } finally { + setRefreshing(false); + spinAnim.stopAnimation(); + } + }, [loadData, refreshing, spinAnim]); + + const spin = spinAnim.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '360deg'], + }); const handleToggle = async (taskId: string) => { await toggleComplete(taskId); @@ -199,6 +219,11 @@ export default function ListDetailScreen() { + + + + + setShowSearch(true)} className="mr-3 p-1"> @@ -256,14 +281,6 @@ export default function ListDetailScreen() { onDragBegin={() => { isDraggingRef.current = true; }} onDragEnd={handleDragEnd} activationDistance={canDrag ? 0 : 10000} - refreshControl={ - - } /> )}