fix:小组件- PUSH
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import * as Crypto from 'expo-crypto';
|
||||
import type { UserProfileV1_2_Extended } from '@/src/features/userProfileScoring';
|
||||
|
||||
/**
|
||||
* 本地存储 key 统一管理,避免 UI 里散落硬编码
|
||||
*/
|
||||
const KEY_CLIENT_USER_ID = 'client.userId';
|
||||
const KEY_ONBOARDING_COMPLETED = 'onboarding.completed';
|
||||
const KEY_PUSH_PROMPT_STATE = 'push.promptState';
|
||||
const KEY_CONTENT_REACTIONS = 'content.reactions';
|
||||
@@ -31,7 +33,16 @@ export type UserProfile = {
|
||||
*/
|
||||
export type UserProfileScoring = UserProfileV1_2_Extended;
|
||||
export type DailyReminderSettings = {
|
||||
/**
|
||||
* 每天推送次数:
|
||||
* - 0:关闭
|
||||
* - 1~5:每天推送 1~5 次
|
||||
*/
|
||||
timesPerDay: number;
|
||||
/**
|
||||
* 仅用于 UI 展示与交互(开关)。
|
||||
* 后端偏好建议使用:enabled = pushEnabled && timesPerDay > 0
|
||||
*/
|
||||
pushEnabled: boolean;
|
||||
};
|
||||
|
||||
@@ -81,6 +92,49 @@ async function setJson<T>(key: string, value: T): Promise<void> {
|
||||
await AsyncStorage.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
function looksLikeUuid(v: string): boolean {
|
||||
// 宽松校验:8-4-4-4-12(不强依赖大小写)
|
||||
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);
|
||||
}
|
||||
|
||||
function uuidV4FromBytes(bytes: Uint8Array): string {
|
||||
// RFC 4122 v4:设置 version 与 variant
|
||||
const b = new Uint8Array(bytes);
|
||||
b[6] = (b[6] & 0x0f) | 0x40;
|
||||
b[8] = (b[8] & 0x3f) | 0x80;
|
||||
|
||||
const hex = Array.from(b)
|
||||
.map((x) => x.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
||||
}
|
||||
|
||||
async function generateUuidV4(): Promise<string> {
|
||||
// 1) 优先使用运行时提供的 randomUUID(如可用)
|
||||
const maybeCrypto = (globalThis as unknown as { crypto?: { randomUUID?: () => string } }).crypto;
|
||||
if (maybeCrypto?.randomUUID) return maybeCrypto.randomUUID();
|
||||
|
||||
// 2) 使用 expo-crypto 生成安全随机数(推荐)
|
||||
const bytes = await Crypto.getRandomBytesAsync(16);
|
||||
return uuidV4FromBytes(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或生成客户端用户标识(UUID)。
|
||||
*
|
||||
* 说明:
|
||||
* - `client_user_id` 用于与后端关联 Push Token 与用户推送偏好
|
||||
* - 它是“安装实例标识”,不等同真实用户账号
|
||||
*/
|
||||
export async function getOrCreateClientUserId(): Promise<string> {
|
||||
const raw = await AsyncStorage.getItem(KEY_CLIENT_USER_ID);
|
||||
if (raw && looksLikeUuid(raw)) return raw;
|
||||
|
||||
const next = await generateUuidV4();
|
||||
await AsyncStorage.setItem(KEY_CLIENT_USER_ID, next);
|
||||
return next;
|
||||
}
|
||||
|
||||
export async function getOnboardingCompleted(): Promise<boolean> {
|
||||
const raw = await AsyncStorage.getItem(KEY_ONBOARDING_COMPLETED);
|
||||
return raw === 'true';
|
||||
@@ -272,13 +326,15 @@ export async function getDailyReminderSettings(): Promise<DailyReminderSettings>
|
||||
});
|
||||
const timesPerDay = Number.isFinite(s.timesPerDay) ? s.timesPerDay : 3;
|
||||
return {
|
||||
timesPerDay: Math.min(10, Math.max(1, Math.round(timesPerDay))),
|
||||
// 需求:0~5(0 表示关闭)
|
||||
timesPerDay: Math.min(5, Math.max(0, Math.round(timesPerDay))),
|
||||
pushEnabled: Boolean(s.pushEnabled),
|
||||
};
|
||||
}
|
||||
|
||||
export async function setDailyReminderSettings(settings: DailyReminderSettings): Promise<void> {
|
||||
const timesPerDay = Math.min(10, Math.max(1, Math.round(settings.timesPerDay)));
|
||||
// 需求:0~5(0 表示关闭)
|
||||
const timesPerDay = Math.min(5, Math.max(0, Math.round(settings.timesPerDay)));
|
||||
await setJson(KEY_DAILY_REMINDER_SETTINGS, {
|
||||
timesPerDay,
|
||||
pushEnabled: Boolean(settings.pushEnabled),
|
||||
|
||||
Reference in New Issue
Block a user