更新换行算法和APP-PUSH
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
import { useEffect, useLayoutEffect, useMemo, useState, useCallback, useRef } from 'react';
|
||||
import { StyleSheet, View, Dimensions, Text, Pressable, PanResponder, Animated as RNAnimated, ImageBackground } from 'react-native';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Dimensions,
|
||||
Text,
|
||||
Pressable,
|
||||
PanResponder,
|
||||
Animated as RNAnimated,
|
||||
ImageBackground,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFocusEffect } from 'expo-router';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
@@ -43,6 +53,8 @@ import LikeIcon from '@/assets/images/icon/like_icon.svg';
|
||||
|
||||
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';
|
||||
|
||||
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
|
||||
|
||||
@@ -97,6 +109,9 @@ export default function HomeScreen() {
|
||||
const [likeFilled, setLikeFilled] = useState(false);
|
||||
const [feedItems, setFeedItems] = useState<FeedItem[]>([]);
|
||||
const [isFetching, setIsFetching] = useState(false);
|
||||
const [cardWidth, setCardWidth] = useState<number | null>(null);
|
||||
const [wrappedText, setWrappedText] = useState<string>('');
|
||||
const wrapLogRef = useRef<{ key: string } | null>(null);
|
||||
|
||||
// 解决语言切换时重复触发拉取/清空导致“文案不停跳动”的问题:
|
||||
// 用 ref 持有最新状态,避免 useCallback 依赖 feedItems/isFetching 造成函数 identity 变化 → effect 重复执行
|
||||
@@ -176,6 +191,120 @@ export default function HomeScreen() {
|
||||
};
|
||||
}, [currentFeed, index]);
|
||||
|
||||
// Home 文案:使用自主换行算法(Text Wrap 模块)
|
||||
// - 通过 onLayout 获取容器宽度
|
||||
// - 注入真实测量实现,确保“宽度派”评分与实际渲染一致
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
// 未拿到宽度前先用原文(避免闪烁)
|
||||
if (!cardWidth || cardWidth <= 0) {
|
||||
if (__DEV__) {
|
||||
const key = `noWidth|${item.id}|${String(cardWidth)}`;
|
||||
if (wrapLogRef.current?.key !== key) {
|
||||
wrapLogRef.current = { key };
|
||||
console.log('[TextWrap][Home] cardWidth 未就绪,先回退原文', {
|
||||
itemId: item.id,
|
||||
lang: recoLang,
|
||||
cardWidth,
|
||||
themeMode,
|
||||
});
|
||||
}
|
||||
}
|
||||
setWrappedText(item.text);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}
|
||||
|
||||
const paddingHorizontal = themeMode === 'scenery' ? 50 : 30;
|
||||
const availableWidth = Math.max(0, Math.floor(cardWidth - paddingHorizontal * 2));
|
||||
|
||||
const lang = recoLang === 'en' ? 'EN' : 'TC';
|
||||
|
||||
const fontFamily =
|
||||
lang === 'EN'
|
||||
? 'STIXTwoText'
|
||||
: Platform.select({
|
||||
ios: 'System',
|
||||
android: 'sans-serif',
|
||||
default: 'System',
|
||||
});
|
||||
|
||||
const fontSpec = {
|
||||
fontSize: 22,
|
||||
fontWeight: lang === 'EN' ? '600' : '700',
|
||||
fontFamily: String(fontFamily ?? 'System'),
|
||||
};
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
if (__DEV__) {
|
||||
const key = `start|${item.id}|${lang}|${availableWidth}|${themeMode}`;
|
||||
if (wrapLogRef.current?.key !== key) {
|
||||
wrapLogRef.current = { key };
|
||||
console.log('[TextWrap][Home] wrapText 开始', {
|
||||
itemId: item.id,
|
||||
lang,
|
||||
themeMode,
|
||||
cardWidth,
|
||||
paddingHorizontal,
|
||||
availableWidth,
|
||||
fontSpec,
|
||||
textPreview: String(item.text ?? '').slice(0, 80),
|
||||
textLength: String(item.text ?? '').length,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const res = await wrapText({
|
||||
text: item.text,
|
||||
lang,
|
||||
context: 'APP',
|
||||
availableWidth,
|
||||
maxLines: 3,
|
||||
overflowMode: 'CLIP',
|
||||
lineMode: 'AUTO',
|
||||
configVersion: 'v1',
|
||||
debug: __DEV__,
|
||||
fontSpec,
|
||||
contextProfile: `APP|${Platform.OS}|home|${lang}`,
|
||||
measureWidthImpl: defaultMeasureWidthImpl,
|
||||
});
|
||||
|
||||
if (cancelled) return;
|
||||
if (__DEV__) {
|
||||
console.log('[TextWrap][Home] wrapText 成功', {
|
||||
itemId: item.id,
|
||||
wrappedText: res.wrappedText,
|
||||
linesCount: res.lines.length,
|
||||
meta: res.meta,
|
||||
});
|
||||
}
|
||||
setWrappedText(res.wrappedText);
|
||||
} catch (error) {
|
||||
// 任何异常都回退到原文,避免影响 Home 主流程
|
||||
if (__DEV__) {
|
||||
console.log('[TextWrap][Home] wrapText 异常,回退原文', {
|
||||
itemId: item.id,
|
||||
lang,
|
||||
availableWidth,
|
||||
fontSpec,
|
||||
errorName: (error as any)?.name,
|
||||
errorMessage: String((error as any)?.message ?? error),
|
||||
errorStack: (error as any)?.stack,
|
||||
});
|
||||
}
|
||||
if (cancelled) return;
|
||||
setWrappedText(item.text);
|
||||
}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [item.text, cardWidth, recoLang, themeMode]);
|
||||
|
||||
// 异步拉取新文案
|
||||
const fetchNewFeed = useCallback(async () => {
|
||||
if (isFetchingRef.current) return;
|
||||
@@ -430,9 +559,15 @@ export default function HomeScreen() {
|
||||
</CircleIconButton>
|
||||
</View>
|
||||
|
||||
<Animated.View style={[styles.card, textAnimatedStyle, themeMode === 'scenery' && styles.sceneryCard]}>
|
||||
<Animated.View
|
||||
style={[styles.card, textAnimatedStyle, themeMode === 'scenery' && styles.sceneryCard]}
|
||||
onLayout={(e) => {
|
||||
const w = e.nativeEvent.layout.width;
|
||||
if (Number.isFinite(w) && w > 0) setCardWidth(w);
|
||||
}}
|
||||
>
|
||||
<Text style={[styles.text, isEnglish && styles.textEnglish, themeMode === 'scenery' && styles.sceneryText]}>
|
||||
{item.text}
|
||||
{wrappedText || item.text}
|
||||
</Text>
|
||||
</Animated.View>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user