Initial import: Music_Server, MusicFree, catalog-sync
This commit is contained in:
+89
@@ -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`
|
||||
+130
@@ -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.
|
||||
Reference in New Issue
Block a user