小组件换行文案更新
This commit is contained in:
@@ -55,6 +55,7 @@ import { getBootId } from '@/src/utils/bootSession';
|
||||
import { advanceSuixinState, buildInitialSuixinState, NEUTRAL_THEME_COLORS } from '@/src/features/suixinTheme';
|
||||
import { wrapText } from '@/src/features/textWrap';
|
||||
import { defaultMeasureWidthImpl } from '@/src/features/textWrap/measure';
|
||||
import { ensureDailyWidgetRecoUpToDate } from '@/src/modules/dailyWidgetReco';
|
||||
|
||||
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
|
||||
|
||||
@@ -265,11 +266,24 @@ export default function HomeScreen() {
|
||||
maxLines: 3,
|
||||
overflowMode: 'CLIP',
|
||||
lineMode: 'AUTO',
|
||||
configVersion: 'v1',
|
||||
// Home:采用“更偏好语气停顿/更好看”的排版风格微调(不影响算法默认 v1)
|
||||
configVersion: 'v1-home',
|
||||
debug: __DEV__,
|
||||
fontSpec,
|
||||
contextProfile: `APP|${Platform.OS}|home|${lang}`,
|
||||
measureWidthImpl: defaultMeasureWidthImpl,
|
||||
scoringOverrides:
|
||||
lang === 'TC'
|
||||
? {
|
||||
// 更偏好在逗号/句号等处断行(即便宽度允许也不一定要塞满)
|
||||
weights: { R_PUNCT_BREAK: 180 },
|
||||
// 让“理想行宽”更短,避免宽屏下过度延后断行
|
||||
idealWidthRatio: { APP: 0.82 },
|
||||
// 更宽容短行(尤其是第一行在标点处停顿)
|
||||
minPreferredRatio: 0.45,
|
||||
shortLastLineRatio: 0.45,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
if (cancelled) return;
|
||||
@@ -377,6 +391,11 @@ export default function HomeScreen() {
|
||||
setIndex(0);
|
||||
fetchNewFeed();
|
||||
}
|
||||
|
||||
// Widget:前台辅助刷新(尽力而为)
|
||||
// - 写入 App Group 的 dailyReco 缓存
|
||||
// - 生成 wrapped_text_by_family,供 Widget 直接渲染
|
||||
ensureDailyWidgetRecoUpToDate({ reason: 'home_focus' }).catch(() => {});
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
@@ -444,13 +463,41 @@ export default function HomeScreen() {
|
||||
});
|
||||
}, [busy, index, currentFeed.length, isFetching, fetchNewFeed, translateY, opacity]);
|
||||
|
||||
// 切换到上一条文案的统一动画逻辑(下滑触发)
|
||||
const triggerPrevContent = useCallback(() => {
|
||||
if (busy) return;
|
||||
setBusy(true);
|
||||
|
||||
// 1. 当前文案向下移动并消失
|
||||
translateY.value = withTiming(40, { duration: 300, easing: Easing.out(Easing.quad) });
|
||||
opacity.value = withTiming(0, { duration: 300 }, (finished) => {
|
||||
if (finished) {
|
||||
// 2. 切换数据索引(循环回退)
|
||||
const nextIndex = index - 1 < 0 ? Math.max(0, currentFeed.length - 1) : index - 1;
|
||||
runOnJS(setIndex)(nextIndex);
|
||||
runOnJS(setLikeFilled)(false);
|
||||
|
||||
// 3. 准备上一条文案:先瞬移到上方 40pt
|
||||
translateY.value = -40;
|
||||
|
||||
// 4. 上一条文案向下移动到原位并显现
|
||||
translateY.value = withTiming(0, { duration: 400, easing: Easing.out(Easing.back(1)) });
|
||||
opacity.value = withTiming(1, { duration: 400 }, (finished) => {
|
||||
if (finished) {
|
||||
runOnJS(setBusy)(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [busy, index, currentFeed.length, translateY, opacity]);
|
||||
|
||||
const lastTapRef = useRef<number>(0);
|
||||
|
||||
// 使用 Ref 解决 PanResponder 闭包陷阱,确保手势回调能拿到最新的 state 和 function
|
||||
const handlersRef = useRef({ onPressLike, triggerNextContent });
|
||||
const handlersRef = useRef({ onPressLike, triggerNextContent, triggerPrevContent });
|
||||
useEffect(() => {
|
||||
handlersRef.current = { onPressLike, triggerNextContent };
|
||||
}, [onPressLike, triggerNextContent]);
|
||||
handlersRef.current = { onPressLike, triggerNextContent, triggerPrevContent };
|
||||
}, [onPressLike, triggerNextContent, triggerPrevContent]);
|
||||
|
||||
// 使用系统自带的 PanResponder 代替第三方手势库
|
||||
const panResponder = useRef(
|
||||
@@ -475,9 +522,11 @@ export default function HomeScreen() {
|
||||
}
|
||||
lastTapRef.current = now;
|
||||
|
||||
// 2. 上滑逻辑判定
|
||||
// 2. 上滑/下滑逻辑判定
|
||||
if (gestureState.dy < -50) { // 上滑超过 50pt
|
||||
runOnJS(handlersRef.current.triggerNextContent)();
|
||||
} else if (gestureState.dy > 50) { // 下滑超过 50pt
|
||||
runOnJS(handlersRef.current.triggerPrevContent)();
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -549,13 +598,13 @@ export default function HomeScreen() {
|
||||
onPress={() => setThemeOpen(true)}
|
||||
accessibilityLabel={t('home.theme')}
|
||||
>
|
||||
<ThemeIcon width={18} height={18} />
|
||||
<ThemeIcon width={20} height={20} />
|
||||
</CircleIconButton>
|
||||
<CircleIconButton
|
||||
onPress={() => setProfileOpen(true)}
|
||||
accessibilityLabel={t('home.profile')}
|
||||
>
|
||||
<MyIcon width={18} height={18} />
|
||||
<MyIcon width={20} height={20} />
|
||||
</CircleIconButton>
|
||||
</View>
|
||||
|
||||
@@ -577,15 +626,16 @@ export default function HomeScreen() {
|
||||
onPress={onPressLike}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('home.like')}
|
||||
hitSlop={20}
|
||||
// 稍微增大可点击区域,提升单手操作成功率
|
||||
hitSlop={24}
|
||||
style={styles.reactionInner}
|
||||
>
|
||||
{likeFilled ? (
|
||||
<LikeFilledIcon width={35} height={36} color="#EA6969" />
|
||||
<LikeFilledIcon width={40} height={41} color="#EA6969" />
|
||||
) : (
|
||||
<LikeIcon
|
||||
width={35}
|
||||
height={36}
|
||||
width={40}
|
||||
height={41}
|
||||
color={themeMode === 'scenery' ? '#FFFFFF' : '#5E2A28'}
|
||||
/>
|
||||
)}
|
||||
@@ -615,7 +665,8 @@ function CircleIconButton({
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
hitSlop={10}
|
||||
// 稍微增大可点击区域,提升易用性
|
||||
hitSlop={14}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
style={styles.circleBtn}
|
||||
@@ -640,9 +691,9 @@ const styles = StyleSheet.create({
|
||||
zIndex: 30,
|
||||
},
|
||||
circleBtn: {
|
||||
width: 34,
|
||||
height: 34,
|
||||
borderRadius: 17,
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: 'rgba(255,255,255,0.75)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
||||
Reference in New Issue
Block a user