Refactor components and enhance background task management
- Updated font sizes and weights in BasalMetabolismCard, MoodCard, HealthDataCard, and NutritionRadarCard for improved readability. - Removed loading state from MoodCard to simplify the component. - Adjusted styles in WeightHistoryCard for better layout and spacing. - Integrated expo-background-fetch for improved background task handling. - Updated Info.plist to include background fetch capability. - Enhanced background task registration and execution logic in backgroundTaskManager. - Added debug function to manually trigger background task execution for testing purposes.
This commit is contained in:
10
app.json
10
app.json
@@ -18,7 +18,8 @@
|
|||||||
"NSPhotoLibraryUsageDescription": "应用需要访问相册以选择您的体态照片用于AI测评。",
|
"NSPhotoLibraryUsageDescription": "应用需要访问相册以选择您的体态照片用于AI测评。",
|
||||||
"NSPhotoLibraryAddUsageDescription": "应用需要写入相册以保存拍摄的体态照片(可选)。",
|
"NSPhotoLibraryAddUsageDescription": "应用需要写入相册以保存拍摄的体态照片(可选)。",
|
||||||
"UIBackgroundModes": [
|
"UIBackgroundModes": [
|
||||||
"processing"
|
"background-fetch",
|
||||||
|
"background-processing"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -69,7 +70,12 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"expo-background-task"
|
[
|
||||||
|
"expo-background-fetch",
|
||||||
|
{
|
||||||
|
"minimumInterval": 15
|
||||||
|
}
|
||||||
|
]
|
||||||
],
|
],
|
||||||
"experiments": {
|
"experiments": {
|
||||||
"typedRoutes": true
|
"typedRoutes": true
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { useFocusEffect } from '@react-navigation/native';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import * as Haptics from 'expo-haptics';
|
import * as Haptics from 'expo-haptics';
|
||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
|
import Lottie from 'lottie-react-native';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { Alert, FlatList, Image, RefreshControl, SafeAreaView, StatusBar, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
import { Alert, FlatList, Image, RefreshControl, SafeAreaView, StatusBar, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||||
|
|
||||||
@@ -384,11 +385,12 @@ export default function GoalsScreen() {
|
|||||||
borderBottomLeftRadius: 24,
|
borderBottomLeftRadius: 24,
|
||||||
borderBottomRightRadius: 24,
|
borderBottomRightRadius: 24,
|
||||||
}}>
|
}}>
|
||||||
{/* 右下角图片 */}
|
{/* 右下角Lottie动画 */}
|
||||||
<Image
|
<Lottie
|
||||||
source={require('@/assets/images/task/imageTodo.png')}
|
source={require('@/assets/lottie/Goal.json')}
|
||||||
style={styles.bottomRightImage}
|
style={styles.bottomRightImage}
|
||||||
resizeMode="contain"
|
autoPlay
|
||||||
|
loop
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -716,10 +718,10 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
bottomRightImage: {
|
bottomRightImage: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 56,
|
top: 40,
|
||||||
right: 36,
|
right: 36,
|
||||||
width: 80,
|
width: 120,
|
||||||
height: 80,
|
height: 120,
|
||||||
},
|
},
|
||||||
// 任务进度卡片中的按钮样式
|
// 任务进度卡片中的按钮样式
|
||||||
cardHeaderButtons: {
|
cardHeaderButtons: {
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ export default function ExploreScreen() {
|
|||||||
});
|
});
|
||||||
}, [userProfile]);
|
}, [userProfile]);
|
||||||
|
|
||||||
const { registerTask } = useBackgroundTasks();
|
const { registerTask, isInitialized } = useBackgroundTasks();
|
||||||
// 心情相关状态
|
// 心情相关状态
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [isMoodLoading, setIsMoodLoading] = useState(false);
|
const [isMoodLoading, setIsMoodLoading] = useState(false);
|
||||||
@@ -416,27 +416,34 @@ export default function ExploreScreen() {
|
|||||||
}, [loadAllData, currentSelectedDate]);
|
}, [loadAllData, currentSelectedDate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 注册后台任务 - 只处理健康数据和压力检查
|
// 只有在后台任务管理器初始化完成后才注册任务
|
||||||
registerTask({
|
if (isInitialized) {
|
||||||
id: 'health-data-task',
|
console.log('后台任务管理器已初始化,开始注册健康数据任务...');
|
||||||
name: 'health-data-task',
|
registerTask({
|
||||||
handler: async () => {
|
id: 'health-data-task',
|
||||||
try {
|
name: 'health-data-task',
|
||||||
console.log('后台任务:更新健康数据和检查压力水平...');
|
handler: async () => {
|
||||||
// 后台任务只更新健康数据,强制刷新以获取最新数据
|
try {
|
||||||
await loadHealthData(undefined, true);
|
console.log('后台任务:更新健康数据和检查压力水平...');
|
||||||
|
// 后台任务只更新健康数据,强制刷新以获取最新数据
|
||||||
|
await loadHealthData(undefined, true);
|
||||||
|
|
||||||
// 执行压力检查
|
// 执行压力检查
|
||||||
await checkStressLevelAndNotify();
|
await checkStressLevelAndNotify();
|
||||||
|
|
||||||
// 执行喝水目标检查
|
// 执行喝水目标检查
|
||||||
await checkWaterGoalAndNotify();
|
await checkWaterGoalAndNotify();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('健康数据任务执行失败:', error);
|
console.error('健康数据任务执行失败:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
}).then(() => {
|
||||||
}, []);
|
console.log('健康数据任务注册成功');
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error('健康数据任务注册失败:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isInitialized]);
|
||||||
|
|
||||||
// 检查压力水平并发送通知
|
// 检查压力水平并发送通知
|
||||||
const checkStressLevelAndNotify = React.useCallback(async () => {
|
const checkStressLevelAndNotify = React.useCallback(async () => {
|
||||||
@@ -599,16 +606,18 @@ export default function ExploreScreen() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<WeightHistoryCard />
|
|
||||||
|
|
||||||
{/* 日期选择器 */}
|
{/* 日期选择器 */}
|
||||||
<DateSelector
|
<DateSelector
|
||||||
selectedIndex={selectedIndex}
|
selectedIndex={selectedIndex}
|
||||||
onDateSelect={onSelectDate}
|
onDateSelect={onSelectDate}
|
||||||
showMonthTitle={true}
|
showMonthTitle={false}
|
||||||
disableFutureDates={true}
|
disableFutureDates={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* 营养摄入雷达图卡片 */}
|
{/* 营养摄入雷达图卡片 */}
|
||||||
<NutritionRadarCard
|
<NutritionRadarCard
|
||||||
nutritionSummary={nutritionSummary}
|
nutritionSummary={nutritionSummary}
|
||||||
@@ -624,6 +633,8 @@ export default function ExploreScreen() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<WeightHistoryCard />
|
||||||
|
|
||||||
{/* 真正瀑布流布局 */}
|
{/* 真正瀑布流布局 */}
|
||||||
<View style={styles.masonryContainer}>
|
<View style={styles.masonryContainer}>
|
||||||
{/* 左列 */}
|
{/* 左列 */}
|
||||||
@@ -768,7 +779,7 @@ const styles = StyleSheet.create({
|
|||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
},
|
},
|
||||||
headerContainer: {
|
headerContainer: {
|
||||||
marginBottom: 20,
|
marginBottom: 10,
|
||||||
},
|
},
|
||||||
headerContent: {
|
headerContent: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -1002,8 +1013,8 @@ const styles = StyleSheet.create({
|
|||||||
masonryContainer: {
|
masonryContainer: {
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
gap: 12,
|
gap: 16,
|
||||||
marginTop: 16,
|
marginTop: 6,
|
||||||
},
|
},
|
||||||
masonryColumn: {
|
masonryColumn: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -1023,6 +1034,7 @@ const styles = StyleSheet.create({
|
|||||||
elevation: 6,
|
elevation: 6,
|
||||||
minHeight: 100,
|
minHeight: 100,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
marginTop: 6
|
||||||
},
|
},
|
||||||
basalMetabolismCardOverride: {
|
basalMetabolismCardOverride: {
|
||||||
margin: -16, // 抵消 masonryCard 的 padding
|
margin: -16, // 抵消 masonryCard 的 padding
|
||||||
|
|||||||
@@ -527,7 +527,7 @@ export default function FoodLibraryScreen() {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#F8F9FA',
|
backgroundColor: Colors.light.pageBackgroundEmphasis,
|
||||||
},
|
},
|
||||||
customButton: {
|
customButton: {
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
|
|||||||
1
assets/lottie/Goal.json
Normal file
1
assets/lottie/Goal.json
Normal file
File diff suppressed because one or more lines are too long
@@ -133,14 +133,13 @@ const styles = StyleSheet.create({
|
|||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
fontSize: 24,
|
fontSize: 16,
|
||||||
fontWeight: '800',
|
fontWeight: '600',
|
||||||
color: '#0F172A',
|
color: '#0F172A',
|
||||||
lineHeight: 28,
|
lineHeight: 28,
|
||||||
},
|
},
|
||||||
unit: {
|
unit: {
|
||||||
fontSize: 14,
|
fontSize: 12,
|
||||||
fontWeight: '500',
|
|
||||||
color: '#64748B',
|
color: '#64748B',
|
||||||
marginLeft: 6,
|
marginLeft: 6,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ export const DateSelector: React.FC<DateSelectorProps> = ({
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
paddingVertical: 8,
|
|
||||||
},
|
},
|
||||||
monthTitle: {
|
monthTitle: {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface MoodCardProps {
|
|||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MoodCard({ moodCheckin, onPress, isLoading = false }: MoodCardProps) {
|
export function MoodCard({ moodCheckin, onPress }: MoodCardProps) {
|
||||||
const moodConfig = moodCheckin ? getMoodConfig(moodCheckin.moodType) : null;
|
const moodConfig = moodCheckin ? getMoodConfig(moodCheckin.moodType) : null;
|
||||||
const animationRef = useRef<LottieView>(null);
|
const animationRef = useRef<LottieView>(null);
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ export function MoodCard({ moodCheckin, onPress, isLoading = false }: MoodCardPr
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity onPress={onPress} style={styles.moodCardContent} disabled={isLoading}>
|
<TouchableOpacity onPress={onPress} style={styles.moodCardContent} >
|
||||||
<View style={styles.moodCardHeader}>
|
<View style={styles.moodCardHeader}>
|
||||||
<Text style={styles.cardTitle}>心情</Text>
|
<Text style={styles.cardTitle}>心情</Text>
|
||||||
<LottieView
|
<LottieView
|
||||||
@@ -32,11 +32,7 @@ export function MoodCard({ moodCheckin, onPress, isLoading = false }: MoodCardPr
|
|||||||
style={styles.lottieAnimation}
|
style={styles.lottieAnimation}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
{isLoading ? (
|
{moodCheckin ? (
|
||||||
<View style={styles.moodPreview}>
|
|
||||||
<Text style={styles.moodLoadingText}>加载中...</Text>
|
|
||||||
</View>
|
|
||||||
) : moodCheckin ? (
|
|
||||||
<View style={styles.moodPreview}>
|
<View style={styles.moodPreview}>
|
||||||
<Text style={styles.moodPreviewText}>
|
<Text style={styles.moodPreviewText}>
|
||||||
{moodConfig?.label || moodCheckin.moodType}
|
{moodConfig?.label || moodCheckin.moodType}
|
||||||
@@ -69,8 +65,8 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
|
|
||||||
lottieAnimation: {
|
lottieAnimation: {
|
||||||
width: 40,
|
width: 30,
|
||||||
height: 40,
|
height: 30,
|
||||||
},
|
},
|
||||||
|
|
||||||
moodPreview: {
|
moodPreview: {
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ const styles = StyleSheet.create({
|
|||||||
shadowOpacity: 0.1,
|
shadowOpacity: 0.1,
|
||||||
shadowRadius: 3.84,
|
shadowRadius: 3.84,
|
||||||
elevation: 5,
|
elevation: 5,
|
||||||
|
marginTop: 12
|
||||||
},
|
},
|
||||||
cardHeader: {
|
cardHeader: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -300,7 +301,7 @@ const styles = StyleSheet.create({
|
|||||||
gap: 4,
|
gap: 4,
|
||||||
},
|
},
|
||||||
mainValue: {
|
mainValue: {
|
||||||
fontSize: 12,
|
fontSize: 14,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: '#192126',
|
color: '#192126',
|
||||||
},
|
},
|
||||||
@@ -320,7 +321,7 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
calculationValue: {
|
calculationValue: {
|
||||||
fontSize: 9,
|
fontSize: 11,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: '#192126',
|
color: '#192126',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -61,12 +61,12 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'flex-end',
|
alignItems: 'flex-end',
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
fontSize: 20,
|
fontSize: 16,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: '#192126',
|
color: '#192126',
|
||||||
},
|
},
|
||||||
unit: {
|
unit: {
|
||||||
fontSize: 14,
|
fontSize: 12,
|
||||||
color: '#666',
|
color: '#666',
|
||||||
marginLeft: 4,
|
marginLeft: 4,
|
||||||
marginBottom: 2,
|
marginBottom: 2,
|
||||||
|
|||||||
@@ -195,19 +195,6 @@ export function WeightHistoryCard() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// 如果正在加载,显示加载状态
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}>
|
|
||||||
<View style={styles.cardHeader}>
|
|
||||||
<Text style={styles.cardTitle}>体重记录</Text>
|
|
||||||
</View>
|
|
||||||
<View style={styles.emptyContent}>
|
|
||||||
<Text style={styles.emptyDescription}>加载中...</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有体重数据,显示引导卡片
|
// 如果没有体重数据,显示引导卡片
|
||||||
if (!hasWeight) {
|
if (!hasWeight) {
|
||||||
@@ -574,6 +561,7 @@ const styles = StyleSheet.create({
|
|||||||
shadowOpacity: 0.1,
|
shadowOpacity: 0.1,
|
||||||
shadowRadius: 8,
|
shadowRadius: 8,
|
||||||
elevation: 3,
|
elevation: 3,
|
||||||
|
marginTop: 16
|
||||||
},
|
},
|
||||||
cardHeader: {
|
cardHeader: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -649,6 +637,7 @@ const styles = StyleSheet.create({
|
|||||||
summaryInfo: {
|
summaryInfo: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
marginTop: 10,
|
||||||
},
|
},
|
||||||
chartContainer: {
|
chartContainer: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { BackgroundTaskType as BackgroundTask, backgroundTaskManager, TaskStatusType as TaskStatus } from '@/services/backgroundTaskManager';
|
import { BackgroundTaskType as BackgroundTask, backgroundTaskManager, TaskStatusType as TaskStatus } from '@/services/backgroundTaskManager';
|
||||||
|
import * as BackgroundFetch from 'expo-background-fetch';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
export interface UseBackgroundTasksReturn {
|
export interface UseBackgroundTasksReturn {
|
||||||
@@ -16,7 +17,7 @@ export interface UseBackgroundTasksReturn {
|
|||||||
cleanupTaskStatuses: () => Promise<void>;
|
cleanupTaskStatuses: () => Promise<void>;
|
||||||
|
|
||||||
// 后台任务状态
|
// 后台任务状态
|
||||||
backgroundTaskStatus: string | null;
|
backgroundTaskStatus: BackgroundFetch.BackgroundFetchStatus | null;
|
||||||
getBackgroundTaskStatus: () => Promise<void>;
|
getBackgroundTaskStatus: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ export const useBackgroundTasks = (): UseBackgroundTasksReturn => {
|
|||||||
const [isInitialized, setIsInitialized] = useState(false);
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
const [taskStatuses, setTaskStatuses] = useState<TaskStatus[]>([]);
|
const [taskStatuses, setTaskStatuses] = useState<TaskStatus[]>([]);
|
||||||
const [registeredTasks, setRegisteredTasks] = useState<BackgroundTask[]>([]);
|
const [registeredTasks, setRegisteredTasks] = useState<BackgroundTask[]>([]);
|
||||||
const [backgroundTaskStatus, setBackgroundTaskStatus] = useState<string | null>(null);
|
const [backgroundTaskStatus, setBackgroundTaskStatus] = useState<BackgroundFetch.BackgroundFetchStatus | null>(null);
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -87,7 +88,7 @@ export const useBackgroundTasks = (): UseBackgroundTasksReturn => {
|
|||||||
const getBackgroundTaskStatus = useCallback(async () => {
|
const getBackgroundTaskStatus = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const status = await backgroundTaskManager.getBackgroundTaskStatus();
|
const status = await backgroundTaskManager.getBackgroundTaskStatus();
|
||||||
setBackgroundTaskStatus(status ? status.toString() : null);
|
setBackgroundTaskStatus(status);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取后台任务状态失败:', error);
|
console.error('获取后台任务状态失败:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ PODS:
|
|||||||
- ExpoModulesCore
|
- ExpoModulesCore
|
||||||
- ExpoAsset (11.1.7):
|
- ExpoAsset (11.1.7):
|
||||||
- ExpoModulesCore
|
- ExpoModulesCore
|
||||||
|
- ExpoBackgroundFetch (13.1.6):
|
||||||
|
- ExpoModulesCore
|
||||||
- ExpoBackgroundTask (0.2.8):
|
- ExpoBackgroundTask (0.2.8):
|
||||||
- ExpoModulesCore
|
- ExpoModulesCore
|
||||||
- ExpoBlur (14.1.5):
|
- ExpoBlur (14.1.5):
|
||||||
@@ -1994,6 +1996,7 @@ DEPENDENCIES:
|
|||||||
- Expo (from `../node_modules/expo`)
|
- Expo (from `../node_modules/expo`)
|
||||||
- ExpoAppleAuthentication (from `../node_modules/expo-apple-authentication/ios`)
|
- ExpoAppleAuthentication (from `../node_modules/expo-apple-authentication/ios`)
|
||||||
- ExpoAsset (from `../node_modules/expo-asset/ios`)
|
- ExpoAsset (from `../node_modules/expo-asset/ios`)
|
||||||
|
- ExpoBackgroundFetch (from `../node_modules/expo-background-fetch/ios`)
|
||||||
- ExpoBackgroundTask (from `../node_modules/expo-background-task/ios`)
|
- ExpoBackgroundTask (from `../node_modules/expo-background-task/ios`)
|
||||||
- ExpoBlur (from `../node_modules/expo-blur/ios`)
|
- ExpoBlur (from `../node_modules/expo-blur/ios`)
|
||||||
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
|
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
|
||||||
@@ -2136,6 +2139,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/expo-apple-authentication/ios"
|
:path: "../node_modules/expo-apple-authentication/ios"
|
||||||
ExpoAsset:
|
ExpoAsset:
|
||||||
:path: "../node_modules/expo-asset/ios"
|
:path: "../node_modules/expo-asset/ios"
|
||||||
|
ExpoBackgroundFetch:
|
||||||
|
:path: "../node_modules/expo-background-fetch/ios"
|
||||||
ExpoBackgroundTask:
|
ExpoBackgroundTask:
|
||||||
:path: "../node_modules/expo-background-task/ios"
|
:path: "../node_modules/expo-background-task/ios"
|
||||||
ExpoBlur:
|
ExpoBlur:
|
||||||
@@ -2351,6 +2356,7 @@ SPEC CHECKSUMS:
|
|||||||
Expo: 8685113c16058e8b3eb101dd52d6c8bca260bbea
|
Expo: 8685113c16058e8b3eb101dd52d6c8bca260bbea
|
||||||
ExpoAppleAuthentication: 8a661b6f4936affafd830f983ac22463c936dad5
|
ExpoAppleAuthentication: 8a661b6f4936affafd830f983ac22463c936dad5
|
||||||
ExpoAsset: ef06e880126c375f580d4923fdd1cdf4ee6ee7d6
|
ExpoAsset: ef06e880126c375f580d4923fdd1cdf4ee6ee7d6
|
||||||
|
ExpoBackgroundFetch: 6dcade705c90ae5b7e2d0836b9145cae8f5f3070
|
||||||
ExpoBackgroundTask: 6c1990438e45b5c4bbbc7d75aa6b688d53602fe8
|
ExpoBackgroundTask: 6c1990438e45b5c4bbbc7d75aa6b688d53602fe8
|
||||||
ExpoBlur: 3c8885b9bf9eef4309041ec87adec48b5f1986a9
|
ExpoBlur: 3c8885b9bf9eef4309041ec87adec48b5f1986a9
|
||||||
ExpoFileSystem: 7f92f7be2f5c5ed40a7c9efc8fa30821181d9d63
|
ExpoFileSystem: 7f92f7be2f5c5ed40a7c9efc8fa30821181d9d63
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>processing</string>
|
<string>processing</string>
|
||||||
|
<string>fetch</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>SplashScreen</string>
|
<string>SplashScreen</string>
|
||||||
|
|||||||
13
package-lock.json
generated
13
package-lock.json
generated
@@ -23,6 +23,7 @@
|
|||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"expo": "~53.0.20",
|
"expo": "~53.0.20",
|
||||||
"expo-apple-authentication": "6.4.2",
|
"expo-apple-authentication": "6.4.2",
|
||||||
|
"expo-background-fetch": "^13.1.6",
|
||||||
"expo-background-task": "~0.2.8",
|
"expo-background-task": "~0.2.8",
|
||||||
"expo-blur": "~14.1.5",
|
"expo-blur": "~14.1.5",
|
||||||
"expo-constants": "~17.1.7",
|
"expo-constants": "~17.1.7",
|
||||||
@@ -7073,6 +7074,18 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expo-background-fetch": {
|
||||||
|
"version": "13.1.6",
|
||||||
|
"resolved": "https://mirrors.tencent.com/npm/expo-background-fetch/-/expo-background-fetch-13.1.6.tgz",
|
||||||
|
"integrity": "sha512-hl4kR32DaxoHFYqNsILLZG2mWssCkUb4wnEAHtDGmpxUP4SCnJILcAn99J6AGDFUw5lF6FXNZZCXNfcrFioO4Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"expo-task-manager": "~13.1.6"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"expo": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/expo-background-task": {
|
"node_modules/expo-background-task": {
|
||||||
"version": "0.2.8",
|
"version": "0.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/expo-background-task/-/expo-background-task-0.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/expo-background-task/-/expo-background-task-0.2.8.tgz",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"expo": "~53.0.20",
|
"expo": "~53.0.20",
|
||||||
"expo-apple-authentication": "6.4.2",
|
"expo-apple-authentication": "6.4.2",
|
||||||
|
"expo-background-fetch": "^13.1.6",
|
||||||
"expo-background-task": "~0.2.8",
|
"expo-background-task": "~0.2.8",
|
||||||
"expo-blur": "~14.1.5",
|
"expo-blur": "~14.1.5",
|
||||||
"expo-constants": "~17.1.7",
|
"expo-constants": "~17.1.7",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import * as ExpoBackgroundTask from 'expo-background-task';
|
import * as BackgroundFetch from 'expo-background-fetch';
|
||||||
import * as TaskManager from 'expo-task-manager';
|
import * as TaskManager from 'expo-task-manager';
|
||||||
|
|
||||||
// 任务类型定义
|
// 任务类型定义
|
||||||
@@ -63,13 +63,17 @@ class BackgroundTaskManager {
|
|||||||
|
|
||||||
// 注册后台任务
|
// 注册后台任务
|
||||||
private async registerBackgroundTask(): Promise<void> {
|
private async registerBackgroundTask(): Promise<void> {
|
||||||
const BACKGROUND_TASK = 'background-task';
|
const BACKGROUND_FETCH_TASK = 'background-fetch-task';
|
||||||
|
|
||||||
|
console.log('注册后台获取任务');
|
||||||
|
|
||||||
|
// 定义后台获取任务
|
||||||
|
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
|
||||||
|
console.log('后台获取任务被系统调用');
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
console.log('注册后台任务');
|
|
||||||
// 定义后台任务
|
|
||||||
TaskManager.defineTask(BACKGROUND_TASK, async () => {
|
|
||||||
try {
|
try {
|
||||||
console.log('开始执行后台任务');
|
console.log(`开始执行后台任务 - ${now.toISOString()}`);
|
||||||
|
|
||||||
// 执行所有注册的任务
|
// 执行所有注册的任务
|
||||||
const results = await this.executeAllTasks();
|
const results = await this.executeAllTasks();
|
||||||
@@ -77,19 +81,29 @@ class BackgroundTaskManager {
|
|||||||
console.log('后台任务执行完成:', results);
|
console.log('后台任务执行完成:', results);
|
||||||
|
|
||||||
// 返回成功状态
|
// 返回成功状态
|
||||||
return ExpoBackgroundTask.BackgroundTaskResult.Success;
|
return BackgroundFetch.BackgroundFetchResult.NewData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('后台任务执行失败:', error);
|
console.error('后台任务执行失败:', error);
|
||||||
return ExpoBackgroundTask.BackgroundTaskResult.Failed;
|
return BackgroundFetch.BackgroundFetchResult.Failed;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 注册后台任务
|
// 注册后台获取任务
|
||||||
await ExpoBackgroundTask.registerTaskAsync(BACKGROUND_TASK, {
|
try {
|
||||||
minimumInterval: 15, // 最小间隔60分钟
|
const status = await BackgroundFetch.getStatusAsync();
|
||||||
});
|
if (status === BackgroundFetch.BackgroundFetchStatus.Available) {
|
||||||
|
await BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
|
||||||
console.log('后台任务注册成功');
|
minimumInterval: 15 * 60, // 15分钟(以秒为单位)
|
||||||
|
stopOnTerminate: false,
|
||||||
|
startOnBoot: true,
|
||||||
|
});
|
||||||
|
console.log('后台获取任务注册成功');
|
||||||
|
} else {
|
||||||
|
console.warn('后台获取不可用,状态:', status);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('注册后台获取任务失败:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册自定义任务
|
// 注册自定义任务
|
||||||
@@ -212,8 +226,8 @@ class BackgroundTaskManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查后台任务状态
|
// 检查后台任务状态
|
||||||
public async getBackgroundTaskStatus(): Promise<ExpoBackgroundTask.BackgroundTaskStatus | null> {
|
public async getBackgroundTaskStatus(): Promise<BackgroundFetch.BackgroundFetchStatus | null> {
|
||||||
return await ExpoBackgroundTask.getStatusAsync();
|
return await BackgroundFetch.getStatusAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存任务状态到本地存储
|
// 保存任务状态到本地存储
|
||||||
@@ -254,6 +268,42 @@ class BackgroundTaskManager {
|
|||||||
|
|
||||||
await this.saveTaskStatuses();
|
await this.saveTaskStatuses();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 调试函数:强制触发后台任务执行
|
||||||
|
public async debugExecuteBackgroundTask(): Promise<void> {
|
||||||
|
console.log('=== 调试:手动触发后台任务执行 ===');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取后台任务状态
|
||||||
|
const status = await this.getBackgroundTaskStatus();
|
||||||
|
console.log('后台获取状态:', status);
|
||||||
|
|
||||||
|
// 执行所有注册的任务
|
||||||
|
console.log('当前注册的任务数量:', this.tasks.size);
|
||||||
|
this.tasks.forEach((task, id) => {
|
||||||
|
console.log(`- 任务ID: ${id}, 名称: ${task.name}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await this.executeAllTasks();
|
||||||
|
console.log('任务执行结果:', results);
|
||||||
|
|
||||||
|
// 显示任务状态
|
||||||
|
const taskStatuses = this.getAllTaskStatuses();
|
||||||
|
taskStatuses.forEach(status => {
|
||||||
|
console.log(`任务 ${status.id} 状态:`, {
|
||||||
|
isRegistered: status.isRegistered,
|
||||||
|
executionCount: status.executionCount,
|
||||||
|
lastExecution: status.lastExecution?.toISOString(),
|
||||||
|
lastError: status.lastError
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('调试执行失败:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('=== 调试执行完成 ===');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出单例实例
|
// 导出单例实例
|
||||||
|
|||||||
Reference in New Issue
Block a user