Selaa lähdekoodia

Merge branch 'master' into feature/桌面卡片

# Conflicts:
#	entry/src/main/resources/base/element/string.json
chendeben 1 vuosi sitten
vanhempi
sitoutus
87183a59ac

+ 2 - 2
AppScope/app.json5

@@ -2,8 +2,8 @@
   "app": {
     "bundleName": "com.xgplayer.ttmusic.hm",
     "vendor": "example",
-    "versionCode": 20250706,
-    "versionName": "1.4.5",
+    "versionCode": 20250730,
+    "versionName": "1.4.9",
     "icon": "$media:app_icon",
     "label": "$string:app_name",
     "multiAppMode": {

+ 2 - 2
build-profile.json5

@@ -30,11 +30,11 @@
         "material": {
           "certpath": "C:\\Users\\xjy08\\.ohos\\config\\default_com.xgplayer.ttmusic.hm4_li00v0NJ2KHEZXa_MEhSBo4Q7SMa2KHpzKM8lnBZT1Y=.cer",
           "keyAlias": "debugKey",
-          "keyPassword": "0000001BD7E2F4E219049C47BEB1C5DF12A84283327CAF6F062BF09C2DAD9BE662F2E0C6EF1B32269C0327",
+          "keyPassword": "0000001AB566250EBD7C67D2C8924C31CD3DFD677F81C557634D6A43B297C0A495387873160D9D53B05E",
           "profile": "C:\\Users\\xjy08\\.ohos\\config\\default_com.xgplayer.ttmusic.hm4_li00v0NJ2KHEZXa_MEhSBo4Q7SMa2KHpzKM8lnBZT1Y=.p7b",
           "signAlg": "SHA256withECDSA",
           "storeFile": "C:\\Users\\xjy08\\.ohos\\config\\default_com.xgplayer.ttmusic.hm4_li00v0NJ2KHEZXa_MEhSBo4Q7SMa2KHpzKM8lnBZT1Y=.p12",
-          "storePassword": "0000001B4A054D932A09F7166962992C88D0496184DEDEC08203B3BADBBC1BA9FC6C3F656C9AA5123B3F89"
+          "storePassword": "0000001AFC825305444178D488204AA8C04191C31A741521056131690BAFC0531CDA7D19F1D5E1E8031B"
         }
       }
     ]

+ 28 - 0
entry/src/main/ets/common/constants/UpdateConstants.ets

@@ -0,0 +1,28 @@
+/**
+ * 更新相关常量配置
+ */
+export class UpdateConstants {
+  /**
+   * 更新日志URL
+   * 可以根据需要修改为你的更新日志地址
+   */
+  static readonly UPDATE_LOG_URL: string = 'https://static.ss5.xyz/files/update_log.html';
+
+
+  /**
+   * 是否启用更新日志功能
+   */
+  static readonly ENABLE_UPDATE_LOG: boolean = true;
+  /**
+   * 是否在每次启动时都检查更新日志
+   */
+  static readonly CHECK_ON_EVERY_LAUNCH: boolean = false;
+  /**
+   * 更新日志弹窗显示延迟时间(毫秒)
+   */
+  static readonly DIALOG_SHOW_DELAY: number = 1500;
+  /**
+   * 构建时间戳(用于区分不同的构建版本)
+   */
+  static readonly BUILD_TIMESTAMP: string = Date.now().toString();
+}

+ 186 - 0
entry/src/main/ets/common/util/ConfigManager.ets

@@ -0,0 +1,186 @@
+/**
+ * 配置管理器
+ * 负责从远程API获取配置参数并保存到AppStorage中
+ */
+import { http } from '@kit.NetworkKit';
+import Logger from './Logger';
+
+/**
+ * 配置项接口定义
+ */
+export interface ConfigItem {
+  name: string;
+  description: string;
+  value: string | number | boolean;
+  type: 'json' | 'boolean' | 'number' | 'string';
+}
+
+/**
+ * JSON配置值类型 - 使用ESObject作为通用对象类型
+ */
+export type JsonConfigValue = ESObject;
+
+/**
+ * 配置值联合类型
+ */
+export type ConfigValue = string | number | boolean | ESObject;
+
+/**
+ * API响应接口定义
+ */
+export interface ConfigResponse {
+  code: number;
+  msg: string;
+  data: ConfigItem[];
+}
+
+/**
+ * 配置管理器类
+ */
+export class ConfigManager {
+  private static readonly TAG = 'ConfigManager';
+  private static readonly CONFIG_API_URL = 'https://pay.ss5.xyz/switches/lists';
+  private static readonly REQUEST_TIMEOUT = 5000; // 5秒超时
+
+  /**
+   * 初始化配置 - 从API获取配置并保存到AppStorage
+   * @returns Promise<boolean> 是否初始化成功
+   */
+  public static async initConfig(): Promise<boolean> {
+    try {
+      Logger.info(ConfigManager.TAG, '开始初始化配置...');
+      
+      const configData = await ConfigManager.fetchConfigFromAPI();
+      if (!configData) {
+        Logger.error(ConfigManager.TAG, '获取配置数据失败');
+        return false;
+      }
+
+      ConfigManager.saveConfigToAppStorage(configData);
+      Logger.info(ConfigManager.TAG, '配置初始化完成');
+      return true;
+    } catch (error) {
+      Logger.error(ConfigManager.TAG, '配置初始化失败:' + error);
+      return false;
+    }
+  }
+
+  /**
+   * 从API获取配置数据
+   * @returns Promise<ConfigItem[] | null> 配置数据数组或null
+   */
+  private static async fetchConfigFromAPI(): Promise<ConfigItem[] | null> {
+    try {
+      const httpRequest = http.createHttp();
+      const options: http.HttpRequestOptions = {
+        method: http.RequestMethod.GET,
+        readTimeout: ConfigManager.REQUEST_TIMEOUT,
+        connectTimeout: ConfigManager.REQUEST_TIMEOUT,
+        header: {
+          'Content-Type': 'application/json'
+        }
+      };
+
+      Logger.info(ConfigManager.TAG, '请求配置API: ' + ConfigManager.CONFIG_API_URL);
+      const response: http.HttpResponse = await httpRequest.request(ConfigManager.CONFIG_API_URL, options);
+
+      if (response.responseCode === 200) {
+        const responseData = response.result as string;
+        const configResponse: ConfigResponse = JSON.parse(responseData);
+        
+        if (configResponse.code === 0) {
+          Logger.info(ConfigManager.TAG, `成功获取${configResponse.data.length}个配置项`);
+          return configResponse.data;
+        } else {
+          Logger.error(ConfigManager.TAG, 'API返回错误:' + configResponse.msg);
+          return null;
+        }
+      } else {
+        Logger.error(ConfigManager.TAG, '请求失败,状态码:' + response.responseCode);
+        return null;
+      }
+    } catch (error) {
+      Logger.error(ConfigManager.TAG, '请求配置API异常:' + error);
+      return null;
+    }
+  }
+
+  /**
+   * 将配置数据保存到AppStorage
+   * @param configData 配置数据数组
+   */
+  private static saveConfigToAppStorage(configData: ConfigItem[]): void {
+    try {
+      configData.forEach(config => {
+        let processedValue: ConfigValue = config.value;
+        
+        // 根据类型处理值
+        switch (config.type) {
+          case 'json':
+            try {
+              processedValue = JSON.parse(config.value as string) as ESObject;
+            } catch (e) {
+              Logger.error(ConfigManager.TAG, `解析JSON配置失败 ${config.name}: ${e}`);
+              processedValue = config.value;
+            }
+            break;
+          case 'boolean':
+            processedValue = Boolean(config.value);
+            break;
+          case 'number':
+            processedValue = Number(config.value);
+            break;
+          case 'string':
+          default:
+            processedValue = String(config.value);
+            break;
+        }
+
+        // 保存到AppStorage
+        AppStorage.setOrCreate(config.name, processedValue);
+        Logger.info(ConfigManager.TAG, `保存配置 ${config.name}: ${processedValue} (${config.type})`);
+      });
+    } catch (error) {
+      Logger.error(ConfigManager.TAG, '保存配置到AppStorage失败:' + error);
+    }
+  }
+
+  /**
+   * 获取配置值
+   * @param key 配置键名
+   * @param defaultValue 默认值
+   * @returns 配置值
+   */
+  public static getConfig<T extends ConfigValue>(key: string, defaultValue: T): T {
+    try {
+      const value: ConfigValue | undefined = AppStorage.get(key) as ConfigValue | undefined;
+      return value !== undefined ? value as T : defaultValue;
+    } catch (error) {
+      Logger.error(ConfigManager.TAG, `获取配置失败 ${key}: ${error}`);
+      return defaultValue;
+    }
+  }
+
+  /**
+   * 设置配置值
+   * @param key 配置键名
+   * @param value 配置值
+   */
+  public static setConfig(key: string, value: ConfigValue): void {
+    try {
+      AppStorage.setOrCreate(key, value);
+      Logger.info(ConfigManager.TAG, `更新配置 ${key}: ${value}`);
+    } catch (error) {
+      Logger.error(ConfigManager.TAG, `设置配置失败 ${key}: ${error}`);
+    }
+  }
+
+  /**
+   * 刷新配置 - 重新从API获取配置
+   * @returns Promise<boolean> 是否刷新成功
+   */
+  public static async refreshConfig(): Promise<boolean> {
+    Logger.info(ConfigManager.TAG, '刷新配置...');
+    return await ConfigManager.initConfig();
+  }
+}

+ 37 - 0
entry/src/main/ets/common/util/MediaTable.ets

@@ -83,6 +83,12 @@ export default class MediaTable {
     this.accountTable.deleteData(predicates, callback);
   }
 
+  deleteDataFilePath(filePath: string, callback: Function) {
+    let predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
+    predicates.equalTo('filePath', filePath);
+    this.accountTable.deleteData(predicates, callback);
+  }
+
   deleteDataForParentPath(parentPath:string, callback: Function) {
     let predicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
     predicates.equalTo('parentPath', parentPath);
@@ -226,6 +232,13 @@ export default class MediaTable {
       obj.md5Str  = resultSet.getString(resultSet.getColumnIndex('md5Str'));
       obj.extra_json  = resultSet.getString(resultSet.getColumnIndex('extra_json'));
       obj.pyStr  = resultSet.getString(resultSet.getColumnIndex('pyStr'));
+
+      obj.bit_rate  = resultSet.getString(resultSet.getColumnIndex('bit_rate'));
+      obj.probe_score  = resultSet.getDouble(resultSet.getColumnIndex('probe_score'));
+      obj.year  = resultSet.getString(resultSet.getColumnIndex('year'));
+      obj.nb_streams  = resultSet.getDouble(resultSet.getColumnIndex('nb_streams'));
+      obj.nb_programs  = resultSet.getDouble(resultSet.getColumnIndex('nb_programs'));
+
       const valueBucket: relationalStore.ValuesBucket = obj
       // Step 4: 执行更新
       const updatePredicates = new relationalStore.RdbPredicates(RdbUtils.MEDIA_TABLE.tableName);
@@ -543,6 +556,13 @@ export default class MediaTable {
     item.md5Str = safeGet('md5Str');
     item.extra_json = safeGet('extra_json');
     item.pyStr = safeGet('pyStr');
+
+    item.bit_rate = safeGet('bit_rate');
+    item.probe_score = safeGetNumber('probe_score');
+    item.year = safeGet('year');
+    item.nb_streams = safeGetNumber('nb_streams');
+    item.nb_programs = safeGetNumber('nb_programs');
+
     return item;
   }
 
@@ -614,5 +634,22 @@ function generateBucket(item: VideoItem): relationalStore.ValuesBucket {
     obj.pyStr = item.pyStr;
   }
 
+
+  if(item.bit_rate){
+    obj.bit_rate = item.bit_rate;
+  }
+  if(item.probe_score){
+    obj.probe_score = item.probe_score;
+  }
+  if(item.year){
+    obj.year = item.year;
+  }
+  if(item.nb_streams){
+    obj.nb_streams = item.nb_streams;
+  }
+  if(item.nb_programs){
+    obj.nb_programs = item.nb_programs;
+  }
+
   return obj;
 }

+ 20 - 1
entry/src/main/ets/common/util/RdbUtils.ets

@@ -61,12 +61,19 @@ export default class RdbUtils {
       '        md5Str TEXT,\n' +
       '        extra_json TEXT,\n' +
       '        pyStr TEXT,\n' +
+
+      '        bit_rate TEXT,\n' +
+      '        probe_score INTEGER DEFAULT 0,\n' +
+      '        year TEXT,\n' +
+      '        nb_streams INTEGER DEFAULT 0,\n' +
+      '        nb_programs INTEGER DEFAULT 0,\n' +
+
       '        mimeType TEXT' +
       ')',
     columns: ['id', 'name', 'filePath','mtype', 'videoSize', 'cTime','size', 'artist', 'album',
       'fileName','parentPath','isFav','pixelMapPath','pixelMapToString',
       'duration',   'sampleRate','playCount',  'lastPlayedStr', 'trackCount',
-      'lyricContent','md5Str','extra_json','pyStr','mimeType']
+      'lyricContent','md5Str','extra_json','pyStr','bit_rate','probe_score','year','nb_streams','nb_programs','mimeType']
   };
 
   constructor(tableName: string, sqlCreateTable: string, columns: Array<string>) {
@@ -140,6 +147,12 @@ export default class RdbUtils {
             'extra_json': 'TEXT',
             'mimeType': 'TEXT',
             'pyStr': 'TEXT',
+
+            'bit_rate': 'TEXT',
+            'probe_score': 'INTEGER DEFAULT 0',
+            'year': 'TEXT',
+            'nb_streams': 'INTEGER DEFAULT 0',
+            'nb_programs': 'INTEGER DEFAULT 0',
           };
           
           // 逐个添加列,不依赖于检查结果
@@ -265,6 +278,8 @@ export default class RdbUtils {
           callback(ret);
         });
       });
+    }else{
+      Logger.error(RdbUtils.RDB_TAG, 'RdbStore2 is not initialized.');
     }
   }
 
@@ -279,13 +294,17 @@ export default class RdbUtils {
     if (this.rdbStore) {
       this.rdbStore.delete(predicates, (err, ret) => {
         if (err) {
+          Logger.info('xiaozheng 校正数据开始 deleteData5 = ' )
           Logger.error(RdbUtils.RDB_TAG, `deleteData() failed, err: ${err}`);
           callback(resFlag);
           return;
         }
+        Logger.info('xiaozheng 校正数据开始 deleteData6 = ' )
         Logger.info(RdbUtils.RDB_TAG, `deleteData() finished: ${ret}`);
         callback(!resFlag);
       });
+    }else{
+      Logger.info(RdbUtils.RDB_TAG, 'deleteData rdbStore() 未初始化!');
     }
   }
 

+ 113 - 0
entry/src/main/ets/common/util/UpdateLogManager.ets

@@ -0,0 +1,113 @@
+/**
+ * 更新日志管理器
+ * 负责管理更新日志的显示逻辑、版本检查等功能
+ */
+import { AppUtil, PreferencesUtil } from '@pura/harmony-utils';
+import { UpdateConstants } from '../constants/UpdateConstants';
+import Logger from './Logger';
+import { ConfigManager } from './ConfigManager';
+
+export class UpdateLogManager {
+  private static readonly TAG = 'UpdateLogManager';
+
+  /**
+   * 检查并显示更新日志
+   * @returns Promise<boolean> 是否显示了更新日志
+   */
+  public static async checkAndShowUpdateLog(): Promise<boolean> {
+    try {
+      // 从API配置中获取是否显示更新日志的设置
+      const showUpdateLog = ConfigManager.getConfig('show_update_log', false);
+      if (!showUpdateLog) {
+        Logger.info(UpdateLogManager.TAG, '更新日志功能已通过API配置禁用');
+        return false;
+      }
+      Logger.info(UpdateLogManager.TAG, '更新日志功能已通过API配置启用');
+
+      // if (!UpdateConstants.ENABLE_UPDATE_LOG) {
+      //   Logger.info(UpdateLogManager.TAG, '更新日志功能已禁用');
+      //   return false;
+      // }
+
+      const shouldShow = UpdateLogManager.shouldShowUpdateLog();
+      if (!shouldShow) {
+        Logger.info(UpdateLogManager.TAG, '无需显示更新日志');
+        return false;
+      }
+
+      Logger.info(UpdateLogManager.TAG, '准备显示更新日志弹窗');
+      return true;
+    } catch (error) {
+      Logger.error(UpdateLogManager.TAG, '检查更新日志失败:' + error);
+      return false;
+    }
+  }
+
+
+  /**
+   * 检查是否需要显示更新日志
+   */
+  private static shouldShowUpdateLog(): boolean {
+    try {
+      const currentVersion = UpdateLogManager.getCurrentVersion();
+      const lastShownVersion = PreferencesUtil.getStringSync('last_shown_update_version', '');
+
+      // 如果配置为每次启动都检查
+      if (UpdateConstants.CHECK_ON_EVERY_LAUNCH) {
+        Logger.info(UpdateLogManager.TAG, '配置为每次启动都显示更新日志');
+        return true;
+      }
+
+      // 检查版本是否更新
+      const isVersionUpdated = lastShownVersion !== currentVersion;
+
+      if (isVersionUpdated) {
+        Logger.info(UpdateLogManager.TAG, `检测到版本更新:${lastShownVersion} -> ${currentVersion}`);
+      }
+
+      return isVersionUpdated;
+    } catch (error) {
+      Logger.error(UpdateLogManager.TAG, '检查更新日志显示条件失败:' + error);
+      return false;
+    }
+  }
+
+  /**
+   * 获取当前应用版本号
+   */
+  private static getCurrentVersion(): string {
+    try {
+      return AppUtil.getVersionName();
+    } catch (error) {
+      Logger.error(UpdateLogManager.TAG, '获取应用版本号失败:' + error);
+      return '1.0.0';
+    }
+  }
+
+  /**
+   * 标记当前版本的更新日志已显示
+   */
+  public static markCurrentVersionShown(): void {
+    try {
+      const currentVersion = UpdateLogManager.getCurrentVersion();
+      PreferencesUtil.putSync('last_shown_update_version', currentVersion);
+      Logger.info(UpdateLogManager.TAG, `已标记版本 ${currentVersion} 的更新日志为已显示`);
+    } catch (error) {
+      Logger.error(UpdateLogManager.TAG, '标记更新日志已显示失败:' + error);
+    }
+  }
+
+  /**
+   * 重置更新日志显示状态(用于测试)
+   */
+  public static resetUpdateLogStatus(): void {
+    try {
+      PreferencesUtil.deleteSync('last_shown_update_version');
+      Logger.info(UpdateLogManager.TAG, '已重置更新日志显示状态');
+    } catch (error) {
+      Logger.error(UpdateLogManager.TAG, '重置更新日志显示状态失败:' + error);
+    }
+  }
+
+
+}

