Initial import: Music_Server, MusicFree, catalog-sync

This commit is contained in:
2026-05-23 16:51:14 +08:00
commit 069af30dba
847 changed files with 179878 additions and 0 deletions
@@ -0,0 +1,89 @@
# Ignore Battery Optimization Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 补齐 Android 忽略电池优化权限链路,减少锁屏后一段时间被系统回收导致的停播问题。
**Architecture:** 复用现有 `NativeUtils` 原生模块承接 Android 电池优化查询与申请能力,在权限页增加对应入口和状态展示。JS 层先补回归测试,再用最小改动补 Manifest、原生模块、类型声明与文案。
**Tech Stack:** React Native, Kotlin Android module, Jest, react-test-renderer
---
### Task 1: Permissions Page Regression Test
**Files:**
- Create: `src/pages/permissions/index.test.tsx`
- Modify: `src/pages/permissions/index.tsx`
- [ ] **Step 1: Write the failing test**
```tsx
it("renders ignore battery optimization entry and calls native request handler", async () => {
// mock NativeUtils.isIgnoringBatteryOptimizations -> true
// render Permissions
// assert translated entry exists
// trigger onPress
// expect NativeUtils.requestIgnoreBatteryOptimizations toHaveBeenCalled()
});
```
- [ ] **Step 2: Run test to verify it fails**
Run: `npx jest src/pages/permissions/index.test.tsx --runInBand`
Expected: FAIL because the current page does not render or call battery optimization APIs.
- [ ] **Step 3: Write minimal implementation**
```tsx
type IPermissionTypes = "floatingWindow" | "fileStorage" | "batteryOptimization";
updates.batteryOptimization = await NativeUtils.isIgnoringBatteryOptimizations();
onPress={() => NativeUtils.requestIgnoreBatteryOptimizations()}
```
- [ ] **Step 4: Run test to verify it passes**
Run: `npx jest src/pages/permissions/index.test.tsx --runInBand`
Expected: PASS
### Task 2: Android Native Bridge
**Files:**
- Modify: `android/app/src/main/AndroidManifest.xml`
- Modify: `android/app/src/main/java/fun/upup/musicfree/utils/UtilsModule.kt`
- Modify: `src/native/utils/index.ts`
- Modify: `src/types/core/i18n/index.d.ts`
- Modify: `src/core/i18n/languages/zh-cn.json`
- Modify: `src/core/i18n/languages/en-us.json`
- Modify: `src/core/i18n/languages/zh-tw.json`
- [ ] **Step 1: Add Android permission declaration**
```xml
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
```
- [ ] **Step 2: Add native query and request methods**
```kotlin
@ReactMethod
fun isIgnoringBatteryOptimizations(promise: Promise) { ... }
@ReactMethod
fun requestIgnoreBatteryOptimizations(promise: Promise) { ... }
```
- [ ] **Step 3: Expose methods to JS and add i18n keys**
```ts
isIgnoringBatteryOptimizations: () => Promise<boolean>;
requestIgnoreBatteryOptimizations: () => Promise<boolean>;
```
- [ ] **Step 4: Run targeted verification**
Run: `npx jest src/pages/permissions/index.test.tsx --runInBand`
Expected: PASS
Run: `npx eslint src/pages/permissions/index.tsx src/pages/permissions/index.test.tsx src/native/utils/index.ts`
Expected: exit code `0`
@@ -0,0 +1,130 @@
# TopList Platform Filter Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add an in-page platform filter to the MusicFree toplist screen so `Music_Server` toplists can be narrowed to `全部 / QQ音乐 / 网易云 / 酷我` without changing plugin or server APIs.
**Architecture:** Keep the existing plugin-level `TabView` unchanged and implement the new behavior entirely inside the current toplist scene. A small pure helper will derive filter tags and filtered sections from the existing `IMusicSheetGroupItem[]` response, and the board panel UI will render a horizontal tag row only when more than one platform group exists.
**Tech Stack:** React Native, TypeScript, Jotai state already present in toplist page, Jest for pure logic tests.
---
### Task 1: Add Pure Toplist Filter Logic
**Files:**
- Create: `D:\source\MusicFree\src\pages\topList\hooks\topListPlatformFilter.ts`
- Create: `D:\source\MusicFree\src\pages\topList\hooks\topListPlatformFilter.test.ts`
- [ ] **Step 1: Write the failing test**
```ts
import {
buildTopListPlatformTags,
filterTopListSections,
} from "./topListPlatformFilter";
describe("topListPlatformFilter", () => {
const sections = [
{ title: "QQ音乐", data: [{ id: "qq-1" }] },
{ title: "网易云", data: [{ id: "wy-1" }] },
{ title: "酷我", data: [{ id: "kw-1" }] },
];
it("prepends 全部 when there are multiple platform groups", () => {
expect(buildTopListPlatformTags(sections)).toEqual([
{ id: "", title: "全部" },
{ id: "QQ音乐", title: "QQ音乐" },
{ id: "网易云", title: "网易云" },
{ id: "酷我", title: "酷我" },
]);
});
it("returns all sections for 全部", () => {
expect(filterTopListSections(sections, "")).toEqual(sections);
});
it("returns only the matching platform section", () => {
expect(filterTopListSections(sections, "网易云")).toEqual([
{ title: "网易云", data: [{ id: "wy-1" }] },
]);
});
it("returns an empty list when the platform is missing", () => {
expect(filterTopListSections(sections, "咪咕")).toEqual([]);
});
});
```
- [ ] **Step 2: Run test to verify it fails**
Run: `npm test -- --runInBand src/pages/topList/hooks/topListPlatformFilter.test.ts`
Expected: FAIL because `topListPlatformFilter.ts` does not exist yet.
- [ ] **Step 3: Write minimal implementation**
```ts
const ALL_ID = "";
export function buildTopListPlatformTags(sections: IMusic.IMusicSheetGroupItem[]) {
if (!Array.isArray(sections) || sections.length <= 1) {
return [];
}
return [
{ id: ALL_ID, title: "全部" },
...sections.map(section => ({
id: section.title ?? "",
title: section.title ?? "",
})),
].filter(tag => tag.id || tag.title === "全部");
}
export function filterTopListSections(
sections: IMusic.IMusicSheetGroupItem[],
platformId: string,
) {
if (!platformId) {
return sections ?? [];
}
return (sections ?? []).filter(section => section.title === platformId);
}
```
- [ ] **Step 4: Run test to verify it passes**
Run: `npm test -- --runInBand src/pages/topList/hooks/topListPlatformFilter.test.ts`
Expected: PASS.
### Task 2: Wire the Filter into TopList UI
**Files:**
- Modify: `D:\source\MusicFree\src\pages\topList\components\boardPanel.tsx`
- Reuse: `D:\source\MusicFree\src\components\base\typeTag.tsx`
- [ ] **Step 1: Add local selected-platform state and render tags**
Add a horizontal `ScrollView` above the `SectionList` that renders `TypeTag` entries built from `buildTopListPlatformTags(topListData?.data || [])`. Default selected tag id is `""`.
- [ ] **Step 2: Filter sections before passing them to SectionList**
Call `filterTopListSections(topListData?.data || [], selectedPlatformId)` and use the result as the `sections` prop.
- [ ] **Step 3: Keep existing loading and empty-state behavior**
Do not change:
- loading gate based on `RequestStateCode.FINISHED`
- `TopListItem` rendering
- section header rendering
- toplist detail navigation
- [ ] **Step 4: Run the new logic test again**
Run: `npm test -- --runInBand src/pages/topList/hooks/topListPlatformFilter.test.ts`
Expected: PASS.
- [ ] **Step 5: Smoke-check the touched file compiles cleanly**
Run: `npx tsc --noEmit --pretty false`
Expected: exit code `0` if the repository typechecks cleanly. If unrelated repo-wide issues block this, note the blocker and at least ensure the edited file imports resolve correctly.