IOS小组件/文案
This commit is contained in:
@@ -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 (
|
||||
|
||||
@@ -17,6 +17,9 @@ import { getOrCreateClientUserId } from '@/src/storage/appStorage';
|
||||
Notifications.setNotificationHandler({
|
||||
handleNotification: async () => ({
|
||||
shouldShowAlert: true,
|
||||
// 新版 expo-notifications 类型要求显式返回 banner/list 行为
|
||||
shouldShowBanner: true,
|
||||
shouldShowList: true,
|
||||
shouldPlaySound: false,
|
||||
shouldSetBadge: false,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user