+ 437 - 5
entry/src/main/ets/common/util/Utility.ets

@@ -24,6 +24,66 @@ import { pinyin4js } from '@ohos/pinyin4js';
 import { VipData } from '../../viewmodel/VipData';
 import { LocalMusic } from '../../view/LocalMusic';
 import { VipPage } from '../../pages/VipPage';
+import { FFmpeg, FFProgressMessageParser } from '@sj/ffmpeg';
+interface FFMpegTags {
+  album?: string;
+  artist?: string;
+  title?: string;
+  track?: string;
+  TYER?: string;
+  date?: string;
+  LYRICS?: string;
+  lyrics?: string;       // 小写变体
+  USLT?: string;         // ID3v2同步歌词
+  UNSYNCEDLYRICS?: string; // ID3v2非同步歌词
+  // Add any other tag properties you expect
+}
+
+interface FFprobeFormat {
+  filename: string;
+  nb_streams: number;
+  nb_programs: number;
+  format_name: string;
+  format_long_name: string;
+  duration: string;
+  size: string;
+  bit_rate: string;
+  probe_score: number;
+  tags?: FFMpegTags;
+}
+
+interface FFprobeStream {
+  // Define stream properties as needed
+  codec_type?: string;  // 流类型,如"audio"、"video"
+  sample_rate?: string; // 采样率
+  bit_rate?: string;    // 比特率
+  disposition?: StreamDisposition;  // 添加disposition属性
+}
+
+interface StreamDisposition {
+  default?: number;
+  dub?: number;
+  original?: number;
+  comment?: number;
+  lyrics?: number;
+  karaoke?: number;
+  forced?: number;
+  hearing_impaired?: number;
+  visual_impaired?: number;
+  clean_effects?: number;
+  attached_pic?: number;  // 添加封面图片标识
+  timed_thumbnails?: number;
+  captions?: number;
+  descriptions?: number;
+  metadata?: number;
+  dependent?: number;
+  still_image?: number;
+}
+
+interface FFprobeMetadata {
+  streams: FFprobeStream[];
+  format: FFprobeFormat;
+}
 
 export class Utility {
 
@@ -47,6 +107,11 @@ export class Utility {
     return PreferencesUtil.getBooleanSync('hasActiveSubscription', false);
   }
 
+  static isForever(): boolean {
+    // 判断是不是永久会员
+    return PreferencesUtil.getBooleanSync('isForever', false);
+  }
+
   /**
    * 旧版客户端判断会员状态
    * @returns
@@ -64,8 +129,10 @@ export class Utility {
     let isNoble = false
     if(StrUtil.isNotEmpty(expireDate)){
       if(expireDate===VipPage.FOREVER_DATE){
+        PreferencesUtil.putSync('isForever',true)
         isNoble = true
       }else{
+        PreferencesUtil.putSync('isForever',false)
         const currentDate = new Date();
         const targetDate = DateUtil.getFormatDate(expireDate)
 
@@ -601,6 +668,17 @@ export class Utility {
 
   //获取音乐资源的属性值,
   static async uriGetMusicAssetsFromFile(context:Context,uri:string,type:number,isLoadPixelMap?:boolean): Promise<VideoItem> {
+    //华为官方不支持的格式 不支持内嵌歌词Dsf,aif,aiff,wav  不支持内嵌封面dsf,aif,aiff
+    if(StrUtil.isNotEmpty(uri)){
+      if(uri.toLowerCase().endsWith('.dsf')
+        ||uri.toLowerCase().endsWith('.aif')
+        // ||uri.toLowerCase().endsWith('.wav')
+        ||uri.toLowerCase().endsWith('.aiff')){
+        return Utility.readMetaInfoFFmpeg(context,uri,type)
+      }
+    }
+
+
     let item:VideoItem = new VideoItem('',uri,uri,type,0,'')
     try {
       console.info('asset file.uri: ', uri);
@@ -744,7 +822,18 @@ export class Utility {
         item.isFav = 0;
         item.playCount = 0;
         item.pyStr = pinyin4js.getShortPinyin(musicName)
-        console.info('onecold pyStr = '+pinyin4js.getShortPinyin(musicName));
+
+        // item.lyricContent = await extractLyricsContent(uri)
+        let metaItem = await parseAudioMetadata(uri)
+        if(metaItem){
+          item.lyricContent = metaItem.lyricContent
+          item.bit_rate = formatBitrateToKbps(metaItem.bit_rate  || "0");
+          item.year = metaItem.year ||'unknown'
+          item.probe_score = metaItem.probe_score
+          item.nb_streams = metaItem.nb_streams
+          item.nb_programs = metaItem.nb_programs
+        }
+
       })
     } catch (error) {
       console.error('uriGetAssetsFromFile failed with err: ' + JSON.stringify(error));
@@ -752,10 +841,124 @@ export class Utility {
 
     return item
 
-
   }
 
 
+  static async  readMetaInfoFFmpeg(context:Context,inputPath: string,type:number): Promise<VideoItem> {
+    return new Promise((resolve, reject) => {
+      let commands = ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", inputPath];
+      let outputJson = "";
+
+      FFmpeg.execute(commands,  {
+        logCallback: (logLevel: number, logMessage: string) => console.log(`[${logLevel}]${logMessage}`),
+        outputCallback: (message: string) => {
+          outputJson += message;
+        },
+      }).then(async () => {
+        try {
+          let videoItem:VideoItem = new VideoItem('',inputPath,inputPath,type,0,'')
+          const metadata: FFprobeMetadata = JSON.parse(outputJson);
+          const format = metadata.format;
+
+          let file = fs.openSync(inputPath, fs.OpenMode.READ_ONLY | fs.OpenMode.CREATE)
+          console.info('readMetaInfoFFmpeg asset file.path: ', file.path);
+          videoItem = new VideoItem(file.name,inputPath,inputPath,type,0,'')
+          await fs.stat(file.fd).then(async (stat: fs.Stat) => {
+
+            // 获取音频流的采样率
+            let sampleRate = '';
+            if (metadata.streams  && metadata.streams.length  > 0) {
+              // 查找第一个音频流
+              const audioStream = metadata.streams.find(stream  => StrUtil.isNotEmpty(stream.sample_rate));
+              if (audioStream&&audioStream.sample_rate)  {
+                sampleRate = audioStream.sample_rate;
+              }
+            }
+
+            let videoSize =  stat.size
+            let fileSize = Utility.formatFSize(videoSize)
+            //按照添加时间
+            let addTime = Utility.getFormatDateStr(new Date(),'yyyy-MM-dd HH:mm');
+            // Extract artist and title from tags
+            const tags = format.tags  || {};
+            const artist = tags.artist  || '';
+            const title = tags.title  || '';
+            const album = tags.album  || '';
+
+            let name: string = title;
+            if (!name) {
+              name = getFileNameWithoutExtension(inputPath);
+            }
+            // Create VideoItem
+            videoItem = new VideoItem(
+              name,
+              inputPath, // id can be generated or left empty
+              inputPath,
+              type, // assuming it's local
+              videoSize,
+              addTime // convert to ISO string
+            );
+
+            // Set additional properties from format metadata
+            videoItem.artist  = artist;
+            videoItem.album  = album;
+            videoItem.mimeType = format.format_name
+            videoItem.sampleRate = sampleRate
+            videoItem.pyStr = pinyin4js.getShortPinyin(name)
+            videoItem.fileName  = FileUtil.getFileName(inputPath);
+            if(format.duration)
+              videoItem.duration = formatDuration(format.duration.toString()||'00:00')
+            videoItem.size  = fileSize;
+            videoItem.bit_rate  =formatBitrateToKbps(format.bit_rate  || "0");
+            videoItem.probe_score  = format.probe_score;
+            videoItem.nb_streams  = format.nb_streams;
+            videoItem.nb_programs  = format.nb_programs;
+            videoItem.year  = tags.TYER || tags.date  || 'unknown'; // try different tag names for year
+            videoItem.lyricContent  = tags.LYRICS || tags.lyrics  ||  tags.USLT || tags.UNSYNCEDLYRICS || '';
+
+            // 检查是否有封面图片流
+            const hasCover = metadata.streams.some(stream  =>
+            stream.disposition?.attached_pic  === 1
+            );
+            console.info('readMetaInfoFFmpeg asset hasCover: ', hasCover);
+            // 如果有封面图片,则提取
+            if (hasCover) {
+
+              try {
+                let md5Name = await MD5.digestSync(inputPath)
+                // const imageName = `${md5Name}.jpg`;
+                const imagePath = `${context.filesDir}${FileUtil.separator}${md5Name}`;
+                console.info('readMetaInfoFFmpeg asset imagePath: ', imagePath);
+                // 提取封面图片
+                // await extractCoverImage(inputPath, imagePath);
+                await FFmpegCover(inputPath, imagePath);
+
+                // 检查图片是否生成成功
+                if (fs.accessSync(imagePath))  {
+                  videoItem.pixelMapPath  = imagePath;
+
+                }
+              } catch (error) {
+                console.warn(' 提取封面图片失败:', error.message);
+              }
+            }
+
+            console.info('Successfully  parsed metadata:', videoItem);
+            resolve(videoItem);
+          })
+
+
+        } catch (error) {
+          console.error('Failed  to parse metadata:', error);
+          reject(new Error('Failed to parse metadata: ' + error.message));
+        }
+      }).catch((error: Error) => {
+        console.error(`Execution  failed with error: ${error.message}`);
+        reject(error);
+      });
+    });
+  }
+
 
   private completionNum(num: number): string | number {
     if (num < 10) {
@@ -865,6 +1068,18 @@ export class Utility {
     }
     return list
   }
+  //获取列表有封面的值
+  static getFirstCoverFromList(localList:Array<VideoItem>):string{
+    let list: Array<string> = [];
+    for(let i=0;i<localList.length;i++){
+      let pix = localList[i].pixelMapPath
+      if(pix){
+       return pix
+      }
+
+    }
+    return ''
+  }
 
   //根据type获取对应的播放列表(CommonConstants.TYPE_LOCAL:本地,CommonConstants.TYPE_Dir:私密视频)
   static getGlobalNameList(localList:Array<VideoItem>,type:number){
@@ -1223,11 +1438,19 @@ interface VideoNameParts {
   numeric: number;
 }
 
+// 提取文件名中的非数字和数字部分
 function extractParts(name: string): VideoNameParts {
-  const match = name.match(/^(\D*)(\d*)/);
+  // Adjust the regex to handle names that start with numbers
+  const match = name.match(/^(\D*?)(\d+)(\.\w+)?$/);
+  if (match) {
+    return {
+      nonNumeric: match[1] || '', // Ensure nonNumeric is not undefined
+      numeric: parseInt(match[2], 10),
+    };
+  }
   return {
-    nonNumeric: match?.[1] || '',
-    numeric: parseInt(match?.[2] || '0', 10)
+    nonNumeric: name,
+    numeric: 0,
   };
 }
 
@@ -1289,6 +1512,31 @@ function convertSecondsToTime(secondsStr: string): string {
   }
 }
 
+/**
+ * 秒数 → 智能时间格式(自动选择 MM:SS 或 HH:MM:SS)
+ * @param seconds 秒数字符串(如 "283.524351" 或 "3675")
+ * @param forceHHMMSS 强制使用 HH:MM:SS 格式(默认自动判断)
+ * @returns 格式化后的时间字符串
+ */
+function formatDuration(seconds: string, forceHHMMSS: boolean = false): string {
+  // 1. 校验输入
+  const secNum = parseFloat(seconds);
+  if (isNaN(secNum) || secNum < 0) return forceHHMMSS ? "00:00:00" : "00:00";
+
+  // 2. 计算时间分量
+  const totalSec = Math.floor(secNum);
+  const hours = Math.floor(totalSec  / 3600);
+  const mins = Math.floor((totalSec  % 3600) / 60);
+  const secs = totalSec % 60;
+
+  // 3. 格式化输出
+  const pad = (n: number) => n.toString().padStart(2,  '0');
+
+  return forceHHMMSS || hours > 0
+    ? `${pad(hours)}:${pad(mins)}:${pad(secs)}`  // HH:MM:SS
+    : `${pad(mins)}:${pad(secs)}`;              // MM:SS
+}
+
 
 // 定义解析结果的数据结构
 class MusicInfo {
@@ -1344,4 +1592,188 @@ function parseMusicFileName(fileName: string): MusicInfo {
   }
 
   return result;
+}
+
+function getFileNameWithoutExtension(filePath: string): string {
+  const fileName = filePath.split('/').pop()  || '';
+  const lastDotIndex = fileName.lastIndexOf('.');
+  return lastDotIndex > 0 ? fileName.substring(0,  lastDotIndex) : fileName;
+}
+
+/**
+ * 从音乐文件中提取封面
+ * @param inputPath 音乐文件路径
+ * @returns Promise<void>
+ */
+async function extractCoverImage(inputPath: string, outputPath: string): Promise<void> {
+  const commands = [
+    'ffmpeg',
+    '-i', inputPath,
+    '-an',              // 禁用音频
+    '-vcodec', 'copy',  // 直接复制视频流
+    '-f', 'image2',     // 强制输出为图片
+    '-y',               // 覆盖输出文件
+    outputPath
+  ];
+
+  return new Promise((resolve, reject) => {
+    FFmpeg.execute(commands,  {
+      logCallback: (logLevel: number, logMessage: string) => {
+        console.log(`[${logLevel}]${logMessage}`);
+      },
+      outputCallback: (message: string) => {
+        console.log(`FFmpeg  output: ${message}`);
+      },
+    }).then(() => resolve())
+      .catch((error: BusinessError) => reject(error));
+  });
+}
+
+
+/**
+ * 从音乐文件中提取封面
+ * @param inputPath 音乐文件路径
+ * @returns Promise<void>
+ */
+async function FFmpegCover(inputPath: string, outputPath: string) {
+  let commands = ["ffmpeg", "-i", inputPath, "-map", "0:v", "-c:v", "copy", outputPath];
+  FFmpeg.execute(commands, {
+    logCallback: (logLevel: number, logMessage: string) => {
+      console.info(`[FFmpeg LOG] [${logLevel}]${logMessage}`)
+    },
+    progressCallback: (message: string) => {
+      console.info(`[FFmpeg progress]${JSON.stringify(FFProgressMessageParser.parse(message))}`)
+    },
+  })
+    .then(() => {
+      console.info("FFmpeg execution succeeded.");
+    })
+    .catch((error: Error) => {
+      console.error(`FFmpeg execution failed with error: ${error.message}`);
+    });
+}
+
+/**
+ * 从音乐文件中提取歌词内容
+ * @param inputPath 音乐文件路径
+ * @returns Promise<string> 直接返回歌词内容,若无歌词则返回空字符串
+ */
+async function extractLyricsContent(inputPath: string): Promise<string> {
+  return new Promise(async (resolve, reject) => {
+    try {
+      // 1. 使用ffprobe获取元数据
+      const commands = [
+        'ffprobe',
+        '-v', 'quiet',
+        '-print_format', 'json',
+        '-show_format',
+        inputPath
+      ];
+
+      let outputJson = '';
+      await FFmpeg.execute(commands,  {
+        outputCallback: (message: string) => outputJson += message,
+      });
+
+      // 2. 解析歌词标签
+      const metadata:FFprobeMetadata = JSON.parse(outputJson);
+      const tags = metadata.format?.tags  || {};
+
+      // 3. 从常见标签中查找歌词(优先级顺序)
+      const lyricContent =
+        tags.LYRICS ||    // 标准标签
+        tags.lyrics  ||    // 小写变体
+        tags.USLT ||      // ID3v2标签
+        tags.UNSYNCEDLYRICS ||
+          '';
+
+      resolve(lyricContent.trim());
+
+    } catch (error) {
+      reject(`解析失败: ${error instanceof Error ? error.message  : String(error)}`);
+    }
+  });
+}
+
+
+/**
+ * 解析音乐文件元数据(包含歌词和其他关键字段)
+ * @param inputPath 文件路径
+ * @returns Promise<VideoItem> 包含完整元数据的对象
+ */
+async function parseAudioMetadata(inputPath: string): Promise<VideoItem> {
+  return new Promise(async (resolve, reject) => {
+    try {
+      // 1. 执行FFprobe命令
+      const commands: string[] = [
+        'ffprobe',
+        '-v', 'quiet',
+        '-print_format', 'json',
+        '-show_format',
+        '-show_streams',
+        inputPath
+      ];
+
+      let outputJson = '';
+      await FFmpeg.execute(commands,  {
+        outputCallback: (message: string) => outputJson += message,
+      });
+
+      // 2. 解析元数据
+      const metadata = JSON.parse(outputJson)  as FFprobeMetadata;
+      const format:FFprobeFormat = metadata.format  ;
+      const tags = format.tags||{} ;
+      // 3. 构建VideoItem基础信息
+      const videoItem = new VideoItem(
+        tags.title  || getFileNameWithoutExtension(inputPath),
+        inputPath, // id
+        inputPath,
+        CommonConstants.TYPE_LOCAL,
+        0,
+        new Date().toISOString() // 使用当前时间作为默认创建时间
+      );
+
+      // 4. 设置关键字段
+      videoItem.bit_rate  = format.bit_rate  || '';
+      videoItem.probe_score  = format.probe_score  || 0;
+      videoItem.year  = tags.TYER || tags.date  || '';
+      videoItem.lyricContent  =
+        tags.LYRICS ??
+        tags.lyrics  ??
+        tags.USLT ??
+        tags.UNSYNCEDLYRICS ??
+          '';
+
+      // 5. 设置其他可选字段
+      videoItem.artist  = tags.artist  || '';
+      videoItem.album  = tags.album  || '';
+      videoItem.duration  = format.duration  || '';
+      videoItem.nb_streams  = format.nb_streams;
+      videoItem.nb_programs  = format.nb_programs;
+
+      resolve(videoItem);
+
+    } catch (error) {
+      reject(new Error(`元数据解析失败: ${error instanceof Error ? error.message  : String(error)}`));
+    }
+  });
+}
+
+
+/**
+ * 将比特率(bps)转换为 kbps 并格式化
+ * @param bitRate 比特率字符串(如 "5644802")
+ * @param decimalPlaces 保留小数位数(默认0)
+ * @returns 格式化后的 kbps 字符串(如 "5644 kbps")
+ */
+function formatBitrateToKbps(bitRate: string, decimalPlaces: number = 0): string {
+  // 1. 转换为数字
+  const bitsPerSecond = parseInt(bitRate);
+  if (isNaN(bitsPerSecond) || bitsPerSecond < 0) return "0 kbps";
+
+  // 2. 计算 kbps(1 kbps = 1000 bps)
+  const kbps = bitsPerSecond / 1000;
+
+  // 3. 格式化输出
+  return `${kbps.toFixed(decimalPlaces)}  kbps`;
 }

+ 264 - 0
entry/src/main/ets/dialog/OnlineUpdateLog.ets

@@ -0,0 +1,264 @@
+/**
+ * 在线更新日志弹窗组件
+ * 参考WebIndex.ets的成功实现,确保WebView正常工作
+ */
+import { webview } from '@kit.ArkWeb';
+import { UpdateConstants } from '../common/constants/UpdateConstants';
+import { UpdateLogManager } from '../common/util/UpdateLogManager';
+import { ConfigurationConstant } from '@kit.AbilityKit';
+import { AppUtil, PreferencesUtil } from '@pura/harmony-utils';
+import { CommonConstants } from '../common/constants/CommonConstants';
+
+@Component
+export default struct OnlineUpdateLog {
+  /** 弹窗控制器 */
+  controller?: CustomDialogController;
+  /** 关闭回调 */
+  onClose?: () => void;
+  /** 更新日志URL */
+  @State updateLogUrl: string = '';
+  /** 当前应用版本 */
+  @State currentVersion: string = '';
+  /** WebView控制器 */
+  private webViewController: webview.WebviewController = new webview.WebviewController();
+  /** 是否加载完成 */
+  @State isLoading: boolean = true;
+  /** 加载错误信息 */
+  @State errorMessage: string = '';
+  /** 加载进度 */
+  @State progressValue: number = 0;
+  /** 进度条是否可见 */
+  @State progressVisible: boolean = true;
+  /** 深色模式 */
+  @State isDarkMode: boolean = false;
+  @StorageProp('currentColorMode') currentMode: number = ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
+  @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
+
+  aboutToAppear() {
+    try {
+      // 获取当前应用版本
+      this.getCurrentVersion();
+      // 设置更新日志URL - 直接使用在线URL
+      this.updateLogUrl = UpdateConstants.UPDATE_LOG_URL;
+      // 检查深色模式
+      this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK;
+      let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR);
+      AppStorage.setOrCreate('themeColor', themeColor);
+      this.themeColor = themeColor
+      console.info('OnlineUpdateLogDialog URL:', this.updateLogUrl);
+      console.info('OnlineUpdateLogDialog 开始加载在线更新日志');
+      
+      // 确保WebView控制器已初始化
+      if (!this.webViewController) {
+        this.webViewController = new webview.WebviewController();
+      }
+    } catch (error) {
+      console.error('OnlineUpdateLogDialog aboutToAppear error:', error);
+      this.errorMessage = '初始化失败';
+      this.isLoading = false;
+    }
+  }
+
+  /**
+   * 获取当前应用版本号
+   */
+  private getCurrentVersion() {
+    try {
+      this.currentVersion = AppUtil.getVersionName()//UpdateConstants.APP_VERSION;
+    } catch (error) {
+      this.currentVersion = '1.0.0';
+    }
+  }
+
+  /**
+   * 记录已显示的版本,避免重复显示
+   */
+  private markVersionShown() {
+    try {
+      UpdateLogManager.markCurrentVersionShown();
+    } catch (error) {
+      console.error('OnlineUpdateLogDialog markVersionShown error:', error);
+    }
+  }
+
+
+  /**
+   * 组件销毁时清理资源
+   */
+  aboutToDisappear() {
+    try {
+      console.info('OnlineUpdateLogDialog aboutToDisappear');
+      // 清理WebView控制器
+      if (this.webViewController) {
+        // 这里可以添加WebView的清理逻辑,如果需要的话
+      }
+    } catch (error) {
+      console.error('OnlineUpdateLogDialog aboutToDisappear error:', error);
+    }
+  }
+
+  build() {
+    Column() {
+
+
+      // 版本信息和进度条
+      Column() {
+        // 进度条 - 使用主题色
+        if (this.progressVisible && this.progressValue < 100) {
+          Column() {
+            Progress({ value: this.progressValue, total: 100, type: ProgressType.Linear })
+              .width('100%')
+              .height(4)
+              .color(this.themeColor)
+              .backgroundColor(this.isDarkMode ? '#4A4A4A' : '#E0E0E0')
+              .borderRadius(2)
+            
+            Text(`加载中... ${this.progressValue}%`)
+              .fontSize(10)
+              .fontColor(this.isDarkMode ? '#E0E0E0' : Color.Gray)
+              .margin({ top: 4 })
+              .alignSelf(ItemAlign.End)
+          }
+          .width('100%')
+        }
+      }
+      .width('100%')
+      .padding({ left: 20, right: 20, top: 12, bottom: 8 })
+      .backgroundColor(this.isDarkMode ? '#3A3A3A' : '#F8F9FA')
+      .borderRadius({
+        topLeft: 0,
+        topRight: 0,
+        bottomLeft: 8,
+        bottomRight: 8
+      })
+
+      // WebView内容区域 - 完全参考WebIndex.ets的实现
+      if (this.errorMessage) {
+        // 错误状态
+        Column() {
+          // 错误图标背景
+          Column() {
+            Text('❌')
+              .fontSize(32)
+          }
+          .width(64)
+          .height(64)
+          .backgroundColor(this.isDarkMode ? '#4A4A4A' : '#FFF5F5')
+          .borderRadius(32)
+          .justifyContent(FlexAlign.Center)
+          .alignItems(HorizontalAlign.Center)
+          .border({
+            width: 1,
+            color: this.isDarkMode ? '#666666' : '#FED7D7'
+          })
+          
+          Text('加载失败')
+            .fontSize(16)
+            .fontColor(this.isDarkMode ? Color.White : Color.Black)
+            .fontWeight(FontWeight.Medium)
+            .margin({ top: 16 })
+          
+          Text(this.errorMessage)
+            .fontSize(12)
+            .fontColor(this.isDarkMode ? '#E0E0E0' : Color.Gray)
+            .margin({ top: 8, left: 20, right: 20 })
+            .textAlign(TextAlign.Center)
+            .maxLines(3)
+          
+          Button('重试')
+            .fontSize(14)
+            .backgroundColor(this.themeColor)
+            .fontColor(Color.White)
+            .borderRadius(8)
+            .width(120)
+            .height(40)
+            .margin({ top: 20 })
+            .onClick(() => {
+              try {
+                this.errorMessage = '';
+                this.isLoading = true;
+                this.progressValue = 0;
+                this.progressVisible = true;
+                // 重新加载
+                if (this.webViewController) {
+                  this.webViewController.refresh();
+                }
+              } catch (error) {
+                console.error('OnlineUpdateLogDialog 重试失败:', error);
+                this.errorMessage = '重试失败,请稍后再试';
+              }
+            })
+        }
+        .width('100%')
+        .height(350)
+        .justifyContent(FlexAlign.Center)
+        .alignItems(HorizontalAlign.Center)
+        .padding(20)
+        .backgroundColor(this.isDarkMode ? '#2C2C2C' : Color.White)
+      } else {
+        // WebView内容 - 完全按照WebIndex.ets的方式实现
+        Web({
+          src: this.updateLogUrl,
+          controller: this.webViewController
+        })
+          .width('100%')
+          .height(350)
+          .borderRadius(12)
+          .layoutWeight(1)
+          .backgroundColor(this.isDarkMode ? '#2C2C2C' : Color.White)
+          // .border({
+          //   width: 1,
+          //   color: this.isDarkMode ? '#333333' : '#E0E0E0'
+          // })
+          .margin({ left: 12, right: 12, bottom: 8 })
+          .darkMode(this.isDarkMode ? WebDarkMode.On : WebDarkMode.Off)
+          .forceDarkAccess(this.isDarkMode)
+          .onProgressChange((event) => {
+            if (event) {
+              console.info('OnlineUpdateLogDialog WebView进度:', event.newProgress);
+              this.progressValue = event.newProgress;
+              
+              // 进度完成时隐藏进度条
+              if (event.newProgress >= 100) {
+                setTimeout(() => {
+                  this.progressVisible = false;
+                  this.isLoading = false;
+                }, 500);
+              }
+            }
+          })
+          .onPageBegin(() => {
+            console.info('OnlineUpdateLogDialog WebView开始加载:', this.updateLogUrl);
+            this.isLoading = true;
+            this.errorMessage = '';
+            this.progressValue = 0;
+            this.progressVisible = true;
+          })
+          .onPageEnd(() => {
+            console.info('OnlineUpdateLogDialog WebView加载完成');
+            this.isLoading = false;
+            setTimeout(() => {
+              this.progressVisible = false;
+            }, 1000);
+          })
+          .onErrorReceive((event) => {
+            const errorInfo = event?.error?.getErrorInfo() || '网络错误';
+            console.error('OnlineUpdateLogDialog WebView加载错误:', errorInfo);
+            this.isLoading = false;
+            this.progressVisible = false;
+            this.errorMessage = `加载失败:${errorInfo}`;
+          })
+      }
+    }
+    .backgroundColor(this.isDarkMode ? '#2C2C2C' : Color.White)
+    .borderRadius(12)
+    .width('92%')
+    .constraintSize({ maxHeight: '85%' })
+    .shadow({
+      radius: 16,
+      color: this.isDarkMode ? 'rgba(0,0,0,0.8)' : 'rgba(0,0,0,0.15)',
+      offsetX: 0,
+      offsetY: 4
+    })
+  }
+}

