|
@@ -0,0 +1,404 @@
|
|
|
|
|
+import preferences from '@ohos.data.preferences';
|
|
|
|
|
+import { hilog } from '@kit.PerformanceAnalysisKit';
|
|
|
|
|
+import { WidgetSize, WidgetTheme } from './WidgetTypes';
|
|
|
|
|
+import { UserPreferences } from './WidgetConfigManager';
|
|
|
|
|
+import { ObjectUtils } from './ObjectUtils';
|
|
|
|
|
+
|
|
|
|
|
+const TAG = 'Heanup UserPreferencesService';
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 用户偏好设置错误类
|
|
|
|
|
+ */
|
|
|
|
|
+class UserPreferencesError extends Error {
|
|
|
|
|
+ constructor(message: string) {
|
|
|
|
|
+ super(message);
|
|
|
|
|
+ this.name = 'UserPreferencesError';
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+const USER_PREFERENCES_NAME = 'user_widget_preferences';
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 偏好设置变化事件接口
|
|
|
|
|
+ */
|
|
|
|
|
+export interface PreferencesChangeEvent {
|
|
|
|
|
+ type: 'preferences_changed';
|
|
|
|
|
+ changedKeys: string[];
|
|
|
|
|
+ newPreferences: UserPreferences;
|
|
|
|
|
+ timestamp: number;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 用户偏好设置服务
|
|
|
|
|
+ * 负责用户偏好设置的存储和管理
|
|
|
|
|
+ * 需求: 5.4, 6.3
|
|
|
|
|
+ */
|
|
|
|
|
+export class UserPreferencesService {
|
|
|
|
|
+ private static instance: UserPreferencesService;
|
|
|
|
|
+ private preferencesStore: preferences.Preferences | null = null;
|
|
|
|
|
+ private currentPreferences: UserPreferences | null = null;
|
|
|
|
|
+ private changeListeners: Set<PreferencesChangeListener> = new Set();
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取单例实例
|
|
|
|
|
+ */
|
|
|
|
|
+ public static getInstance(): UserPreferencesService {
|
|
|
|
|
+ if (!UserPreferencesService.instance) {
|
|
|
|
|
+ UserPreferencesService.instance = new UserPreferencesService();
|
|
|
|
|
+ }
|
|
|
|
|
+ return UserPreferencesService.instance;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private constructor() {
|
|
|
|
|
+ this.initPreferences();
|
|
|
|
|
+ hilog.info(0x0000, TAG, 'UserPreferencesService initialized');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 初始化偏好设置存储
|
|
|
|
|
+ */
|
|
|
|
|
+ private async initPreferences(): Promise<void> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ this.preferencesStore = await preferences.getPreferences(getContext(), USER_PREFERENCES_NAME);
|
|
|
|
|
+ await this.loadPreferences();
|
|
|
|
|
+ hilog.info(0x0000, TAG, 'User preferences initialized successfully');
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ hilog.error(0x0000, TAG, `Failed to initialize user preferences: ${error}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取用户偏好设置
|
|
|
|
|
+ */
|
|
|
|
|
+ public async getPreferences(): Promise<UserPreferences> {
|
|
|
|
|
+ if (this.currentPreferences) {
|
|
|
|
|
+ return this.currentPreferences;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await this.loadPreferences();
|
|
|
|
|
+ return this.currentPreferences || this.getDefaultPreferences();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 更新用户偏好设置
|
|
|
|
|
+ * @param updates 要更新的偏好设置
|
|
|
|
|
+ */
|
|
|
|
|
+ public async updatePreferences(updates: Partial<UserPreferences>): Promise<void> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const currentPrefs = await this.getPreferences();
|
|
|
|
|
+ const newPreferences: UserPreferences = ObjectUtils.mergeUserPreferences(currentPrefs, updates);
|
|
|
|
|
+
|
|
|
|
|
+ await this.savePreferences(newPreferences);
|
|
|
|
|
+
|
|
|
|
|
+ // 通知变化监听器
|
|
|
|
|
+ const changedKeys = Object.keys(updates);
|
|
|
|
|
+ this.notifyPreferencesChange(changedKeys, newPreferences);
|
|
|
|
|
+
|
|
|
|
|
+ hilog.info(0x0000, TAG, `User preferences updated: ${changedKeys.join(', ')}`);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ hilog.error(0x0000, TAG, `Failed to update user preferences: ${error}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 重置用户偏好设置为默认值
|
|
|
|
|
+ */
|
|
|
|
|
+ public async resetPreferences(): Promise<void> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const defaultPrefs = this.getDefaultPreferences();
|
|
|
|
|
+ await this.savePreferences(defaultPrefs);
|
|
|
|
|
+
|
|
|
|
|
+ // 通知所有设置都已更改
|
|
|
|
|
+ const allKeys = Object.keys(defaultPrefs);
|
|
|
|
|
+ this.notifyPreferencesChange(allKeys, defaultPrefs);
|
|
|
|
|
+
|
|
|
|
|
+ hilog.info(0x0000, TAG, 'User preferences reset to default');
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ hilog.error(0x0000, TAG, `Failed to reset user preferences: ${error}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取特定偏好设置值
|
|
|
|
|
+ * @param key 设置键
|
|
|
|
|
+ * @returns 设置值
|
|
|
|
|
+ */
|
|
|
|
|
+ public async getPreference(key: string): Promise<Object> {
|
|
|
|
|
+ const preferences = await this.getPreferences();
|
|
|
|
|
+ const prefsObj = preferences as Object as Record<string, Object>;
|
|
|
|
|
+ return prefsObj[key];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置特定偏好设置值
|
|
|
|
|
+ * @param key 设置键
|
|
|
|
|
+ * @param value 设置值
|
|
|
|
|
+ */
|
|
|
|
|
+ public async setPreference(key: string, value: Object): Promise<void> {
|
|
|
|
|
+ const updates: Partial<UserPreferences> = {};
|
|
|
|
|
+ const updatesRecord = updates as Record<string, Object>;
|
|
|
|
|
+ updatesRecord[key] = value;
|
|
|
|
|
+ await this.updatePreferences(updates);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 导出用户偏好设置
|
|
|
|
|
+ * @returns 偏好设置的JSON字符串
|
|
|
|
|
+ */
|
|
|
|
|
+ public async exportPreferences(): Promise<string> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const preferences = await this.getPreferences();
|
|
|
|
|
+ return JSON.stringify(preferences, null, 2);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ hilog.error(0x0000, TAG, `Failed to export preferences: ${error}`);
|
|
|
|
|
+ throw new UserPreferencesError(`Failed to export preferences: ${error}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 导入用户偏好设置
|
|
|
|
|
+ * @param preferencesJson 偏好设置的JSON字符串
|
|
|
|
|
+ */
|
|
|
|
|
+ public async importPreferences(preferencesJson: string): Promise<void> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const importedPrefs = JSON.parse(preferencesJson) as UserPreferences;
|
|
|
|
|
+
|
|
|
|
|
+ // 验证导入的数据
|
|
|
|
|
+ const validatedPrefs = this.validatePreferences(importedPrefs);
|
|
|
|
|
+
|
|
|
|
|
+ await this.savePreferences(validatedPrefs);
|
|
|
|
|
+
|
|
|
|
|
+ // 通知所有设置都已更改
|
|
|
|
|
+ const allKeys = Object.keys(validatedPrefs);
|
|
|
|
|
+ this.notifyPreferencesChange(allKeys, validatedPrefs);
|
|
|
|
|
+
|
|
|
|
|
+ hilog.info(0x0000, TAG, 'User preferences imported successfully');
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ hilog.error(0x0000, TAG, `Failed to import preferences: ${error}`);
|
|
|
|
|
+ throw new UserPreferencesError(`Failed to import preferences: ${error}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 注册偏好设置变化监听器
|
|
|
|
|
+ * @param listener 监听器
|
|
|
|
|
+ */
|
|
|
|
|
+ public addPreferencesChangeListener(listener: PreferencesChangeListener): void {
|
|
|
|
|
+ this.changeListeners.add(listener);
|
|
|
|
|
+ hilog.info(0x0000, TAG, 'Preferences change listener added');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 移除偏好设置变化监听器
|
|
|
|
|
+ * @param listener 监听器
|
|
|
|
|
+ */
|
|
|
|
|
+ public removePreferencesChangeListener(listener: PreferencesChangeListener): void {
|
|
|
|
|
+ this.changeListeners.delete(listener);
|
|
|
|
|
+ hilog.info(0x0000, TAG, 'Preferences change listener removed');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 检查偏好设置是否为默认值
|
|
|
|
|
+ */
|
|
|
|
|
+ public async isDefaultPreferences(): Promise<boolean> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const currentPrefs = await this.getPreferences();
|
|
|
|
|
+ const defaultPrefs = this.getDefaultPreferences();
|
|
|
|
|
+
|
|
|
|
|
+ return JSON.stringify(currentPrefs) === JSON.stringify(defaultPrefs);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ hilog.error(0x0000, TAG, `Failed to check if preferences are default: ${error}`);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取偏好设置统计信息
|
|
|
|
|
+ */
|
|
|
|
|
+ public async getPreferencesStats(): Promise<PreferencesStats> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const preferences = await this.getPreferences();
|
|
|
|
|
+ const isDefault = await this.isDefaultPreferences();
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ isDefault: isDefault,
|
|
|
|
|
+ syncEnabled: preferences.syncWithMainApp,
|
|
|
|
|
+ themeMode: preferences.defaultTheme,
|
|
|
|
|
+ cacheEnabled: preferences.enableCache,
|
|
|
|
|
+ privacyLevel: this.calculatePrivacyLevel(preferences),
|
|
|
|
|
+ lastModified: Date.now() // 实际应该存储真实的修改时间
|
|
|
|
|
+ };
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ hilog.error(0x0000, TAG, `Failed to get preferences stats: ${error}`);
|
|
|
|
|
+ throw new UserPreferencesError(`Failed to get preferences stats: ${error}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 加载偏好设置
|
|
|
|
|
+ */
|
|
|
|
|
+ private async loadPreferences(): Promise<void> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (!this.preferencesStore) {
|
|
|
|
|
+ await this.initPreferences();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const prefsStr = await this.preferencesStore?.get('user_preferences', '') as string;
|
|
|
|
|
+ if (prefsStr) {
|
|
|
|
|
+ this.currentPreferences = JSON.parse(prefsStr) as UserPreferences;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.currentPreferences = this.getDefaultPreferences();
|
|
|
|
|
+ await this.savePreferences(this.currentPreferences);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ hilog.error(0x0000, TAG, `Failed to load preferences: ${error}`);
|
|
|
|
|
+ this.currentPreferences = this.getDefaultPreferences();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 保存偏好设置
|
|
|
|
|
+ */
|
|
|
|
|
+ private async savePreferences(preferences: UserPreferences): Promise<void> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (!this.preferencesStore) {
|
|
|
|
|
+ await this.initPreferences();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await this.preferencesStore?.put('user_preferences', JSON.stringify(preferences));
|
|
|
|
|
+ await this.preferencesStore?.flush();
|
|
|
|
|
+
|
|
|
|
|
+ this.currentPreferences = preferences;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ hilog.error(0x0000, TAG, `Failed to save preferences: ${error}`);
|
|
|
|
|
+ throw new UserPreferencesError(`Failed to save preferences: ${error}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取默认偏好设置
|
|
|
|
|
+ */
|
|
|
|
|
+ private getDefaultPreferences(): UserPreferences {
|
|
|
|
|
+ return {
|
|
|
|
|
+ // 全局偏好
|
|
|
|
|
+ defaultTheme: WidgetTheme.AUTO,
|
|
|
|
|
+ defaultSize: WidgetSize.MEDIUM,
|
|
|
|
|
+
|
|
|
|
|
+ // 同步设置
|
|
|
|
|
+ syncWithMainApp: true,
|
|
|
|
|
+ syncThemeColor: true,
|
|
|
|
|
+
|
|
|
|
|
+ // 性能设置
|
|
|
|
|
+ enableCache: true,
|
|
|
|
|
+ cacheExpiry: 30000,
|
|
|
|
|
+
|
|
|
|
|
+ // 隐私设置
|
|
|
|
|
+ showSongInfo: true,
|
|
|
|
|
+ showArtistInfo: true,
|
|
|
|
|
+ showAlbumCover: true
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 验证偏好设置数据
|
|
|
|
|
+ */
|
|
|
|
|
+ private validatePreferences(preferences: Object): UserPreferences {
|
|
|
|
|
+ const defaultPrefs = this.getDefaultPreferences();
|
|
|
|
|
+
|
|
|
|
|
+ // 类型保护:确保 preferences 是一个对象
|
|
|
|
|
+ if (typeof preferences !== 'object' || preferences === null) {
|
|
|
|
|
+ return defaultPrefs;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const prefsObj = preferences as Record<string, Object>;
|
|
|
|
|
+
|
|
|
|
|
+ // 确保所有必需的字段都存在且类型正确
|
|
|
|
|
+ return {
|
|
|
|
|
+ defaultTheme: this.validateTheme(prefsObj.defaultTheme) || defaultPrefs.defaultTheme,
|
|
|
|
|
+ defaultSize: this.validateSize(prefsObj.defaultSize) || defaultPrefs.defaultSize,
|
|
|
|
|
+ syncWithMainApp: typeof prefsObj.syncWithMainApp === 'boolean' ? prefsObj.syncWithMainApp : defaultPrefs.syncWithMainApp,
|
|
|
|
|
+ syncThemeColor: typeof prefsObj.syncThemeColor === 'boolean' ? prefsObj.syncThemeColor : defaultPrefs.syncThemeColor,
|
|
|
|
|
+ enableCache: typeof prefsObj.enableCache === 'boolean' ? prefsObj.enableCache : defaultPrefs.enableCache,
|
|
|
|
|
+ cacheExpiry: typeof prefsObj.cacheExpiry === 'number' && prefsObj.cacheExpiry > 0 ? prefsObj.cacheExpiry : defaultPrefs.cacheExpiry,
|
|
|
|
|
+ showSongInfo: typeof prefsObj.showSongInfo === 'boolean' ? prefsObj.showSongInfo : defaultPrefs.showSongInfo,
|
|
|
|
|
+ showArtistInfo: typeof prefsObj.showArtistInfo === 'boolean' ? prefsObj.showArtistInfo : defaultPrefs.showArtistInfo,
|
|
|
|
|
+ showAlbumCover: typeof prefsObj.showAlbumCover === 'boolean' ? prefsObj.showAlbumCover : defaultPrefs.showAlbumCover
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 验证主题值
|
|
|
|
|
+ */
|
|
|
|
|
+ private validateTheme(theme: Object): WidgetTheme | null {
|
|
|
|
|
+ const themeValues = [WidgetTheme.AUTO, WidgetTheme.LIGHT, WidgetTheme.DARK];
|
|
|
|
|
+ if (themeValues.includes(theme as WidgetTheme)) {
|
|
|
|
|
+ return theme as WidgetTheme;
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 验证尺寸值
|
|
|
|
|
+ */
|
|
|
|
|
+ private validateSize(size: Object): WidgetSize | null {
|
|
|
|
|
+ const sizeValues = [WidgetSize.SMALL, WidgetSize.MEDIUM, WidgetSize.LARGE];
|
|
|
|
|
+ if (sizeValues.includes(size as WidgetSize)) {
|
|
|
|
|
+ return size as WidgetSize;
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 计算隐私级别
|
|
|
|
|
+ */
|
|
|
|
|
+ private calculatePrivacyLevel(preferences: UserPreferences): 'high' | 'medium' | 'low' {
|
|
|
|
|
+ let privacyScore = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (!preferences.showSongInfo) privacyScore++;
|
|
|
|
|
+ if (!preferences.showArtistInfo) privacyScore++;
|
|
|
|
|
+ if (!preferences.showAlbumCover) privacyScore++;
|
|
|
|
|
+
|
|
|
|
|
+ if (privacyScore >= 2) return 'high';
|
|
|
|
|
+ if (privacyScore === 1) return 'medium';
|
|
|
|
|
+ return 'low';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 通知偏好设置变化
|
|
|
|
|
+ */
|
|
|
|
|
+ private notifyPreferencesChange(changedKeys: string[], newPreferences: UserPreferences): void {
|
|
|
|
|
+ const event: PreferencesChangeEvent = {
|
|
|
|
|
+ type: 'preferences_changed',
|
|
|
|
|
+ changedKeys: changedKeys,
|
|
|
|
|
+ newPreferences: newPreferences,
|
|
|
|
|
+ timestamp: Date.now()
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const listeners = Array.from(this.changeListeners);
|
|
|
|
|
+ for (let i = 0; i < listeners.length; i++) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ listeners[i].onPreferencesChanged(event);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ hilog.error(0x0000, TAG, `Error notifying preferences change listener: ${error}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 偏好设置变化监听器接口
|
|
|
|
|
+ */
|
|
|
|
|
+export interface PreferencesChangeListener {
|
|
|
|
|
+ onPreferencesChanged(event: PreferencesChangeEvent): void;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 偏好设置统计信息接口
|
|
|
|
|
+ */
|
|
|
|
|
+export interface PreferencesStats {
|
|
|
|
|
+ isDefault: boolean;
|
|
|
|
|
+ syncEnabled: boolean;
|
|
|
|
|
+ themeMode: WidgetTheme;
|
|
|
|
|
+ cacheEnabled: boolean;
|
|
|
|
|
+ privacyLevel: 'high' | 'medium' | 'low';
|
|
|
|
|
+ lastModified: number;
|
|
|
|
|
+}
|