diff --git a/app/(tabs)/challenges.tsx b/app/(tabs)/challenges.tsx
index 5fe88a4..f338b2b 100644
--- a/app/(tabs)/challenges.tsx
+++ b/app/(tabs)/challenges.tsx
@@ -1,5 +1,4 @@
import ChallengeProgressCard from '@/components/challenges/ChallengeProgressCard';
-import { IconSymbol } from '@/components/ui/IconSymbol';
import { Colors } from '@/constants/Colors';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useColorScheme } from '@/hooks/useColorScheme';
@@ -127,7 +126,7 @@ export default function ChallengesScreen() {
挑战
参与精选活动,保持每日动力
-
+ {/*
-
+ */}
{ongoingChallenge ? (
diff --git a/app/challenges/[id].tsx b/app/challenges/[id].tsx
index c679fd7..ebbf660 100644
--- a/app/challenges/[id].tsx
+++ b/app/challenges/[id].tsx
@@ -139,6 +139,7 @@ export default function ChallengeDetailScreen() {
const progress = challenge?.progress;
const rankingData = useMemo(() => challenge?.rankings ?? [], [challenge?.rankings]);
+
const participantAvatars = useMemo(
() => rankingData.filter((item) => item.avatar).map((item) => item.avatar as string).slice(0, 6),
[rankingData],
@@ -296,11 +297,11 @@ export default function ChallengeDetailScreen() {
tone="light"
transparent
withSafeTop={false}
- right={
-
-
-
- }
+ // right={
+ //
+ //
+ //
+ // }
/>
diff --git a/services/backgroundTaskManager.ts b/services/backgroundTaskManager.ts
index 9bad20c..fa0a3a5 100644
--- a/services/backgroundTaskManager.ts
+++ b/services/backgroundTaskManager.ts
@@ -1,7 +1,8 @@
import { store } from '@/store';
import AsyncStorage from '@/utils/kvStore';
import { log } from '@/utils/logger';
-import { StandReminderHelpers, WaterNotificationHelpers } from '@/utils/notificationHelpers';
+import { listChallenges } from '@/services/challengesApi';
+import { ChallengeNotificationHelpers, StandReminderHelpers, WaterNotificationHelpers } from '@/utils/notificationHelpers';
import * as BackgroundTask from 'expo-background-task';
import * as TaskManager from 'expo-task-manager';
import { TaskManagerTaskBody } from 'expo-task-manager';
@@ -99,6 +100,55 @@ async function executeStandReminderTask(): Promise {
}
}
+async function executeChallengeReminderTask(): Promise {
+ try {
+ console.log('执行挑战鼓励提醒后台任务...');
+
+ const state = store.getState();
+ const normalizedUserName = state.user.profile?.name?.trim();
+ const userName = normalizedUserName && normalizedUserName.length > 0 ? normalizedUserName : '朋友';
+
+ const challenges = await listChallenges();
+ const joinedChallenges = challenges.filter((challenge) => challenge.isJoined && challenge.progress);
+
+ if (!joinedChallenges.length) {
+ console.log('没有加入的挑战或挑战没有进度,跳过挑战提醒');
+ return;
+ }
+
+ const todayKey = new Date().toISOString().slice(0, 10);
+
+ for (const challenge of joinedChallenges) {
+ const progress = challenge.progress;
+ if (!progress || progress.checkedInToday) {
+ continue;
+ }
+
+ const storageKey = `@challenge_encouragement_sent:${challenge.id}`;
+ const lastSent = await AsyncStorage.getItem(storageKey);
+ if (lastSent === todayKey) {
+ continue;
+ }
+
+ try {
+ await ChallengeNotificationHelpers.sendEncouragementNotification({
+ userName,
+ challengeTitle: challenge.title,
+ challengeId: challenge.id,
+ });
+
+ await AsyncStorage.setItem(storageKey, todayKey);
+ } catch (notificationError) {
+ console.error('发送挑战鼓励通知失败:', notificationError);
+ }
+ }
+
+ console.log('挑战鼓励提醒后台任务完成');
+ } catch (error) {
+ console.error('执行挑战鼓励提醒后台任务失败:', error);
+ }
+}
+
// 发送测试通知以验证后台任务执行
async function sendTestNotification(): Promise {
try {
@@ -149,6 +199,8 @@ async function executeBackgroundTasks(): Promise {
// 执行站立提醒检查任务
// await executeStandReminderTask();
+ await executeChallengeReminderTask();
+
console.log('后台任务执行完成');
} catch (error) {
console.error('执行后台任务失败:', error);
diff --git a/services/challengesApi.ts b/services/challengesApi.ts
index f6ec29d..5be96a7 100644
--- a/services/challengesApi.ts
+++ b/services/challengesApi.ts
@@ -6,6 +6,7 @@ export type ChallengeProgressDto = {
completed: number;
target: number;
remaining: number
+ checkedInToday: boolean;
};
export type RankingItemDto = {
diff --git a/services/notifications.ts b/services/notifications.ts
index e58d740..8a193ad 100644
--- a/services/notifications.ts
+++ b/services/notifications.ts
@@ -161,6 +161,10 @@ export class NotificationService {
if (data?.url) {
router.push(data.url as any);
}
+ } else if (data?.type === NotificationTypes.CHALLENGE_ENCOURAGEMENT) {
+ console.log('用户点击了挑战提醒通知', data);
+ const targetUrl = (data?.url as string) || '/(tabs)/challenges';
+ router.push(targetUrl as any);
} else if (data?.type === 'mood_reminder') {
// 处理心情提醒通知
console.log('用户点击了心情提醒通知', data);
@@ -506,6 +510,7 @@ export const NotificationTypes = {
MOOD_REMINDER: 'mood_reminder',
WATER_REMINDER: 'water_reminder',
REGULAR_WATER_REMINDER: 'regular_water_reminder',
+ CHALLENGE_ENCOURAGEMENT: 'challenge_encouragement',
} as const;
// 便捷方法
diff --git a/utils/notificationHelpers.ts b/utils/notificationHelpers.ts
index 626d8e1..6029ea6 100644
--- a/utils/notificationHelpers.ts
+++ b/utils/notificationHelpers.ts
@@ -1,5 +1,5 @@
import * as Notifications from 'expo-notifications';
-import { NotificationData, notificationService } from '../services/notifications';
+import { NotificationData, NotificationTypes, notificationService } from '../services/notifications';
import { getNotificationEnabled } from './userPreferences';
/**
@@ -287,6 +287,33 @@ export class GoalNotificationHelpers {
}
}
+export class ChallengeNotificationHelpers {
+ static buildChallengesTabUrl(): string {
+ return '/(tabs)/challenges';
+ }
+
+ static async sendEncouragementNotification(params: {
+ userName: string;
+ challengeTitle: string;
+ challengeId: string;
+ }): Promise {
+ const { userName, challengeTitle, challengeId } = params;
+ const notification: NotificationData = {
+ title: '挑战提醒',
+ body: `${userName},今天还没有完成「${challengeTitle}」挑战,快来打卡吧!`,
+ data: {
+ type: NotificationTypes.CHALLENGE_ENCOURAGEMENT,
+ challengeId,
+ url: ChallengeNotificationHelpers.buildChallengesTabUrl(),
+ },
+ sound: true,
+ priority: 'high',
+ };
+
+ return notificationService.sendImmediateNotification(notification);
+ }
+}
+
/**
* 营养相关的通知辅助函数
*/