diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..2012a62 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,60 @@ +name: ๐Ÿ› Bug Report +description: Create a report to help us reproduce and fix a bug in Cygnus Player. +title: "[BUG]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thank you for reporting a bug! Please ensure you are testing against local MP3 files and M3U/M3U8 playlists. + - type: input + id: version + attributes: + label: Cygnus Player Version + description: What version of Cygnus Player are you using? (Check Settings screen or version.properties) + placeholder: "e.g., 1.0.2" + validations: + required: true + - type: input + id: environment + attributes: + label: Device & Android Version + description: What device and Android version are you using? + placeholder: "e.g., Pixel 8 Pro running Android 16 (API 36) / Android Emulator API 36" + validations: + required: true + - type: textarea + id: description + attributes: + label: Description & Reproduction Steps + description: Explain the issue clearly and list the exact steps to reproduce it. + placeholder: | + 1. Open Cygnus Player + 2. Load M3U playlist "rock_classics.m3u" + 3. Tap Play/Pause toggle + 4. See error... + validations: + required: true + - type: textarea + id: expected-actual + attributes: + label: Expected vs Actual Behavior + description: Describe what you expected to happen versus what actually occurred. + validations: + required: true + - type: textarea + id: playlist-context + attributes: + label: Playlist & Audio Context + description: Detail relevant audio format, M3U path structure (relative vs absolute), ReplayGain tags, or SAF root folder permissions. + placeholder: "e.g., Relative path M3U playlist with 15,000 MP3 files stored in Scoped Storage root Music folder." + validations: + required: false + - type: textarea + id: logs + attributes: + label: Logcat / Stack Trace / Screenshots + description: Paste any relevant logcat output, stack traces, or upload screenshots. + render: shell + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..bf3eda7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Contribution Guidelines + url: https://github.com/FesterHead/cygnus-player/blob/develop/CONTRIBUTING.md + about: Review project scope, architectural rules, and technical standards before opening a pull request. + - name: Storage & SAF Setup + url: https://github.com/FesterHead/cygnus-player/blob/develop/README.md#-%EF%B8%8F-storage--scoped-storage-compliance + about: Guidelines for setting up Scoped Storage access and MediaStore relative path resolution. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..3ec2bb0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,41 @@ +name: ๐Ÿš€ Feature Request +description: Propose an idea or enhancement for Cygnus Player. +title: "[FEATURE]: " +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Thank you for proposing a feature! Before submitting, please review [CONTRIBUTING.md](../CONTRIBUTING.md) to ensure your request aligns with Cygnus Player's minimalist design philosophy. + - type: checkboxes + id: scope-alignment + attributes: + label: Alignment with Project Scope + options: + - label: I confirm this feature is strictly for **local MP3 playback** and **M3U/M3U8 playlists**. + required: true + - label: I confirm this proposal maintains a **minimalist UI** (no seek bars, no previous/next skip controls). + required: true + - label: I confirm this proposal maintains a low memory footprint ($O(1)$ relative to queue size). + required: true + - type: textarea + id: problem-statement + attributes: + label: Problem Statement / Use Case + description: What problem does this feature solve or what workflow does it enhance? + validations: + required: true + - type: textarea + id: proposed-solution + attributes: + label: Proposed Solution + description: Describe how you envision this feature working. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternative Solutions Considered + description: Have you considered any alternative solutions or workarounds? + validations: + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f92e366 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ +# ๐Ÿ“ Description + +Please include a summary of the change, the rationale behind it, and any relevant motivation or context. + +Fixes / Closes #(issue number) + +## ๐ŸŒŒ Scope & Architecture Checklist + +Please confirm that your Pull Request adheres to Cygnus Player's core constraints: + +- [ ] **MP3 Playback Only**: Changes are strictly for local MP3 audio files and M3U/M3U8 playlists. +- [ ] **Minimalist UX**: Maintains single Play/Pause control; no seek bars or skip/navigation buttons added. +- [ ] **Linear Memory Footprint ($O(1)$)**: Low memory footprint relative to queue size maintained using primitive arrays (`LongArray`/`IntArray`) and Room sliding cursors. +- [ ] **Gapless & Audio Focus**: Media3 gapless playback and Android Audio Focus handling preserved. + +## ๐Ÿงช Testing Checklist + +- [ ] I have executed `./gradlew test` locally and all unit tests pass. +- [ ] New classes, interfaces, methods, and functions include full KDoc documentation (`@param`, `@return`). +- [ ] No raw absolute file paths or unhandled exceptions introduced. +- [ ] `CHANGELOG.md` updated under `## [Unreleased]` (if applicable). diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 85c7c1b..3fbfd75 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -14,6 +14,8 @@ jobs: build: name: Build & Test runs-on: ubuntu-latest + env: + HAS_KEYSTORE: ${{ secrets.KEYSTORE_BASE64 != '' }} steps: - name: Checkout code @@ -32,12 +34,17 @@ jobs: - name: Run Unit Tests run: ./gradlew test + - name: Build Debug APK + run: ./gradlew assembleDebug + - name: Decode Keystore + if: env.HAS_KEYSTORE == 'true' env: ENCODED_KEYSTORE: ${{ secrets.KEYSTORE_BASE64 }} run: echo $ENCODED_KEYSTORE | base64 --decode > cygnus-release.keystore - name: Build Release APK + if: env.HAS_KEYSTORE == 'true' env: KEYSTORE_FILE_PATH: ../cygnus-release.keystore KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} @@ -45,7 +52,8 @@ jobs: KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} run: ./gradlew assembleRelease - - name: Upload APK Artifact + - name: Upload Release APK Artifact + if: env.HAS_KEYSTORE == 'true' uses: actions/upload-artifact@v4 with: name: cygnus-player-release diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..51ccd70 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,12 @@ +{ + "MD013": false, + "MD024": { + "siblings_only": true + }, + "MD025": false, + "MD033": { + "allowed_elements": [ + "img" + ] + } +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 4f81299..177d54d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,15 @@ { - "java.configuration.updateBuildConfiguration": "automatic" + "java.configuration.updateBuildConfiguration": "automatic", + "markdownlint.config": { + "MD013": false, + "MD024": { + "siblings_only": true + }, + "MD025": false, + "MD033": { + "allowed_elements": [ + "img" + ] + } + } } diff --git a/AGENTS.md b/AGENTS.md index 9346e6b..b707352 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,12 +7,12 @@ You are an expert Android Engineer specializing in low-overhead audio architectu ## 1. Commit and Push Protocol - **No Automated Git Operations:** The AI Agent is strictly prohibited from executing `git commit` or `git push`. -- **Incremental Changes:** Work will be broken down into small, logical increments. The Agent will pause after each increment to allow the user to review, commit, and push via the Android Studio UI. +- **Incremental Changes:** Work will be broken down into small, logical increments. The Agent will pause after each increment to allow the user to review, commit, and push via their preferred IDE interface (such as Antigravity IDE or Android Studio). - **Branching Strategy:** All feature development and AI-assisted coding must target the `develop` branch. Pull requests from `develop` to `main` are used to trigger signed Release builds via GitHub Actions. - **Versioning Strategy:** The `version.properties` file in the project root is the source of truth for the app's version. To avoid `BuildConfig` instability in experimental environments, a manual `VersionInfo.kt` object must also be maintained in sync with this file. Before any code is merged into `main` for a release, the AI Agent must verify and update `VERSION_NAME` and `VERSION_CODE` in both `version.properties` and `app/src/main/java/com/festerhead/cygnusplayer/VersionInfo.kt`. The GitHub Action will use `version.properties` to automatically publish a public Release. - **Static Analysis:** The Agent must run `analyze_file` (IDE Inspections) on every modified file to identify and resolve unused code, deprecations, and style issues before task completion. - **Documentation Standards:** Every new class, interface, method, and function must include proper KDoc documentation. KDoc must clearly explain the purpose, parameters (`@param`), and return values (`@return`). For Room entities, explain the role of each column. -- **AI Commit Messages:** The user will utilize the Android Studio AI-generated commit message feature. +- **AI Commit Messages:** The user will utilize their preferred IDE's AI-generated commit message feature (such as Antigravity IDE or Android Studio). ## 2. Automated Testing Strategy @@ -26,7 +26,7 @@ You are an expert Android Engineer specializing in low-overhead audio architectu - **Package Namespace:** `com.festerhead.cygnusplayer` -- **Target Platform:** Android 17 (API Level 37) +- **Target Platform:** Android 16 (API Level 36) - **Language:** Always generate concise Kotlin code using modern language features (coroutines, flows). Do not write Java boilerplate. @@ -41,11 +41,11 @@ You are an expert Android Engineer specializing in low-overhead audio architectu - **Accessibility & Theme:** Force a Dark Mode UI using the Monokai Pro (Filter Spectrum) color palette. Prioritize high-contrast Purple (#AB9DF2), Blue/Cyan (#78DCE8), and Orange (#FC9867) to ensure readability for red-green color-blind users. Avoid relying on Red/Green for critical state information. - **UI/UX Philosophy:** The player is strictly minimalist. No "Next" or "Previous" buttons. No "Seek" bar (unless for visual progress only). The UI should focus on the current track and its position in the sequence (e.g., `2112/47533`). - - **Controls:** A single, high-contrast Play/Pause toggle that dynamically changes icon based on playback state. + - **Controls:** A single, high-contrast Play/Pause toggle that dynamically changes icon based on playback state. - **Shuffle Cycling:** Changing the Shuffle Mode on the main player screen is not supported. Playlists are "minted" with an immutable Shuffle Mode upon loading; to change the mode, the playlist must be deleted from history and re-added. - **Smart ReplayGain:** ReplayGain is automatic and context-aware. Use `ALBUM_GAIN` for `SEQUENTIAL` and `RANDOM_FOLDER_SEQUENTIAL` modes. Use `TRACK_GAIN` for `TRACK_RANDOM`. - - **Text Handling:** - - **Main App:** Use `Modifier.basicMarquee()` for song titles and album names to ensure long strings are fully readable. + - **Text Handling:** + - **Main App:** Use `Modifier.basicMarquee()` for song titles and album names to ensure long strings are fully readable. - **Widget:** Use clean truncation (Ellipsis) for text to maintain a static, glanceable layout. - **Changelog Maintenance:** Maintain `CHANGELOG.md` according to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Ensure the version exactly matches `VERSION_NAME` in `version.properties`. Mint the numbered release heading before opening a pull request to `main`. When minting a new release, you MUST explicitly ask the user whether to increment the MAJOR, MINOR, or PATCH version number before finalizing the file changes. @@ -56,9 +56,9 @@ You are an expert Android Engineer specializing in low-overhead audio architectu ## 1. M3U Sequence Mapping (Crucial) -- Traditional media queues struggle with duplicate entries of the same file path. +- Traditional media queues struggle with duplicate entries of the same file path. -- To handle repeated tracks cleanly, never map paths directly to the playlist queue. +- To handle repeated tracks cleanly, never map paths directly to the playlist queue. - Always map database records and `MediaItem` tags to a unique, sequential ID (`sequence\_id` or `queue\_id`) so the playlist loop engine treats consecutive duplicate files as separate, unique nodes in the playback sequence. @@ -84,7 +84,7 @@ You are an expert Android Engineer specializing in low-overhead audio architectu ## 4. Scrobbling Integration -- Do not write internal code for Last.fm or external tracking metrics. +- Do not write internal code for Last.fm or external tracking metrics. - Simply ensure the `MediaSession` metadata is updated instantly and accurately on every track transition. External apps like Pano Scrobbler will capture these standard broadcasted system events natively. @@ -97,13 +97,13 @@ You are an expert Android Engineer specializing in low-overhead audio architectu - **Manageable Playlist History:** The file selection screen must maintain a list of recently opened M3U files for quick switching, ordered by most recently opened. Users must be able to remove individual playlists from this history to keep the selection interface clean. - **Relative M3U Path Resolution:** - - The app must resolve playlist track paths relative to the directory where the `.m3u` / `.m3u8` file is located. - - Absolute file paths (e.g., paths containing Windows drive letters or starting with `/`) are explicitly unsupported to ensure library portability. - - The parser must ignore `#EXTINF` metadata tags, relying exclusively on the physical audio files as the source of truth for Artist, Album, and Title. - - **Initial M3U Parsing:** The initial parser only extracts and mints relative file paths and `sequence_ids` into the database. - - **Async Metadata Extraction:** Metadata extraction (reading physical ID3 tags/ReplayGain) is handled asynchronously in the background by a separate worker to ensure UI and playback responsiveness. - - **Metadata Fallback:** If tags are missing from the physical file after background processing, the extractor must return `""` for those fields to maintain UI consistency. - - **Minting Workflow:** When a new playlist is added, the user must explicitly choose its initial `ShuffleMode`. This "Minted Identity" is immutable once selected; to change the mode, the playlist must be deleted from history and re-added. This ensures deterministic behavior for specialized collections. + - The app must resolve playlist track paths relative to the directory where the `.m3u` / `.m3u8` file is located. + - Absolute file paths (e.g., paths containing Windows drive letters or starting with `/`) are explicitly unsupported to ensure library portability. + - The parser must ignore `#EXTINF` metadata tags, relying exclusively on the physical audio files as the source of truth for Artist, Album, and Title. + - **Initial M3U Parsing:** The initial parser only extracts and mints relative file paths and `sequence_ids` into the database. + - **Async Metadata Extraction:** Metadata extraction (reading physical ID3 tags/ReplayGain) is handled asynchronously in the background by a separate worker to ensure UI and playback responsiveness. + - **Metadata Fallback:** If tags are missing from the physical file after background processing, the extractor must return `""` for those fields to maintain UI consistency. + - **Minting Workflow:** When a new playlist is added, the user must explicitly choose its initial `ShuffleMode`. This "Minted Identity" is immutable once selected; to change the mode, the playlist must be deleted from history and re-added. This ensures deterministic behavior for specialized collections. - If a relative file path appears multiple times sequentially or non-sequentially, the parser must cleanly instantiate a new database queue entry with a distinct `sequence\_id` for every occurrence. @@ -114,17 +114,17 @@ You are an expert Android Engineer specializing in low-overhead audio architectu - **Immutable Identity:** A playlist's shuffle strategy is locked at the moment of "minting" (load time). The UI allows for manual "Reshuffle" actions within the same mode, but changing the mode requires a fresh load. - **Forward-Only Principle:** Manual reshuffles or mode transitions (if implemented) must never "wrap around" to previously played tracks in the current album or folder context. Any tracks appearing before the current playback anchor in the folder sequence are **permanently discarded** from the new queue to keep the experience forward-moving. - - **Structural Truncation:** Any tracks appearing before the current playback anchor in the folder sequence are permanently discarded from the new queue array. - - **Persistence:** This truncated `LongArray` must be persisted back to the `PlaylistStateEntity` in Room immediately. - - **UI Synchronization:** Updating the database with the new, truncated array ensures the UI counter (e.g., `1/91`) remains perfectly synchronized with the active playback sequence. + - **Structural Truncation:** Any tracks appearing before the current playback anchor in the folder sequence are permanently discarded from the new queue array. + - **Persistence:** This truncated `LongArray` must be persisted back to the `PlaylistStateEntity` in Room immediately. + - **UI Synchronization:** Updating the database with the new, truncated array ensures the UI counter (e.g., `1/91`) remains perfectly synchronized with the active playback sequence. - **TRACK_RANDOM:** Shuffle mapping must be calculated using unique `sequence_id` bounds. UI should display current shuffled position (e.g., `5/100`). - **RANDOM_FOLDER_SEQUENTIAL:** - - Shuffle the list of directories. - - Play all tracks in a directory sequentially. - - **History Buffer:** Maintain a history of at least 24 folders. Do not re-select a folder from this history. - - **Constraint:** If the total folder count in the M3U is less than 24, the history buffer size is 0. + - Shuffle the list of directories. + - Play all tracks in a directory sequentially. + - **History Buffer:** Maintain a history of at least 24 folders. Do not re-select a folder from this history. + - **Constraint:** If the total folder count in the M3U is less than 24, the history buffer size is 0. ## 7. Persistence & State @@ -146,7 +146,7 @@ You are an expert Android Engineer specializing in low-overhead audio architectu - Never instantiate heavy domain models or UI state representations for the entire queue or library at once. -- All shuffle mappings, track selections, and queue indices must be processed using low-overhead primitive data types (`IntArray` or `LongArray`). +- All shuffle mappings, track selections, and queue indices must be processed using low-overhead primitive data types (`IntArray` or `LongArray`). - **Sequence ID Streaming:** The `QueueDao` must provide a `getAllQueueIds()` query to stream unique sequential IDs directly into primitive arrays for the `ShuffleEngine`. diff --git a/CHANGELOG.md b/CHANGELOG.md index ce335d7..661b62e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,20 +14,38 @@ Release policy: ## [Unreleased] +## [1.0.3] - 2026-08-01 + +### Added + +- Standardized `.github/PULL_REQUEST_TEMPLATE.md` checklist for contributor pull requests. +- Structured GitHub Issue Forms (`bug_report.yml`, `feature_request.yml`, and `config.yml`) in `.github/ISSUE_TEMPLATE/`. +- Official `CONTRIBUTING.md` guidelines detailing contribution workflow, scope boundaries, architectural constraints, and testing standards. +- Project-level `.markdownlint.json` and `.vscode/settings.json` configuration disabling `MD025` (multiple top-level headings) and line length checks, allowing screenshot `` tags, and setting `MD024` sibling-only heading rules. + +### Changed + +- Hardened `.github/workflows/pr-build.yml` for fork PRs by isolating signing keystore steps behind secret presence checks. +- Updated `README.md` Project Status & Disclaimer section to indicate that Pull Requests aligning with project goals may be accepted, linking to `CONTRIBUTING.md`. +- Refined Markdown formatting, list indentation, line spacing, and heading syntax across `README.md`, `TESTING_STRATEGY.md`, `LICENSE`, `AGENTS.md`, and KDoc comments in `VersionInfo.kt`. + ## [1.0.2] - 2026-07-25 ### Added + - Official `PRIVACY.md` policy documenting on-device storage access and zero data collection. - Project Status & Disclaimer section in `README.md`. ## [1.0.1] - 2026-07-25 ### Added + - Minimalist Settings screen with version info and project links. - "Reset Music Root Folder" utility for easier storage reconfiguration. - Basic database diagnostics (Track and Playlist counts). ### Fixed + - Resolved Android 17.1 (API 37) binder deadlock by deferring startup permission requests. - Improved UI test stability with robust `waitUntil` synchronization. - Stabilized database stress tests for virtualized emulator environments. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0205d65 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,98 @@ +# Contributing to Cygnus Player + +Thank you for your interest in contributing to **Cygnus Player**! + +Cygnus Player is a minimalist, high-performance local audio player for Android, specifically engineered to handle massive, duplicate-heavy M3U/M3U8 playlists of **MP3 files** with sub-millisecond responsiveness. + +We welcome pull requests that align with the project's core philosophy, architecture, and performance standards. Please take a moment to review these guidelines before submitting code changes. + +--- + +## ๐ŸŒŒ Core Project Philosophy & Scope + +Before proposing features or submitting pull requests, please keep the following design constraints in mind: + +1. **Strictly MP3 Playback**: Cygnus Player is optimized exclusively for local MP3 playback. Feature additions for other container formats or streaming services are out of scope. +2. **Minimalist UX**: No "Next" or "Previous" buttons, and no interactive seek bar. Playback is an immutable journey once started. Controls are limited to a single high-contrast Play/Pause toggle. +3. **Linear Memory Footprint ($O(1)$)**: To support libraries of 78,000+ tracks and queue sizes exceeding 47,000 items, queue structures must rely on low-overhead primitive arrays (`LongArray`/`IntArray`) and Room database sliding cursors. +4. **Gapless Transitions**: Playback engine transitions must be zero-latency via `androidx.media3` (`ExoPlayer` & `MediaSessionService`). +5. **High-Contrast Accessibility**: The UI strictly uses the **Monokai Pro (Filter Spectrum)** color palette. Visual indicators prioritize high-contrast Purple (`#AB9DF2`), Blue/Cyan (`#78DCE8`), and Orange (`#FC9867`) to ensure readability for red-green color-blind users. + +--- + +## ๐ŸŒฟ Branching Strategy & Workflow + +* **Development Branch (`develop`)**: All feature development, refactoring, and experimental work target the `develop` branch. +* **Release Branch (`main`)**: Pull Requests submitted against `main` trigger the automated **Android Release Build** GitHub Actions workflow. +* **No Direct Automated Commits**: AI agents and automated scripts are prohibited from running `git commit` or `git push` directly. All changes must be manually reviewed and committed by human maintainers. + +### Versioning Protocol + +If your pull request introduces a new version release: + +1. Update `VERSION_NAME` and `VERSION_CODE` in `version.properties`. +2. Keep `app/src/main/java/com/festerhead/cygnusplayer/VersionInfo.kt` synchronized with `version.properties`. +3. Update `CHANGELOG.md` following [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Keep unreleased changes under `## [Unreleased]`. + +--- + +## ๐Ÿ— Architectural & Coding Guidelines + +### 1. M3U Sequence Mapping + +Traditional media queues struggle with duplicate entries of the same audio file. Cygnus Player assigns every playlist entry a unique, sequential ID (`sequence_id` or `queue_id`). Duplicate tracks within an M3U file must always be instantiated as distinct nodes in the Room database. + +### 2. Relative Path Resolution & Storage + +* All M3U relative paths are resolved relative to the `.m3u` / `.m3u8` file location using the Android **MediaStore API** and **Storage Access Framework (SAF)**. +* Absolute file paths (e.g., paths starting with `/` or containing Windows drive letters) are explicitly unsupported to ensure library portability. + +### 3. Playback Randomness Engine + +* **Supported Modes**: `SEQUENTIAL`, `TRACK_RANDOM`, and `RANDOM_FOLDER_SEQUENTIAL` (with a 24-folder history buffer). +* **Immutable Identity**: A playlist's shuffle mode is locked upon creation ("minting"). Changing modes requires re-adding the playlist. +* **Forward-Only Principle**: Manual reshuffles must never wrap around to previously played tracks in the current album or folder context. Played tracks are structurally truncated from active queue arrays. + +### 4. Smart ReplayGain + +* Use `ALBUM_GAIN` for `SEQUENTIAL` and `RANDOM_FOLDER_SEQUENTIAL` modes. +* Use `TRACK_GAIN` for `TRACK_RANDOM` mode. + +### 5. Documentation & Code Quality + +* **KDoc**: Every new class, interface, method, and public function must include comprehensive KDoc documentation explaining purpose, parameters (`@param`), and return values (`@return`). +* **Static Analysis**: All code must pass IDE inspections without deprecation warnings, unused imports, or style violations. + +--- + +## ๐Ÿงช Testing Standards + +Cygnus Player enforces a **Zero-Manual-Discovery** testing philosophy. Regressions must be caught by automated tests before code is merged. + +1. **Logic-First Unit Testing**: Core logic (`M3uParser`, `ShuffleEngine`, ReplayGain multipliers) must have JVM unit test coverage (`app/src/test`). Use real `.m3u8` test resource files. +2. **Database Integrity**: Test all Room DAO operations using in-memory SQLite databases. +3. **Compose Previews & Tests**: Every Jetpack Compose UI component must provide a `@Preview` and a corresponding `ComposeContentTestRule` test in `app/src/androidTest`. +4. **Media3 Service Tests**: Verify playback state transitions and audio focus handling via Robolectric or Media3 test helpers. + +To run the local test suite: + +```powershell +# Wake up and unlock connected emulator +adb shell input keyevent 224; adb shell wm dismiss-keyguard + +# Run full test suite +./gradlew test connectedDebugAndroidTest +``` + +--- + +## ๐Ÿ“‹ Pull Request Submission Checklist + +Before submitting a Pull Request: + +* [ ] Project builds cleanly via `./gradlew :app:assembleDebug`. +* [ ] All unit tests pass via `./gradlew test`. +* [ ] Code follows Kotlin style conventions with comprehensive KDoc comments. +* [ ] No raw absolute file paths or unhandled exceptions introduced. +* [ ] `CHANGELOG.md` is updated under `## [Unreleased]`. +* [ ] Pull request description clearly outlines the rationale, scope, and testing verification performed. diff --git a/LICENSE b/LICENSE index 0ae3701..b780138 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -MIT License +# MIT License Copyright (c) 2026 FesterHead diff --git a/README.md b/README.md index ec4d423..4877451 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Designed for collectors with large libraries, Cygnus Player prioritizes a low me ## โš ๏ธ Project Status & Disclaimer -Cygnus Player is a personal, open-source hobby project provided strictly **as-is** without official support, bug tracking, or feature updates. Public Issues and Pull Requests are disabled, and contributions are not being accepted. You are welcome to fork the repository and adapt the code to suit your own needs under the terms of the license. +Cygnus Player is a personal, open-source hobby project provided strictly **as-is** without official support or formal issue tracking. Feature scope is intentionally tight and focused on a minimalist MP3 playback engine. However, pull requests targeting `main` (or `develop`) that align with project goals and technical standards may be accepted. Please review [CONTRIBUTING.md](CONTRIBUTING.md) before submitting code changes. You are also welcome to fork the repository and adapt the code to suit your own needs under the terms of the MIT License. ## ๐ŸŒŒ Why "Cygnus Player"? @@ -56,9 +56,9 @@ Modern commercial streaming apps and feature-bloated players treat music like a Cygnus Player is fully compatible with modern Android **Scoped Storage** requirements. To ensure high-performance relative path resolution for massive libraries, please follow these steps: -1. **Select Music Root**: On first launch, use the prompt to select your main music directory (e.g., `Internal Storage > Music`). This grants Cygnus persistent, recursive access to your entire library. -2. **Relative Path Resolution**: The app uses the **MediaStore API** to map M3U relative paths (e.g., `Rush/2112/01 - 2112.mp3`) to system-registered content URIs. This avoids restricted direct filesystem access and ensures absolute sequence integrity. -3. **Permissions**: Ensure the `READ_MEDIA_AUDIO` permission is granted to allow the system to index your music files for the MediaStore. +1. **Select Music Root**: On first launch, use the prompt to select your main music directory (e.g., `Internal Storage > Music`). This grants Cygnus persistent, recursive access to your entire library. +2. **Relative Path Resolution**: The app uses the **MediaStore API** to map M3U relative paths (e.g., `Rush/2112/01 - 2112.mp3`) to system-registered content URIs. This avoids restricted direct filesystem access and ensures absolute sequence integrity. +3. **Permissions**: Ensure the `READ_MEDIA_AUDIO` permission is granted to allow the system to index your music files for the MediaStore. ## ๐Ÿค– AI-Assisted Development @@ -201,22 +201,22 @@ Cygnus Player utilizes GitHub Actions for continuous integration and delivery: The app's version is maintained in two locations for build stability: -1. **`version.properties`**: The primary source of truth used by Gradle and CI/CD. -2. **`VersionInfo.kt`**: A static object in the source code used by the UI to avoid `BuildConfig` race conditions in experimental environments. +1. **`version.properties`**: The primary source of truth used by Gradle and CI/CD. +2. **`VersionInfo.kt`**: A static object in the source code used by the UI to avoid `BuildConfig` race conditions in experimental environments. Before merging to `main` to trigger a release, ensure both files are updated: ```properties # version.properties -VERSION_NAME=1.0.2 -VERSION_CODE=3 +VERSION_NAME=1.0.3 +VERSION_CODE=4 ``` ```kotlin // VersionInfo.kt object VersionInfo { - const val VERSION_NAME = "1.0.2" - const val VERSION_CODE = 3 + const val VERSION_NAME = "1.0.3" + const val VERSION_CODE = 4 } ``` diff --git a/TESTING_STRATEGY.md b/TESTING_STRATEGY.md index 95dd2c7..3da9bfb 100644 --- a/TESTING_STRATEGY.md +++ b/TESTING_STRATEGY.md @@ -12,37 +12,37 @@ To achieve **Zero-Manual-Discovery** of bugs, Cygnus Player employs a rigorous m - **Target:** M3U Parsers, Shuffle Mapping Logic, ReplayGain Multipliers, and Queue Sequence IDs for **MP3 files**. - **Frameworks:** JUnit 4 (Unit), JUnit 5 (Unit), MockK, Kotlin Coroutines Test. -- **Approach:** - - **Logic-First Unit Testing:** Core components like `M3uParser` and `ShuffleEngine` (mapping logic) are tested via JVM unit tests for sub-second validation. - - **Forward-Only Verification:** Explicitly verify that re-shuffles or folder transitions discard previously played tracks and never "wrap around" to the start of an album. - - Use real-world `.m3u8` resource files (located in `app/src/test/resources/playlists`) to verify path resolution for both Windows and Unix style sources. - - Stress-test the `ShuffleEngine` with 50,000+ entries (using `massive_library.m3u8`) to ensure $O(1)$ memory performance and sequence integrity. +- **Approach:** + - **Logic-First Unit Testing:** Core components like `M3uParser` and `ShuffleEngine` (mapping logic) are tested via JVM unit tests for sub-second validation. + - **Forward-Only Verification:** Explicitly verify that re-shuffles or folder transitions discard previously played tracks and never "wrap around" to the start of an album. + - Use real-world `.m3u8` resource files (located in `app/src/test/resources/playlists`) to verify path resolution for both Windows and Unix style sources. + - Stress-test the `ShuffleEngine` with 50,000+ entries (using `massive_library.m3u8`) to ensure $O(1)$ memory performance and sequence integrity. ## 2. Database & Persistence - **Target:** Room DAOs, Migration Logic, and SQLite Indexing. - **Frameworks:** AndroidX Test, Room In-Memory Databases. -- **Approach:** - - Every DAO method must have a test case verifying CRUD operations. - - Specifically verify that duplicate `file_path` entries result in unique `sequence_id` records as per the architecture. - - **Logic Consistency:** Verify that `TrackEntity` correctly defaults missing metadata to `""` and that `PlaylistStateEntity` correctly serializes the `ShuffleMode` enum. +- **Approach:** + - Every DAO method must have a test case verifying CRUD operations. + - Specifically verify that duplicate `file_path` entries result in unique `sequence_id` records as per the architecture. + - **Logic Consistency:** Verify that `TrackEntity` correctly defaults missing metadata to `""` and that `PlaylistStateEntity` correctly serializes the `ShuffleMode` enum. ## 3. UI & Jetpack Compose - **Target:** All Composables, UI State transitions, and Accessibility. - **Frameworks:** Compose Test Rule (Instrumented), Screenshot Testing (optional/future), Glance Testing for widgets. -- **Approach:** - - Use `@Preview` for every component to ensure Monokai Pro color compliance. - - `ComposeContentTestRule` is used in **instrumented tests** (`app/src/androidTest`) to verify that UI nodes respond correctly to state changes (e.g., history list rendering and Snackbar visibility). - - Test for high-contrast accessibility labels. +- **Approach:** + - Use `@Preview` for every component to ensure Monokai Pro color compliance. + - `ComposeContentTestRule` is used in **instrumented tests** (`app/src/androidTest`) to verify that UI nodes respond correctly to state changes (e.g., history list rendering and Snackbar visibility). + - Test for high-contrast accessibility labels. ## 4. Media Playback (Media3) - **Target:** `MediaSessionService`, ExoPlayer state transitions, Audio Focus handling. - **Frameworks:** Robolectric, Media3 Test Utilities. -- **Approach:** - - Simulate `BECOMING_NOISY` and phone call interruptions to verify automatic pausing. - - Verify that `MediaMetadata` is updated accurately on every track transition for Scrobbler compatibility. +- **Approach:** + - Simulate `BECOMING_NOISY` and phone call interruptions to verify automatic pausing. + - Verify that `MediaMetadata` is updated accurately on every track transition for Scrobbler compatibility. ## 5. Continuous Validation diff --git a/app/src/main/java/com/festerhead/cygnusplayer/VersionInfo.kt b/app/src/main/java/com/festerhead/cygnusplayer/VersionInfo.kt index 9beac72..1647b89 100644 --- a/app/src/main/java/com/festerhead/cygnusplayer/VersionInfo.kt +++ b/app/src/main/java/com/festerhead/cygnusplayer/VersionInfo.kt @@ -2,11 +2,11 @@ package com.festerhead.cygnusplayer /** * Static version information for the Cygnus Player application. - * - * This object is used instead of the auto-generated BuildConfig class to ensure + * + * This object is used instead of the auto-generated BuildConfig class to ensure * build stability across environments where the Android toolchain may be inconsistent. */ object VersionInfo { - const val VERSION_NAME = "1.0.2" - const val VERSION_CODE = 3 + const val VERSION_NAME = "1.0.3" + const val VERSION_CODE = 4 } diff --git a/version.properties b/version.properties index 6895434..44116a2 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -VERSION_NAME=1.0.2 -VERSION_CODE=3 +VERSION_NAME=1.0.3 +VERSION_CODE=4