+ 77 - 18
entry/src/main/ets/entryability/EntryAbility.ets

@@ -24,6 +24,7 @@ import Logger from '../common/util/Logger';
 import statusBarManager from '@hms.pcService.statusBarManager';
 import StatusBarViewExtensionAbility from '@hms.pcService.StatusBarViewExtensionAbility';
 import { SpiderMan } from '@simplepeng/spider-man';
+import { smartMobilityCommon } from '@kit.CarKit';
 
 /**
  * 主Ability类,继承自UIAbility
@@ -35,7 +36,7 @@ import { SpiderMan } from '@simplepeng/spider-man';
 export default class EntryAbility extends UIAbility {
     // UI上下文对象,用于获取窗口信息
     private uiContext?: UIContext;
-
+    private awareness: smartMobilityCommon.SmartMobilityAwareness = smartMobilityCommon.getSmartMobilityAwareness();
     /**
      * 窗口尺寸变化回调函数
      * @param windowSize 新的窗口尺寸对象
@@ -55,6 +56,11 @@ export default class EntryAbility extends UIAbility {
         // 记录尺寸变化日志
         LogUtil.info('pura onWindowSizeChange currentHeightBreakpoint= '+heightBp);
         LogUtil.info( 'pura onWindowSizeChange currentWidthBreakpoint= '+widthBp);
+        AppStorage.setOrCreate('windowWidth', windowSize.width);
+        AppStorage.setOrCreate('windowHeight', windowSize.height);
+
+        LogUtil.info('hicar onWindowSizeChange width= '+windowSize.width);
+        LogUtil.info( 'hicar onWindowSizeChange height= '+windowSize.height);
 
     };
 
@@ -75,23 +81,22 @@ export default class EntryAbility extends UIAbility {
         AppStorage.setOrCreate('context', this.context);
         hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
 
-        let timeout = 5000
-        if(Utility.isNoble())
-            timeout = 3000
         setTimeout(async ()=>{
             this.loadDoWant(want)
-            // await this.handleParam(want)
-        },timeout)
+            await this.handleParam(want)
+        },1000)
 
         this.handleWeChatCallIfNeed(want)
 
+        this.getHiCarStatus()
+
     }
 
     async onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
         hilog.info(0x0000, 'testTag', `onNewWant, want=${JSON.stringify(want)}`);
         super.onNewWant(want, launchParam);
         this.loadDoWant(want)
-        // await this.handleParam(want)
+        await this.handleParam(want)
         this.handleWeChatCallIfNeed(want)
 
     }
@@ -109,20 +114,27 @@ export default class EntryAbility extends UIAbility {
             return;
         }
         hilog.info(0x0000, 'testTag', `onCreate or onNewWant, uri=${uri}`);
-        const eventData: emitter.EventData = {
-            data: {
-                message: uri
+
+        this.doSendEmit(uri)
+    }
+    //广播通知打开播放器播放视频或者音频
+    doSendEmit(uri:string){
+        setTimeout(async ()=>{
+            let eventData: emitter.EventData = {
+                data: {
+                    message: uri
+                }
+            };
+            if(Utility.isMeidaByExtension(uri)){
+                emitter.emit({ eventId: 2 }, eventData); // 发送音频打开广播事件
+            }else{
+                emitter.emit({ eventId: 1 }, eventData); // 发送视频打开广播事件
             }
-        };
-        if(Utility.isMeidaByExtension(uri)){
-            emitter.emit({ eventId: 2 }, eventData); // 发送音频打开广播事件
-        }else{
-            emitter.emit({ eventId: 1 }, eventData); // 发送视频打开广播事件
-        }
+        },300)
 
     }
 
-    // 华为分享拉起接收
+    // 华为分享拉起接收   处理分享数据
     // 1. 改造 handleParam 为异步函数,让其返回 Promise
     async handleParam(want: Want) {
         try {
@@ -133,7 +145,7 @@ export default class EntryAbility extends UIAbility {
             for (const record of records) {
                 if (record.uri) {
                     uri = record.uri;
-
+                    this.doSendEmit(uri)
                     break;
                 }
             }
@@ -145,6 +157,13 @@ export default class EntryAbility extends UIAbility {
 
     onDestroy() {
         hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
+        let types: smartMobilityCommon.SmartMobilityType[] = [smartMobilityCommon.SmartMobilityType.CAR_HOP];
+        // 出行连接状态回调函数
+        const callBack = (info: smartMobilityCommon.SmartMobilityInfo) => {
+            hilog.info(0x0000, 'Received smart mobility info: ', JSON.stringify(info));
+        };
+        // 解注册智慧出行连接状态的监听 示例2
+        this.awareness.off('smartMobilityStatus', types, callBack);
     }
 
     onWindowStageCreate(windowStage: window.WindowStage) {
@@ -204,6 +223,11 @@ export default class EntryAbility extends UIAbility {
                 LogUtil.info( 'getMainWindow currentHeightBreakpoint= '+heightBp);
                 LogUtil.info( 'getMainWindow currentWidthBreakpoint= '+widthBp);
                 data.on('windowSizeChange', this.onWindowSizeChange);
+
+                AppStorage.setOrCreate('windowWidth', data.getWindowProperties().windowRect.width);
+                AppStorage.setOrCreate('windowHeight', data.getWindowProperties().windowRect.height);
+                LogUtil.info('hicar getMainWindow width= '+data.getWindowProperties().windowRect.width);
+                LogUtil.info( 'hicar getMainWindow height= '+data.getWindowProperties().windowRect.height);
             }).catch((err: BusinessError) => {
                 console.error(`Failed to obtain the main window. Cause code: ${err.code}, message: ${err.message}`);
             });
@@ -230,6 +254,41 @@ export default class EntryAbility extends UIAbility {
     }
 
 
+    getHiCarStatus(){
+        this.awareness = smartMobilityCommon.getSmartMobilityAwareness();
+
+        // 业务类型
+        let types: smartMobilityCommon.SmartMobilityType[] = [smartMobilityCommon.SmartMobilityType.HICAR];
+        // 获取出行业务连接状态
+        let info = this.awareness.getSmartMobilityStatus(types[0]);
+
+        hilog.info(0x0000, 'getHiCarStatus  info: ', JSON.stringify(info));
+        if(info&&info.status==1){
+            AppStorage.setOrCreate('isHiCarStatus', true);
+        }else{
+            AppStorage.setOrCreate('isHiCarStatus', false);
+        }
+
+        // 出行连接状态回调函数
+        const callBack = (info: smartMobilityCommon.SmartMobilityInfo) => {
+            hilog.info(0x0000, 'getHiCarStatus Received smart mobility info: ', JSON.stringify(info));
+            if(info&&info.status==1){
+                AppStorage.setOrCreate('isHiCarStatus', true);
+            }else{
+                AppStorage.setOrCreate('isHiCarStatus', false);
+            }
+            this.sendChangeEvent()
+        };
+        // 注册智慧出行连接状态的监听
+        this.awareness.on('smartMobilityStatus', types, callBack);
+
+
+    }
 
+    //发送广播通知更新UI
+    sendChangeEvent() {
+        const eventData: emitter.EventData = {};
+        emitter.emit({ eventId: 333 }, eventData); // 发送广播通知更新doSwipBack
+    }
 
 }

+ 0 - 142
entry/src/main/ets/entryability/ShareUIAbility.ets

@@ -1,142 +0,0 @@
-import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
-import { window } from '@kit.ArkUI';
-import { systemShare } from '@kit.ShareKit';
-import { BusinessError, emitter } from '@kit.BasicServicesKit';
-import { hilog } from '@kit.PerformanceAnalysisKit';
-import { AppUtil, GlobalContext, LogUtil, StrUtil } from '@pura/harmony-utils';
-import { Utility } from '../common/util/Utility';
-import { DemoConstants } from './DemoConstants';
-import { WXApi, WXEventHandler } from '../common/util/WXApiWrap';
-
-
-export default class ShareUIAbility extends UIAbility {
-  //一多断点开发
-  private uiContext?: UIContext;
-  private onWindowSizeChange: (windowSize: window.Size) => void = (windowSize: window.Size) => {
-    let widthBp: WidthBreakpoint = this.uiContext!.getWindowWidthBreakpoint();
-    AppStorage.setOrCreate('currentWidthBreakpoint', widthBp);
-    let heightBp: HeightBreakpoint = this.uiContext!.getWindowHeightBreakpoint();
-    AppStorage.setOrCreate('currentHeightBreakpoint', heightBp);
-  };
-
-  onAvoidAreaChange = (data: window.AvoidAreaOptions) => {
-
-    let topRectHeight =  px2vp(data.area.topRect.height);
-    AppStorage.setOrCreate('topRectHeight', topRectHeight);
-    LogUtil.info('onecold onAvoidAreaChange topRectHeight = '+topRectHeight);
-    let bottomRectHeight =  px2vp(data.area.bottomRect.height);
-    AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight);
-
-  }
-
-  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
-    AppStorage.setOrCreate('context', this.context);
-    let timeout = 2000
-    // if(Utility.isNoble())
-    //   timeout = 2000
-    setTimeout(()=>{
-      this.handleShare(want)//分享打开的音频
-    },timeout)
-    this.handleWeChatCallIfNeed(want)
-  }
-
-  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
-    hilog.info(0x0000, 'ShareUIAbility', `onNewWant, want=${JSON.stringify(want)}`);
-    super.onNewWant(want, launchParam);
-    this.handleShare(want)
-
-    this.handleWeChatCallIfNeed(want)
-  }
-
-
-  private handleWeChatCallIfNeed(want: Want) {
-    WXApi.handleWant(want, WXEventHandler)
-  }
-
-  //处理其他app点击分享的视频或音频的打开本app播放器播放视频或者音频
-  handleShare(want: Want){
-    systemShare.getSharedData(want)
-      .then((data: systemShare.SharedData) => {
-        data.getRecords().forEach((record: systemShare.SharedRecord) => {
-
-          let uri = record.uri
-          if (uri == null || uri == undefined|| StrUtil.isEmpty(uri)) {
-            console.info('uri is invalid');
-            return;
-          }
-          hilog.info(0x0000, 'testTag', `onCreate or onNewWant, uri=${uri}`);
-          const eventData: emitter.EventData = {
-            data: {
-              message: uri
-            }
-          };
-          if(Utility.isMeidaByExtension(uri)){
-            emitter.emit({ eventId: 2 }, eventData); // 发送音频打开广播事件
-          }else{
-            emitter.emit({ eventId: 1 }, eventData); // 发送视频打开广播事件
-          }
-          // 处理分享数据
-        });
-      })
-      .catch((error: BusinessError) => {
-        console.error(`Failed to getSharedData. Code: ${error.code}, message: ${error.message}`);
-        this.context.terminateSelf();
-      });
-
-
-  }
-
-
-  onWindowStageCreate(windowStage: window.WindowStage): void {
-    // Main window is created, set main page for this ability
-    AppUtil.init(this.context);
-
-    // PreferencesUtil.init(CommonConstants.PLAY_STORE)
-    // Main window is created, set main page for this ability
-
-
-    //1.获取应用主窗口。
-    let windowClass: window.Window | null = null;
-    windowStage.getMainWindow((err: BusinessError, data) => {
-      windowClass = data;
-
-      globalThis.windowClass = data // 赋值给全局变量windowClass
-      let avoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)
-      let topRectHeight =  px2vp(avoidArea.topRect.height);
-      AppStorage.setOrCreate('topRectHeight', topRectHeight);
-      LogUtil.info('onecold  topRectHeight = '+topRectHeight);
-      windowClass.on('avoidAreaChange', this.onAvoidAreaChange)
-    })
-    AppStorage.setOrCreate('windowStage',windowStage);
-
-
-
-
-    GlobalContext.getContext().setObject('windowClass',windowClass)
-    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
-
-    windowStage.loadContent('pages/SplashIndex', (err, data) => {
-      if (err.code) {
-        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
-        return;
-      }
-
-      //一多断点开发
-      windowStage.getMainWindow().then((data: window.Window) => {
-        this.uiContext = data.getUIContext();
-        let widthBp: WidthBreakpoint = this.uiContext.getWindowWidthBreakpoint();
-        let heightBp: HeightBreakpoint = this.uiContext.getWindowHeightBreakpoint();
-        AppStorage.setOrCreate('currentWidthBreakpoint', widthBp);
-        AppStorage.setOrCreate('currentHeightBreakpoint', heightBp);
-        data.on('windowSizeChange', this.onWindowSizeChange);
-      }).catch((err: BusinessError) => {
-        console.error(`Failed to obtain the main window. Cause code: ${err.code}, message: ${err.message}`);
-      });
-      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
-    });
-
-    DemoConstants.windowStage = windowStage
-
-
-  }
-}

+ 6 - 2
entry/src/main/ets/pages/AboutPage.ets

@@ -216,13 +216,17 @@ export struct AboutPage{
               })
             })
         }
-        .height('100%')
         .width('100%')
 
       }
-      .height(px2vp(DisplayUtil.getHeight()-AppUtil.getStatusBarHeight()-AppUtil.getNavigationIndicatorHeight()))
+      .layoutWeight(1)
+      .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
+        .animation({ duration: 380, curve: Curve.Ease,delay:50 }))
       .backgroundColor(this.isDarkMode?Color.Black:this.themeColor)
       .linearGradient(!this.isDarkMode ? {colors:[[this.themeColor, 0.0], [this.getGradientColor(this.themeColor, 40), 0.6]]} : undefined)
     }
+    .layoutWeight(1)
+    .width('100%')
+    .height('100%')
   }
 }    

+ 205 - 110
entry/src/main/ets/pages/NewIndex.ets

@@ -1,4 +1,4 @@
-import { AppUtil, DeviceUtil, DisplayUtil, PreferencesUtil, ToastUtil } from '@pura/harmony-utils';
+import { AppUtil, DeviceUtil, DisplayUtil, LogUtil, PreferencesUtil, ToastUtil } from '@pura/harmony-utils';
 import TitleBar from '../view/TitleBar';
 import { router } from '@kit.ArkUI';
 import mainViewModel, { MainViewModel } from '../viewmodel/MainViewModel';
@@ -34,7 +34,11 @@ import json from '@ohos.util.json';
 import { UserCenter } from './UserCenter';
 import { ScanFilePage } from './ScanFilePage';
 import { AboutPage } from './AboutPage';
-
+import { smartMobilityCommon } from '@kit.CarKit';
+import { UpdateLogManager } from '../common/util/UpdateLogManager';
+import OnlineUpdateLogDialog from '../dialog/OnlineUpdateLog';
+import OnlineUpdateLog from '../dialog/OnlineUpdateLog';
+// import { FFmpeg, FFProgressMessageParser } from '@sj/ffmpeg';
 
 const TAG = 'NewIndex'; // 日志标签
 
@@ -71,9 +75,14 @@ struct NewIndex {
   context = getContext(this);
   /** 音乐是否为零状态(预留) */
   @Provide('isMusicZero') isMusicZero: boolean = false;
