Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
ikun0014 2025-02-07 14:02:33 +08:00
commit 954b15d277
No known key found for this signature in database
GPG Key ID: CB646D33779F3C89
45 changed files with 380 additions and 10073 deletions

View File

@ -66,6 +66,7 @@ jobs:
cd artifacts
md5sum *.apk >> ../publish/changeLog.md
echo -e '```\n' >> ../publish/changeLog.md
echo -e '\n[软件安装包说明](https://lyswhut.github.io/lx-music-doc/download#%E8%BD%AF%E4%BB%B6%E5%AE%89%E8%A3%85%E5%8C%85%E8%AF%B4%E6%98%8E)\n' >> ../publish/changeLog.md
- name: Get package version
run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV

View File

@ -6,6 +6,65 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
## [1.7.0](https://github.com/lyswhut/lx-music-mobile/compare/v1.6.0...v1.7.0) - 2025-01-27
落雪祝大家新年快乐!
### 关于之前提到的新项目
新项目我取名叫 Any Listen希望它能像它的名字一样让我们能到处任意听歌。
经过一年多的开发因各种原因实际进度比预期的慢但还是赶在年前发布了第一个web服务预览版第一个版本仅支持播放服务器上的歌曲扩展功能暂时未能开放但已趋于完成一两个月内可以搞定。
目前的版本仅是“能用”的状态因时间关系部分UI未能重新设计但后面会继续完善。
该项目目前的目标用户是拥有自己服务器且上面存储有歌曲的人使用。
项目刚发布,文档未能完善,遇到使用问题或有任何建议欢迎提 issue 交流,
项目地址: https://github.com/any-listen/any-listen
---
*为了防止歌曲缓存被第三方软件当做垃圾意外清理,歌曲缓存不再存储到缓存目录。若想清理缓存,需去「设置 → 其他 → 资源缓存管理」清理。*
*更新到该版本后首次播放歌曲时,会将之前的歌曲缓存迁移到新位置,需要等待的时间取决于你已缓存资源的大小。*
*感谢 @3gf8jv4dv 对 LX 系列项目翻译、文档等文案的大幅修订优化。*
### 新增
- 新增蓝牙歌词支持,可以通过「设置 → 播放设置 → 显示蓝牙歌词」启用(#615
- 新增繁体中文语言(#659, @3gf8jv4dv
- 将 LX Music 设置为「音乐应用」类,允许将 LX Music 设置为系统默认音乐播放器
- 支持在程序外使用 LX Music 打开常见音乐文件及 `.js``.json``.lxmc` 等文件
### 优化
- 防止歌曲缓存被第三方软件当做垃圾意外清理
- 优化正常播放结束时的下一首歌曲播放衔接度,在歌曲即将结束播放时将预获取下一首歌曲的播放链接,减少自动切歌时的等待时间
- 优化歌曲换源机制,提升换源正确率
- 首次使用的提示窗口可以通过点击背景或者返回键关闭(#577
- 上移 Toast 位置避免遮挡播放模式图标(#603, @sibojia
- 优化简体中文文案编排,大幅修订英语文案编排(#658, #660 等, @3gf8jv4dv
- 优化应用图标质量,在高版本系统上使用矢量图标
### 修复
- 修复导出文件到范围存储类型的目录时,扩展名丢失的问题
- 修复切换列表播放歌曲时可能会出现播放的歌曲不对应的问题
- 修复内置列表名称硬编码和语言切换显示的问题(#662
- 修复某些情况下进播放详情页时,详情页不显示或应用界面无响应的问题
- 修复低版本 Android 在某些情况下对 Emoji 字符编码的处理问题
### 变更
- 歌曲缓存不再存储到缓存目录
- 不再长期缓存换源歌曲信息
### 其他
- 更新 Readme 文档,优化文案编排(#651, Thanks @3gf8jv4dv
- 更新 Issue 模板(#652, @3gf8jv4dv
- 更新项目文档(@3gf8jv4dv
- 更新 React Native 到 v0.73.11
## [1.6.0](https://github.com/lyswhut/lx-music-mobile/compare/v1.5.0...v1.6.0) - 2024-08-24
### 新增

View File

@ -1,64 +1,69 @@
<p align="center"><a href="https://github.com/ikunshare/ikun-music-mobile"><img width="200" src="https://github.com/ikunshare/ikun-music-mobile/blob/master/doc/images/icon.png" alt="lx-music logo"></a></p>
<p align="center"><a href="https://github.com/lyswhut/lx-music-mobile"><img width="200" src="https://github.com/lyswhut/lx-music-mobile/blob/master/doc/images/icon.png" alt="lx-music logo"></a></p>
<h1 align="center">LX Music 移动版</h1>
<p align="center">
<a href="https://github.com/ikunshare/ikun-music-mobile/releases"><img src="https://img.shields.io/github/release/ikunshare/ikun-music-mobile" alt="Release version"></a>
<a href="https://github.com/ikunshare/ikun-music-mobile/actions/workflows/release.yml"><img src="https://github.com/ikunshare/ikun-music-mobile/workflows/Build/badge.svg" alt="Build status"></a>
<a href="https://github.com/ikunshare/ikun-music-mobile/actions/workflows/beta-pack.yml"><img src="https://github.com/ikunshare/ikun-music-mobile/workflows/Build%20Beta/badge.svg" alt="Build status"></a>
<a href="https://github.com/facebook/react-native"><img src="https://img.shields.io/github/package-json/dependency-version/ikunshare/ikun-music-mobile/react-native/master" alt="React native version"></a>
<!-- <a href="https://github.com/ikunshare/ikun-music-mobile/releases"><img src="https://img.shields.io/github/downloads/ikunshare/ikun-music-mobile/latest/total" alt="Downloads"></a> -->
<a href="https://github.com/ikunshare/ikun-music-mobile/tree/dev"><img src="https://img.shields.io/github/package-json/v/ikunshare/ikun-music-mobile/dev" alt="Dev branch version"></a>
<!-- <a href="https://github.com/ikunshare/ikun-music-mobile/blob/master/LICENSE"><img src="https://img.shields.io/github/license/ikunshare/ikun-music-mobile" alt="License"></a> -->
<a href="https://github.com/lyswhut/lx-music-mobile/releases"><img src="https://img.shields.io/github/release/lyswhut/lx-music-mobile" alt="Release version"></a>
<a href="https://github.com/lyswhut/lx-music-mobile/actions/workflows/release.yml"><img src="https://github.com/lyswhut/lx-music-mobile/workflows/Build/badge.svg" alt="Build status"></a>
<a href="https://github.com/lyswhut/lx-music-mobile/actions/workflows/beta-pack.yml"><img src="https://github.com/lyswhut/lx-music-mobile/workflows/Build%20Beta/badge.svg" alt="Build status"></a>
<a href="https://github.com/facebook/react-native"><img src="https://img.shields.io/github/package-json/dependency-version/lyswhut/lx-music-mobile/react-native/master" alt="React native version"></a>
<!-- <a href="https://github.com/lyswhut/lx-music-mobile/releases"><img src="https://img.shields.io/github/downloads/lyswhut/lx-music-mobile/latest/total" alt="Downloads"></a> -->
<a href="https://github.com/lyswhut/lx-music-mobile/tree/dev"><img src="https://img.shields.io/github/package-json/v/lyswhut/lx-music-mobile/dev" alt="Dev branch version"></a>
<!-- <a href="https://github.com/lyswhut/lx-music-mobile/blob/master/LICENSE"><img src="https://img.shields.io/github/license/lyswhut/lx-music-mobile" alt="License"></a> -->
</p>
<p align="center">一个基于 React Native 开发的音乐软件</p>
## 说明
支持的平台:
所用技术栈:
- React Native
- Redux
已支持的平台:
- Android
*注:由于没有相关开发环境及证书,所以 **没有计划支持 iOS**。*<br>
***注:目前没有计划支持 iOS 和 HarmonyOS NEXT**。*<br>
*桌面版项目地址:<https://github.com/lyswhut/lx-music-desktop>*
软件变化请查看 [更新日志](https://github.com/ikunshare/ikun-music-mobile/blob/master/CHANGELOG.md)。
软件变化请查看[更新日志](https://github.com/lyswhut/lx-music-mobile/blob/master/CHANGELOG.md)。
软件下载请查看 [GitHub Releases](https://github.com/ikunshare/ikun-music-mobile/releases)。
软件下载请查看 [GitHub Releases](https://github.com/lyswhut/lx-music-mobile/releases)。
使用常见问题请参阅 [常见问题](https://lyswhut.github.io/lx-music-doc/mobile/faq)。
使用常见问题请参阅[移动版常见问题](https://lyswhut.github.io/lx-music-doc/mobile/faq)。
目前本项目的原始发布地址只有 [**GitHub Releases**](https://github.com/ikunshare/ikun-music-mobile/releases),其他渠道均为第三方转载发布,与本项目无关!
目前本项目的原始发布地址只有 [**GitHub**](https://github.com/lyswhut/lx-music-mobile/releases),其他渠道均为第三方转载发布,与本项目无关!
为了提高使用门槛本软件内的默认设置、UI 操作不以新手友好为目标,所以使用前建议先根据你的喜好浏览调整一遍软件设置,阅读一遍 [音乐播放列表机制](https://lyswhut.github.io/lx-music-doc/mobile/faq/playlist)。
为了提高使用门槛本软件内的默认设置、UI 操作不以新手友好为目标,所以使用前建议先根据你的喜好浏览调整一遍软件设置,阅读一遍[音乐播放列表机制](https://lyswhut.github.io/lx-music-doc/mobile/faq/playlist)。
### 数据同步服务
从 v1.0.0 起,我们发布了一个独立 [数据同步服务](https://github.com/lyswhut/lx-music-sync-server#readme)如果你有服务器,可以将其部署到服务器上作为私人多端同步服务使用,详情看该项目说明。
从 v1.0.0 起,我们发布了一个独立的[数据同步服务](https://github.com/lyswhut/lx-music-sync-server#readme)如果你有服务器,可以将其部署到服务器上作为私人多端同步服务使用,详情看该项目说明。
## 贡献代码
本项目欢迎 PR但为了 PR 能顺利合并,需要注意以下几点:
- 对于添加新功能的 PR建议在提交 PR 前先创建 issue 进行说明,以确认该功能是否确实需要;
- 对于添加新功能的 PR建议在提交 PR 前先创建 Issue 进行说明,以确认该功能是否确实需要;
- 对于修复 bug 的 PR请提供修复前后的说明及重现方式
- 对于其他类型的 PR则适当附上说明。
贡献代码步骤:
1. 参照 [源码使用方法](https://lyswhut.github.io/lx-music-doc/mobile/use-source-code) 设置开发环境;
1. 参照[源码使用方法](https://lyswhut.github.io/lx-music-doc/mobile/use-source-code)设置开发环境;
2. 克隆本仓库代码并切换至 `dev` 分支进行开发;
3. 提交 PR 至 `dev` 分支。
<!--
## 用户界面
<p><a href="https://github.com/ikunshare/ikun-music-mobile"><img width="100%" src="https://github.com/ikunshare/ikun-music-mobile/blob/master/doc/images/app.png" alt="lx-music mobile UI"></a></p> -->
<p><img width="100%" src="https://github.com/lyswhut/lx-music-mobile/blob/master/doc/images/app.png" alt="lx-music mobile UI"></p> -->
## 项目协议
本项目基于 [Apache License 2.0](https://github.com/ikunshare/ikun-music-mobile/blob/master/LICENSE) 许可证发行,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
本项目基于 [Apache License 2.0](https://github.com/lyswhut/lx-music-mobile/blob/master/LICENSE) 许可证发行,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
---

View File

@ -31,12 +31,64 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="lxmusic" android:host="*" />
</intent-filter>
<intent-filter android:priority="80">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.OPENABLE"/>
<data android:scheme="file"/>
<data android:scheme="content" />
<data android:mimeType="audio/*" />
<data android:mimeType="vnd.android.cursor.dir/audio"/>
<data android:mimeType="application/ogg"/>
<data android:mimeType="application/x-ogg"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.APP_MUSIC" />
</intent-filter>
<intent-filter>
<action android:name="com.cyanogenmod.eleven.AUDIO_PLAYER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter android:priority="80">
<!-- <action android:name="android.intent.action.PICK"/> -->
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.OPENABLE"/>
<data android:scheme="file"/>
<data android:scheme="content" />
<data android:host="*"/>
<data android:mimeType="application/octet-stream"/>
<data android:mimeType="text/javascript"/>
<data android:mimeType="application/x-javascript"/>
<data android:mimeType="application/javascript"/>
<!-- https://stackoverflow.com/a/66402243 -->
<data android:pathPattern=".*\\.js" />
<data android:pathPattern=".*\\..*\\.js" />
<data android:pathPattern=".*\\..*\\..*\\.js" />
<data android:pathSuffix=".js" tools:targetApi="s" />
<data android:mimeType="application/json"/>
<data android:pathPattern=".*\\.json" />
<data android:pathPattern=".*\\..*\\.json" />
<data android:pathPattern=".*\\..*\\..*\\.json" />
<data android:pathSuffix=".json" tools:targetApi="s" />
<data android:pathPattern=".*\\.lxmc" />
<data android:pathPattern=".*\\..*\\.lxmc" />
<data android:pathPattern=".*\\..*\\..*\\.lxmc" />
<data android:pathSuffix=".lxmc" tools:targetApi="s" />
</intent-filter>
</activity>
<!-- Define a FileProvider for API24+ -->

View File

@ -377,7 +377,7 @@ globalThis.lx_setup = (key, id, name, description, version, author, homepage, ra
},
md5(str) {
if (typeof str !== 'string') throw new Error('param required a string')
const md5 = nativeFuncs.utils_str2md5(str)
const md5 = nativeFuncs.utils_str2md5(encodeURIComponent(str))
// console.log('md5', str, md5)
return md5
},

View File

@ -1,5 +1,6 @@
package com.ikunshare.music.mobile.userApi;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@ -13,9 +14,9 @@ import com.whl.quickjs.android.QuickJSLoader;
import com.whl.quickjs.wrapper.QuickJSContext;
import java.io.InputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
public class QuickJS {
@ -86,15 +87,22 @@ public class QuickJS {
});
jsContext.getGlobalObject().setProperty("__lx_native_call__utils_str2md5", args -> {
try {
// Log.d("UserApi [script call]", "utils_str2md5: " + args[0]);
String str;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
str = URLDecoder.decode((String) args[0], StandardCharsets.UTF_8);
} else {
str = URLDecoder.decode((String) args[0], "UTF-8");
}
// Log.d("UserApi [script call]", "utils_str2md5: " + str);
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] inputBytes = ((String) args[0]).getBytes();
byte[] md5Bytes = md.digest(inputBytes);
byte[] md5Bytes = md.digest(str.getBytes(StandardCharsets.UTF_8));
StringBuilder md5String = new StringBuilder();
for (byte b : md5Bytes) {
md5String.append(String.format("%02x", b));
}
return md5String.toString();
} catch (NoSuchAlgorithmException e) {
} catch (Exception e) {
Log.e("UserApi [utils]", "utils_str2md5 error: " + e.getMessage());
return "";
}

View File

@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="447.94"
android:viewportHeight="447.94">
<group android:scaleX="0.48"
android:scaleY="0.48"
android:translateX="116.4644"
android:translateY="116.4644">
<path
android:pathData="M203.81,0.48c-19.67,-3.35 -35.76,11.14 -35.76,31.09v206.17c-11.64,-4.27 -24.17,-6.72 -37.28,-6.72c-59.9,0 -108.47,48.57 -108.47,108.47c0,59.9 48.56,108.46 108.47,108.46c34.14,0 64.54,-15.82 84.41,-40.48l-49.66,-49.66c-15.12,-15.11 -11.71,-28.9 -9.54,-34.14c2.17,-5.23 9.51,-17.4 30.88,-17.4h18.08v-56.88c0,-21.13 14.62,-38.86 34.27,-43.74c0.03,-44.37 0.03,-81.81 0.03,-81.81c140.15,0 131.72,83.97 115.32,132.2c-6.42,18.88 -2.6,22.05 10.89,7.35C536.47,77.11 298.38,16.57 203.81,0.48z"
android:fillColor="#5ed698"/>
<path
android:pathData="M301.06,223.88h-50.99c-3.91,0 -7.57,0.95 -10.89,2.52c-8.62,4.09 -14.61,12.8 -14.61,22.97v76.51h-37.71c-14.08,0 -17.43,8.07 -7.47,18.03l46.89,46.9l31.25,31.25c4.98,4.99 11.51,7.47 18.03,7.47c6.52,0 13.05,-2.48 18.03,-7.47l78.15,-78.14c9.95,-9.96 6.61,-18.03 -7.47,-18.03h-37.71v-76.51C326.57,235.29 315.15,223.88 301.06,223.88z"
android:fillColor="#4daf7c"/>
</group>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -10,6 +10,9 @@ buildscript {
ndkVersion = "26.1.10909125"
kotlinVersion = "1.9.24" // Or any version above 1.3.x
RNNKotlinVersion = kotlinVersion
// https://github.com/DylanVann/react-native-fast-image/blob/9ab80fcd570b7f56da66ab20e52c9a35934067c9/docs/app-glide-module.md
excludeAppGlideModule = true
}
repositories {
google()

9916
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"name": "ikun-music-mobile",
"version": "3.5.9",
"versionCode": 69,
"versionCode": 70,
"private": true,
"scripts": {
"dev": "react-native run-android --active-arch-only",
@ -45,9 +45,10 @@
"homepage": "https://github.com/ikunshare/ikun-music-mobile#readme",
"dependencies": {
"@craftzdog/react-native-buffer": "^6.0.5",
"@react-native-async-storage/async-storage": "^2.1.0",
"@react-native-clipboard/clipboard": "^1.15.0",
"@react-native-async-storage/async-storage": "^2.1.1",
"@react-native-clipboard/clipboard": "^1.16.1",
"@react-native-community/slider": "^4.5.5",
"he": "^1.2.0",
"iconv-lite": "^0.6.3",
"lrc-file-parser": "^2.4.1",
"message2call": "^0.1.3",
@ -56,29 +57,29 @@
"react-native": "0.73.11",
"react-native-background-timer": "github:lyswhut/react-native-background-timer#55ecaa80880e9cec1fff81f3ce10e6250ab3c40c",
"react-native-exception-handler": "^2.10.10",
"react-native-fast-image": "^8.6.3",
"react-native-file-system": "github:lyswhut/react-native-file-system#2a37b90dbb8d37c5180777d0bf0d4f160812b0c1",
"react-native-fs": "^2.20.0",
"react-native-local-media-metadata": "github:lyswhut/react-native-local-media-metadata#5eac1b6c70e934a1ad7b800b3ab7122f1075f704",
"react-native-local-media-metadata": "github:lyswhut/react-native-local-media-metadata#f2d03999413fa8fc9b0a25cde07c18d1a56988f0",
"react-native-navigation": "7.39.2",
"react-native-pager-view": "6.6.1",
"react-native-pager-view": "6.7.0",
"react-native-quick-base64": "^2.1.2",
"react-native-quick-md5": "^3.0.6",
"react-native-track-player": "github:lyswhut/react-native-track-player#57eeeba57e28702fa931d4aeb753da225b6fcf6c",
"react-native-vector-icons": "^10.2.0",
"rn-fetch-blob": "^0.12.0"
"rn-fetch-blob": "^0.12.0",
"react-native-track-player": "github:lyswhut/react-native-track-player#75c097a4a46bea19970540bbc1bd38527ea73cde",
"react-native-vector-icons": "^10.2.0"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/eslint-parser": "^7.25.9",
"@babel/core": "^7.26.7",
"@babel/eslint-parser": "^7.26.5",
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
"@babel/preset-env": "^7.26.0",
"@babel/runtime": "^7.26.0",
"@react-native/babel-preset": "^0.74.88",
"@react-native/metro-config": "^0.74.88",
"@react-native/typescript-config": "^0.74.88",
"@babel/preset-env": "^7.26.7",
"@babel/runtime": "^7.26.7",
"@react-native/babel-preset": "^0.74.89",
"@react-native/metro-config": "^0.74.89",
"@react-native/typescript-config": "^0.74.89",
"@tsconfig/react-native": "^3.0.5",
"@types/react": "^18.3.14",
"@types/he": "^1.2.3",
"@types/react": "^18.3.18",
"@types/react-native": "^0.72.8",
"@types/react-native-background-timer": "^2.0.2",
"@types/react-native-vector-icons": "^6.4.18",
@ -86,8 +87,8 @@
"changelog-parser": "^3.0.1",
"eslint-config-standard": "^17.1.0",
"eslint-config-standard-with-typescript": "^43.0.1",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.1.0",
"typescript": "^5.7.2"
"typescript": "^5.7.3"
}
}

View File

@ -1,34 +1,4 @@
*为了防止歌曲缓存被第三方软件当做垃圾意外清理,歌曲缓存不再存储到缓存目录。若想清理缓存,需去「设置 → 其他 → 资源缓存管理」清理。*
*更新到该版本后首次播放歌曲时,会将之前的歌曲缓存迁移到新位置,需要等待的时间取决于你已缓存资源的大小。*
### 新增
- 新增蓝牙歌词支持,可以通过「设置 → 播放设置 → 显示蓝牙歌词」启用(#615
- 新增繁体中文语言(#659, @3gf8jv4dv
### 优化
- 防止歌曲缓存被第三方软件当做垃圾意外清理
- 优化正常播放结束时的下一首歌曲播放衔接度,在歌曲即将结束播放时将预获取下一首歌曲的播放链接,减少自动切歌时的等待时间
- 优化歌曲换源机制,提升换源正确率
- 首次使用的提示窗口可以通过点击背景或者返回键关闭(#577
- 上移 Toast 位置避免遮挡播放模式图标(#603, @sibojia
- 优化简体中文文案编排,大幅修订英语文案编排(#658, #660 等, @3gf8jv4dv
### 修复
- 修复导出文件到范围存储类型的目录时,扩展名丢失的问题
- 修复切换列表播放歌曲时可能会出现播放的歌曲不对应的问题
- 修复内置列表名称硬编码和语言切换显示的问题(#662
### 变更
- 歌曲缓存不再存储到缓存目录
- 不再长期缓存换源歌曲信息
### 其他
- 更新 Readme 文档,优化文案编排(#651, Thanks @3gf8jv4dv
- 更新 Issue 模板(#652, @3gf8jv4dv
- 更新 React Native 到 v0.73.11
- 修复 tx 歌单搜索名字、描述出现乱码的问题
- 修复解析某些本地歌词文件时出现乱码的问题(#694

View File

@ -1,18 +1,18 @@
import { useTheme } from '@/store/theme/hook'
import { BorderRadius } from '@/theme'
import { createStyle } from '@/utils/tools'
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { View, type ViewProps, Image as _Image, StyleSheet } from 'react-native'
import FastImage, { type FastImageProps } from 'react-native-fast-image'
import { type ComponentProps, memo, useCallback, useEffect, useMemo, useState } from 'react'
import { View, type ViewProps, StyleSheet, Image as FastImage } from 'react-native'
// import FastImage, { type FastImageProps } from 'react-native-fast-image'
import Text from './Text'
import { useLayout } from '@/utils/hooks'
export type { OnLoadEvent } from 'react-native-fast-image'
// export type { OnLoadEvent } from 'react-native-fast-image'
export interface ImageProps extends ViewProps {
style: FastImageProps['style']
style: ComponentProps<typeof FastImage>['style']
url?: string | number | null
cache?: boolean
resizeMode?: FastImageProps['resizeMode']
resizeMode?: ComponentProps<typeof FastImage>['resizeMode']
onError?: (url: string | number) => void
}
@ -34,7 +34,7 @@ const EmptyPic = memo(({ style, nativeID }: { style: ImageProps['style'], native
)
})
const Image = memo(({ url, cache, resizeMode = FastImage.resizeMode.cover, style, onError, nativeID }: ImageProps) => {
const Image = memo(({ url, cache, resizeMode = 'cover', style, onError, nativeID }: ImageProps) => {
const [isError, setError] = useState(false)
const handleError = useCallback(() => {
setError(true)
@ -44,7 +44,7 @@ const Image = memo(({ url, cache, resizeMode = FastImage.resizeMode.cover, style
setError(false)
}, [url])
let uri = typeof url == 'number'
? _Image.resolveAssetSource(url).uri
? FastImage.resolveAssetSource(url).uri
: url?.startsWith('/')
? 'file://' + url
: url
@ -57,8 +57,9 @@ const Image = memo(({ url, cache, resizeMode = FastImage.resizeMode.cover, style
source={{
uri: uri!,
headers: defaultHeaders,
priority: FastImage.priority.normal,
cache: cache === false ? 'web' : 'immutable',
cache: cache === false ? 'reload' : 'force-cache',
// priority: FastImage.priority.normal,
// cache: cache === false ? 'web' : 'immutable',
}}
onError={handleError}
resizeMode={resizeMode}
@ -73,10 +74,10 @@ const Image = memo(({ url, cache, resizeMode = FastImage.resizeMode.cover, style
})
export const getSize = (uri: string, success: (width: number, height: number) => void, failure?: (error: any) => void) => {
_Image.getSize(uri, success, failure)
FastImage.getSize(uri, success, failure)
}
export const clearMemoryCache = async() => {
return Promise.all([FastImage.clearMemoryCache(), FastImage.clearDiskCache()])
// return Promise.all([FastImage.clearMemoryCache(), FastImage.clearDiskCache()])
}
export default Image

View File

@ -0,0 +1,36 @@
import { readMetadata } from '@/utils/localMediaMetadata'
import { handleImportList } from '@/screens/Home/Views/Setting/settings/Backup/actions'
import { handleImportLocalFile } from '@/screens/Home/Views/Setting/settings/Basic/UserApiEditModal/action'
import { type FileType } from '@/utils/fs'
import { confirmDialog } from '@/utils/tools'
import playerState from '@/store/player/state'
import { addTempPlayList } from '@/core/player/tempPlayList'
import { LIST_IDS } from '@/config/constant'
import { playNext } from '@/core/player/player'
import { buildLocalMusicInfo, buildLocalMusicInfoByFilePath } from '@/screens/Home/Views/Mylist/MyList/listAction'
export const handleFileLXMCAction = async(file: FileType) => {
if (!(await confirmDialog({
message: global.i18n.t('deep_link_file_lxmc_confirm_tip', { name: file.name }),
}))) return
handleImportList(file.path)
}
export const handleFileMusicAction = async(file: FileType) => {
const info = await readMetadata(file.path)
const isPlaying = !!playerState.playMusicInfo.musicInfo
const musicInfo = info ? buildLocalMusicInfo(file.path, info) : buildLocalMusicInfoByFilePath(file)
console.log(musicInfo)
addTempPlayList([{ listId: LIST_IDS.PLAY_LATER, musicInfo, isTop: true }])
if (isPlaying) void playNext()
}
export const handleFileJSAction = async(file: FileType) => {
if (!(await confirmDialog({
message: global.i18n.t('deep_link_file_js_confirm_tip', { name: file.name }),
}))) return
handleImportLocalFile(file.path)
}

View File

@ -3,6 +3,8 @@ import { errorDialog } from './utils'
import { handleMusicAction } from './musicAction'
import { handlePlayerAction, type PlayerAction } from './playerAction'
import { handleSonglistAction } from './songlistAction'
import { extname, stat } from '@/utils/fs'
import { handleFileMusicAction, handleFileJSAction, handleFileLXMCAction } from './fileAction'
const handleLinkAction = async(link: string) => {
@ -38,14 +40,59 @@ const handleLinkAction = async(link: string) => {
// default: throw new Error('Unknown type: ' + type)
}
}
const handleFileAction = async(link: string) => {
const file = await stat(link)
// console.log(file)
switch (extname(file.name)) {
case 'json':
case 'lxmc':
await handleFileLXMCAction(file)
break
case 'js':
await handleFileJSAction(file)
break
case 'ogg':
case 'flac':
case 'wav':
case 'mp3':
await handleFileMusicAction(file)
break
default:
if (!file.mimeType?.startsWith('audio/')) throw new Error('Unknown file type')
await handleFileMusicAction(file)
break
}
}
// const handleHttpAction = async(link: string) => {
// }
const runLinkAction = async(link: string) => {
if (!link.startsWith('lxmusic://')) return
if (link.startsWith('lxmusic://')) {
try {
await handleLinkAction(link)
} catch (err: any) {
errorDialog(err.message)
// focusWindow()
}
} else if (link.startsWith('file://') || link.startsWith('content://')) {
try {
await handleFileAction(link)
} catch (err: any) {
errorDialog(err.message)
// focusWindow()
}
}
// else if (/^https?:\/\//.test(link)) {
// try {
// await handleHttpAction(link)
// } catch (err: any) {
// errorDialog(err.message)
// // focusWindow()
// }
// }
}
export const initDeeplink = async() => {

View File

@ -30,14 +30,16 @@
"confirm_tip": "Just to double check, do you really want to do this?",
"copy_name": "Share Song",
"copy_name_tip": "Copied",
"create_new_folder": "Create new directory",
"create_new_folder": "Create new folder",
"create_new_folder_error_tip": "The name entered is invalid",
"create_new_folder_tip": "Please enter a new directory name",
"create_new_folder_tip": "Please enter a new folder name",
"date_format_hour": "{num} hours ago",
"date_format_minute": "{num} minutes ago",
"date_format_second": "{num} seconds ago",
"deep_link__handle_error_tip": "Call failed: {message}",
"delete": "Delete",
"deep_link_file_js_confirm_tip": "Are you sure you want to import the \"{name}\" music API?",
"deep_link_file_lxmc_confirm_tip": "Are you sure you want to import the \"{name}\" list file?",
"delete": "Remove",
"dialog_cancel": "No",
"dialog_confirm": "OK",
"disagree": "Deny",
@ -46,18 +48,16 @@
"duplicate_list_tip": "You have collected this list \"{name}\", do you need to update the songs in it?",
"edit_metadata": "Edit Metadata",
"exit_app_tip": "Are you sure you want to exit the app?",
"ignoring_battery_optimization_check_tip": "LX Music is \"Restricted\" or \"Optimized\" in \"App battery usage\", which may cause LX Music to be prevented by the system when playing music in the background. Do you need to set LX Music to “Unrestricted”?",
"ignoring_battery_optimization_check_tip": "LX Music is \"Restricted\" or \"Optimized\" in \"App battery usage\", which may cause LX Music to be prevented by the system when playing music in the background. Do you need to set LX Music to \"Unrestricted\"?",
"ignoring_battery_optimization_check_title": "Background Running Permission Reminder",
"input_error": "Don't input indiscriminately 😡",
"list_name_default": "Default",
"list_name_love": "Loved",
"input_error": "Don't type indiscriminately 😡",
"list_add_btn_title": "Add the song(s) to \"{name}\"",
"list_add_tip_exists": "This song already exists in the list, don't click me again~😡",
"list_add_title_first_add": "Add",
"list_add_title_first_move": "Move",
"list_add_title_last": "to...",
"list_create": "Create List",
"list_create_input_placeholder": "What name do you think of...",
"list_create_input_placeholder": "What name do you want...",
"list_duplicate_tip": "A list with the same name already exists. Do you want to continue creating it?",
"list_edit_action_tip_add_failed": "Failed to add",
"list_edit_action_tip_add_success": "Successfully added",
@ -84,6 +84,9 @@
"list_multi_add_title_first_add": "Add the selected",
"list_multi_add_title_first_move": "Move the selected",
"list_multi_add_title_last": "songs to ...",
"list_name_default": "Default",
"list_name_love": "Loved",
"list_name_temp": "Temp List",
"list_remove": "Remove",
"list_remove_music_multi_tip": "Do you really want to remove the selected {num} songs?",
"list_remove_tip": "Do you really want to remove \"{name}\"?",
@ -93,10 +96,9 @@
"list_select_all": "Select All",
"list_select_cancel": "Cancel",
"list_select_local_file": "Add Local Songs",
"list_select_local_file_desc": "Choose local song directory",
"list_select_local_file_empty_tip": "No songs found in current directory",
"list_select_local_file_desc": "Choose local song folder",
"list_select_local_file_empty_tip": "No songs found in current folder",
"list_select_local_file_result_failed_tip": "Found {total} song(s), successfully added {success} song(s), failed to add {failed} song(s). View the error log for details.",
"list_select_local_file_result_tip": "Found {Total} song(s), all added!",
"list_select_local_file_temp_add_tip": "Found {total} matching files, quickly added to the current list, will now start the file metadata reading process. Please do not exit the app!",
"list_select_range": "Range",
@ -115,7 +117,6 @@
"list_sort_modal_by_up": "Ascending",
"list_sync": "Update",
"list_sync_confirm_tip": "This will replace the songs in \"{name}\" with the songs in the online list, are you sure you want to update?",
"list_name_temp": "Temp List",
"list_update_error": "Failed to update \"{name}\"",
"list_update_success": "Successfully updated \"{name}\"",
"list_updating": "Updating",
@ -164,7 +165,7 @@
"nav_top": "Charts",
"never_show": "Never show again",
"no_item": "The list is empty...",
"notifications_check_tip": "You have not allowed LX Music to show notifications, or the “MusicService” notification category in the LX Music notification settings is disabled, which prevents you from using the notification for actions such as pausing, switching songs, and so on. Do you want to enable it?",
"notifications_check_tip": "You have not allowed LX Music to show notifications, or the \"MusicService\" notification category in the LX Music notification settings is disabled, which prevents you from using the notification for actions such as pausing, switching songs, and so on. Do you want to enable it?",
"notifications_check_title": "Notification Permission Reminder",
"ok": "OK",
"open_storage_error_tip": "The entered path is illegal",
@ -174,7 +175,7 @@
"open_storage_select_path_tip": "TIP: For external storage, if you still cannot access it after granting storage permissions, you can click the following button to select the path to allow access.",
"open_storage_tip": "Enter storage path",
"open_storage_title": "Manually enter the path to specify external memory in the following",
"parent_dir_name": "Parent directory",
"parent_dir_name": "Parent folder",
"pause": "Pause",
"play": "Play",
"play_all": "Play all",
@ -303,11 +304,11 @@
"setting_list_click_action": "Automatically switch to current list when clicking a song in the list (Only valid for \"Playlists\" and \"Charts\" page)",
"setting_list_show interval": "Show song length",
"setting_list_show_album_name": "Show song album name",
"setting_lyric_desktop_permission_tip": "To use this feature, you need to grant LX Music the permission to display hover windows in the system permission settings, do you go to the relevant page to grant this permission?",
"setting_lyric_desktop": "Desktop Lyric",
"setting_lyric_desktop_enable": "Show lyric window",
"setting_lyric_desktop_lock": "Lock lyric window",
"setting_lyric_desktop_maxlineNum": "Maximum Number of Lines",
"setting_lyric_desktop_permission_tip": "To use this feature, you need to grant LX Music the permission to display hover windows in the system permission settings.\n\nDo you go to the relevant page to grant this permission?",
"setting_lyric_desktop_single_line": "Do not wrap lyrics",
"setting_lyric_desktop_text_opacity": "Lyric Font Transparency",
"setting_lyric_desktop_text_size": "Lyric Font Size",
@ -340,7 +341,7 @@
"setting_play_audio_offload": "Enable audio offload",
"setting_play_audio_offload_tip": "You can enable this option to save power consumption, but on some devices there may be an issue where all songs prompt \"Error loading music\" or \"Unable to play the whole song in full\", this is due to a bug in the current system. \n\nFor those experiencing this issue you can turn off the option and restart the app completely and try again.",
"setting_play_auto_clean_played_list": "Automatically empty the played list",
"setting_play_auto_clean_played_list_tip": "In the shuffle mode, when switching between songs by clicking on a song in the \"Same list as playlist\", if \" Automatically empty played list\" is enabled, the songs in the list will participate in the random again.",
"setting_play_auto_clean_played_list_tip": "In the shuffle mode, when switching between songs by clicking on a song in the same list as playlist, if \"Automatically empty played list\" is enabled, the songs in the list will participate in the random again.",
"setting_play_cache_size": "Maximum Cache Size (MB)",
"setting_play_cache_size_no_cache": "Cache Disabled",
"setting_play_cache_size_save_tip": "The cache is set and takes effect after restarting the app.",
@ -502,7 +503,7 @@
"version_tip_downloaded": "The apk has been downloaded.",
"version_tip_failed": "Failed to download the apk. You can either retry or go to the project address and download the new update manually.",
"version_tip_latest": "The app is up to date, please enjoy it~🥂",
"version_tip_min": "The download has been switched to background. You can navigate to “Settings → Update” to re-open this pop-up window.",
"version_tip_min": "The download has been switched to background. You can navigate to \"Settings → Update\" to re-open this pop-up window.",
"version_tip_unknown": "Failed to get the latest version information. Recommended to manually go to the project address to check for a new version.",
"version_title_checking": "⏳ Checking for updates ⏳",
"version_title_failed": "❌ Failed to download ❌",

View File

@ -37,6 +37,8 @@
"date_format_minute": "{num} 分钟前",
"date_format_second": "{num} 秒前",
"deep_link__handle_error_tip": "调用失败:{message}",
"deep_link_file_js_confirm_tip": "确认要导入自定义源文件「{name}」吗?",
"deep_link_file_lxmc_confirm_tip": "确认要导入列表文件「{name}」吗?",
"delete": "移除",
"dialog_cancel": "我不",
"dialog_confirm": "好的",
@ -49,8 +51,6 @@
"ignoring_battery_optimization_check_tip": "LX Music 没有在「忽略电池优化」的白名单中,这可能会导致在后台播放音乐时被系统暂停。是否将 LX Music 加入该白名单中?",
"ignoring_battery_optimization_check_title": "后台运行权限设置提醒",
"input_error": "不要乱输好吧😡",
"list_name_default": "试听列表",
"list_name_love": "我的收藏",
"list_add_btn_title": "把该歌曲添加到「{name}」",
"list_add_tip_exists": "列表已经存在这首歌啦,不要再点我啦~😡",
"list_add_title_first_add": "添加",
@ -72,7 +72,7 @@
"list_import": "导入",
"list_import_part_button_cancel": "不要啊",
"list_import_part_button_confirm": "覆盖掉",
"list_import_part_confirm": "导入的列表{importName})与本地列表({localName}的 ID 相同,是否覆盖本地列表?",
"list_import_part_confirm": "导入的列表「{importName}」与本地列表「{localName}」的 ID 相同,是否覆盖本地列表?",
"list_import_part_desc": "选择列表文件",
"list_import_tip__alldata": "这是一个「所有数据」备份文件,你需要去这里导入:\n\n设置 → 备份与恢复 → 列表数据 → 导入列表",
"list_import_tip__failed": "导入失败",
@ -84,6 +84,9 @@
"list_multi_add_title_first_add": "添加已选的",
"list_multi_add_title_first_move": "移动已选的",
"list_multi_add_title_last": "首歌曲到...",
"list_name_default": "试听列表",
"list_name_love": "我的收藏",
"list_name_temp": "临时列表",
"list_remove": "移除",
"list_remove_music_multi_tip": "你真的想要移除所选的 {num} 首歌曲吗?",
"list_remove_tip": "你真的想要移除「{name}」吗?",
@ -94,7 +97,7 @@
"list_select_cancel": "取消",
"list_select_local_file": "添加本地歌曲",
"list_select_local_file_desc": "选择本地歌曲文件夹",
"list_select_local_file_empty_tip": "没有在当前目录中发现歌曲",
"list_select_local_file_empty_tip": "没有在当前文件夹中发现歌曲",
"list_select_local_file_result_failed_tip": "共发现 {total} 首歌曲,成功添加 {success} 首,失败 {failed} 首,可到错误日志查看添加失败的歌曲",
"list_select_local_file_result_tip": "共发现 {total} 首歌曲,已全部添加!",
"list_select_local_file_temp_add_tip": "共找到 {total} 个符合要求的文件,已快速添加到当前列表,现在将进入文件标签读取流程,请勿退出应用!",
@ -114,13 +117,12 @@
"list_sort_modal_by_up": "升序",
"list_sync": "更新",
"list_sync_confirm_tip": "这将会把「{name}」内的歌曲替换成在线列表的歌曲,你确认要更新吗?",
"list_name_temp": "临时列表",
"list_update_error": "「{name}」更新失败",
"list_update_success": "「{name}」更新成功",
"list_updating": "更新中",
"lists__duplicate": "重复歌曲",
"lists_dislike_music_add_tip": "已添加",
"lists_dislike_music_singer_tip": "你真的不喜欢 {singer} 的「{name}」吗?",
"lists_dislike_music_singer_tip": "你真的不喜欢「{singer}」的「{name}」吗?",
"lists_dislike_music_tip": "你真的不喜欢「{name}」吗?",
"load_failed": "啊~加载失败了😥",
"loading": "加载中...",
@ -173,7 +175,7 @@
"open_storage_select_path_tip": "提示:对于外部存储,在授予存储权限后仍然无法访问时,可以点击下面的按钮选择允许访问的路径。",
"open_storage_tip": "输入存储路径",
"open_storage_title": "请手动在下方输入路径以指定外置存储器",
"parent_dir_name": "上一级目录",
"parent_dir_name": "上一级文件夹",
"pause": "暂停",
"play": "播放",
"play_all": "播放全部",
@ -300,14 +302,14 @@
"setting_list_add_music_location_type": "添加歌曲到列表时的位置",
"setting_list_add_music_location_type_bottom": "底部",
"setting_list_add_music_location_type_top": "顶部",
"setting_list_click_action": "点击列表里的歌曲时自动切换到当前列表播放(仅对歌单、排行榜有效)",
"setting_list_click_action": "点击列表里的歌曲时自动切换到当前列表播放(仅对「歌单」和「排行榜」有效)",
"setting_list_show interval": "显示歌曲时长",
"setting_list_show_album_name": "显示歌曲专辑名",
"setting_lyric_desktop_permission_tip": "桌面歌词功能需要在系统权限设置中授予 LX Music 显示悬浮窗口的权限才能使用,是否去相关界面授予该权限?",
"setting_lyric_desktop": "桌面歌词",
"setting_lyric_desktop_enable": "显示歌词",
"setting_lyric_desktop_lock": "锁定歌词",
"setting_lyric_desktop_maxlineNum": "最大行数",
"setting_lyric_desktop_permission_tip": "桌面歌词功能需要在系统权限设置中授予 LX Music 显示悬浮窗口的权限才能使用,是否去相关界面授予该权限?",
"setting_lyric_desktop_single_line": "使用单行歌词",
"setting_lyric_desktop_text_opacity": "歌词字体透明度",
"setting_lyric_desktop_text_size": "歌词字体大小",

View File

@ -37,6 +37,8 @@
"date_format_minute": "{num} 分鐘前",
"date_format_second": "{num} 秒前",
"deep_link__handle_error_tip": "呼叫失敗:{message}",
"deep_link_file_js_confirm_tip": "確認要匯入自訂來源 API「{name}」嗎?",
"deep_link_file_lxmc_confirm_tip": "確認要匯入清單檔案「{name}」嗎?",
"delete": "移除",
"dialog_cancel": "我不",
"dialog_confirm": "好的",
@ -70,7 +72,7 @@
"list_import": "匯入",
"list_import_part_button_cancel": "不要啊",
"list_import_part_button_confirm": "覆蓋掉",
"list_import_part_confirm": "匯入的清單{importName})與本機清單({localName}的 ID 相同,是否覆蓋本機清單?",
"list_import_part_confirm": "匯入的清單「{importName}」與本機清單「{localName}」的 ID 相同,是否覆蓋本機清單?",
"list_import_part_desc": "選取清單檔案",
"list_import_tip__alldata": "這是一個「所有資料」備份檔案,你需要去這裡匯入:\n\n設定 → 備份與復原 → 清單資料 → 匯入清單",
"list_import_tip__failed": "匯入失敗",
@ -95,7 +97,7 @@
"list_select_cancel": "取消",
"list_select_local_file": "添加本機歌曲",
"list_select_local_file_desc": "選取本機歌曲資料夾",
"list_select_local_file_empty_tip": "沒有在目前目錄中發現歌曲",
"list_select_local_file_empty_tip": "沒有在目前資料夾中發現歌曲",
"list_select_local_file_result_failed_tip": "共發現 {total} 首歌曲,成功添加 {success} 首,失敗 {failed} 首,可到錯誤日誌查看添加失敗的歌曲",
"list_select_local_file_result_tip": "共發現 {total} 首歌曲,已全部添加!",
"list_select_local_file_temp_add_tip": "共找到 {total} 個符合要求的檔案,已快速添加到目前清單,現在將進入檔案標籤讀取流程,請勿退出應用!",
@ -120,7 +122,7 @@
"list_updating": "更新中",
"lists__duplicate": "重複歌曲",
"lists_dislike_music_add_tip": "已添加",
"lists_dislike_music_singer_tip": "你真的不喜歡 {singer} 的「{name}」嗎?",
"lists_dislike_music_singer_tip": "你真的不喜歡「{singer}」的「{name}」嗎?",
"lists_dislike_music_tip": "你真的不喜歡「{name}」嗎?",
"load_failed": "啊~載入失敗了😥",
"loading": "載入中...",
@ -173,7 +175,7 @@
"open_storage_select_path_tip": "提示:對於外部儲存,在授予儲存權限後仍然無法訪問時,可以點擊下面的按鈕選取允許訪問的路徑。",
"open_storage_tip": "輸入儲存路徑",
"open_storage_title": "請手動在下方輸入路徑以指定外部儲存器",
"parent_dir_name": "上一級目錄",
"parent_dir_name": "上一級資料夾",
"pause": "暫停",
"play": "播放",
"play_all": "播放全部",
@ -230,7 +232,7 @@
"setting_backup_all_export_desc": "選取備份儲存位置",
"setting_backup_all_import": "匯入",
"setting_backup_all_import_desc": "選取備份檔案",
"setting_backup_part": "清單資料(與桌面版清單備檔案通用)",
"setting_backup_part": "清單資料(與桌面版清單備檔案通用)",
"setting_backup_part_export_list": "匯出清單",
"setting_backup_part_export_list_desc": "選取歌單備份檔案儲存位置",
"setting_backup_part_export_list_tip_failed": "歌單匯出失敗",

View File

@ -25,7 +25,7 @@ const SearchInput = forwardRef<SearchInputType, SearchInputProps>(({ onSearch },
return (
<Input
onChangeText={handleChangeText}
placeholder="Find for something..."
placeholder="Search for something..."
value={text}
style={styles.input}
// onFocus={showTipList}

View File

@ -92,7 +92,7 @@ export const handleSync = (listInfo: LX.List.UserListInfo) => {
})
}
const buildLocalMusicInfoByFilePath = (file: FileType): LX.Music.MusicInfoLocal => {
export const buildLocalMusicInfoByFilePath = (file: FileType): LX.Music.MusicInfoLocal => {
const index = file.name.lastIndexOf('.')
return {
id: file.path,
@ -109,7 +109,7 @@ const buildLocalMusicInfoByFilePath = (file: FileType): LX.Music.MusicInfoLocal
},
}
}
const buildLocalMusicInfo = (filePath: string, metadata: MusicMetadataFull): LX.Music.MusicInfoLocal => {
export const buildLocalMusicInfo = (filePath: string, metadata: MusicMetadataFull): LX.Music.MusicInfoLocal => {
return {
id: filePath,
name: metadata.name,

View File

@ -76,6 +76,8 @@ const styles = StyleSheet.create({
marginBottom: 5,
},
clearBtn: {
gap: 5,
flexDirection: 'row',
flexWrap: 'wrap',
},
})

View File

@ -31,7 +31,7 @@ export default forwardRef<MusicListType, MusicListProps>(({ componentId }, ref)
headerRef.current?.setInfo({
name: (info.name || listDetailInfo.info.name) ?? '',
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
desc: info.desc || listDetailInfo.info.desc || '',
desc: listDetailInfo.info.desc || info.desc || '',
playCount: (info.play_count ?? listDetailInfo.info.play_count) ?? '',
imgUrl: info.img ?? listDetailInfo.info.img,
})
@ -43,7 +43,7 @@ export default forwardRef<MusicListType, MusicListProps>(({ componentId }, ref)
headerRef.current?.setInfo({
name: (info.name || listDetailInfo.info.name) ?? '',
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
desc: info.desc || listDetailInfo.info.desc || '',
desc: listDetailInfo.info.desc || info.desc || '',
playCount: (info.play_count ?? listDetailInfo.info.play_count) ?? '',
imgUrl: info.img ?? listDetailInfo.info.img,
})
@ -54,7 +54,7 @@ export default forwardRef<MusicListType, MusicListProps>(({ componentId }, ref)
headerRef.current?.setInfo({
name: (info.name || listDetailInfo.info.name) ?? '',
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
desc: info.desc || listDetailInfo.info.desc || '',
desc: listDetailInfo.info.desc || info.desc || '',
playCount: (info.play_count ?? listDetailInfo.info.play_count) ?? '',
imgUrl: info.img ?? listDetailInfo.info.img,
})

View File

@ -81,7 +81,7 @@ export default {
global.state_event.playPlayedListChanged({ ...state.playedList })
},
addTempPlayList(list: LX.Player.TempPlayListItem[]) {
const topList: Array<{ listId: string, musicInfo: LX.Music.MusicInfo | LX.Download.ListItem }> = []
const topList: Array<{ listId: string | null, musicInfo: LX.Music.MusicInfo | LX.Download.ListItem }> = []
const bottomList = list.filter(({ isTop, ...musicInfo }) => {
if (isTop) {
topList.push(musicInfo)

View File

@ -57,7 +57,7 @@ declare global {
/**
* id
*/
listId: string
listId: string | null
/**
*
*/

View File

@ -71,18 +71,6 @@ export const formatPlayTime2 = (time: number) => {
}
const encodeNames = {
'&nbsp;': ' ',
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&apos;': "'",
'&#039;': "'",
} as const
export const decodeName = (str: string | null = '') => {
return str?.replace(/(?:&amp;|&lt;|&gt;|&quot;|&apos;|&#039;|&nbsp;)/gm, (s: string) => encodeNames[s as keyof typeof encodeNames]) ?? ''
}
export const isUrl = (path: string) => /https?:\/\//.test(path)

View File

@ -8,7 +8,7 @@ const errorHandler = (e: Error, isFatal: boolean) => {
Alert.alert(
'💥Unexpected error occurred💥',
`
bug😭GitHub反馈APP后重新启
bug 😭 GitHub
Error:
${isFatal ? 'Fatal:' : ''} ${e.name} ${e.message}

View File

@ -15,6 +15,8 @@ export type {
// export const externalDirectoryPath = RNFS.ExternalDirectoryPath
export const extname = (name: string) => name.lastIndexOf('.') > 0 ? name.substring(name.lastIndexOf('.') + 1) : ''
export const temporaryDirectoryPath = Dirs.CacheDir
export const externalStorageDirectoryPath = Dirs.SDCardDir
export const privateStorageDirectoryPath = Dirs.DocumentDir

View File

@ -1,4 +1,5 @@
import { dateFormat } from './common'
import he from 'he'
export { tranditionalize as langS2T } from '@/utils/simplify-chinese-main'
@ -192,3 +193,8 @@ export const formatPlayCount = (num: number): string => {
if (num > 10000) return `${Math.trunc(num / 1000) / 10}`
return String(num)
}
export const decodeName = (str: string | null = '') => {
if (!str) return ''
return he.decode(str)
}

View File

@ -1,4 +1,4 @@
import { temporaryDirectoryPath, readDir, unlink } from '@/utils/fs'
import { temporaryDirectoryPath, readDir, unlink, extname } from '@/utils/fs'
import { readPic as _readPic } from 'react-native-local-media-metadata'
export {
type MusicMetadata,
@ -15,7 +15,11 @@ const picCachePath = temporaryDirectoryPath + '/local-media-metadata'
export const scanAudioFiles = async(dirPath: string) => {
const files = await readDir(dirPath)
return files.filter(file => file.mimeType?.startsWith('audio/')).map(file => file)
return files.filter(file => {
if (file.mimeType?.startsWith('audio/')) return true
if (extname(file?.name ?? '') === 'ogg') return true
return false
}).map(file => file)
}
const clearPicCache = async() => {

View File

@ -2,5 +2,7 @@ import { existsFile } from './fs'
export const getLocalFilePath = async(musicInfo: LX.Music.MusicInfoLocal): Promise<string> => {
return (await existsFile(musicInfo.meta.filePath)) ? musicInfo.meta.filePath : ''
if (await existsFile(musicInfo.meta.filePath)) return musicInfo.meta.filePath
// 直接从应用外 intent 调用打开的文件ogg等类型无法判断文件是否存在但这类文件路径为纯数字
return /\/\d+$/.test(musicInfo.meta.filePath) ? musicInfo.meta.filePath : ''
}

View File

@ -137,7 +137,7 @@ export default {
img: item.cover_url_medium,
// grade: item.favorcnt / 10,
total: item.song_ids?.length,
desc: item.desc,
desc: decodeName(item.desc).replace(/<br>/g, '\n'),
source: 'tx',
})),
total: data.total,
@ -305,13 +305,13 @@ export default {
return {
play_count: formatPlayCount(item.listennum),
id: String(item.dissid),
author: item.creator.name,
name: item.dissname,
author: decodeName(item.creator.name),
name: decodeName(item.dissname),
time: dateFormat(item.createtime, 'Y-M-D'),
img: item.imgurl,
// grade: item.favorcnt / 10,
total: item.song_count,
desc: item.introduction,
desc: decodeName(decodeName(item.introduction)).replace(/<br>/g, '\n'),
source: 'tx',
}
}),

View File

@ -1,5 +1,5 @@
import { stringMd5 } from 'react-native-quick-md5'
import { decodeName } from '../common'
import { decodeName } from '../index'
/**
* 获取音乐音质