IOS小组件/文案

This commit is contained in:
吕新雨
2026-02-05 01:49:29 +08:00
parent 4c03fce720
commit 8f84f25616
17 changed files with 500 additions and 63 deletions

View File

@@ -25,6 +25,9 @@ import {
getRecoFeedHistory,
recordRecoFeedServed,
type ThemeMode,
getSuixinThemeState,
setSuixinThemeState,
type SuixinThemeStateV1,
} from '@/src/storage/appStorage';
import { fetchRecoFeed } from '@/src/services/recoApi';
@@ -38,6 +41,9 @@ import MyIcon from '@/assets/images/home/my.svg';
import LikeFilledIcon from '@/assets/images/home/like_filled.svg';
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';
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
// 预定义风景图列表
@@ -83,6 +89,7 @@ export default function HomeScreen() {
const insets = useSafeAreaInsets();
const [index, setIndex] = useState(0);
const [themeMode, setThemeModeState] = useState<ThemeMode>('scenery');
const [suixinBgColor, setSuixinBgColor] = useState<string>(NEUTRAL_THEME_COLORS[1]);
const [themeOpen, setThemeOpen] = useState(false);
const [profileOpen, setProfileOpen] = useState(false);
const [profileName, setProfileName] = useState<string | undefined>(undefined);
@@ -95,12 +102,55 @@ export default function HomeScreen() {
// 用 ref 持有最新状态,避免 useCallback 依赖 feedItems/isFetching 造成函数 identity 变化 → effect 重复执行
const feedItemsRef = useRef<FeedItem[]>([]);
const isFetchingRef = useRef(false);
const themeModeRef = useRef<ThemeMode>('scenery');
const suixinStateRef = useRef<SuixinThemeStateV1 | null>(null);
useEffect(() => {
feedItemsRef.current = feedItems;
}, [feedItems]);
useEffect(() => {
isFetchingRef.current = isFetching;
}, [isFetching]);
useEffect(() => {
themeModeRef.current = themeMode;
}, [themeMode]);
const ensureSuixinReady = useCallback(async () => {
const bootId = getBootId();
const stored = await getSuixinThemeState();
if (stored && stored.boot_id === bootId) {
suixinStateRef.current = stored;
setSuixinBgColor(stored.last_color || NEUTRAL_THEME_COLORS[1]);
return stored;
}
const profile = await getUserProfileScoring();
const next = buildInitialSuixinState({ bootId, profile });
suixinStateRef.current = next;
setSuixinBgColor(next.last_color || NEUTRAL_THEME_COLORS[1]);
await setSuixinThemeState(next);
return next;
}, []);
const advanceSuixinOnNextContent = useCallback(async () => {
if (themeModeRef.current !== 'suixin') return;
const bootId = getBootId();
let current = suixinStateRef.current;
if (!current) {
current = await getSuixinThemeState();
}
// 冷启动后首次触发/或状态丢失:先初始化
if (!current || current.boot_id !== bootId) {
await ensureSuixinReady();
return;
}
const next = advanceSuixinState(current);
suixinStateRef.current = next;
setSuixinBgColor(next.last_color || NEUTRAL_THEME_COLORS[1]);
await setSuixinThemeState(next);
}, [ensureSuixinReady]);
// 动画相关 Shared Values
const translateY = useSharedValue(0);
@@ -180,6 +230,14 @@ export default function HomeScreen() {
if (cancelled) return;
setThemeModeState(mode);
setProfileName(profile.name);
// 随心:若当前主题为随心,进入 Home 时确保状态就绪(仅冷启动会话重算)
if (mode === 'suixin') {
ensureSuixinReady().catch(() => {
// ignore失败时回退默认中性底色
setSuixinBgColor(NEUTRAL_THEME_COLORS[1]);
});
}
// 语言切换时:旧语言缓存不复用,触发重新拉取
if (cache && cache.items.length > 0 && (cache.lang ?? 'en') === recoLang) {
@@ -194,16 +252,19 @@ export default function HomeScreen() {
return () => {
cancelled = true;
};
}, [fetchNewFeed, recoLang])
}, [fetchNewFeed, recoLang, ensureSuixinReady])
);
const backgroundColor = useMemo(() => {
if (themeMode === 'suixin') {
return suixinBgColor;
}
if (themeMode === 'color') {
const colorIndex = Math.floor(index / 10) % THEME_COLORS.length;
return THEME_COLORS[colorIndex];
}
return '#F4D6C2'; // 风景模式下的默认底色(图片加载前显示)
}, [themeMode, index]);
}, [themeMode, suixinBgColor, index]);
// 计算当前应该显示的风景图索引(滑动 10 次切换一张)
const natureImageIndex = useMemo(() => {
@@ -233,6 +294,7 @@ export default function HomeScreen() {
// 2. 切换数据索引
runOnJS(setIndex)(index + 1);
runOnJS(setLikeFilled)(false);
runOnJS(advanceSuixinOnNextContent)();
// 检查是否需要拉取新文案(当接近当前列表末尾时,例如还剩 5 条)
if (index + 5 >= currentFeed.length && !isFetching) {
@@ -333,6 +395,13 @@ export default function HomeScreen() {
setThemeModeState(next);
await setThemeMode(next);
setThemeOpen(false);
// 切换到随心:不主动重算(除非冷启动会话变化/状态不存在),仅确保可用
if (next === 'suixin') {
await ensureSuixinReady().catch(() => {
setSuixinBgColor(NEUTRAL_THEME_COLORS[1]);
});
}
}
return (