import formProvider from '@ohos.app.form.formProvider'; import formBindingData from '@ohos.app.form.formBindingData'; import preferences from '@ohos.data.preferences'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { WidgetData, WidgetSize, WidgetTheme, FormattedWidgetData, PreferencesData, CacheStats } from './WidgetTypes'; const TAG = 'WidgetDataManager'; const WIDGET_PREFERENCES_NAME = 'widget_data_prefs'; const CACHE_EXPIRY_TIME = 30 * 1000; // 30秒缓存过期时间 /** * 缓存项接口 */ interface CacheItem { data: WidgetData; timestamp: number; expiry: number; } /** * 卡片数据管理器 * 负责卡片数据的持久化存储、缓存和更新 */ export class WidgetDataManager { private preferencesStore: preferences.Preferences | null = null; private dataCache: Map = new Map(); private lastUpdateTime: number = 0; private context: object | null = null; constructor(context?: object) { this.context = context || null; this.initPreferences(); this.startCacheCleanup(); } /** * 初始化数据存储 */ private async initPreferences(): Promise { try { if (this.context) { const store = await preferences.getPreferences(this.context as Context, WIDGET_PREFERENCES_NAME); this.preferencesStore = store; hilog.info(0x0000, TAG, 'Preferences initialized successfully with context'); } else { hilog.warn(0x0000, TAG, 'No context provided, preferences initialization skipped'); } } catch (error) { hilog.error(0x0000, TAG, `Failed to initialize preferences: ${error}`); } } /** * 获取初始卡片数据 */ getInitialWidgetData(): WidgetData { const initialData: WidgetData = { playState: { isPlaying: false, isPaused: true, isLoading: false }, currentSong: { id: '', title: '暂无播放', artist: '未知艺术家', album: '未知专辑', coverImagePath: '', duration: 0 }, progress: { currentPosition: 0, duration: 0, percentage: 0, currentTimeText: '00:00', totalTimeText: '00:00' }, playlist: { hasNext: false, hasPrevious: false, currentIndex: 0, totalCount: 0 }, config: { size: WidgetSize.MEDIUM, theme: WidgetTheme.AUTO, showProgress: true, showCover: true } }; return initialData; } /** * 保存卡片数据 */ async saveWidgetData(formId: string, data: WidgetData | FormattedWidgetData): Promise { try { if (!this.preferencesStore) { await this.initPreferences(); } const dataKey = `widget_${formId}`; await this.preferencesStore?.put(dataKey, JSON.stringify(data)); await this.preferencesStore?.flush(); hilog.info(0x0000, TAG, `Widget data saved for form: ${formId}`); } catch (error) { hilog.error(0x0000, TAG, `Failed to save widget data: ${error}`); } } /** * 获取卡片数据 */ async getWidgetData(formId: string): Promise { try { if (!this.preferencesStore) { await this.initPreferences(); } const dataKey = `widget_${formId}`; const dataStr = await this.preferencesStore?.get(dataKey, '') as string; if (dataStr) { return JSON.parse(dataStr) as WidgetData; } else { return this.getInitialWidgetData() as WidgetData; } } catch (error) { hilog.error(0x0000, TAG, `Failed to get widget data: ${error}`); return this.getInitialWidgetData() as WidgetData; } } /** * 更新卡片显示 */ async updateWidget(formId: string, data: WidgetData | FormattedWidgetData): Promise { try { hilog.info(0x0000, TAG, `Updating widget ${formId} with data type check`); let formattedData: FormattedWidgetData; // 检查数据类型,如果已经是格式化数据则直接使用 if (this.isFormattedWidgetData(data)) { hilog.info(0x0000, TAG, `Data is already formatted for widget ${formId}`); formattedData = data as FormattedWidgetData; // 如果是格式化数据,需要转换回WidgetData进行存储 const widgetData = this.convertToWidgetData(formattedData); await this.saveWidgetData(formId, widgetData); } else { hilog.info(0x0000, TAG, `Formatting raw data for widget ${formId}`); // 保存原始数据到本地存储 await this.saveWidgetData(formId, data as WidgetData); // 格式化数据用于卡片显示 formattedData = this.formatDataForWidget(data as WidgetData); } hilog.info(0x0000, TAG, `Final formatted data for ${formId}: isPlaying=${formattedData.isPlaying}, hasNext=${formattedData.hasNext}, hasPrevious=${formattedData.hasPrevious}, title=${formattedData.songTitle}, coverImage=${formattedData.coverImage ? 'present' : 'empty'}, showCover=${formattedData.showCover}`); // 创建卡片绑定数据 const formData = formBindingData.createFormBindingData(formattedData); // 更新卡片 await formProvider.updateForm(formId, formData); hilog.info(0x0000, TAG, `Widget updated successfully: ${formId}`); } catch (error) { hilog.error(0x0000, TAG, `Failed to update widget: ${error}`); } } /** * 批量更新所有卡片 */ async updateAllWidgets(data?: WidgetData): Promise { try { if (!this.preferencesStore) { await this.initPreferences(); } // 获取所有卡片ID const allKeys = await this.preferencesStore?.getAll(); const emptyPrefs: PreferencesData = {}; const widgetKeys = Object.keys(allKeys || emptyPrefs).filter(key => key.startsWith('widget_')); for (const key of widgetKeys) { const formId: string = key.replace('widget_', ''); const widgetData: WidgetData = data || await this.getWidgetData(formId); await this.updateWidget(formId, widgetData); } hilog.info(0x0000, TAG, `Updated ${widgetKeys.length} widgets`); } catch (error) { hilog.error(0x0000, TAG, `Failed to update all widgets: ${error}`); } } /** * 删除卡片数据 */ async removeWidgetData(formId: string): Promise { try { if (!this.preferencesStore) { await this.initPreferences(); } const dataKey = `widget_${formId}`; await this.preferencesStore?.delete(dataKey); await this.preferencesStore?.flush(); hilog.info(0x0000, TAG, `Widget data removed for form: ${formId}`); } catch (error) { hilog.error(0x0000, TAG, `Failed to remove widget data: ${error}`); } } /** * 格式化数据用于卡片显示 */ public formatDataForWidget(data: WidgetData): FormattedWidgetData { hilog.info(0x0000, TAG, `Formatting data for widget: hasNext=${data.playlist.hasNext}, hasPrevious=${data.playlist.hasPrevious}, currentIndex=${data.playlist.currentIndex}, totalCount=${data.playlist.totalCount}`); // 修复按钮状态:基于当前索引和总数重新计算正确的按钮状态 let hasNext = data.playlist.hasNext; let hasPrevious = data.playlist.hasPrevious; hilog.info(0x0000, TAG, `Original button states: hasNext=${hasNext}, hasPrevious=${hasPrevious}, currentIndex=${data.playlist.currentIndex}, totalCount=${data.playlist.totalCount}`); // 如果有播放列表,重新计算按钮状态 if (data.playlist.totalCount > 1) { const correctHasNext = data.playlist.currentIndex < data.playlist.totalCount - 1; const correctHasPrevious = data.playlist.currentIndex > 0; // 如果计算出的状态与当前状态不一致,进行修复 if (hasNext !== correctHasNext || hasPrevious !== correctHasPrevious) { hasNext = correctHasNext; hasPrevious = correctHasPrevious; hilog.info(0x0000, TAG, `Fixed button states: hasNext=${hasNext}, hasPrevious=${hasPrevious}, currentIndex=${data.playlist.currentIndex}, totalCount=${data.playlist.totalCount}`); } else { hilog.info(0x0000, TAG, `Button states are correct: hasNext=${hasNext}, hasPrevious=${hasPrevious}`); } } else if (data.playlist.totalCount <= 1) { // 如果只有一首歌或没有歌,按钮都应该禁用 hasNext = false; hasPrevious = false; hilog.info(0x0000, TAG, `Single or no song, buttons disabled: totalCount=${data.playlist.totalCount}`); } const formattedData: FormattedWidgetData = { // 播放状态 isPlaying: data.playState.isPlaying, isPaused: data.playState.isPaused, isLoading: data.playState.isLoading, // 歌曲信息 songTitle: this.truncateText(data.currentSong.title, 20), songArtist: this.truncateText(data.currentSong.artist, 15), songAlbum: this.truncateText(data.currentSong.album, 15), coverImage: data.currentSong.coverImagePath && data.currentSong.coverImagePath.trim() !== '' ? data.currentSong.coverImagePath : '', // 播放进度 currentTime: data.progress.currentTimeText, totalTime: data.progress.totalTimeText, progressPercentage: data.progress.percentage, // 控制按钮状态(使用修复后的值) hasNext: hasNext, hasPrevious: hasPrevious, // 卡片配置 showProgress: data.config.showProgress, showCover: data.config.showCover, widgetSize: data.config.size as string, // 时间戳用于强制更新 timestamp: Date.now() }; hilog.info(0x0000, TAG, `Formatted widget data: isPlaying=${formattedData.isPlaying}, hasNext=${formattedData.hasNext}, hasPrevious=${formattedData.hasPrevious}, title=${formattedData.songTitle}`); return formattedData; } /** * 截断文本 */ private truncateText(text: string, maxLength: number): string { if (text.length <= maxLength) { return text; } return text.substring(0, maxLength - 1) + '…'; } /** * 启动缓存清理定时器 */ private startCacheCleanup(): void { setInterval(() => { this.cleanExpiredCache(); }, 60 * 1000); // 每分钟清理一次过期缓存 } /** * 清理过期缓存 */ private cleanExpiredCache(): void { const now = Date.now(); const expiredKeys: string[] = []; this.dataCache.forEach((item: CacheItem, key: string) => { if (now > item.expiry) { expiredKeys.push(key); } }); expiredKeys.forEach((key: string) => { this.dataCache.delete(key); }); if (expiredKeys.length > 0) { hilog.info(0x0000, TAG, `Cleaned ${expiredKeys.length} expired cache items`); } } /** * 从缓存获取数据 */ private getCachedData(formId: string): WidgetData | null { const cacheKey = `cache_${formId}`; const cacheItem = this.dataCache.get(cacheKey); if (cacheItem && Date.now() < cacheItem.expiry) { hilog.info(0x0000, TAG, `Cache hit for form: ${formId}`); return cacheItem.data; } if (cacheItem) { // 缓存已过期,删除 this.dataCache.delete(cacheKey); hilog.info(0x0000, TAG, `Cache expired for form: ${formId}`); } return null; } /** * 设置缓存数据 */ private setCachedData(formId: string, data: WidgetData): void { const cacheKey = `cache_${formId}`; const now = Date.now(); const cacheItem: CacheItem = { data: data, timestamp: now, expiry: now + CACHE_EXPIRY_TIME }; this.dataCache.set(cacheKey, cacheItem); hilog.info(0x0000, TAG, `Data cached for form: ${formId}`); } /** * 获取卡片数据(带缓存) */ async getWidgetDataWithCache(formId: string): Promise { // 先尝试从缓存获取 const cachedData = this.getCachedData(formId); if (cachedData) { return cachedData; } // 缓存未命中,从持久化存储获取 const data = await this.getWidgetData(formId); // 设置缓存 this.setCachedData(formId, data); return data; } /** * 更新卡片数据(带缓存) */ async updateWidgetWithCache(formId: string, data: WidgetData): Promise { // 更新缓存 this.setCachedData(formId, data); // 更新卡片显示 await this.updateWidget(formId, data); } /** * 清除指定卡片的缓存 */ clearWidgetCache(formId: string): void { const cacheKey = `cache_${formId}`; if (this.dataCache.has(cacheKey)) { this.dataCache.delete(cacheKey); hilog.info(0x0000, TAG, `Cache cleared for form: ${formId}`); } } /** * 清除所有缓存 */ clearAllCache(): void { const cacheSize = this.dataCache.size; this.dataCache.clear(); hilog.info(0x0000, TAG, `All cache cleared, ${cacheSize} items removed`); } /** * 获取缓存统计信息 */ getCacheStats(): CacheStats { const stats: CacheStats = { size: this.dataCache.size, hitRate: 0, // 可以在实际使用中统计命中率 lastUpdate: this.lastUpdateTime }; return stats; } /** * 预热缓存 */ async preloadCache(): Promise { try { if (!this.preferencesStore) { await this.initPreferences(); } const allKeys = await this.preferencesStore?.getAll(); const emptyPrefs: PreferencesData = {}; const widgetKeys = Object.keys(allKeys || emptyPrefs).filter(key => key.startsWith('widget_')); for (const key of widgetKeys) { const formId: string = key.replace('widget_', ''); const data: WidgetData = await this.getWidgetData(formId); this.setCachedData(formId, data); } hilog.info(0x0000, TAG, `Cache preloaded for ${widgetKeys.length} widgets`); } catch (error) { hilog.error(0x0000, TAG, `Failed to preload cache: ${error}`); } } /** * 检查是否为格式化的卡片数据 */ private isFormattedWidgetData(data: WidgetData | FormattedWidgetData): boolean { // FormattedWidgetData有timestamp字段,而WidgetData没有 return (data as FormattedWidgetData).timestamp !== undefined && typeof (data as FormattedWidgetData).timestamp === 'number'; } /** * 将格式化数据转换为WidgetData */ private convertToWidgetData(formattedData: FormattedWidgetData): WidgetData { const widgetData: WidgetData = { playState: { isPlaying: formattedData.isPlaying, isPaused: formattedData.isPaused, isLoading: formattedData.isLoading }, currentSong: { id: '', // FormattedWidgetData中没有id,使用空字符串 title: formattedData.songTitle, artist: formattedData.songArtist, album: formattedData.songAlbum, coverImagePath: formattedData.coverImage, duration: 0 // FormattedWidgetData中没有duration,使用0 }, progress: { currentPosition: 0, // 需要从时间文本反推,这里简化处理 duration: 0, percentage: formattedData.progressPercentage, currentTimeText: formattedData.currentTime, totalTimeText: formattedData.totalTime }, playlist: { hasNext: formattedData.hasNext, hasPrevious: formattedData.hasPrevious, currentIndex: 0, totalCount: 0 }, config: { size: this.parseWidgetSize(formattedData.widgetSize), theme: WidgetTheme.AUTO, showProgress: formattedData.showProgress, showCover: formattedData.showCover } }; return widgetData; } /** * 解析卡片尺寸字符串 */ private parseWidgetSize(sizeStr: string): WidgetSize { switch (sizeStr.toLowerCase()) { case 'small': return WidgetSize.SMALL; case 'large': return WidgetSize.LARGE; case 'medium': default: return WidgetSize.MEDIUM; } } }