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={
-
- }
/>
)}