Thanks for your interest in improving PixelPlayerOSS! This guide covers the conventions that aren't obvious from the code alone. For a deeper architectural overview, see CLAUDE.md (written for AI assistants but accurate for humans too).
git clone https://github.com/lostf1sh/PixelPlayerOSS.git
cd PixelPlayerOSS
# Universal debug APK for local installation
JAVA_HOME=/usr/lib/jvm/java-21-openjdk ./gradlew :app:assembleDebug -Ppixelplayer.enableAbiSplits=falseRequirements: JDK 21, Android compile/target SDK 37, min SDK 30 (Android 11+).
Kotlin code style is official.
Please run these and include the results in your PR description:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk ./gradlew :app:compileDebugKotlin
JAVA_HOME=/usr/lib/jvm/java-21-openjdk ./gradlew :app:lintDebug
JAVA_HOME=/usr/lib/jvm/java-21-openjdk ./gradlew :app:testDebugUnitTestRun a single test class/method:
JAVA_HOME=/usr/lib/jvm/java-21-openjdk ./gradlew :app:testDebugUnitTest \
--tests "com.lostf1sh.pixelplayeross.SomeTestClass.someMethod"Unit tests use JUnit Jupiter (
useJUnitPlatform()), not JUnit 4 — useorg.junit.jupiter.api.Test, notorg.junit.Test.
These are easy to get wrong and will come up in review:
- Slice
PlayerUiState, never collect the whole thing. It updates on every position tick, so collecting all ~30 fields in a screen causes recomposition storms. Map to a small slice +distinctUntilChanged(). The reference pattern isPlayerUiSheetSliceV2inUnifiedPlayerSheetV2. See app/performance_analysis.md before touching list rendering, animations, orPlayerUiState. - Prefer
ImmutableList<T>overList<T>in@Composableparams for strong-skipping (kotlinx.collections.immutable). But.toImmutableList()isO(n)— don't call it on every recomposition. New widely-used domain models go inapp/compose_stability.conf. - Navigate with
navController.navigateSafely(...)(fromNavControllerExtensions.kt), nevernavController.navigate(...)directly — it retries on theIllegalStateExceptionthrown by rapid taps. - Log with Timber, not
android.util.Log. Debug builds plant aDebugTree; release usesReleaseTree(WARN/ERROR/WTF only). RawLog.xbypasses the release filter. - Room schema changes require a migration (schemas are exported to
app/schemas). Destructive migration is only allowed in debug builds. @Singletonis already the dominant scope — be cautious about adding more (flagged as a low-end memory risk in the perf audit). Prefer@ViewModelScopedfor single-consumer state.- Keep user-facing strings in
res/values/strings*.xml(stringResource(...)), not hardcoded in Composables.
- Keep changes focused; one logical change per PR.
- Describe what changed and why, and include build/test results.
- Do not add a
Co-Authored-By:trailer to commits or PR bodies.
Open an issue using the bug-report template. Please include your app version, device/Android version, and which music source (local / Navidrome / Jellyfin) is involved.