feat(fasting): 添加周期性断食计划功能
实现完整的周期性断食计划系统,支持每日自动续订和通知管理: - 新增周期性断食状态管理(activeCycle、currentCycleSession、cycleHistory) - 实现周期性断食会话的自动完成和续订逻辑 - 添加独立的周期性断食通知系统,避免与单次断食通知冲突 - 支持暂停/恢复周期性断食计划 - 添加周期性断食数据持久化和水合功能 - 优化断食界面,优先显示周期性断食信息 - 新增空状态引导界面,提升用户体验 - 保持单次断食功能向后兼容
This commit is contained in:
@@ -6,45 +6,84 @@ import { FASTING_PLANS, FastingPlan, getPlanById, getRecommendedStart } from '@/
|
||||
import { ROUTES } from '@/constants/Routes';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useCountdown } from '@/hooks/useCountdown';
|
||||
import { useFastingCycleNotifications } from '@/hooks/useFastingCycleNotifications';
|
||||
import { useFastingNotifications } from '@/hooks/useFastingNotifications';
|
||||
import {
|
||||
clearActiveSchedule,
|
||||
rescheduleActivePlan,
|
||||
scheduleFastingPlan,
|
||||
selectActiveFastingPlan,
|
||||
selectActiveFastingSchedule,
|
||||
clearActiveSchedule,
|
||||
completeCurrentCycleSession,
|
||||
hydrateFastingCycle,
|
||||
pauseFastingCycle,
|
||||
rescheduleActivePlan,
|
||||
resumeFastingCycle,
|
||||
scheduleFastingPlan,
|
||||
selectActiveCyclePlan,
|
||||
// 周期性断食相关的 selectors
|
||||
selectActiveFastingCycle,
|
||||
selectActiveFastingPlan,
|
||||
selectActiveFastingSchedule,
|
||||
selectCurrentCyclePlan,
|
||||
selectCurrentCycleSession,
|
||||
selectCurrentFastingPlan,
|
||||
selectCurrentFastingTimes,
|
||||
selectCycleHistory,
|
||||
selectIsInCycleMode,
|
||||
startFastingCycle,
|
||||
stopFastingCycle,
|
||||
updateFastingCycleTime
|
||||
} from '@/store/fastingSlice';
|
||||
import {
|
||||
buildDisplayWindow,
|
||||
calculateFastingWindow,
|
||||
getFastingPhase,
|
||||
getPhaseLabel,
|
||||
loadPreferredPlanId,
|
||||
savePreferredPlanId
|
||||
buildDisplayWindow,
|
||||
getFastingPhase,
|
||||
getPhaseLabel,
|
||||
// 周期性断食相关的工具函数
|
||||
loadActiveFastingCycle,
|
||||
loadCurrentCycleSession,
|
||||
loadCycleHistory,
|
||||
loadPreferredPlanId,
|
||||
saveActiveFastingCycle,
|
||||
saveCurrentCycleSession,
|
||||
saveCycleHistory,
|
||||
savePreferredPlanId
|
||||
} from '@/utils/fasting';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import dayjs from 'dayjs';
|
||||
import { useRouter } from 'expo-router';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
export default function FastingTabScreen() {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const insets = useSafeAreaInsets();
|
||||
const scrollViewRef = React.useRef<ScrollView>(null);
|
||||
|
||||
// 单次断食计划的状态
|
||||
const activeSchedule = useAppSelector(selectActiveFastingSchedule);
|
||||
const activePlan = useAppSelector(selectActiveFastingPlan);
|
||||
|
||||
// 周期性断食计划的状态
|
||||
const activeCycle = useAppSelector(selectActiveFastingCycle);
|
||||
const currentCycleSession = useAppSelector(selectCurrentCycleSession);
|
||||
const cycleHistory = useAppSelector(selectCycleHistory);
|
||||
const activeCyclePlan = useAppSelector(selectActiveCyclePlan);
|
||||
const currentCyclePlan = useAppSelector(selectCurrentCyclePlan);
|
||||
|
||||
// 统一的当前断食信息(优先显示周期性)
|
||||
const currentPlan = useAppSelector(selectCurrentFastingPlan);
|
||||
const currentTimes = useAppSelector(selectCurrentFastingTimes);
|
||||
const isInCycleMode = useAppSelector(selectIsInCycleMode);
|
||||
|
||||
const defaultPlan = FASTING_PLANS.find((plan) => plan.id === '14-10') ?? FASTING_PLANS[0];
|
||||
const [preferredPlanId, setPreferredPlanId] = useState<string | undefined>(activePlan?.id ?? undefined);
|
||||
const [preferredPlanId, setPreferredPlanId] = useState<string | undefined>(currentPlan?.id ?? undefined);
|
||||
|
||||
// 数据持久化
|
||||
useEffect(() => {
|
||||
if (!activePlan?.id) return;
|
||||
setPreferredPlanId(activePlan.id);
|
||||
void savePreferredPlanId(activePlan.id);
|
||||
}, [activePlan?.id]);
|
||||
if (!currentPlan?.id) return;
|
||||
setPreferredPlanId(currentPlan.id);
|
||||
void savePreferredPlanId(currentPlan.id);
|
||||
}, [currentPlan?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -53,7 +92,7 @@ export default function FastingTabScreen() {
|
||||
try {
|
||||
const savedPlanId = await loadPreferredPlanId();
|
||||
if (cancelled) return;
|
||||
if (activePlan?.id) return;
|
||||
if (currentPlan?.id) return;
|
||||
if (savedPlanId && getPlanById(savedPlanId)) {
|
||||
setPreferredPlanId(savedPlanId);
|
||||
}
|
||||
@@ -66,15 +105,86 @@ export default function FastingTabScreen() {
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [activePlan?.id]);
|
||||
}, [currentPlan?.id]);
|
||||
|
||||
const currentPlan: FastingPlan | undefined = useMemo(() => {
|
||||
if (activePlan) return activePlan;
|
||||
if (preferredPlanId) return getPlanById(preferredPlanId) ?? defaultPlan;
|
||||
return defaultPlan;
|
||||
}, [activePlan, preferredPlanId, defaultPlan]);
|
||||
// 加载周期性断食数据
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
// 使用新的通知管理 hook
|
||||
const hydrateCycleData = async () => {
|
||||
try {
|
||||
if (cancelled) return;
|
||||
|
||||
const [cycleData, sessionData, historyData] = await Promise.all([
|
||||
loadActiveFastingCycle(),
|
||||
loadCurrentCycleSession(),
|
||||
loadCycleHistory(),
|
||||
]);
|
||||
|
||||
if (cancelled) return;
|
||||
|
||||
dispatch(hydrateFastingCycle({
|
||||
activeCycle: cycleData,
|
||||
currentCycleSession: sessionData,
|
||||
cycleHistory: historyData,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.warn('加载周期性断食数据失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
hydrateCycleData();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
// 保存周期性断食数据,增加错误处理
|
||||
useEffect(() => {
|
||||
const saveCycleData = async () => {
|
||||
try {
|
||||
if (activeCycle) {
|
||||
await saveActiveFastingCycle(activeCycle);
|
||||
} else {
|
||||
await saveActiveFastingCycle(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存周期性断食计划失败', error);
|
||||
// TODO: 可以在这里添加用户提示
|
||||
}
|
||||
};
|
||||
saveCycleData();
|
||||
}, [activeCycle]);
|
||||
|
||||
useEffect(() => {
|
||||
const saveSessionData = async () => {
|
||||
try {
|
||||
if (currentCycleSession) {
|
||||
await saveCurrentCycleSession(currentCycleSession);
|
||||
} else {
|
||||
await saveCurrentCycleSession(null);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存断食会话失败', error);
|
||||
// TODO: 可以在这里添加用户提示
|
||||
}
|
||||
};
|
||||
saveSessionData();
|
||||
}, [currentCycleSession]);
|
||||
|
||||
useEffect(() => {
|
||||
const saveHistoryData = async () => {
|
||||
try {
|
||||
await saveCycleHistory(cycleHistory);
|
||||
} catch (error) {
|
||||
console.error('保存断食历史失败', error);
|
||||
// TODO: 可以在这里添加用户提示
|
||||
}
|
||||
};
|
||||
saveHistoryData();
|
||||
}, [cycleHistory]);
|
||||
|
||||
// 使用单次断食通知管理 hook
|
||||
const {
|
||||
isReady: notificationsReady,
|
||||
isLoading: notificationsLoading,
|
||||
@@ -84,11 +194,24 @@ export default function FastingTabScreen() {
|
||||
verifyAndSync,
|
||||
forceSync,
|
||||
clearError,
|
||||
} = useFastingNotifications(activeSchedule, currentPlan);
|
||||
} = useFastingNotifications(activeSchedule, activePlan);
|
||||
|
||||
// 使用周期性断食通知管理 hook
|
||||
const {
|
||||
isReady: cycleNotificationsReady,
|
||||
isLoading: cycleNotificationsLoading,
|
||||
error: cycleNotificationError,
|
||||
lastSyncTime: cycleLastSyncTime,
|
||||
verifyAndSync: verifyAndSyncCycle,
|
||||
forceSync: forceSyncCycle,
|
||||
clearError: clearCycleError,
|
||||
} = useFastingCycleNotifications(activeCycle, currentCycleSession, currentCyclePlan);
|
||||
|
||||
// 每次进入页面时验证通知
|
||||
// 添加节流机制,避免频繁触发验证
|
||||
const lastVerifyTimeRef = React.useRef<number>(0);
|
||||
const lastCycleVerifyTimeRef = React.useRef<number>(0);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const now = Date.now();
|
||||
@@ -104,21 +227,35 @@ export default function FastingTabScreen() {
|
||||
}, [verifyAndSync])
|
||||
);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const now = Date.now();
|
||||
const timeSinceLastVerify = now - lastCycleVerifyTimeRef.current;
|
||||
|
||||
// 如果距离上次验证不足 30 秒,跳过本次验证
|
||||
if (timeSinceLastVerify < 30000) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastCycleVerifyTimeRef.current = now;
|
||||
verifyAndSyncCycle();
|
||||
}, [verifyAndSyncCycle])
|
||||
);
|
||||
|
||||
// 使用统一的当前断食时间
|
||||
const scheduleStart = useMemo(() => {
|
||||
if (activeSchedule) return new Date(activeSchedule.startISO);
|
||||
if (currentPlan) return getRecommendedStart(currentPlan);
|
||||
if (currentTimes) {
|
||||
return new Date(currentTimes.startISO);
|
||||
}
|
||||
return undefined;
|
||||
}, [activeSchedule, currentPlan]);
|
||||
}, [currentTimes]);
|
||||
|
||||
const scheduleEnd = useMemo(() => {
|
||||
if (activeSchedule && currentPlan) {
|
||||
return new Date(activeSchedule.endISO);
|
||||
}
|
||||
if (currentPlan && scheduleStart) {
|
||||
return calculateFastingWindow(scheduleStart, currentPlan.fastingHours).end;
|
||||
if (currentTimes) {
|
||||
return new Date(currentTimes.endISO);
|
||||
}
|
||||
return undefined;
|
||||
}, [activeSchedule, currentPlan, scheduleStart]);
|
||||
}, [currentTimes]);
|
||||
|
||||
const phase = getFastingPhase(scheduleStart ?? null, scheduleEnd ?? null);
|
||||
const countdownTarget = phase === 'fasting' ? scheduleEnd : scheduleStart;
|
||||
@@ -147,29 +284,76 @@ export default function FastingTabScreen() {
|
||||
}, [notificationError]);
|
||||
|
||||
const recommendedDate = useMemo(() => {
|
||||
if (!currentPlan) return undefined;
|
||||
return getRecommendedStart(currentPlan);
|
||||
}, [currentPlan]);
|
||||
const planToUse = currentPlan || defaultPlan;
|
||||
return getRecommendedStart(planToUse);
|
||||
}, [currentPlan, defaultPlan]);
|
||||
|
||||
// 调试信息(开发环境)
|
||||
useEffect(() => {
|
||||
if (__DEV__ && lastSyncTime) {
|
||||
console.log('断食通知状态:', {
|
||||
console.log('单次断食通知状态:', {
|
||||
ready: notificationsReady,
|
||||
loading: notificationsLoading,
|
||||
error: notificationError,
|
||||
notificationIds,
|
||||
lastSyncTime,
|
||||
schedule: activeSchedule?.startISO,
|
||||
plan: currentPlan?.id,
|
||||
plan: activePlan?.id,
|
||||
});
|
||||
}
|
||||
}, [notificationsReady, notificationsLoading, notificationError, notificationIds, lastSyncTime, activeSchedule?.startISO, currentPlan?.id]);
|
||||
}, [notificationsReady, notificationsLoading, notificationError, notificationIds, lastSyncTime, activeSchedule?.startISO, activePlan?.id]);
|
||||
|
||||
// 自动续订断食周期
|
||||
// 修改为使用每日固定时间,而非相对时间计算
|
||||
useEffect(() => {
|
||||
if (!activeSchedule || !currentPlan) return;
|
||||
if (__DEV__ && cycleLastSyncTime) {
|
||||
console.log('周期性断食通知状态:', {
|
||||
ready: cycleNotificationsReady,
|
||||
loading: cycleNotificationsLoading,
|
||||
error: cycleNotificationError,
|
||||
lastSyncTime: cycleLastSyncTime,
|
||||
cycle: activeCycle?.planId,
|
||||
session: currentCycleSession?.cycleDate,
|
||||
});
|
||||
}
|
||||
}, [cycleNotificationsReady, cycleNotificationsLoading, cycleNotificationError, cycleLastSyncTime, activeCycle?.planId, currentCycleSession?.cycleDate]);
|
||||
|
||||
// 周期性断食的自动续订逻辑
|
||||
// 移除1小时限制,但需要用户手动确认开始下一轮周期
|
||||
useEffect(() => {
|
||||
if (!currentCycleSession || !activeCycle || !currentCyclePlan) return;
|
||||
if (!activeCycle.enabled) return; // 如果周期已暂停,不自动完成
|
||||
if (phase !== 'completed') return;
|
||||
|
||||
const end = dayjs(currentCycleSession.endISO);
|
||||
if (!end.isValid()) return;
|
||||
|
||||
const now = dayjs();
|
||||
if (now.isBefore(end)) return;
|
||||
|
||||
// 检查当前会话是否已经标记为完成
|
||||
if (currentCycleSession.completed) {
|
||||
if (__DEV__) {
|
||||
console.log('当前会话已完成,跳过自动完成');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
console.log('自动完成当前断食周期:', {
|
||||
cycleDate: currentCycleSession.cycleDate,
|
||||
planId: currentCycleSession.planId,
|
||||
endTime: end.format('YYYY-MM-DD HH:mm'),
|
||||
timeSinceEnd: now.diff(end, 'minute') + '分钟',
|
||||
});
|
||||
}
|
||||
|
||||
// 完成当前周期并创建下一个周期
|
||||
// 这会自动创建下一天的会话,不需要用户手动操作
|
||||
dispatch(completeCurrentCycleSession());
|
||||
}, [dispatch, currentCycleSession, activeCycle, currentCyclePlan, phase]);
|
||||
|
||||
// 保留原有的单次断食自动续订逻辑(向后兼容)
|
||||
useEffect(() => {
|
||||
if (!activeSchedule || !activePlan) return;
|
||||
if (phase !== 'completed') return;
|
||||
|
||||
const start = dayjs(activeSchedule.startISO);
|
||||
@@ -202,7 +386,7 @@ export default function FastingTabScreen() {
|
||||
nextStart = nextStart.add(1, 'day');
|
||||
}
|
||||
|
||||
const nextEnd = nextStart.add(currentPlan.fastingHours, 'hour');
|
||||
const nextEnd = nextStart.add(activePlan.fastingHours, 'hour');
|
||||
|
||||
if (__DEV__) {
|
||||
console.log('自动续订断食周期:', {
|
||||
@@ -217,19 +401,27 @@ export default function FastingTabScreen() {
|
||||
start: nextStart.toISOString(),
|
||||
origin: 'auto',
|
||||
}));
|
||||
}, [dispatch, activeSchedule, currentPlan, phase]);
|
||||
}, [dispatch, activeSchedule, activePlan, phase]);
|
||||
|
||||
const handleAdjustStart = () => {
|
||||
if (!currentPlan) return;
|
||||
setShowPicker(true);
|
||||
};
|
||||
|
||||
const handleConfirmStart = (date: Date) => {
|
||||
if (!currentPlan) return;
|
||||
if (activeSchedule) {
|
||||
// 如果没有当前计划,使用默认计划
|
||||
const planToUse = currentPlan || defaultPlan;
|
||||
|
||||
// 如果处于周期性模式,更新周期性时间
|
||||
if (isInCycleMode && activeCycle) {
|
||||
const hour = date.getHours();
|
||||
const minute = date.getMinutes();
|
||||
dispatch(updateFastingCycleTime({ startHour: hour, startMinute: minute }));
|
||||
} else if (activeSchedule) {
|
||||
// 单次断食模式,重新安排
|
||||
dispatch(rescheduleActivePlan({ start: date.toISOString(), origin: 'manual' }));
|
||||
} else {
|
||||
dispatch(scheduleFastingPlan({ planId: currentPlan.id, start: date.toISOString(), origin: 'manual' }));
|
||||
// 创建新的单次断食计划
|
||||
dispatch(scheduleFastingPlan({ planId: planToUse.id, start: date.toISOString(), origin: 'manual' }));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -242,12 +434,62 @@ export default function FastingTabScreen() {
|
||||
};
|
||||
|
||||
const handleResetPlan = () => {
|
||||
dispatch(clearActiveSchedule());
|
||||
// 如果没有活跃计划,不执行任何操作
|
||||
if (!currentPlan) return;
|
||||
|
||||
if (isInCycleMode) {
|
||||
// 停止周期性断食
|
||||
dispatch(stopFastingCycle());
|
||||
} else {
|
||||
// 清除单次断食
|
||||
dispatch(clearActiveSchedule());
|
||||
}
|
||||
};
|
||||
|
||||
// 新增:启动周期性断食
|
||||
const handleStartCycle = async (plan: FastingPlan, startHour: number, startMinute: number) => {
|
||||
try {
|
||||
dispatch(startFastingCycle({
|
||||
planId: plan.id,
|
||||
startHour,
|
||||
startMinute
|
||||
}));
|
||||
|
||||
// 等待数据保存完成
|
||||
// 注意:dispatch 是同步的,但我们需要确保数据被正确保存
|
||||
console.log('周期性断食计划已启动', {
|
||||
planId: plan.id,
|
||||
startHour,
|
||||
startMinute
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('启动周期性断食失败', error);
|
||||
// TODO: 添加用户错误提示
|
||||
}
|
||||
};
|
||||
|
||||
// 新增:暂停/恢复周期性断食
|
||||
const handleToggleCycle = async () => {
|
||||
if (!activeCycle) return;
|
||||
|
||||
try {
|
||||
if (activeCycle.enabled) {
|
||||
dispatch(pauseFastingCycle());
|
||||
console.log('周期性断食已暂停');
|
||||
} else {
|
||||
dispatch(resumeFastingCycle());
|
||||
console.log('周期性断食已恢复');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换周期性断食状态失败', error);
|
||||
// TODO: 添加用户错误提示
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.safeArea]}>
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
contentContainerStyle={[styles.scrollContainer, {
|
||||
paddingTop: insets.top,
|
||||
paddingBottom: 120
|
||||
@@ -266,7 +508,7 @@ export default function FastingTabScreen() {
|
||||
onDismiss={clearError}
|
||||
/>
|
||||
|
||||
{currentPlan && (
|
||||
{currentPlan ? (
|
||||
<FastingOverviewCard
|
||||
plan={currentPlan}
|
||||
phaseLabel={getPhaseLabel(phase)}
|
||||
@@ -281,6 +523,48 @@ export default function FastingTabScreen() {
|
||||
onResetPress={handleResetPlan}
|
||||
progress={progress}
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.emptyStateCard}>
|
||||
<View style={styles.emptyStateHeader}>
|
||||
<Text style={styles.emptyStateTitle}>开始您的断食之旅</Text>
|
||||
<Text style={styles.emptyStateSubtitle}>选择适合的断食计划,开启健康生活</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.emptyStateContent}>
|
||||
<View style={styles.emptyStateIcon}>
|
||||
<Ionicons name="time-outline" size={48} color="#6F7D87" />
|
||||
</View>
|
||||
<Text style={styles.emptyStateDescription}>
|
||||
断食可以帮助改善代谢、控制体重,让身体获得充分的休息和修复时间。
|
||||
</Text>
|
||||
<Text style={styles.defaultPlanInfo}>
|
||||
默认使用 14-10 热门计划(断食14小时,进食10小时)
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.emptyStateActions}>
|
||||
<TouchableOpacity
|
||||
style={styles.primaryButton}
|
||||
onPress={() => setShowPicker(true)}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Text style={styles.primaryButtonText}>开始断食计划</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.secondaryButton}
|
||||
onPress={() => {
|
||||
// 滚动到计划列表
|
||||
setTimeout(() => {
|
||||
scrollViewRef.current?.scrollTo({ y: 600, animated: true });
|
||||
}, 100);
|
||||
}}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Text style={styles.secondaryButtonText}>浏览断食方案</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{currentPlan && (
|
||||
@@ -394,4 +678,92 @@ const styles = StyleSheet.create({
|
||||
fontSize: 12,
|
||||
color: '#8A96A3',
|
||||
},
|
||||
emptyStateCard: {
|
||||
borderRadius: 28,
|
||||
padding: 24,
|
||||
backgroundColor: '#FFFFFF',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 16 },
|
||||
shadowOpacity: 0.08,
|
||||
shadowRadius: 24,
|
||||
elevation: 6,
|
||||
marginBottom: 20,
|
||||
},
|
||||
emptyStateHeader: {
|
||||
alignItems: 'center',
|
||||
marginBottom: 24,
|
||||
},
|
||||
emptyStateTitle: {
|
||||
fontSize: 24,
|
||||
fontWeight: '700',
|
||||
color: '#2E3142',
|
||||
marginBottom: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
emptyStateSubtitle: {
|
||||
fontSize: 16,
|
||||
color: '#6F7D87',
|
||||
textAlign: 'center',
|
||||
lineHeight: 22,
|
||||
},
|
||||
emptyStateContent: {
|
||||
alignItems: 'center',
|
||||
marginBottom: 32,
|
||||
},
|
||||
emptyStateIcon: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 40,
|
||||
backgroundColor: 'rgba(111, 125, 135, 0.1)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
emptyStateDescription: {
|
||||
fontSize: 15,
|
||||
color: '#4A5460',
|
||||
textAlign: 'center',
|
||||
lineHeight: 22,
|
||||
paddingHorizontal: 20,
|
||||
marginBottom: 12,
|
||||
},
|
||||
defaultPlanInfo: {
|
||||
fontSize: 13,
|
||||
color: '#8A96A3',
|
||||
textAlign: 'center',
|
||||
lineHeight: 18,
|
||||
paddingHorizontal: 20,
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
emptyStateActions: {
|
||||
gap: 12,
|
||||
},
|
||||
primaryButton: {
|
||||
backgroundColor: '#2E3142',
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 24,
|
||||
borderRadius: 24,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
primaryButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#FFFFFF',
|
||||
},
|
||||
secondaryButton: {
|
||||
backgroundColor: 'transparent',
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 24,
|
||||
borderRadius: 24,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderWidth: 1.5,
|
||||
borderColor: '#2E3142',
|
||||
},
|
||||
secondaryButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#2E3142',
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user