feat: 完善个人中心与小组件功能,优化 Onboarding 交互与手势体验
This commit is contained in:
@@ -1,66 +1,147 @@
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'expo-router';
|
||||
import * as Notifications from 'expo-notifications';
|
||||
import { OnboardingLayout } from '@/components/onboarding/OnboardingLayout';
|
||||
import { NameInputStep } from '@/components/onboarding/NameInputStep';
|
||||
import { IntentSelectionStep } from '@/components/onboarding/IntentSelectionStep';
|
||||
import { setOnboardingCompleted, setUserProfile } from '@/src/storage/appStorage';
|
||||
import { SelectionStep } from '@/components/onboarding/SelectionStep';
|
||||
import { ReminderStep } from '@/components/onboarding/ReminderStep';
|
||||
import { setOnboardingCompleted, setUserProfile, setDailyReminderSettings } from '@/src/storage/appStorage';
|
||||
|
||||
const STEPS = [
|
||||
{ id: 'name', type: 'name', title: '我可以怎么称呼你?' },
|
||||
{
|
||||
id: 'status',
|
||||
type: 'selection',
|
||||
title: '媽媽的狀態?',
|
||||
options: [
|
||||
{ id: 'pregnant', label: '懷孕中/準備成為媽媽' },
|
||||
{ id: 'has_kids', label: '已經有孩子' },
|
||||
{ id: 'no_fill', label: '不想填寫' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'emotion',
|
||||
type: 'selection',
|
||||
title: '當下情緒狀態?',
|
||||
options: [
|
||||
{ id: 'happy', label: '愉悅、滿足' },
|
||||
{ id: 'calm', label: '平靜、安穩' },
|
||||
{ id: 'stressed', label: '被壓得有點喘不過氣' },
|
||||
{ id: 'low', label: '情緒低落' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'influence',
|
||||
type: 'selection',
|
||||
title: '是什麼影響了你最近的感受?',
|
||||
options: [
|
||||
{ id: 'family', label: '家庭與孩子' },
|
||||
{ id: 'work', label: '工作或學習' },
|
||||
{ id: 'relationship', label: '親密關係' },
|
||||
{ id: 'friends', label: '朋友與人際' },
|
||||
{ id: 'health', label: '身心健康' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'support',
|
||||
type: 'selection',
|
||||
title: '最需要什麼支持?',
|
||||
options: [
|
||||
{ id: 'emotional', label: '情緒支持' },
|
||||
{ id: 'parenting', label: '育兒壓力' },
|
||||
{ id: 'self_worth', label: '自我價值' },
|
||||
{ id: 'anxiety', label: '焦慮舒緩' },
|
||||
{ id: 'balance', label: '休息與平衡' },
|
||||
]
|
||||
},
|
||||
{ id: 'reminder', type: 'reminder', title: '你需要每天几次提醒?' },
|
||||
];
|
||||
|
||||
export default function OnboardingScreen() {
|
||||
const router = useRouter();
|
||||
const [step, setStep] = useState(0);
|
||||
const [stepIndex, setStepIndex] = useState(0);
|
||||
const [name, setName] = useState('');
|
||||
const [intents, setIntents] = useState<string[]>([]);
|
||||
const [selections, setSelections] = useState<Record<string, string[]>>({});
|
||||
const [reminderTimes, setReminderTimes] = useState(3);
|
||||
|
||||
const currentStep = STEPS[stepIndex];
|
||||
|
||||
async function onFinish() {
|
||||
// Save whatever input we have
|
||||
await setUserProfile({ name, intents });
|
||||
// 请求推送权限
|
||||
const { status } = await Notifications.requestPermissionsAsync();
|
||||
const pushEnabled = status === 'granted';
|
||||
|
||||
await setUserProfile({
|
||||
name,
|
||||
intents: Object.values(selections).flat()
|
||||
});
|
||||
await setDailyReminderSettings({
|
||||
timesPerDay: reminderTimes,
|
||||
pushEnabled: pushEnabled
|
||||
});
|
||||
await setOnboardingCompleted(true);
|
||||
router.replace('/(onboarding)/push-prompt');
|
||||
router.replace('/(app)/home');
|
||||
}
|
||||
|
||||
async function onNext() {
|
||||
if (step === 0) {
|
||||
setStep(1);
|
||||
const onNext = () => {
|
||||
if (stepIndex < STEPS.length - 1) {
|
||||
setStepIndex(stepIndex + 1);
|
||||
} else {
|
||||
await onFinish();
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async function onSkip() {
|
||||
// Skip logic: move to next step regardless of input
|
||||
if (step === 0) {
|
||||
setStep(1);
|
||||
} else {
|
||||
await onFinish();
|
||||
const onBack = () => {
|
||||
if (stepIndex > 0) {
|
||||
setStepIndex(stepIndex - 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Step 0: Next button requires input
|
||||
// Step 1: Next button always enabled (can proceed with empty selection)
|
||||
const nextEnabled = step === 0 ? name.trim().length > 0 : true;
|
||||
const onSkip = () => {
|
||||
router.replace('/(app)/home');
|
||||
};
|
||||
|
||||
const handleToggleSelection = (id: string) => {
|
||||
setSelections(prev => {
|
||||
const currentIds = prev[currentStep.id] || [];
|
||||
const nextIds = currentIds.includes(id)
|
||||
? currentIds.filter(i => i !== id)
|
||||
: [...currentIds, id];
|
||||
return { ...prev, [currentStep.id]: nextIds };
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<OnboardingLayout
|
||||
title={currentStep.title}
|
||||
currentStep={stepIndex}
|
||||
totalSteps={STEPS.length - 1}
|
||||
onSkip={onSkip}
|
||||
onNext={onNext}
|
||||
nextEnabled={nextEnabled}
|
||||
onBack={onBack}
|
||||
showBackButton={stepIndex > 0}
|
||||
>
|
||||
{step === 0 ? (
|
||||
{currentStep.type === 'name' && (
|
||||
<NameInputStep
|
||||
value={name}
|
||||
onChangeText={setName}
|
||||
onSubmitEditing={name.trim().length > 0 ? onNext : undefined}
|
||||
onNext={onNext}
|
||||
/>
|
||||
) : (
|
||||
<IntentSelectionStep
|
||||
selectedIds={intents}
|
||||
onToggle={(id) => {
|
||||
setIntents(prev =>
|
||||
prev.includes(id)
|
||||
? prev.filter(i => i !== id)
|
||||
: [...prev, id]
|
||||
);
|
||||
}}
|
||||
)}
|
||||
|
||||
{currentStep.type === 'selection' && (
|
||||
<SelectionStep
|
||||
options={currentStep.options!}
|
||||
selectedIds={selections[currentStep.id] || []}
|
||||
onToggle={handleToggleSelection}
|
||||
onNext={onNext}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentStep.type === 'reminder' && (
|
||||
<ReminderStep
|
||||
value={reminderTimes}
|
||||
onChange={setReminderTimes}
|
||||
onFinish={onFinish}
|
||||
/>
|
||||
)}
|
||||
</OnboardingLayout>
|
||||
|
||||
Reference in New Issue
Block a user