+  @State isShowTitleBar: boolean = true //是否显示分类导航条
   /** 本地音乐列表,持久化存储 */
   @StorageLink('musicLocalList') musicLocalList: Array<VideoItem> = []
   @Provide isFavMusic: boolean = false
+  @StorageProp('windowWidth') windowWidth: number = 0;
+  @StorageProp('windowHeight') windowHeight: number = 0;
+  @StorageProp('isHiCarStatus') isHiCarStatus: boolean = false;
+  @State isShowUpdateDialog: boolean = false //是否显示更新日志开关
   /** 标题栏配置模型 */
   @State titleBarModel: TitleBar.Model = new TitleBar.Model()
     .setTitleTextStyle(FontStyle.Normal)
@@ -89,7 +98,7 @@ struct NewIndex {
     .setLeftTitleBackground($r('app.color.title_bar_bg'))
     .setOnLeftClickListener(() => {
       // 打开菜单栏动画
-      animateTo({ duration: 555 }, () => {
+      this.getUIContext()?.animateTo({ duration: 555 }, () => {
         this.isShowDrawer = true
         this.offsetX = 0
       })
@@ -140,7 +149,7 @@ struct NewIndex {
       } else {
         if (this.isShowDrawer) {
           // 如果抽屉菜单打开,先关闭抽屉
-          animateTo({ duration: 555 }, () => {
+          this.getUIContext()?.animateTo({ duration: 555 }, () => {
             this.isShowDrawer = false
           })
         } else {
@@ -165,6 +174,11 @@ struct NewIndex {
     this.hasActiveSubscription = PreferencesUtil.getBooleanSync('hasActiveSubscription', false);
   }
 
+
+  onPageShow() {
+
+
+  }
   /**
    * 页面显示生命周期钩子
    * - 注册断点系统
@@ -175,6 +189,8 @@ struct NewIndex {
    */
 
   async aboutToAppear() {
+    this.isShowTitleBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_TITLTBAR, true)
+
     Utility.enableFullScreen()
     this.topRectHeight = px2vp(AppUtil.getStatusBarHeight());
     Utility.getAppName(getContext(this)).then((appName: string) => {
@@ -210,8 +226,44 @@ struct NewIndex {
       this.refreshUserInfoState();
     });
 
+    let eventSetting: emitter.InnerEvent = { eventId: 333 }
+    // 监听广播事件(通用设置配置更新)
+    emitter.on(eventSetting, (eventData: emitter.EventData) => {
+      this.isShowTitleBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_TITLTBAR, true)
+
+    });
+
+    console.log('getHiCarStatus isHiCarStatus:' + this.isHiCarStatus)
+    if(this.isHiCarStatus||this.isBigScreen()){
+      this.getUIContext()?.animateTo({ duration: 555 }, () => {
+        this.isShowDrawer = true
+        this.offsetX = 0
+      })
+      this.isShowTitleBar = false
+    }
+
+    // 检查并显示更新日志
+    this.checkAndShowUpdateLog();
+  }
+
+  /**
+   * 检查并显示更新日志
+   */
+  private async checkAndShowUpdateLog() {
+    try {
+      const shouldShow = await UpdateLogManager.checkAndShowUpdateLog();
+      if (shouldShow) {
+          this.isShowUpdateDialog = !this.isShowUpdateDialog
+          UpdateLogManager.markCurrentVersionShown()
+      }
+    } catch (error) {
+      console.error('NewIndex checkAndShowUpdateLog error:', error);
+    }
   }
 
+
+
+
   /**
    * 页面消失生命周期钩子
    * 注销断点系统
@@ -221,6 +273,7 @@ struct NewIndex {
     this.breakpointSystem.unregister();
     emitter.off(888);
     emitter.off(1001);
+
   }
 
   build() {
@@ -229,8 +282,6 @@ struct NewIndex {
         this.getLeftView()
       }
 
-      // .backgroundColor(Color.Transparent)
-
       Column() {
         Stack() {
           // 本地音乐内容区
@@ -246,25 +297,33 @@ struct NewIndex {
           AboutPage()
             .visibility(this.mType === 4 ? Visibility.Visible : Visibility.None)
           
-          // 抽屉打开时的遮罩层,用于拦截点击事件
-          if (this.isShowDrawer) {
+          // 抽屉打开时的遮罩层,用于拦截点击事件  这个只能正常尺寸的手机竖屏的才能生效
+          if (this.isShowDrawer&&this.isPhonePortrait()) {
             Column()
               .width('100%')
               .height('100%')
               .backgroundColor(Color.Transparent)
               .onClick(() => {
                 // 点击遮罩层关闭抽屉
-                animateTo({ duration: 555 }, () => {
+                this.getUIContext()?.animateTo({ duration: 555 }, () => {
                   this.isShowDrawer = false
                 })
               })
           }
         }
+        .bindSheet($$this.isShowUpdateDialog, this.updateSheet(), {
+          height: '90%',
+          dragBar: true,
+          preferType: this.currentBreakpoint !== BreakpointTypeEnum.SM ? SheetType.CENTER : SheetType.BOTTOM,
+          showClose: true,
+          blurStyle: BlurStyle.Regular,
+          title: { title: '更新日志' }
+        })
       }
     }
     .showControlButton(false)
     .minContentWidth(0)
-    .sideBarWidth(this.isBigScreen() ? 300 : 270)
+    .sideBarWidth(260)
     .autoHide(true)
     .showSideBar($$this.isShowDrawer)
     .onChange((value: boolean) => {
@@ -273,6 +332,45 @@ struct NewIndex {
 
   }
 
+
+
+  //是不是hicar的屏幕分辨率
+  isHiCar() {
+    const hiCarAspectRatios: HiCarAspectRatio[] = [
+      { ratio: 800 / 480, name: "800x480" },
+      { ratio: 762 / 752, name: "762x752" },
+      { ratio: 968 / 1280, name: "968x1280" },
+      { ratio: 1200 / 1200, name: "1200x1200" },
+      { ratio: 1280 / 720, name: "1280x720" },
+      { ratio: 1920 / 1080, name: "1920x1080" }
+    ];
+    const currentRatio: number = this.windowWidth / this.windowHeight;
+    const RATIO_TOLERANCE: number = 0.1; // 宽高比容差
+
+    for (const hiCarRatio of hiCarAspectRatios) {
+      if (Math.abs(currentRatio - hiCarRatio.ratio) <= RATIO_TOLERANCE) {
+        LogUtil.info(`HiCar detected: ${hiCarRatio.name}, actual: ${this.windowWidth}x${this.windowHeight}`);
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  //是不是正常的手机竖屏
+  isPhonePortrait() {
+    LogUtil.info('twocold this.currentHeightBreakpoint = '+this.currentHeightBreakpoint)
+    LogUtil.info('twocold this.currentWidthBreakpoint = '+this.currentWidthBreakpoint)
+    if(DeviceUtil.getDeviceType() === resourceManager.DeviceType.DEVICE_TYPE_PHONE
+      &&this.currentHeightBreakpoint == HeightBreakpoint.HEIGHT_LG
+      &&this.currentWidthBreakpoint==WidthBreakpoint.WIDTH_SM){//如果是手机横屏,返回false
+
+      return true
+    }
+
+    return false
+  }
+
   /**
    * 判断是否横屏
    * @returns
@@ -295,17 +393,7 @@ struct NewIndex {
   getLeftView() {
     Column() {
       Column() {
-        // 抽屉顶部背景图
-        // Image($r('app.media.bg_music'))
-        //   .width('100%')
-        //   .visibility(this.isCoverOpacity()?Visibility.None:Visibility.Visible)
-        //   .height(180)
-        //   .borderRadius({
-        //     topLeft: 0,
-        //     topRight	: 20,
-        //     bottomLeft: 0,
-        //     bottomRight: 0
-        //   })
+
         this.buildUserInfoCard()
 
         this.getDrawerView()
@@ -342,11 +430,11 @@ struct NewIndex {
         .onActionEnd(() => {
           // 手势结束判断是否关闭抽屉
           if (this.offsetX < -DisplayUtil.getWidth() / 15) {
-            animateTo({ duration: 555 }, () => {
+            this.getUIContext()?.animateTo({ duration: 555 }, () => {
               this.isShowDrawer = false
             })
           } else {
-            animateTo({ duration: 555 }, () => {
+            this.getUIContext()?.animateTo({ duration: 555 }, () => {
               this.isShowDrawer = true
               this.offsetX = 0
             })
@@ -371,25 +459,37 @@ struct NewIndex {
   buildUserInfoCard() {
     Column() {
       Row() {
-        Image(this.userAvatarUrl && this.userAvatarUrl.length > 0 ? this.userAvatarUrl : this.userAvatar)
-          .width(50)
-          .height(50)
-          .margin({ left: 12 })
-          .borderRadius('50%')
-          .clip(true)
-          .backgroundColor(this.isDarkMode ? Color.Black : Color.White)
-          .shadow({
-            radius: 8,
-            color: 0x11000000,
-            offsetX: 0,
-            offsetY: 2
-          })
+        Stack({ alignContent: Alignment.BottomEnd }){
+          Image(this.userAvatarUrl && this.userAvatarUrl.length > 0 ? this.userAvatarUrl : this.userAvatar)
+            .width(55)
+            .height(55)
+            .margin({ left: 12 })
+            .borderRadius('50%')
+            .clip(true)
+            // .fillColor(this.themeColor)
+            // .backgroundColor(this.isDarkMode ? Color.Black : Color.White)
+          Column() {
+            Text('VIP')
+              .fontSize(9)
+              .padding(2)
+              .textAlign(TextAlign.Center)
+              .fontWeight(FontWeight.Bolder)
+              .fontColor(Color.White)
+          }
+          .width(28)
+          .height(16)
+          .visibility(Utility.isNoble()?Visibility.Visible:Visibility.None)
+          .borderRadius(15)
+          .backgroundColor(this.themeColor)
+        }
+
+
         Column() {
           Row() {
             Text(this.isLogin ? this.userName : '未登录用户')
-              .fontSize(13)
-              .fontWeight(FontWeight.Bold)
-              .fontColor(this.isDarkMode ? Color.White : Color.Black)
+              .fontSize(14)
+              // .fontWeight(FontWeight.Bold)
+              // .fontColor(this.isDarkMode ? Color.White : Color.Black)
               .textAlign(TextAlign.Start)
               .maxLines(1)
               .width(110)
@@ -400,11 +500,11 @@ struct NewIndex {
 
           if (this.isLogin) {
             if (this.hasActiveSubscription) {
-              Text(this.subscriptionName)
-                .fontSize(13)
-                .fontColor(themeColorWithAlpha(this.themeColor, 0.8, false))
-                .textAlign(TextAlign.Start)
-                .padding({top:5})
+              // Text(this.subscriptionName)
+              //   .fontSize(13)
+              //   .fontColor(themeColorWithAlpha(this.themeColor, 0.8, false))
+              //   .textAlign(TextAlign.Start)
+              //   .padding({top:5})
               this.TimeTextBuilder()
             } else {
               Text('普通用户')
@@ -423,11 +523,12 @@ struct NewIndex {
         .margin({ left: 10 })
 
       }
+      .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.7})
       .onClick(() => {
         // if (!this.isLogin) {
           this.mType = 1
           if (!this.isBigScreen()) {
-            animateTo({ duration: 555 }, () => {
+            this.getUIContext()?.animateTo({ duration: 555 }, () => {
               this.isShowDrawer = false
             })
           // }
@@ -465,7 +566,7 @@ struct NewIndex {
   @Builder
   getDrawerView() {
     List({ space: 0, scroller: this.scroller }) {
-      ForEach(mainViewModel.getDrawerData2(), (item: ItemData, index: number) => {
+      ForEach(this.isHiCar()||!this.isShowTitleBar?mainViewModel.getDrawerData():mainViewModel.getDrawerData2(), (item: ItemData, index: number) => {
         ListItem() {
           Button({ type: ButtonType.Capsule, stateEffect: true }) {
             Row() {
@@ -488,7 +589,7 @@ struct NewIndex {
               Text(item.title)
                 .margin({ left: 10, right: 20 })
                 .fontSize(15)
-                .fontColor(this.mType == index ? this.themeColor : $r('app.color.index_tab_font_color'))
+                .fontColor(this.isTextSelected(index) ? this.themeColor : $r('app.color.index_tab_font_color'))
                 .fontWeight(480)
               Blank()
               // 右侧箭头
@@ -502,60 +603,45 @@ struct NewIndex {
             .height(55)
           }
           .backgroundColor(Color.Transparent)
+          .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 })
           .onClick(async () => {
             // 根据 item.id 跳转或切换功能
             switch (item.id) {
               case MainViewModel.MENU_MUSIC:
                 this.mType = 0
-                if (!this.isBigScreen()) {
-                  animateTo({ duration: 555 }, () => {
-                    this.isShowDrawer = false
-                  })
-                }
+                this.modeType = 0
+                this.doShowDrawer()
                 break
               case MainViewModel.MENU_FILE_SCAN:
                 this.mType = 2
-                if (!this.isBigScreen()) {
-                  animateTo({ duration: 555 }, () => {
-                    this.isShowDrawer = false
-                  })
-                }
+                this.doShowDrawer()
                 break
             // case MainViewModel.MENU_HOME:
             //   this.mType = 0
             //   this.modeType = 0
-            //   animateTo({ duration: 555 }, () => {
+            //   this.getUIContext()?.animateTo({ duration: 555 }, () => {
             //     this.isShowDrawer = false
             //   })
             //   break
               case MainViewModel.MENU_MIEDIA_KU:
                 this.modeType = 1
-                animateTo({ duration: 555 }, () => {
-                  this.isShowDrawer = false
-                })
+                this.mType = 0
+                this.doShowDrawer()
                 break
               case MainViewModel.MENU_MIEDIA_ARTIST:
                 this.modeType = 2
-                animateTo({ duration: 555 }, () => {
-                  this.isShowDrawer = false
-                })
+                this.mType = 0
+                this.doShowDrawer()
                 break
               case MainViewModel.MENU_MIEDIA_ALBUM:
                 this.modeType = 3
-                animateTo({ duration: 555 }, () => {
-                  this.isShowDrawer = false
-                })
+                this.mType = 0
+                this.doShowDrawer()
                 break
               case MainViewModel.MENU_SETTING:
                 this.mType = 3
-                if (!this.isBigScreen()) {
-                  animateTo({ duration: 555 }, () => {
-                    this.isShowDrawer = false
-                  })
-                }
-                // router.pushUrl({
-                //   url: 'pages/SettingPage'
-                // }, router.RouterMode.Single);
+                this.doShowDrawer()
+
                 break
               case MainViewModel.MENU_VIP:
                 router.pushUrl({
@@ -564,31 +650,18 @@ struct NewIndex {
                 break
               case MainViewModel.MENU_USER:
                 this.mType = 1
-                if (!this.isBigScreen()) {
-                  animateTo({ duration: 555 }, () => {
-                    this.isShowDrawer = false
-                  })
-                }
-                // router.pushUrl({
-                //   url: 'pages/UserCenter'
-                // }, router.RouterMode.Single);
+                this.doShowDrawer()
                 break
               case MainViewModel.MENU_NET_CONNECT:
                 this.mType = 1
-                animateTo({ duration: 555 }, () => {
-                  this.isShowDrawer = false
-                })
+                this.doShowDrawer()
                 break
               case MainViewModel.MENU_SIMI:
                 router.pushUrl({
                   url: 'pages/VerifyPage'
                 });
                 break
-              case MainViewModel.MENU_LIKE:
-                router.pushUrl({
-                  url: 'pages/LikeVideoPage'
-                });
-                break
+
               case MainViewModel.MENU_DUTY:
                 router.pushUrl({
                   url: 'pages/WebIndex',
@@ -598,26 +671,10 @@ struct NewIndex {
               case MainViewModel.MENU_HAOPING:
                 Utility.gotoMarket(getContext(this) as common.UIAbilityContext, this.bundleName)
                 break
-              case MainViewModel.MENU_TEST_SPEED:
-                router.pushUrl({
-                  url: 'pages/SpeedIndex'
-                });
-                break
-              case MainViewModel.MENU_OPTIMIZE:
-                router.pushUrl({
-                  url: 'pages/AddSpeedPage'
-                });
-                break
+
               case MainViewModel.MENU_ABOUT:
                 this.mType = 4
-                if (!this.isBigScreen()) {
-                  animateTo({ duration: 555 }, () => {
-                    this.isShowDrawer = false
-                  })
-                }
-                // router.pushUrl({
-                //   url: 'pages/AboutPage'
-                // }, router.RouterMode.Single);
+                this.doShowDrawer()
                 break
               case MainViewModel.MENU_UPDATE:
                 AlertDialog.show({
@@ -648,7 +705,6 @@ struct NewIndex {
             }
           })
         }
-        .clickEffect({ level: ClickEffectLevel.HEAVY })
         .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
           .animation({ duration: 600, curve: Curve.Ease, delay: 60 * index }))
         .width('90%')
@@ -670,6 +726,26 @@ struct NewIndex {
     .edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果
   }
 
+  isTextSelected(index:number):boolean{
+    if(this.isShowTitleBar&&!this.isHiCar()){
+        return this.mType ==index
+    }else{
+      if(this.mType == 0){
+        return this.modeType == index
+      }
+      return this.mType+3 == index
+    }
+    return false
+  }
+
+  doShowDrawer(){
+    if (!this.isBigScreen()) {
+      this.getUIContext()?.animateTo({ duration: 555 }, () => {
+        this.isShowDrawer = false
+      })
+    }
+  }
+
   gotoShare() {
     let shareData: systemShare.SharedData = new systemShare.SharedData({
       utd: uniformTypeDescriptor.UniformDataType.TEXT,
@@ -692,6 +768,19 @@ struct NewIndex {
     });
   }
 
+
+
+  @Builder
+  updateSheet() {
+    Scroll() {
+      Column() {
+        OnlineUpdateLog()
+      }
+    }
+    .width('100%')
+    .height('100%')
+  }
+
   // /**
   //  * ================== 穿山甲广告相关 ==================
   //  */
@@ -860,4 +949,10 @@ function themeColorWithAlpha(themeColor: string, alpha: number, isDarkMode: bool
   const g = parseInt(color.substring(2, 4), 16);
   const b = parseInt(color.substring(4, 6), 16);
   return `rgba(${r},${g},${b},${alpha})`;
+}
+
+// 定义接口
+interface HiCarAspectRatio {
+  ratio: number;
+  name: string;
 }

+ 111 - 37
entry/src/main/ets/pages/ScanFilePage.ets

@@ -22,14 +22,18 @@ import { DialogHelper } from '@pura/harmony-dialog'
 // @Entry
 @Component
 export struct ScanFilePage{
+  @StorageProp('mediaKuList')  mediaKuList: Array<VideoItem> = []; //媒体库文件
   @State rootPath:string = '' //音频根目录
   @State lockPath:string = ''
   @Consume isShowDrawer: boolean;
   @Consume offsetX: number;
   @State strText:string = ''
+  @State textVisi:Visibility=Visibility.Hidden
   @State isStart:boolean = false
   @State isStartCover:boolean = false
+  @State isStartSync:boolean = false
   @State appName:string = ''
+  @Consume mType: number;
   //lottie动画构建渲染上下文
   private mainRenderingSettings: RenderingContextSettings = new RenderingContextSettings(true)
   private path:string = "common/lottie/leida.json"
@@ -94,14 +98,19 @@ export struct ScanFilePage{
     const eventData: emitter.EventData = {};
     emitter.emit({ eventId: 101 }, eventData); // 发送视频打开广播事件
     this.watchStatus(false)
-    ToastUtil.showToast('扫描文件入库成功!')
     if(isOnekey){
       this.isStartCover = false
+      this.strText = '获取成功!'
     }else{
-      this.isStart = false
+      if(this.isStartSync){
+        this.isStartSync = false
+        this.strText = '校正数据成功!'
+      }else{
+        this.strText = '扫描文件入库成功!'
+        this.isStart = false
+      }
 
     }
-    this.strText = ''
     //结束动画
     lottie.pause()
     lottie.stop()
@@ -122,28 +131,17 @@ export struct ScanFilePage{
   }
 
 
-  // @State titleBarModel: TitleBar.Model = new TitleBar.Model()
-  //   .setTitleTextStyle(FontStyle.Normal)
-  //   .setTitleBarStyle(TitleBar.BarStyle.TRANSPARENT)
-  //   .setLeftIcon($r('app.media.left_back_white'))
-  //   .setTitleName("文件扫描")
-  //   .setTitleFontColor(Color.White)
-  //   .setTitleBarBackground(this.themeColor)
-  //   .setLeftTitleBackground(this.themeColor)
-  //   .setTitleBarBottomLineColor(this.themeColor)
-  //   .setOnLeftClickListener(() => {
-  //     router.back()
-  //
-  //   })
   initState(isOnekey:boolean){
-
+    this.textVisi = Visibility.Visible
     this.initLottie(this.path,true)
     if(isOnekey){
+      this.strText = '正在获取'
       this.isStartCover = true
     }else{
+      this.strText = Utility.resourceToString(getContext(this),$r('app.string.scaning'))
       this.isStart = true
     }
-    this.strText = Utility.resourceToString(getContext(this),$r('app.string.scaning'))
+
     lottie.play()
   }
 
@@ -224,11 +222,13 @@ export struct ScanFilePage{
           .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
             .animation({ duration: 380, curve: Curve.Ease,delay:30 }))
 
-          // Text(this.strText)
-          //   .margin({ right: 20,top:20,bottom:20 })
-          //   .fontSize(16)
-          //   .fontColor(Color.Black)
-          //   .fontWeight(480)
+          Text(this.strText)
+            .height(50)
+            .margin({ right: 20 })
+            .fontSize(16)
+            .fontColor($r('app.color.text_color'))
+            .fontWeight(480)
+            .visibility(this.textVisi)
 
 
           Column() {
@@ -274,6 +274,26 @@ export struct ScanFilePage{
             // .width('100%')
             .margin({ left:25,right: 25 ,top:20,bottom:20 })
 
+            Row() {
+
+
+              Image($r('app.media.selected'))
+                .width(16)
+                .height(16)
+                .fillColor(this.themeColor)
+                .margin({ left:10})
+                .alignSelf(ItemAlign.Center)
+              Text($r('app.string.sync_tips'))
+                .margin({ left: 10, right: 20 })
+                .fontSize(15)
+                .fontColor(Color.Gray)
+                .fontWeight(480)
+                .layoutWeight(1)
+
+            }
+            // .width('100%')
+            .margin({ left:25,right: 25 ,bottom:20 })
+
           }
           .width(this.currentBreakpoint !== BreakpointTypeEnum.SM?450:350)
           .backgroundColor($r('app.color.index_background'))
@@ -284,16 +304,13 @@ export struct ScanFilePage{
             .animation({ duration: 380, curve: Curve.Ease,delay:60 }))
 
           Column() {
-            Button(this.isStart?'正在扫描':$r('app.string.start_scan'), { type: ButtonType.Capsule, stateEffect: false })
+            Button($r('app.string.start_scan'), { type: ButtonType.Capsule, stateEffect: false })
 
               .width(180)
               .height(55)
               .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
               .margin({ top: 20, bottom: 10 })
-              .linearGradient({
-                direction: GradientDirection.Right,
-                colors: [['#ff37a0fc', 0.0], ['#67e667', 0.5], ['#f5856e', 1.0]]
-              })
+              .backgroundColor(this.themeColor)
               .enabled(this.isStart ?false:true)
               .onClick(() => {
                 this.doOptimize(false)
@@ -301,15 +318,12 @@ export struct ScanFilePage{
               })
               .alignSelf(ItemAlign.Center)
 
-            Button(this.isStartCover?'正在扫描':$r('app.string.onekey_cover'), { type: ButtonType.Capsule, stateEffect: false })
+            Button($r('app.string.onekey_cover'), { type: ButtonType.Capsule, stateEffect: false })
               .width(180)
               .height(55)
               .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
-              .margin({ top: 10, bottom: 20 })
-              .linearGradient({
-                direction: GradientDirection.Right,
-                colors: [['#ff37a0fc', 0.0], ['#67e667', 0.5], ['#f5856e', 1.0]]
-              })
+              .margin({ top: 10, bottom: 10 })
+              .backgroundColor(this.themeColor)
               .enabled(this.isStartCover ?false:true)
               .onClick(() => {
                 if(PreferencesUtil.getStringSync('COVER_API','')===''){
@@ -321,6 +335,19 @@ export struct ScanFilePage{
               })
               .alignSelf(ItemAlign.Center)
 
+            Button($r('app.string.sync_data'), { type: ButtonType.Capsule, stateEffect: false })
+              .width(180)
+              .height(55)
+              .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
+              .margin({ top: 10, bottom: 20 })
+              .backgroundColor(this.themeColor)
+              .enabled(this.isStartSync ?false:true)
+              .onClick(() => {
+                this.doSyncData()
+
+              })
+              .alignSelf(ItemAlign.Center)
+
 
           }
           .transition(TransitionEffect.move(TransitionEdge.BOTTOM)
@@ -337,6 +364,24 @@ export struct ScanFilePage{
     }
   }
 
+  doSyncData() {
+    this.watchStatus(true)
+    this.initLottie(this.path,true)
+    this.isStartSync = true
+    this.textVisi = Visibility.Visible
+    this.strText = Utility.resourceToString(getContext(this),$r('app.string.sync_dataing'))
+    lottie.play()
+    // 仅提取 filePath 列表
+    const filePaths = this.mediaKuList.map(item => item.filePath);
+    Logger.info('xiaozheng 校正数据开始 this.filePaths length= ' + filePaths.length)
+    const task2 = new taskpool.Task(syncDataTask, getContext(this), filePaths);
+    taskpool.execute(task2, taskpool.Priority.HIGH).then(()=>{
+      this.endScan(false)
+    }).catch((e:object)=>{
+      console.info("task2 catch e: " + e);
+    })
+  }
+
   showTipsDialog() {
     DialogHelper.showCustomContentDialog({
       dialogId: 'tips',
@@ -380,9 +425,10 @@ export struct ScanFilePage{
           .margin({ left: 6 })
           .onClick(() => {
             DialogHelper.closeDialog('tips'); //关闭弹框
-            router.pushUrl({
-              url: 'pages/SettingPage'
-            }, router.RouterMode.Single);
+            this.mType = 3
+            // router.pushUrl({
+            //   url: 'pages/SettingPage'
+            // }, router.RouterMode.Single);
           })
       }
 
@@ -395,6 +441,34 @@ export struct ScanFilePage{
 
 
 
+//扫描数据库校正对比数据是否真实存在,不存在在删除数据
+@Concurrent
+async function syncDataTask(context: Context, mediaKuList: Array<string>) {
+  const table: MediaTable = new MediaTable(context);
+  for (const item of mediaKuList) {
+    const filePath = item;
+    // 检查 filePath 是否存在
+    const exists:boolean = await FileUtil.accessSync(filePath);
+    // 如果 filePath 不存在,则删除数据库中的数据
+    if (!exists) {
+      table.getRdbStore(context,  async (err:Error) => {
+        if (err) {
+          return;
+        }
+        Logger.info('xiaozheng 校正数据开始 this.exists = ' + exists+' filepath = ' +filePath)
+        await table.deleteDataFilePath(filePath, (result:boolean) => {
+          if(result){
+            Logger.info(`xiaozheng Deleted item success with filePath: ${filePath}`);
+          }
+
+        });
+      })
+
+    }
+  }
+}
+
+//扫描文件入库
 @Concurrent
 async function  scanDirectoryTask(context: Context, dirPath: string, lockPath: string,cover_api:string) {
   const stack: string[] = [dirPath];

+ 243 - 31
entry/src/main/ets/pages/SettingPage.ets

@@ -48,9 +48,15 @@ export struct SettingPage {
   static readonly IS_SHOW_SLLYRIC: string = 'IS_SHOW_SLLYRIC';
   static readonly IS_CIRCLE_BTN: string = 'isCircleBtn';
   static readonly IS_COVER_TOP: string = 'IS_COVER_TOP';
+  static readonly IS_SHOW_HEADER: string = 'IS_SHOW_HEADER';
   static readonly IS_COVER_TOP_BIG: string = 'IS_COVER_TOP_BIG';
   public static THEME_COLOR_KEY: string = 'THEME_COLOR';
   public static TWO_FINGER_TYPE: string = 'twoFingerType';
+  public static LONG_PRESS_SPEED: string = 'longPressSpeed';
+  public static IS_PLAYLIST_BG_GRASS: string = 'isPlayListBgGrass';
+  static readonly IS_SWIPE: string = 'isSwipe'
+  static readonly IS_AUTO_HIDE_PROGRESS: string = 'IS_AUTO_HIDE_PROGRESS';
+
   public static THEME_COLOR_LIST: Array<ThemeColorItem> = [
     { name: '玫瑰粉', color: '#FF4081', isVip: false },
     { name: '经典蓝', color: '#0A59F7', isVip: false },
@@ -88,10 +94,12 @@ export struct SettingPage {
   @State isShowHistory: boolean = true //是否显示最近播放
   @State isShowPlayPageBack: boolean = false //是否显示播放页返回键
   @State isShowSingleLineLyric: boolean = false //是否显示播放页返回键
+  @State isShowHeader: boolean = true
   @State isCustomizeBg: boolean = false //自定义背景界面
   @State isCustomizeBgSheet: boolean = false //自定义背景界面
   @State isShowTitleBar: boolean = true //是否显示分类导航条
   @State isShowAllBar: boolean = true //是否显示播放全部条
+  @State longPressSpeed: number = 3 //长按默认倍数
   @State blurValue: number = 0 //背景模糊
   @State bgBrightness: number = 0 //背景亮度
   @State isGridMusic: boolean = true //是否网格布局
@@ -99,9 +107,14 @@ export struct SettingPage {
   @State twoFingerType: number = 3 //双指放大瘦小Grid或list的大小
   @State isScrollHide: boolean = false //是否滚动隐藏
   @State isSameTimePlay: boolean = false //是否和其他app同时播放
+  @State isCoverRectangle: boolean = false
   @State isSavePlayMode: boolean = true
   @State isCoverTop: boolean = true
   @State isCoverTopBig: boolean = false//顶部大封面部分手机显示会和播放控制页重叠
+  @State isSwipe: boolean = false //listItem的左滑开关
+  @State isPlayListBgGrass: boolean = true//是否播放列表玻璃透明效果
+  @State is_auto_hide_progress: boolean = false
+
 
   @State customizeBgPath: string | undefined = '';
   context = getContext(this);
@@ -213,9 +226,15 @@ export struct SettingPage {
     this.isCoverTop = PreferencesUtil.getBooleanSync(SettingPage.IS_COVER_TOP, false)
     this.isShowTitleBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_TITLTBAR, true)
     this.isShowAllBar = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_ALLBAR, true)
-    this.isShowPlayPageBack = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_PLAYPAGE_BACK, false)
+    this.isShowPlayPageBack = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_PLAYPAGE_BACK, true)
     this.isShowSingleLineLyric = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_SLLYRIC, false)
     this.isCoverTopBig = PreferencesUtil.getBooleanSync(SettingPage.IS_COVER_TOP_BIG, false)
+    this.longPressSpeed = PreferencesUtil.getNumberSync(SettingPage.LONG_PRESS_SPEED, 3)
+    this.isShowHeader = PreferencesUtil.getBooleanSync(SettingPage.IS_SHOW_HEADER, true)
+    this.isCoverRectangle = PreferencesUtil.getBooleanSync(SettingPage.IS_COVER_RECTANGLE, true)
+    this.isPlayListBgGrass = PreferencesUtil.getBooleanSync(SettingPage.IS_PLAYLIST_BG_GRASS, true)
+    this.isSwipe = PreferencesUtil.getBooleanSync(SettingPage.IS_SWIPE, true)
+    this.is_auto_hide_progress = PreferencesUtil.getBooleanSync(SettingPage.IS_AUTO_HIDE_PROGRESS, false)
     // 只用 themeMode 控制主题
     this.themeMode = PreferencesUtil.getNumberSync(SettingPage.THEME_MODE, 0)
     this.applyThemeMode(this.themeMode)
@@ -641,6 +660,7 @@ export struct SettingPage {
               Toggle({ type: ToggleType.Switch, isOn: this.isScrollHide })
                 .selectedColor(this.themeColor)
                 .switchPointColor(Color.White)
+                .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
                 .margin({ right: 18 })
                 .onChange((checked: boolean) => {
                   this.isScrollHide = checked;
@@ -657,7 +677,7 @@ export struct SettingPage {
 
             // 网格布局
             Row() {
-              Text('网格布局')
+              Text('网格模式')
                 .margin({ left: 18 })
                 .fontSize(15)
                 .fontColor(Color.Gray)
@@ -667,6 +687,7 @@ export struct SettingPage {
                 .selectedColor(this.themeColor)
                 .switchPointColor(Color.White)
                 .margin({ right: 18 })
+                .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
                 .onChange((checked: boolean) => {
                   this.isGridMusic = checked;
                   PreferencesUtil.put(SettingPage.IS_GRID_MUSIC, this.isGridMusic)
@@ -677,6 +698,55 @@ export struct SettingPage {
             }
             .height(55)
             .clickEffect({ level: ClickEffectLevel.HEAVY })
+            Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
+            // 封面方形
+            Row() {
+              Text('封面方形')
+                .margin({ left: 18 })
+                .fontSize(15)
+                .fontColor(Color.Gray)
+                .fontWeight(480)
+                .layoutWeight(1)
+              Toggle({ type: ToggleType.Switch, isOn: this.isCoverRectangle })
+                .selectedColor(this.themeColor)
+                .switchPointColor(Color.White)
+                .margin({ right: 18 })
+                .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
+                .onChange((checked: boolean) => {
+                  this.isCoverRectangle = checked;
+                  PreferencesUtil.put(SettingPage.IS_COVER_RECTANGLE, this.isCoverRectangle)
+                  this.sendChangeEvent()
+                })
+                .width(50)
+                .height(30);
+            }
+            .height(55)
+            .clickEffect({ level: ClickEffectLevel.HEAVY })
+
+            Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
+            //播放列表透明
+            Row() {
+              Text('播放列表背景透明')
+                .margin({ left: 18 })
+                .fontSize(15)
+                .fontColor(Color.Gray)
+                .fontWeight(480)
+                .layoutWeight(1)
+              Toggle({ type: ToggleType.Switch, isOn: this.isPlayListBgGrass })
+                .selectedColor(this.themeColor)
+                .switchPointColor(Color.White)
+                .margin({ right: 18 })
+                .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
+                .onChange((checked: boolean) => {
+                  this.isPlayListBgGrass = checked;
+                  PreferencesUtil.put(SettingPage.IS_PLAYLIST_BG_GRASS, this.isPlayListBgGrass)
+                  this.sendChangeEvent()
+                })
+                .width(50)
+                .height(30);
+            }
+            .height(55)
+            .clickEffect({ level: ClickEffectLevel.HEAVY })
 
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
 
@@ -732,32 +802,45 @@ export struct SettingPage {
             .height(55)
             .clickEffect({ level: ClickEffectLevel.HEAVY })
 
-            Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
-            // 封面顶部
-            Row() {
-              Text('顶部大封面')
-                .margin({ left: 18 })
-                .fontSize(15)
-                .fontColor(Color.Gray)
-                .fontWeight(480)
-                .layoutWeight(1)
-              Toggle({ type: ToggleType.Switch, isOn: this.isCoverTopBig })
-                .selectedColor(this.themeColor)
-                .switchPointColor(Color.White)
-                .margin({ right: 18 })
-                .onChange((checked: boolean) => {
-                  this.isCoverTopBig = checked;
-                  if(this.isCoverTopBig){
-                    ToastUtil.showToast('启动该开关部分设备封面太大导致和音乐控制重叠!')
-                  }
-                  PreferencesUtil.put(SettingPage.IS_COVER_TOP_BIG, this.isCoverTopBig)
-                  this.sendChangeEvent()
+              Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
+                .opacity(this.isCoverTop ? 1 : 0)
+                .visibility(this.isCoverTop ? Visibility.Visible : Visibility.None)
+                .animation({
+                  duration: 500,
+                  curve: 'ease-in-out' // 可选动画曲线
                 })
-                .width(50)
-                .height(30);
-            }
-            .height(55)
-            .clickEffect({ level: ClickEffectLevel.HEAVY })
+              // 封面顶部
+              Row() {
+                Text('顶部大封面')
+                  .margin({ left: 18 })
+                  .fontSize(15)
+                  .fontColor(Color.Gray)
+                  .fontWeight(480)
+                  .layoutWeight(1)
+                Toggle({ type: ToggleType.Switch, isOn: this.isCoverTopBig })
+                  .selectedColor(this.themeColor)
+                  .switchPointColor(Color.White)
+                  .margin({ right: 18 })
+                  .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
+                  .onChange((checked: boolean) => {
+                    this.isCoverTopBig = checked;
+                    if(this.isCoverTopBig){
+                      ToastUtil.showToast('启动该开关部分设备封面太大导致和音乐控制重叠!')
+                    }
+                    PreferencesUtil.put(SettingPage.IS_COVER_TOP_BIG, this.isCoverTopBig)
+                    this.sendChangeEvent()
+                  })
+                  .width(50)
+                  .height(30);
+              }
+              .height(55)
+              .clickEffect({ level: ClickEffectLevel.HEAVY })
+              .opacity(this.isCoverTop ? 1 : 0)
+              .visibility(this.isCoverTop ? Visibility.Visible : Visibility.None)
+              .animation({
+                duration: 500,
+                curve: 'ease-in-out' // 可选动画曲线
+              })
 
 
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
@@ -773,6 +856,7 @@ export struct SettingPage {
               Toggle({ type: ToggleType.Switch, isOn: this.isCircleBtn })
                 .selectedColor(this.themeColor)
                 .switchPointColor(Color.White)
+                .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
                 .margin({ right: 18 })
                 .onChange((checked: boolean) => {
                   this.isCircleBtn = checked;
@@ -825,6 +909,7 @@ export struct SettingPage {
               Toggle({ type: ToggleType.Switch, isOn: this.isStartAutoPlay })
                 .selectedColor(this.themeColor)
                 .switchPointColor(Color.White)
+                .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
                 .margin({ right: 18 })
                 .onChange((checked: boolean) => {
                   this.isStartAutoPlay = checked;
@@ -836,6 +921,31 @@ export struct SettingPage {
             .height(55)
             .clickEffect({ level: ClickEffectLevel.HEAVY })
 
+
+            Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
+            // 列表是否可左滑
+            Row() {
+              Text('列表是否可左滑')
+                .margin({ left: 18 })
+                .fontSize(15)
+                .fontColor(Color.Gray)
+                .fontWeight(480)
+                .layoutWeight(1)
+              Toggle({ type: ToggleType.Switch, isOn: this.isSwipe })
+                .selectedColor(this.themeColor)
+                .switchPointColor(Color.White)
+                .margin({ right: 18 })
+                .onChange((checked: boolean) => {
+                  this.isSwipe = checked;
+                  PreferencesUtil.put(SettingPage.IS_SWIPE, this.isSwipe)
+                  this.sendChangeEvent()
+                })
+                .width(50)
+                .height(30);
+            }
+            .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
+
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
             // 记住播放进度
             Row() {
@@ -848,6 +958,7 @@ export struct SettingPage {
               Toggle({ type: ToggleType.Switch, isOn: this.isMemoryLastPlay })
                 .selectedColor(this.themeColor)
                 .switchPointColor(Color.White)
+                .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
                 .margin({ right: 18 })
                 .onChange((checked: boolean) => {
                   this.isMemoryLastPlay = checked;
@@ -872,6 +983,7 @@ export struct SettingPage {
               Toggle({ type: ToggleType.Switch, isOn: this.isMusicMemoryPlay })
                 .selectedColor(this.themeColor)
                 .switchPointColor(Color.White)
+                .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
                 .margin({ right: 18 })
                 .onChange((checked: boolean) => {
                   this.isMusicMemoryPlay = checked;
@@ -884,6 +996,46 @@ export struct SettingPage {
             .height(55)
             .clickEffect({ level: ClickEffectLevel.HEAVY })
 
+
+            Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
+
+            // 长按默认倍数
+            Row() {
+              Text('长按默认倍数')
+                .margin({ left: 18 })
+                .fontSize(15)
+                .fontColor(Color.Gray)
+                .fontWeight(480)
+                .layoutWeight(1)
+              Select([//倍速
+                { value: '0.25x' },
+                { value: '0.5x' },
+                { value: '0.75x' },
+                { value: '1x' },
+                { value: '1.25x' },
+                { value: '1.5x' },
+                { value: '1.75x' },
+                { value: '2x' },
+                { value: '2.5x' },
+                { value: '3x' }])
+                .font({ size: 15, weight: FontWeight.Medium })
+                .fontColor(Color.Gray)
+                .margin({right:18})
+                .selected(CommonConstants.video_speed_list.indexOf(this.longPressSpeed))
+                .value(Utility.optimizedFormat(this.longPressSpeed))
+                .onSelect(async (_index: number, text?: string | undefined) => {
+                  let speed = parseFloat(text?.replace('x', '') || '1');
+                  if (!CommonConstants.video_speed_list.includes(speed)) {
+                    speed = 1;
+                  }
+                  this.longPressSpeed = speed
+                  PreferencesUtil.put(SettingPage.LONG_PRESS_SPEED, this.longPressSpeed)
+                  this.sendChangeEvent()
+                })
+            }
+            .height(55)
+            .clickEffect({level:ClickEffectLevel.HEAVY})
+
             Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
             // 默认排序模式
             Row() {
@@ -927,6 +1079,7 @@ export struct SettingPage {
                 .layoutWeight(1)
               Toggle({ type: ToggleType.Switch, isOn: this.isSavePlayMode })
                 .selectedColor(this.themeColor)
+                .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
                 .switchPointColor(Color.White)
                 .margin({ right: 18 })
                 .onChange((checked: boolean) => {
@@ -952,6 +1105,7 @@ export struct SettingPage {
                 .selectedColor(this.themeColor)
                 .switchPointColor(Color.White)
                 .margin({ right: 18 })
+                .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
                 .onChange((checked: boolean) => {
                   this.isSameTimePlay = checked;
                   PreferencesUtil.put(SettingPage.IS_SAMETIME_PLAY, this.isSameTimePlay)
@@ -975,6 +1129,7 @@ export struct SettingPage {
               Toggle({ type: ToggleType.Switch, isOn: this.isMusicBGCover })
                 .selectedColor(this.themeColor)
                 .switchPointColor(Color.White)
+                .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
                 .margin({ right: 18 })
                 .onChange((checked: boolean) => {
                   this.isMusicBGCover = checked;
@@ -1021,12 +1176,11 @@ export struct SettingPage {
         .justifyContent(FlexAlign.Start)
       }
       .scrollBar(BarState.Auto)
-      .height(px2vp(DisplayUtil.getHeight() - AppUtil.getStatusBarHeight() - AppUtil.getNavigationIndicatorHeight()))
       .width('100%')
       .margin({ bottom: 10 })
       .backgroundColor($r('app.color.settings_background'))
     }
-
+    .layoutWeight(1)
     .height('100%')
     .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
   }
@@ -1061,6 +1215,7 @@ export struct SettingPage {
           .layoutWeight(1)
         Toggle({ type: ToggleType.Switch, isOn: this.isShowTitleBar })
           .selectedColor(this.themeColor)
+          .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
           .switchPointColor(Color.White)
           .margin({ right: 18 })
           .onChange((checked: boolean) => {
@@ -1087,6 +1242,7 @@ export struct SettingPage {
           .selectedColor(this.themeColor)
           .switchPointColor(Color.White)
           .margin({ right: 18 })
+          .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
           .onChange((checked: boolean) => {
             this.isShowAllBar = checked;
             PreferencesUtil.put(SettingPage.IS_SHOW_ALLBAR, this.isShowAllBar)
@@ -1111,6 +1267,7 @@ export struct SettingPage {
           .selectedColor(this.themeColor)
           .switchPointColor(Color.White)
           .margin({ right: 18 })
+          .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
           .onChange((checked: boolean) => {
             this.isShowSimi = checked;
             PreferencesUtil.put(SettingPage.IS_SHOW_SIMI, this.isShowSimi)
@@ -1123,7 +1280,7 @@ export struct SettingPage {
       .clickEffect({ level: ClickEffectLevel.HEAVY })
 
       Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
-      // 播放页返回键
+      // 播放页单行歌词
       Row() {
         Text('播放页单行歌词')
           .margin({ left: 18 })
@@ -1135,6 +1292,7 @@ export struct SettingPage {
           .selectedColor(this.themeColor)
           .switchPointColor(Color.White)
           .margin({ right: 18 })
+          .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
           .onChange((checked: boolean) => {
             this.isShowSingleLineLyric = checked;
             PreferencesUtil.put(SettingPage.IS_SHOW_SLLYRIC, this.isShowSingleLineLyric)
@@ -1146,6 +1304,32 @@ export struct SettingPage {
       .height(55)
       .clickEffect({ level: ClickEffectLevel.HEAVY })
 
+      Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
+
+      // 二级菜单新风格
+      Row() {
+        Text('二级菜单新风格')
+          .margin({ left: 18 })
+          .fontSize(15)
+          .fontColor(Color.Gray)
+          .fontWeight(480)
+          .layoutWeight(1)
+        Toggle({ type: ToggleType.Switch, isOn: this.isShowHeader })
+          .selectedColor(this.themeColor)
+          .switchPointColor(Color.White)
+          .margin({ right: 18 })
+          .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
+          .onChange((checked: boolean) => {
+            this.isShowHeader = checked;
+            PreferencesUtil.put(SettingPage.IS_SHOW_HEADER, this.isShowHeader)
+            this.sendChangeEvent()
+          })
+          .width(50)
+          .height(30);
+      }
+      .height(55)
+      .clickEffect({ level: ClickEffectLevel.HEAVY })
+
       Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
       // 播放页返回键
       Row() {
@@ -1159,6 +1343,7 @@ export struct SettingPage {
           .selectedColor(this.themeColor)
           .switchPointColor(Color.White)
           .margin({ right: 18 })
+          .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
           .onChange((checked: boolean) => {
             this.isShowPlayPageBack = checked;
             PreferencesUtil.put(SettingPage.IS_SHOW_PLAYPAGE_BACK, this.isShowPlayPageBack)
@@ -1170,6 +1355,31 @@ export struct SettingPage {
       .height(55)
       .clickEffect({ level: ClickEffectLevel.HEAVY })
 
+      Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
+      // 自动隐藏进度条两侧的按钮
+      Row() {
+        Text('自动隐藏进度条两侧的按钮')
+          .margin({ left: 18 })
+          .fontSize(15)
+          .fontColor(Color.Gray)
+          .fontWeight(480)
+          .layoutWeight(1)
+        Toggle({ type: ToggleType.Switch, isOn: this.is_auto_hide_progress })
+          .selectedColor(this.themeColor)
+          .switchPointColor(Color.White)
+          .margin({ right: 18 })
+          .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
+          .onChange((checked: boolean) => {
+            this.is_auto_hide_progress = checked;
+            PreferencesUtil.put(SettingPage.IS_AUTO_HIDE_PROGRESS, this.is_auto_hide_progress)
+            this.sendChangeEvent()
+          })
+          .width(50)
+          .height(30);
+      }
+      .height(55)
+      .clickEffect({ level: ClickEffectLevel.HEAVY })
+
       Line().width('100%').height(0.3).backgroundColor($r('app.color.index_tab_unselected_font_color'))
       // 显示我的收藏
       Row() {
@@ -1183,6 +1393,7 @@ export struct SettingPage {
           .selectedColor(this.themeColor)
           .switchPointColor(Color.White)
           .margin({ right: 18 })
+          .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
           .onChange((checked: boolean) => {
             this.isShowFAV = checked;
             PreferencesUtil.put(SettingPage.IS_SHOW_FAV, this.isShowFAV)
@@ -1207,6 +1418,7 @@ export struct SettingPage {
           .selectedColor(this.themeColor)
           .switchPointColor(Color.White)
           .margin({ right: 18 })
+          .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
           .onChange((checked: boolean) => {
             this.isShowHistory = checked;
             PreferencesUtil.put(SettingPage.IS_SHOW_HISTORY, this.isShowHistory)
@@ -1469,7 +1681,7 @@ export struct SettingPage {
           Toggle({ type: ToggleType.Switch, isOn: this.isCustomizeBg })
             .selectedColor(this.themeColor)
             .switchPointColor(Color.White)
-            .clickEffect({ level: ClickEffectLevel.HEAVY })
+            .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
             .margin({ right: 18 })
             .onChange((checked: boolean) => {
               this.isCustomizeBg = checked;

+ 21 - 2
entry/src/main/ets/pages/SplashIndex.ets

@@ -5,6 +5,7 @@ import Logger from '../common/util/Logger'
 import { router, window } from '@kit.ArkUI'
 import { CommonConstants } from '../common/constants/CommonConstants'
 import { CSJUtil } from '../common/util/CSJUtil'
+import { ConfigManager } from '../common/util/ConfigManager'
 // import { AdSlotBuilder, CSJAdCreator, CSJAdSdk, CSJSplashAd,
 //   CSJSplashAdCloseType,
 //   CSJSplashAdInteractionListener,
@@ -203,15 +204,33 @@ struct  SplashIndex{
     })
 
     // this.junpToMainNow();
+
+    // 初始化配置
     this.initCSJSDK()
 
   }
 
   //初始化SDK
-  initCSJSDK(){
+  async initCSJSDK(){
+    await this.initAppConfig();
     this.mkDownLoadDir()
+  }
 
-
+  /**
+   * 初始化应用配置
+   */
+  private async initAppConfig(): Promise<void> {
+    try {
+      Logger.info('SplashIndex', '开始初始化应用配置...');
+      const success = await ConfigManager.initConfig();
+      if (success) {
+        Logger.info('SplashIndex', '应用配置初始化成功');
+      } else {
+        Logger.error('SplashIndex', '应用配置初始化失败,使用默认配置');
+      }
+    } catch (error) {
+      Logger.error('SplashIndex', '初始化应用配置异常:' + error);
+    }
   }
   //退出App
   exitApp(){

+ 37 - 16
entry/src/main/ets/pages/UserCenter.ets

@@ -437,7 +437,9 @@ export struct UserCenter {
       Scroll() {
         Column() {
           this.buildUserInfoCard()
-          this.buildVipPlans()
+          if(!Utility.isNoble()||!Utility.isForever()){
+            this.buildVipPlans()
+          }
           this.buildVipFeatures()
 
           // this.buildFunctionMenu()
@@ -466,23 +468,35 @@ export struct UserCenter {
   buildUserInfoCard() {
     Column() {
       Row() {
-        Image(this.userAvatarUrl && this.userAvatarUrl.length > 0 ? this.userAvatarUrl : this.userAvatar)
-          .width(56)
-          .height(56)
-          .borderRadius('50%')
-          .clip(true)
-          .backgroundColor(this.isDarkMode ? Color.Black : Color.White)
-          .shadow({
-            radius: 8,
-            color: 0x11000000,
-            offsetX: 0,
-            offsetY: 2
-          })
+        Stack({ alignContent: Alignment.BottomEnd }){
+          Image(this.userAvatarUrl && this.userAvatarUrl.length > 0 ? this.userAvatarUrl : this.userAvatar)
+            .width(55)
+            .height(55)
+            .margin({ left: 12 })
+            .borderRadius('50%')
+            .clip(true)
+            .backgroundColor(this.isDarkMode ? Color.Black : Color.White)
+          Column() {
+            Text('VIP')
+              .fontSize(9)
+              .padding(2)
+              .textAlign(TextAlign.Center)
+              .fontWeight(FontWeight.Bolder)
+              .fontColor(Color.White)
+          }
+          .width(28)
+          .height(16)
+          .visibility(Utility.isNoble()?Visibility.Visible:Visibility.None)
+          .borderRadius(15)
+          .backgroundColor(this.themeColor)
+        }
+
         Column() {
           Row() {
             Text(this.isLogin ? this.userName : '未登录用户')
-              .fontSize(17)
+              .fontSize(14)
               .fontWeight(FontWeight.Bold)
+              .padding(5)
               .fontColor(this.isDarkMode ? Color.White : Color.Black)
               .textAlign(TextAlign.Start)
               .maxLines(1)
@@ -496,15 +510,18 @@ export struct UserCenter {
             if (this.hasActiveSubscription) {
               Text(this.subscriptionName)
                 .fontSize(13)
+                .padding(5)
                 .fontColor(themeColorWithAlpha(this.themeColor, 0.8, false))
                 .textAlign(TextAlign.Start)
               Text('到期:' + this.subscriptionEndDate)
                 .fontSize(11)
+                .padding(5)
                 .fontColor(this.isDarkMode ? Color.White : Color.Gray)
                 .textAlign(TextAlign.Start)
             } else {
               Text('普通用户')
-                .fontSize(12)
+                .fontSize(13)
+                .padding(5)
                 .fontColor(this.isDarkMode ? Color.White : Color.Grey)
                 .textAlign(TextAlign.Start)
             }
@@ -522,6 +539,7 @@ export struct UserCenter {
               .height(36)
               .padding({ left:20,right:20,top:10,bottom:10 })
               .fontSize(13)
+              .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
               .borderRadius(13)
               .margin({ left: 0 })
               .onClick(() => {
@@ -549,6 +567,7 @@ export struct UserCenter {
 
             Button('登录')
               .fontColor(Color.White)
+              .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
               .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor)
               .height(36)
               .padding({ left:20,right:20,top:10,bottom:10 })
@@ -686,6 +705,7 @@ export struct UserCenter {
             .width('100%')
             .height(145)
           }
+
         })
       }
       .columnsTemplate('1fr 1fr')
@@ -879,10 +899,11 @@ export struct UserCenter {
       Button(this.isVip ? '升级会员' : '立即开通')
         .borderRadius(24)
         .backgroundColor(this.isDarkMode ? $r('app.color.user_center_button_background') : this.themeColor)
-        .width(this.currentBreakpoint !== BreakpointTypeEnum.SM ? '25%' : '60%')
+        .width(200)
         .fontColor(Color.White)
         .fontSize(18)
         .padding(12)
+        .clickEffect({level:ClickEffectLevel.LIGHT, scale: 0.5})
         .fontWeight(FontWeight.Bold)
         .margin({ top: 1, bottom: 15 })
         .alignSelf(ItemAlign.Center)

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 219 - 176
entry/src/main/ets/view/LocalMusic.ets


+ 4 - 3
entry/src/main/ets/view/RotatingCover.ets

@@ -52,11 +52,12 @@ export struct RotatingCover {
   build() {
     Column() {
       Image(this.songLabel === undefined ||StrUtil.isEmpty(this.songLabel)? $r('app.media.music_red') : this.songLabel)
-        .height(32)
-        .width(32)
+        .height(38)
+        .width(38)
         .alt( $r('app.media.music_red'))
         .fillColor(this.themeColor)
-        .borderRadius(16)
+        .borderRadius('100%')
+        .clip(true)
         .margin({ right: 12 })
         .rotate({
           angle: this.rotateAngle,

+ 6 - 6
entry/src/main/ets/viewmodel/MainViewModel.ets

@@ -48,16 +48,16 @@ export  class  MainViewModel{
     let drawerGridData: ItemData[] = [
 
       new ItemData($r('app.string.local_music'), { type: 'symbol', value: $r('sys.symbol.music') }, MainViewModel.MENU_MUSIC, false),
-      // new ItemData($r('app.string.media_ku'), $r('app.media.hm_playlist'),MainViewModel.MENU_MIEDIA_KU,false),
-      // new ItemData($r('app.string.artist'), $r('app.media.kp_music'),MainViewModel.MENU_MIEDIA_ARTIST,false),
-      // new ItemData($r('app.string.album'), $r('app.media.llq'),MainViewModel.MENU_MIEDIA_ALBUM,false),
-
+      new ItemData($r('app.string.media_ku'), $r('app.media.hm_playlist'),MainViewModel.MENU_MIEDIA_KU,false),
+      new ItemData($r('app.string.artist'), $r('app.media.kp_music'),MainViewModel.MENU_MIEDIA_ARTIST,false),
+      new ItemData($r('app.string.album'), $r('app.media.llq'),MainViewModel.MENU_MIEDIA_ALBUM,false),
+      new ItemData($r('app.string.user_center'), { type: 'symbol', value: $r('sys.symbol.person') }, MainViewModel.MENU_USER, false),
       new ItemData($r('app.string.file_scan'), { type: 'symbol', value: $r('sys.symbol.doc_text_badge_magnifyingglass') },MainViewModel.MENU_FILE_SCAN,false),
+      new ItemData($r('app.string.setting'),  { type: 'symbol', value: $r('sys.symbol.gearshape') },MainViewModel.MENU_SETTING,false),
+      new ItemData($r('app.string.about_me'), { type: 'symbol', value: $r('sys.symbol.info_shield') },MainViewModel.MENU_ABOUT,false),
       new ItemData($r('app.string.haoping'), { type: 'symbol', value: $r('sys.symbol.flower') },MainViewModel.MENU_HAOPING,false),
       new ItemData($r('app.string.persion_duty'),{ type: 'symbol', value: $r('sys.symbol.doc_plaintext') },MainViewModel.MENU_DUTY,false),
       new ItemData($r('app.string.yszc'), { type: 'symbol', value: $r('sys.symbol.lock') },MainViewModel.MENU_YSZC,false),
-      new ItemData($r('app.string.about_me'), { type: 'symbol', value: $r('sys.symbol.info_shield') },MainViewModel.MENU_ABOUT,false),
-      new ItemData($r('app.string.setting'),  { type: 'symbol', value: $r('sys.symbol.gearshape') },MainViewModel.MENU_SETTING,false),
       new ItemData($r('app.string.share_tt'),  { type: 'symbol', value: $r('sys.symbol.share') },MainViewModel.MENU_SHARE,false),
       // new ItemData($r('app.string.nor_setting'), $r('app.media.hm_gps'),MainViewModel.MENU_SETTING,false),
     ];

+ 6 - 1
entry/src/main/ets/viewmodel/VideoItem.ets

@@ -45,9 +45,14 @@ export class VideoItem  {
   playCount?:number//播放次数
   lyricContent?:string//歌词内容
 
-  md5Str?:string
+  md5Str?:string//直接用来保存音质的判断
   extra_json?:string
   pyStr?:string//中文歌曲名称拼音的首字母
+  bit_rate?:string//比特率
+  probe_score?:number//评分
+  year?:string//年份
+  nb_streams?:number//流数量
+  nb_programs?:number//节目数量
 
   constructor(name: string, id: string, filePath: string, type:number,videoSize:number,cTime: string,pixelMap?: image.PixelMap,
     size?: string,pixelMapPath?: string,artist?: string,album?: string,fileName?: string, lastPlayed?:Date) {

+ 11 - 57
entry/src/main/module.json5

@@ -50,20 +50,21 @@
             "actions": [
               "action.system.home",
               "ohos.want.action.viewData",
+              "ohos.want.action.sendData"
             ],
             "uris": [
 
               {
                 "scheme": "file",
                 "utd": "general.audio",
-                "linkFeature": "FileOpen"
+                "maxFileSupported": 1
               },
-
               {
                 "scheme": "file",
-                "utd":"general.video",
-                "linkFeature": "FileOpen",
+                "utd": "general.video",
+                "maxFileSupported": 1
               },
+
               {
                 "scheme": "file",
                 "type": "audio/*",
@@ -84,57 +85,6 @@
           }
         ]
       },
-      {
-        "name": "ShareUIAbility",
-        "srcEntry": "./ets/entryability/ShareUIAbility.ets",
-        "description": "$string:EntryAbility_desc",
-        "icon": "$media:icon",
-        "label": "$string:EntryAbility_label",
-        "startWindowIcon": "$media:icon",
-        "startWindowBackground": "$color:start_window_background",
-        "orientation": "portrait",
-        "exported": true,
-        "backgroundModes": [
-          // 长时任务类型的配置项
-          "audioPlayback"
-        ],
-        "skills": [
-          {
-            "actions": [
-              "ohos.want.action.sendData",
-              "ohos.want.action.viewData"
-            ],
-
-            // 目标应用在配置支持接收的数据类型时,需穷举支持的UTD,比如:支持全部图片类型,可声明:general.image
-            // maxFileSupported 对于归属指定类型的文件,标识一次支持接收的最大数量。默认为0,代表不支持此类文件的分享。文件类型归属关系参考:@ohos.data.uniformTypeDescriptor (标准化数据定义与描述)
-            "uris": [
-
-//              {
-//                "scheme": "file",
-//                "utd": "general.audio",
-//                "linkFeature": "FileOpen"
-//              },
-              {
-                "scheme": "file",
-                "type": "audio/*",
-                "linkFeature": "FileOpen",
-                "maxFileSupported": 1
-              },
-              {
-                "scheme": "file",
-                "type": "video/*",
-                "linkFeature": "FileOpen",
-                "maxFileSupported": 1
-              },
-//              {
-//                "scheme": "file",
-//                "utd":"general.video",
-//                "linkFeature": "FileOpen"
-//              }
-            ]
-          }
-        ]
-      }
 
     ],
 
@@ -173,13 +123,17 @@
         "usedScene": {
           "abilities": [
             "EntryAbility",
-            "ShareUIAbility"
           ],
           "when": "always"
         }
       },
 
-
+      {
+        "name": "ohos.permission.ACCESS_CAR_DISTRIBUTED_ENGINE"
+      },
+      {
+        "name": "ohos.permission.ACCESS_SERVICE_NAVIGATION_INFO"
+      }
 
 
     ],

+ 16 - 0
entry/src/main/resources/base/element/string.json

@@ -443,6 +443,22 @@
     {
       "name": "form_PlayerWidgetFormExtensionAbility_desc",
       "value": "天天静听桌面播放器卡片"
+    },
+    {
+      "name": "detail",
+      "value": "详情"
+    },
+    {
+      "name": "sync_data",
+      "value": "校正数据"
+    },
+    {
+      "name": "sync_tips",
+      "value": "如果你在系统的文件管理删除了音乐媒体文件,可以使用校正数据来删除校正对于的数据库。"
+    },
+    {
+      "name": "sync_dataing",
+      "value": "正在校正数据"
     }
   ]
 }

+ 17 - 11
lib/src/main/ets/parse/LyricParser.ts

@@ -45,17 +45,23 @@ export class LyricParser implements IParser {
                 printW(`the lyric line contains ignored tag, line index= ${i}`);
                 continue;
             }
-            if (line.indexOf("ti") > 0) {
-                title = this.parseIdTag(line)
-            } else if (line.indexOf("ar") > 0) {
-                artist = this.parseIdTag(line)
-            } else if (line.indexOf("al") > 0) {
-                album = this.parseIdTag(line)
-            } else if (line.indexOf("by") > 0) {
-                by = this.parseIdTag(line)
-            } else if (line.indexOf("offset") > 0) {
-                offset = Number.parseInt(this.parseIdTag(line))
-            } else {
+            // 修改后的标签检测逻辑,修复英文歌词的时候,部分歌词没有显示出来。
+            if (line.startsWith("[ti:"))  {
+                title = this.parseIdTag(line);
+            }
+            else if (line.startsWith("[ar:"))  {
+                artist = this.parseIdTag(line);
+            }
+            else if (line.startsWith("[al:"))  {
+                album = this.parseIdTag(line);
+            }
+            else if (line.startsWith("[by:"))  {
+                by = this.parseIdTag(line);
+            }
+            else if (line.startsWith("[offset:"))  {
+                offset = Number.parseInt(this.parseIdTag(line));
+            }
+            else {
 
                 // 新增逐字歌词解析逻辑[mm:ss.xx] <mm:ss.xx>
                 if (this.isWordByWordLyric(line)) {

+ 15 - 5
lib/src/main/ets/view/LyricView2.ets

@@ -332,14 +332,24 @@ export struct LyricView2 {
     isTopBottomLine(index:number){
         return  index === 0 || index === this.listAdapter.totalCount()  - 1;
     }
-
+    //修复get Property index out of bounds
     private handleSeekAction() {
         clearTimeout(this.seekUiHideTimeout);
-        let targetPosition = this.listAdapter.getData(this.seekIndex).beginTime;
-        let isPlayerHandled = this.onSeekAction(targetPosition);
-        if (!isPlayerHandled) {
-            this.animateToIndex(this.currentIndex);
+
+        let itemCount = this.listAdapter.totalCount(); // 获取数据项的总数
+
+        // 确保 seekIndex 在有效范围内
+        if (this.seekIndex >= 0 && this.seekIndex < itemCount) {
+            let targetPosition = this.listAdapter.getData(this.seekIndex).beginTime;
+            let isPlayerHandled = this.onSeekAction(targetPosition);
+            if (!isPlayerHandled) {
+                this.animateToIndex(this.currentIndex);
+            }
+        } else {
+            console.error("Seek index out of bounds:", this.seekIndex);
+            // 处理超出范围的情况,比如设定默认值或抛出错误
         }
+
         this.isUserTouching = false;
     }
 

+ 26 - 0
oh-package-lock.json5

@@ -10,6 +10,7 @@
     "@changwei/chardet@^1.0.0": "@changwei/chardet@1.0.0",
     "@chinalike/popup@^0.0.7": "@chinalike/popup@0.0.7",
     "@keke/color-picker@^1.0.4": "@keke/color-picker@1.0.4",
+    "@ohos/juniversalchardet@^2.0.2": "@ohos/juniversalchardet@2.0.2",
     "@ohos/lottie@^2.0.19": "@ohos/lottie@2.0.19",
     "@ohos/pinyin4js@^2.0.2": "@ohos/pinyin4js@2.0.2",
     "@ohos/pulltorefresh@^2.1.1": "@ohos/pulltorefresh@2.1.1",
@@ -19,9 +20,11 @@
     "@seagazer/cclyric@lib": "@seagazer/cclyric@lib",
     "@sgaolei/lrc_parser@^1.0.0": "@sgaolei/lrc_parser@1.0.0",
     "@simplepeng/spider-man@^1.0.1": "@simplepeng/spider-man@1.0.1",
+    "@sj/ffmpeg@^1.2.5": "@sj/ffmpeg@1.2.5",
     "@taobao-ohos/utdid_sdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.0.9.har": "@taobao-ohos/utdid_sdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.0.9.har",
     "class-transformer@^0.5.1": "class-transformer@0.5.1",
     "libblueshield.so@oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield": "libblueshield.so@oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield",
+    "libffmpeg.so@oh_modules/.ohpm/@sj+ffmpeg@1.2.5/oh_modules/@sj/ffmpeg/src/main/cpp/types/libffmpeg": "libffmpeg.so@oh_modules/.ohpm/@sj+ffmpeg@1.2.5/oh_modules/@sj/ffmpeg/src/main/cpp/types/libffmpeg",
     "liblrcparser.so@oh_modules/.ohpm/@sgaolei+lrc_parser@1.0.0/oh_modules/@sgaolei/lrc_parser/src/main/cpp/types/liblrcparser": "liblrcparser.so@oh_modules/.ohpm/@sgaolei+lrc_parser@1.0.0/oh_modules/@sgaolei/lrc_parser/src/main/cpp/types/liblrcparser",
     "pako@^2.1.0": "pako@2.1.0"
   },
@@ -68,6 +71,13 @@
       "resolved": "https://repo.harmonyos.com/ohpm/@keke/color-picker/-/color-picker-1.0.4.har",
       "registryType": "ohpm"
     },
+    "@ohos/juniversalchardet@2.0.2": {
+      "name": "@ohos/juniversalchardet",
+      "version": "2.0.2",
+      "integrity": "sha512-EQiclxDnum59j75rI/t5CIOw6nyzbUB0p1hdaRPOJ+FxH7w7nT8tbga08ZwIkuFLzlAO2TtpkgDGAvkAsF3lDw==",
+      "resolved": "https://repo.harmonyos.com/ohpm/@ohos/juniversalchardet/-/juniversalchardet-2.0.2.har",
+      "registryType": "ohpm"
+    },
     "@ohos/lottie@2.0.19": {
       "name": "@ohos/lottie",
       "version": "2.0.19",
@@ -139,6 +149,16 @@
       "resolved": "https://repo.harmonyos.com/ohpm/@simplepeng/spider-man/-/spider-man-1.0.1.har",
       "registryType": "ohpm"
     },
+    "@sj/ffmpeg@1.2.5": {
+      "name": "@sj/ffmpeg",
+      "version": "1.2.5",
+      "integrity": "sha512-IshbyU5FaIMYkEpXiyBtCQfBWbaBUHz1nt04XiVjbxiC+AjGJ1bHUrFMQINe8YIPGRPwcKrafXPxSnkidRXMRQ==",
+      "resolved": "https://repo.harmonyos.com/ohpm/@sj/ffmpeg/-/ffmpeg-1.2.5.har",
+      "registryType": "ohpm",
+      "dependencies": {
+        "libffmpeg.so": "file:./src/main/cpp/types/libffmpeg"
+      }
+    },
     "@taobao-ohos/utdid_sdk@oh_modules/.ohpm/@cashier_alipay+cashiersdk@15.8.32/oh_modules/@cashier_alipay/cashiersdk/lib/utdid_sdk-1.0.9.har": {
       "name": "@taobao-ohos/utdid_sdk",
       "version": "1.0.9",
@@ -159,6 +179,12 @@
       "resolved": "oh_modules/.ohpm/@alipay+blueshieldsdk@cow+0gass5tzqhymzjby2f6sd+g2xtoatqsiyvi9qsy=/oh_modules/@alipay/blueshieldsdk/src/main/cpp/types/libblueshield",
       "registryType": "local"
     },
+    "libffmpeg.so@oh_modules/.ohpm/@sj+ffmpeg@1.2.5/oh_modules/@sj/ffmpeg/src/main/cpp/types/libffmpeg": {
+      "name": "libffmpeg.so",
+      "version": "1.2.5",
+      "resolved": "oh_modules/.ohpm/@sj+ffmpeg@1.2.5/oh_modules/@sj/ffmpeg/src/main/cpp/types/libffmpeg",
+      "registryType": "local"
+    },
     "liblrcparser.so@oh_modules/.ohpm/@sgaolei+lrc_parser@1.0.0/oh_modules/@sgaolei/lrc_parser/src/main/cpp/types/liblrcparser": {
       "name": "liblrcparser.so",
       "version": "1.0.0",

+ 2 - 1
oh-package.json5

@@ -24,7 +24,8 @@
     "@sgaolei/lrc_parser": "^1.0.0",
     "@ohos/pinyin4js": "^2.0.2",
     "@simplepeng/spider-man": "^1.0.1",
-//    "@ohos/ijkplayer": "^2.0.6"
+    "@ohos/juniversalchardet": "^2.0.2",
+    "@sj/ffmpeg": "^1.2.5"
   },
   "dynamicDependencies": {}
 }

+ 159 - 0
配置系统使用说明.md

@@ -0,0 +1,159 @@
+# 应用配置系统使用说明
+
+## 概述
+
+本应用实现了一个基于远程API的配置管理系统,可以在应用启动时从 `https://pay.ss5.xyz/switches/lists` 获取配置参数,并将这些参数保存到AppStorage中供全应用使用。
+
+## 功能特性
+
+1. **自动初始化**:应用启动时自动从API获取配置
+2. **类型支持**:支持 `json`、`boolean`、`number`、`string` 四种数据类型
+3. **全局访问**:配置保存在AppStorage中,可在任意页面访问
+4. **实时更新**:支持手动刷新配置
+5. **错误处理**:网络异常时使用默认值,不影响应用正常运行
+
+## API接口格式
+
+```json
+{
+  "code": 0,
+  "msg": "请求成功",
+  "data": [
+    {
+      "name": "test_json",
+      "description": "test_json",
+      "value": "{\n \"test_json\": \"test_json\"\n}",
+      "type": "json"
+    },
+    {
+      "name": "show_update_log",
+      "description": "是否显示日志更新弹窗",
+      "value": false,
+      "type": "boolean"
+    },
+    {
+      "name": "test_number",
+      "description": "test_number",
+      "value": 123456,
+      "type": "number"
+    },
+    {
+      "name": "test_string",
+      "description": "test_string",
+      "value": "test_string",
+      "type": "string"
+    }
+  ]
+}
+```
+
+## 核心文件
+
+### 1. ConfigManager.ets
+配置管理器,负责:
+- 从API获取配置数据
+- 解析不同类型的配置值
+- 保存配置到AppStorage
+- 提供配置读取和更新接口
+
+### 2. ConfigDisplayView.ets
+配置展示组件,用于:
+- 在设置页面展示当前配置
+- 提供配置刷新功能
+- 实时监听配置变化
+
+## 使用方法
+
+### 1. 初始化配置(已在SplashIndex中实现)
+
+```typescript
+import { ConfigManager } from '../common/util/ConfigManager';
+
+// 在应用启动时初始化配置
+const success = await ConfigManager.initConfig();
+```
+
+### 2. 读取配置
+
+```typescript
+import { ConfigManager, ConfigValue } from '../common/util/ConfigManager';
+
+// 方法1:使用ConfigManager(推荐,类型安全)
+const showUpdateLog: boolean = ConfigManager.getConfig('show_update_log', false);
+const testNumber: number = ConfigManager.getConfig('test_number', 0);
+const testString: string = ConfigManager.getConfig('test_string', '');
+const testJson: ESObject = ConfigManager.getConfig('test_json', {});
+
+// 方法2:直接从AppStorage读取
+const showUpdateLog = AppStorage.get('show_update_log') as boolean ?? false;
+```
+
+### 3. 在组件中监听配置变化
+
+```typescript
+@Component
+export struct MyComponent {
+  // 使用@StorageLink监听配置变化,确保类型安全
+  @StorageLink('show_update_log') showUpdateLog: boolean = false;
+  @StorageLink('test_number') testNumber: number = 0;
+  @StorageLink('test_string') testString: string = '';
+  @StorageLink('test_json') testJson: ESObject = {};
+
+  build() {
+    Column() {
+      Text(`显示更新日志: ${this.showUpdateLog}`)
+      Text(`测试数字: ${this.testNumber}`)
+      Text(`测试字符串: ${this.testString}`)
+      Text(`JSON配置: ${JSON.stringify(this.testJson)}`)
+    }
+  }
+}
+```
+
+### 4. 更新配置
+
+```typescript
+import { ConfigValue } from '../common/util/ConfigManager';
+
+// 设置单个配置(类型安全)
+ConfigManager.setConfig('show_update_log', true as ConfigValue);
+ConfigManager.setConfig('test_number', 123 as ConfigValue);
+ConfigManager.setConfig('test_string', 'new value' as ConfigValue);
+
+// 刷新所有配置
+const success: boolean = await ConfigManager.refreshConfig();
+```
+
+## 实际应用示例
+
+### UpdateLogManager中的使用
+
+```typescript
+// 从API配置中获取是否显示更新日志的设置
+const showUpdateLog = ConfigManager.getConfig('show_update_log', false);
+if (!showUpdateLog) {
+  Logger.info(UpdateLogManager.TAG, '更新日志功能已通过API配置禁用');
+  return false;
+}
+```
+
+
+## 错误处理
+
+1. **网络异常**:使用默认值,记录错误日志
+2. **JSON解析失败**:保持原始字符串值
+3. **类型转换失败**:使用默认值
+
+## 注意事项
+
+1. 配置初始化是异步操作,确保在使用配置前完成初始化
+2. 配置键名要与API返回的name字段保持一致
+3. 为每个配置提供合理的默认值
+4. 配置变化会自动触发使用@StorageLink的组件重新渲染
+
+## 扩展建议
+
+1. 可以添加配置缓存机制,减少网络请求
+2. 可以添加配置版本控制,支持增量更新
+3. 可以添加配置验证机制,确保配置值的有效性
+4. 可以添加配置分组功能,支持不同模块的配置管理

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä