小组件换行文案更新

This commit is contained in:
吕新雨
2026-02-10 13:35:32 +08:00
29 changed files with 751 additions and 208 deletions

View File

@@ -553,6 +553,8 @@ function DailyReminderPage({ visible, onDone }: { visible: boolean; onDone: () =
function WidgetPage({ onOpenHowTo }: { onOpenHowTo: () => void }) {
const { t, i18n } = useTranslation();
const currentLang = i18n.language;
// 需求:个人主页弹窗「小工具」页暂时隐藏锁屏小工具说明/入口
const showLockScreenWidget = false;
// 根据语言选择图片
const widget1 = currentLang === 'en'
@@ -570,10 +572,12 @@ function WidgetPage({ onOpenHowTo }: { onOpenHowTo: () => void }) {
</Pressable>
<View style={styles.widgetScroll}>
<Pressable style={styles.widgetItem} onPress={onOpenHowTo}>
<Image source={widget1} style={styles.widgetImg1} resizeMode="contain" />
<Text style={styles.widgetLabel}>{t('widget.lockScreen')}</Text>
</Pressable>
{showLockScreenWidget ? (
<Pressable style={styles.widgetItem} onPress={onOpenHowTo}>
<Image source={widget1} style={styles.widgetImg1} resizeMode="contain" />
<Text style={styles.widgetLabel}>{t('widget.lockScreen')}</Text>
</Pressable>
) : null}
<Pressable style={styles.widgetItem} onPress={onOpenHowTo}>
<Image source={widget2} style={styles.widgetImg2} resizeMode="contain" />

View File

@@ -13,7 +13,7 @@ export default function WidgetModal({ visible, onClose }: Props) {
const { t } = useTranslation();
return (
<SheetModal visible={visible} title={t('profile.widget')} onClose={onClose}>
<SheetModal visible={visible} title={t('widget.howToTitle')} onClose={onClose}>
<View style={styles.content}>
<View style={styles.row}>
<PreviewCard label={t('widget.lockScreen')}>

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { View, StyleSheet, TouchableOpacity } from 'react-native';
import { View, StyleSheet, TouchableOpacity, Text } from 'react-native';
import { useTranslation } from 'react-i18next';
import { SerifText } from './SerifText';
import { OnboardingColors } from '@/constants/OnboardingTheme';
import { OnboardingColors, OnboardingFont } from '@/constants/OnboardingTheme';
export const INTENTS = [
{ id: 'love', labelKey: 'intent.love', icon: '❤️' },
@@ -20,7 +19,7 @@ export function IntentSelectionStep({ selectedIds, onToggle }: IntentSelectionSt
const { t } = useTranslation();
return (
<View style={styles.container}>
<SerifText style={styles.title}>{t('intent.title')}</SerifText>
<Text style={styles.title}>{t('intent.title')}</Text>
<View style={styles.grid}>
{INTENTS.map((intent) => {
@@ -35,10 +34,10 @@ export function IntentSelectionStep({ selectedIds, onToggle }: IntentSelectionSt
onPress={() => onToggle(intent.id)}
activeOpacity={0.7}
>
<SerifText style={styles.icon}>{intent.icon}</SerifText>
<SerifText style={[styles.label, isSelected && styles.labelSelected]}>
<Text style={styles.icon}>{intent.icon}</Text>
<Text style={[styles.label, isSelected && styles.labelSelected]}>
{t(intent.labelKey)}
</SerifText>
</Text>
</TouchableOpacity>
);
})}
@@ -56,6 +55,8 @@ const styles = StyleSheet.create({
fontSize: 24,
marginBottom: 40,
textAlign: 'center',
fontFamily: OnboardingFont.question,
color: OnboardingColors.textPrimary,
},
grid: {
flexDirection: 'row',
@@ -86,10 +87,12 @@ const styles = StyleSheet.create({
},
icon: {
fontSize: 32,
fontFamily: OnboardingFont.question,
},
label: {
fontSize: 18,
color: OnboardingColors.textPrimary,
fontFamily: OnboardingFont.question,
},
labelSelected: {
fontWeight: 'bold',

View File

@@ -95,8 +95,7 @@ export function NameInputStep({ value, onChangeText, onNext }: NameInputStepProp
blurOnSubmit={true}
onSubmitEditing={() => {
Keyboard.dismiss();
// 有输入时,“完成”直接进入下一步,避免真机卡在键盘上
if (value.trim().length > 0) onNext();
// 不再自動跳頁,僅收起鍵盤;前進需點擊底部 ➡️
}}
/>
</View>

View File

@@ -1,7 +1,10 @@
import React from 'react';
import { View, StyleSheet, SafeAreaView, TouchableOpacity, StatusBar, Text, Image, Platform } from 'react-native';
import React, { useRef, useEffect } from 'react';
import { View, StyleSheet, SafeAreaView, TouchableOpacity, StatusBar, Text, Image, Platform, Animated, Easing } from 'react-native';
import { useTranslation } from 'react-i18next';
import { OnboardingColors } from '@/constants/OnboardingTheme';
import { OnboardingColors, OnboardingFont } from '@/constants/OnboardingTheme';
const TRANSITION_OFFSET = 24;
const TRANSITION_DURATION = 280;
interface OnboardingLayoutProps {
children: React.ReactNode;
@@ -11,6 +14,8 @@ interface OnboardingLayoutProps {
onSkip: () => void;
onBack?: () => void;
showBackButton?: boolean;
/** 用户名字仅在名字步骤之后的第一个问题currentStep === 1且非空时显示招呼语 */
userName?: string;
}
export function OnboardingLayout({
@@ -20,9 +25,48 @@ export function OnboardingLayout({
totalSteps,
onSkip,
onBack,
showBackButton = false
showBackButton = false,
userName = '',
}: OnboardingLayoutProps) {
const { t } = useTranslation();
const showGreeting = currentStep === 1 && userName.trim().length > 0;
const displayName = userName.trim();
const prevStepRef = useRef(currentStep);
const isFirstRenderRef = useRef(true);
const translateX = useRef(new Animated.Value(0)).current;
const opacity = useRef(new Animated.Value(1)).current;
useEffect(() => {
if (isFirstRenderRef.current) {
isFirstRenderRef.current = false;
prevStepRef.current = currentStep;
return;
}
if (prevStepRef.current === currentStep) return;
const direction = currentStep > prevStepRef.current ? 'forward' : 'back';
prevStepRef.current = currentStep;
const startX = direction === 'forward' ? TRANSITION_OFFSET : -TRANSITION_OFFSET;
translateX.setValue(startX);
opacity.setValue(0.72);
Animated.parallel([
Animated.timing(translateX, {
toValue: 0,
duration: TRANSITION_DURATION,
useNativeDriver: true,
easing: Easing.out(Easing.cubic),
}),
Animated.timing(opacity, {
toValue: 1,
duration: TRANSITION_DURATION,
useNativeDriver: true,
easing: Easing.out(Easing.cubic),
}),
]).start();
}, [currentStep, translateX, opacity]);
return (
<View style={styles.container}>
<StatusBar barStyle="dark-content" />
@@ -49,16 +93,29 @@ export function OnboardingLayout({
</TouchableOpacity>
</View>
{/* Title & Progress Row */}
{/* Title & Progress Row(名字步骤后第一步且名字非空时显示招呼语 + 问题) */}
<View style={styles.titleRow}>
<Text style={styles.questionTitle}>{title}</Text>
<View style={styles.titleBlock}>
{showGreeting && (
<Text style={styles.greetingText}>{t('onboardingSurvey.greeting', { name: displayName })}</Text>
)}
<Text style={styles.questionTitle}>{title}</Text>
</View>
<Text style={styles.progressText}>({currentStep}/{totalSteps})</Text>
</View>
{/* Content */}
<View style={styles.content}>
{/* Contentstep 切换时滑动 + 淡入 */}
<Animated.View
style={[
styles.content,
{
opacity,
transform: [{ translateX }],
},
]}
>
{children}
</View>
</Animated.View>
</SafeAreaView>
</View>
);
@@ -114,18 +171,27 @@ const styles = StyleSheet.create({
alignItems: 'flex-end',
paddingHorizontal: 20,
marginTop: 20,
marginBottom: 20,
marginBottom: 8,
},
titleBlock: {
flex: 1,
justifyContent: 'flex-end',
},
greetingText: {
fontSize: 22,
color: OnboardingColors.questionTitle,
fontFamily: OnboardingFont.question,
marginBottom: 4,
},
questionTitle: {
fontSize: 22,
color: OnboardingColors.questionTitle,
fontFamily: Platform.OS === 'ios' ? 'PingFang TC' : 'sans-serif',
flex: 1,
fontFamily: OnboardingFont.question,
},
progressText: {
fontSize: 18,
color: OnboardingColors.textProgress,
fontFamily: Platform.OS === 'ios' ? 'PingFang TC' : 'sans-serif',
fontFamily: OnboardingFont.question,
marginLeft: 10,
},
content: {

View File

@@ -11,16 +11,16 @@ interface ReminderStepProps {
value: number;
onChange: (value: number) => void;
onFinish: () => void;
onSkip: () => void;
onSkip?: () => void;
}
export function ReminderStep({ value, onChange, onFinish, onSkip }: ReminderStepProps) {
export function ReminderStep({ value, onChange, onFinish }: ReminderStepProps) {
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const handleReduce = () => {
// 允许 050 表示关闭每日提醒
if (value > 0) onChange(value - 1);
// 本页最小为 1不接收提醒请使用右上角 Skip
if (value > 1) onChange(value - 1);
};
const handleAdd = () => {
@@ -36,7 +36,9 @@ export function ReminderStep({ value, onChange, onFinish, onSkip }: ReminderStep
<View style={styles.numberWrapper}>
<Text style={styles.numberText}>{value}</Text>
<Text style={styles.unitText}>{t('dailyReminder.timesUnit')}</Text>
<Text style={styles.unitText}>
{value === 1 ? t('dailyReminder.timesUnitSingular') : t('dailyReminder.timesUnit')}
</Text>
</View>
<TouchableOpacity onPress={handleAdd} activeOpacity={0.7}>
@@ -48,10 +50,6 @@ export function ReminderStep({ value, onChange, onFinish, onSkip }: ReminderStep
<TouchableOpacity onPress={onFinish} activeOpacity={0.8}>
<BtnClicked width={87} height={57} />
</TouchableOpacity>
<TouchableOpacity onPress={onSkip} activeOpacity={0.8} style={styles.skipBtn}>
<Text style={styles.skipText}>{t('onboarding.skip')}</Text>
</TouchableOpacity>
</View>
</View>
);
@@ -93,17 +91,5 @@ const styles = StyleSheet.create({
footer: {
position: 'absolute',
alignItems: 'center',
}
,
skipBtn: {
marginTop: 14,
paddingVertical: 10,
paddingHorizontal: 18,
},
skipText: {
color: OnboardingColors.textPrimary,
fontSize: 15,
fontWeight: '600',
opacity: 0.85,
},
});

View File

@@ -1,9 +1,7 @@
import React from 'react';
import { View, StyleSheet, TouchableOpacity, ScrollView } from 'react-native';
import { View, StyleSheet, TouchableOpacity, ScrollView, Text } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { OnboardingColors } from '@/constants/OnboardingTheme';
import { SerifText } from './SerifText';
import SelectedIcon from '@/assets/images/icon/selected_icon.svg';
import { OnboardingColors, OnboardingFont } from '@/constants/OnboardingTheme';
import BtnNotClicked from '@/assets/images/icon/btn_Notclicked.svg';
import BtnClicked from '@/assets/images/icon/btn_clicked.svg';
@@ -25,7 +23,8 @@ export function SelectionStep({ options, selectedIds, onToggle, onNext, onSkip }
const insets = useSafeAreaInsets();
const footerBottom = insets.bottom + 16;
const footerButtonHeight = 57;
const footerPaddingBottom = footerBottom + footerButtonHeight + 24;
// 底部留白加大,避免最后一项与按钮边框视觉重叠
const footerPaddingBottom = footerBottom + footerButtonHeight + 40;
return (
<View style={styles.container}>
@@ -39,16 +38,11 @@ export function SelectionStep({ options, selectedIds, onToggle, onNext, onSkip }
return (
<TouchableOpacity
key={option.id}
style={styles.optionCard}
style={[styles.optionCard, isSelected && styles.optionCardSelected]}
onPress={() => onToggle(option.id)}
activeOpacity={0.7}
>
<SerifText style={styles.optionText}>{option.label}</SerifText>
{isSelected && (
<View style={styles.iconWrapper}>
<SelectedIcon width={20} height={20} />
</View>
)}
<Text style={styles.optionText}>{option.label}</Text>
</TouchableOpacity>
);
})}
@@ -67,7 +61,7 @@ export function SelectionStep({ options, selectedIds, onToggle, onNext, onSkip }
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
paddingTop: 8,
},
scroll: {
flex: 1,
@@ -82,7 +76,7 @@ const styles = StyleSheet.create({
borderRadius: 20,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
justifyContent: 'center',
paddingHorizontal: 24,
marginBottom: 12,
shadowColor: '#000',
@@ -91,14 +85,14 @@ const styles = StyleSheet.create({
shadowRadius: 10,
elevation: 2,
},
optionCardSelected: {
backgroundColor: OnboardingColors.cardSelected,
},
optionText: {
fontSize: 18,
color: OnboardingColors.textPrimary,
fontWeight: '500',
flex: 1,
},
iconWrapper: {
marginLeft: 10,
fontFamily: OnboardingFont.question,
},
footer: {
position: 'absolute',