Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
db454bc
Add Japanese translations
daedaevibin Jun 24, 2026
7e927c0
Add MultiLangRomanizer functions for Thai, Arabic, Greek, Hebrew, and…
daedaevibin Jun 24, 2026
f53d857
Add romanization and language detection for Cyrillic and Vietnamese
daedaevibin Jun 24, 2026
d74f593
Fix 21 pre-existing lint errors
daedaevibin Jun 24, 2026
2580e86
Add new app languages and update locale configuration
daedaevibin Jun 24, 2026
5a90005
Add LYRICS settings category and restructure settings screens
daedaevibin Jun 24, 2026
a88c9a1
Update CI workflows and build config
daedaevibin Jun 24, 2026
4940904
Misc fixes: AI max tokens and lyrics loading priority
daedaevibin Jun 24, 2026
0f4045b
Add ServerUrlUtils, Vietnamese/Traditional Chinese translations, and …
daedaevibin Jun 24, 2026
54dca07
Merge remote-tracking branch 'upstream/master'
daedaevibin Jun 24, 2026
b98c567
chore(deps): bump the github-actions group with 2 updates
dependabot[bot] Jun 24, 2026
dce462d
chore(deps): bump the gradle-dependencies group with 2 updates
dependabot[bot] Jun 24, 2026
2bfbd3b
Merge pull request #45 from Veridian-Zenith/dependabot/github_actions…
daedaevibin Jun 25, 2026
b6b2096
Merge pull request #46 from Veridian-Zenith/dependabot/gradle/gradle-…
daedaevibin Jun 25, 2026
8607ca0
File cleanup
daedaevibin Jun 25, 2026
ea72d88
chore(deps): bump the gradle-dependencies group across 1 directory wi…
dependabot[bot] Jun 26, 2026
66be53c
perf: defer legacy migrations and non-critical loads out of init hot …
VoidX3D Jun 28, 2026
b4689a6
perf: serialize library data loading to avoid I/O contention on slow …
VoidX3D Jun 28, 2026
37ef4d2
perf: derive isLibraryContentEmpty from in-memory state instead of Ro…
VoidX3D Jun 28, 2026
7b9407d
perf: eliminate loading state flapping during sequential library load
VoidX3D Jun 28, 2026
898f756
perf: add distinctUntilChanged to playerUiState combine collectors
VoidX3D Jun 28, 2026
80fb3bf
feat(ai): add LYRICS prompt type for proper logging of translation re…
VoidX3D Jun 28, 2026
d799cd9
feat(settings): split AI settings into AI Provider tab and Generation…
VoidX3D Jun 28, 2026
08e941e
feat(ai): add SearchableProviderSelector with descriptions and live s…
VoidX3D Jun 28, 2026
b10358a
i18n: add generation parameters strings to all 11 locale files
VoidX3D Jun 28, 2026
5b0444c
cleanup: remove unused hasGeminiApiKey, songCountFlow, and repository…
VoidX3D Jun 28, 2026
273f6d9
fix: add missing LYRICS and GENERATION_PARAMETERS branches in when ex…
VoidX3D Jun 28, 2026
61702e2
fix: add missing @OptIn for ExperimentalMaterial3Api on SearchablePro…
VoidX3D Jun 28, 2026
51209dc
fix: use file-level @OptIn for ExperimentalMaterial3Api in SettingsCo…
VoidX3D Jun 28, 2026
8eac816
Merge pull request #48 from Veridian-Zenith/dependabot/gradle/gradle-…
daedaevibin Jun 28, 2026
c97ddb7
Merge branch 'refs/heads/pr-2497' into review-pr-2497
daedaevibin Jun 28, 2026
86422df
fix(wear): add error_prone_annotations dependency for Dagger 2.60 gen…
daedaevibin Jun 28, 2026
031fe1a
Merge branch 'master' into review-pr-2497
daedaevibin Jun 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,32 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Set up JDK 21
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
with:
cache-provider: 'enhanced'

- name: Pin Kotlin version for CodeQL compatibility
run: |
# More robust sed pattern that handles various formats
sed -i 's/kotlin = "[^"]*"/kotlin = "2.3.20"/g' gradle/libs.versions.toml

# Verify the change was made
grep 'kotlin = "2.3.20"' gradle/libs.versions.toml


- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
with:
cache-provider: 'enhanced'

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: java-kotlin
build-mode: manual

- name: Build with Gradle
run: ${{ github.workspace }}/gradlew :app:assembleDebug :wear:assembleDebug --no-daemon
run: ${{ github.workspace }}/gradlew :app:assembleDebug :wear:assembleDebug --no-daemon --no-build-cache

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/nightly-apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Set up JDK 21
uses: actions/setup-java@v5
Expand All @@ -98,7 +98,7 @@ jobs:

- name: Cache nightly keystore
id: cache-keystore
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: vz-pixelplay.jks
key: ${{ runner.os }}-nightly-keystore-vz-pixelplay
Expand All @@ -117,7 +117,7 @@ jobs:
echo "keyPassword=994273" >> keystore.properties

- name: Build Phone nightly release APKs
run: gradle :app:assembleRelease -Ppixelplay.enableAbiSplits=true
run: gradle :app:assembleRelease -Ppixelplay.enableAbiSplits=true --no-build-cache

- name: Verify Phone nightly split APKs
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/phone-debug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Set up JDK 21
uses: actions/setup-java@v5
Expand All @@ -30,7 +30,7 @@ jobs:
uses: gradle/actions/setup-gradle@v6

- name: Build Phone debug APK
run: gradle :app:assembleDebug -Ppixelplay.enableAbiSplits=true
run: gradle :app:assembleDebug -Ppixelplay.enableAbiSplits=true --no-build-cache

- name: Verify Phone split APKs
run: |
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/phone-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Set up JDK 21
uses: actions/setup-java@v5
Expand All @@ -31,7 +31,7 @@ jobs:

- name: Cache keystore
id: cache-keystore
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: vz-pixelplay.jks
key: ${{ runner.os }}-keystore-vz-pixelplay
Expand All @@ -52,7 +52,7 @@ jobs:
echo "keyPassword=994273" >> keystore.properties

- name: Build Phone release APK
run: gradle :app:assembleRelease -Ppixelplay.enableAbiSplits=true
run: gradle :app:assembleRelease -Ppixelplay.enableAbiSplits=true --no-build-cache

- name: Verify Phone split APKs
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/wearos-apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Set up JDK 21
uses: actions/setup-java@v5
Expand All @@ -30,7 +30,7 @@ jobs:
uses: gradle/actions/setup-gradle@v6

- name: Build Wear OS debug APK
run: gradle :wear:assembleDebug
run: gradle :wear:assembleDebug --no-build-cache

- name: Upload Wear OS APK artifact
uses: actions/upload-artifact@v7.0.1
Expand Down
1 change: 0 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@ dependencies {

// UI Utilities & Extra
implementation(libs.timber)
implementation(libs.generativeai)
implementation(libs.smooth.corner.rect.android.compose)
implementation(libs.reorderables)
implementation(libs.codeview)
Expand Down
20 changes: 20 additions & 0 deletions app/lint.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- Translation completeness cannot be enforced without native speakers -->
<issue id="MissingTranslation" severity="warning" />
<issue id="ExtraTranslation" severity="warning" />
<issue id="MissingQuantity" severity="warning" />
<issue id="ImpliedQuantity" severity="warning" />

<!-- Intentional use of Material Design 3 internal color APIs for dynamic theme -->
<issue id="RestrictedApi" severity="warning" />

<!-- Media3 APIs marked @UnstableApi are used intentionally throughout -->
<issue id="UnsafeOptInUsageError" severity="warning" />

<!-- Compose resource access via LocalContext.current is accepted pattern -->
<issue id="LocalContextGetResourceValueCall" severity="warning" />

<!-- Missing default resource -- resolved by adding declarations to base values/ -->
<issue id="MissingDefaultResource" severity="error" />
</lint>
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@
<action android:name="com.theveloper.pixelplay.action.OPEN_PLAYER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class ExternalPlayerActivity : ComponentActivity() {
return intent.data
}

@Suppress("WrongConstant")
private fun persistUriPermissionIfNeeded(intent: Intent, uri: Uri) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
val hasPersistablePermission = intent.flags and Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION != 0
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/theveloper/pixelplay/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.content.Context
import android.content.ComponentName
import android.content.Intent
import android.os.Build
import androidx.annotation.RequiresApi
import android.graphics.RenderEffect as AndroidRenderEffect
import android.graphics.Shader as AndroidShader
import androidx.compose.ui.graphics.asComposeRenderEffect
Expand Down Expand Up @@ -943,6 +944,7 @@ class MainActivity : ComponentActivity() {
val expansionFractionProvider = remember(playerViewModel.playerContentExpansionFraction) {
{ playerViewModel.playerContentExpansionFraction.value }
}
@Suppress("NewApi")
val blurEffectCache = remember { BlurEffectCache() }

Box(
Expand Down Expand Up @@ -1152,10 +1154,12 @@ class MainActivity : ComponentActivity() {
* blur every animation frame. The radius is quantized at the call site, so this
* only rebuilds ~25 times across the whole expand animation instead of 60+/sec.
*/
@RequiresApi(Build.VERSION_CODES.S)
private class BlurEffectCache {
private var lastRadiusPx: Float = Float.NaN
private var cached: androidx.compose.ui.graphics.RenderEffect? = null

@RequiresApi(Build.VERSION_CODES.S)
fun get(radiusPx: Float): androidx.compose.ui.graphics.RenderEffect? {
if (radiusPx <= 0f) {
lastRadiusPx = 0f
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ class AiHandler @Inject constructor(
AiSystemPromptType.TAGGING -> 0.4f
AiSystemPromptType.PLAYLIST, AiSystemPromptType.DAILY_MIX -> 0.6f
AiSystemPromptType.PERSONA -> 0.85f
AiSystemPromptType.LYRICS -> 0.7f
AiSystemPromptType.GENERAL -> 0.7f
}
} else temperature
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ enum class AiSystemPromptType {
MOOD_ANALYSIS,
PERSONA,
DAILY_MIX,
LYRICS,
GENERAL
}

Expand Down Expand Up @@ -182,6 +183,16 @@ class AiSystemPromptEngine @Inject constructor() {
$dailyMixPersonaPrompt
""".trimIndent()

AiSystemPromptType.LYRICS -> """
<role>Song lyrics translator — you translate lyrics between languages while preserving structure.</role>
<strategy>
- Preserve ALL timestamps and line structure exactly.
- Output each original line followed by its translation on the next line.
- Never add explanations, labels, or formatting beyond the requested format.
- If the source is already in the target language, respond with: ALREADY_IN_TARGET_LANGUAGE
</strategy>
""".trimIndent()

AiSystemPromptType.GENERAL -> """
<role>PixelPlayer Assistant — a knowledgeable music companion.</role>
<strategy>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@ package com.theveloper.pixelplay.data.ai.provider
/**
* Enum representing available AI providers
*/
enum class AiProvider(val displayName: String, val requiresApiKey: Boolean, val hasConfigurableUrl: Boolean = false) {
GEMINI("Google Gemini", requiresApiKey = true),
DEEPSEEK("DeepSeek", requiresApiKey = true),
GROQ("Groq", requiresApiKey = true),
MISTRAL("Mistral", requiresApiKey = true),
NVIDIA("NVIDIA NIM", requiresApiKey = true),
KIMI("Kimi (Moonshot)", requiresApiKey = true),
GLM("Zhipu GLM", requiresApiKey = true),
OPENAI("OpenAI", requiresApiKey = true),
OPENROUTER("OpenRouter", requiresApiKey = true),
OLLAMA("Ollama", requiresApiKey = true),
CUSTOM("Custom Provider", requiresApiKey = true, hasConfigurableUrl = true);
enum class AiProvider(
val displayName: String,
val requiresApiKey: Boolean,
val hasConfigurableUrl: Boolean = false,
val description: String = ""
) {
GEMINI("Google Gemini", requiresApiKey = true, description = "Google's flagship AI models (Gemini 1.5/2.0)"),
DEEPSEEK("DeepSeek", requiresApiKey = true, description = "Open-source reasoning models via DeepSeek API"),
GROQ("Groq", requiresApiKey = true, description = "Ultra-fast inference with Groq LPU hardware"),
MISTRAL("Mistral", requiresApiKey = true, description = "Mistral AI's efficient and powerful models"),
NVIDIA("NVIDIA NIM", requiresApiKey = true, description = "NVIDIA's optimized inference microservices"),
KIMI("Kimi (Moonshot)", requiresApiKey = true, description = "Moonshot AI's long-context Kimi models"),
GLM("Zhipu GLM", requiresApiKey = true, description = "Zhipu AI's bilingual GLM series"),
OPENAI("OpenAI", requiresApiKey = true, description = "GPT-4o, GPT-4, and other OpenAI models"),
OPENROUTER("OpenRouter", requiresApiKey = true, description = "Unified access to 200+ models via OpenRouter"),
OLLAMA("Ollama", requiresApiKey = true, description = "Local models via Ollama (requires running server)"),
CUSTOM("Custom Provider", requiresApiKey = true, hasConfigurableUrl = true, description = "Any OpenAI-compatible API endpoint");

companion object {
fun fromString(value: String): AiProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class AiPreferencesRepository @Inject constructor(
dataStore.data.map { preferences -> preferences[Keys.AI_TOP_K] ?: 64 }

val aiMaxTokens: Flow<Int> =
dataStore.data.map { preferences -> preferences[Keys.AI_MAX_TOKENS] ?: 4096 }
dataStore.data.map { preferences -> preferences[Keys.AI_MAX_TOKENS] ?: 8192 }

val aiPresencePenalty: Flow<Float> =
dataStore.data.map { preferences -> preferences[Keys.AI_PRESENCE_PENALTY] ?: 0.0f }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ enum class AppLanguage(val tag: String, @StringRes val labelRes: Int) {
FRENCH("fr", R.string.settings_language_french),
INDONESIAN("in", R.string.settings_language_indonesian),
ITALIAN("it", R.string.settings_language_italian),
JAPANESE("ja", R.string.settings_language_japanese),
KOREAN("ko", R.string.settings_language_korean),
NORWEGIAN_BOKMAL("nb", R.string.settings_language_norwegian_bokmal),
RUSSIAN("ru", R.string.settings_language_russian),
SIMPLIFIED_CHINESE("zh-CN", R.string.settings_language_chinese),
TURKISH("tr", R.string.settings_language_turkish);
TRADITIONAL_CHINESE("zh-TW", R.string.settings_language_traditional_chinese),
TURKISH("tr", R.string.settings_language_turkish),
VIETNAMESE("vi", R.string.settings_language_vietnamese);

companion object {
val supportedLanguageTags: Set<String> = values().map { it.tag }.toSet()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,14 @@ class LyricsRepositoryImpl @Inject constructor(

val lyricsData = LyricsData(
plainLyrics = lyrics.plain?.joinToString("\n"),
syncedLyrics = lyrics.synced?.joinToString("\n") { "[${formatTimestamp(it.time)}]${it.line}" },
syncedLyrics = lyrics.synced?.joinToString("\n") { line ->
buildString {
append("[${formatTimestamp(line.time)}]${line.line}")
if (!line.translation.isNullOrBlank()) {
append("\n[${formatTimestamp(line.time)}]${line.translation}")
}
}
},
wordByWordLyrics = wordByWordLyrics
)

Expand Down Expand Up @@ -1247,7 +1254,12 @@ class LyricsRepositoryImpl @Inject constructor(
toWordByWordLrc(syncedLyrics)
} else {
syncedLyrics.joinToString("\n") { line ->
"[${formatTimestamp(line.time)}]${line.line}"
buildString {
append("[${formatTimestamp(line.time)}]${line.line}")
if (!line.translation.isNullOrBlank()) {
append("\n[${formatTimestamp(line.time)}]${line.translation}")
}
}
}
}
}
Expand Down Expand Up @@ -1710,6 +1722,8 @@ class LyricsRepositoryImpl @Inject constructor(
MultiLangRomanizer.isJapanese(text) -> MultiLangRomanizer.romanizeJapanese(text) ?: text
MultiLangRomanizer.isChinese(text) -> MultiLangRomanizer.romanizeChinese(text) ?: text
MultiLangRomanizer.isKorean(text) -> MultiLangRomanizer.romanizeKorean(text)
MultiLangRomanizer.isCyrillic(text) -> MultiLangRomanizer.romanizeCyrillic(text) ?: text
MultiLangRomanizer.isVietnamese(text) -> MultiLangRomanizer.romanizeVietnamese(text)
else -> text
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,6 @@ interface MusicRepository {
storageFilter: com.theveloper.pixelplay.data.model.StorageFilter = com.theveloper.pixelplay.data.model.StorageFilter.ALL
): Flow<Int>

/**
* Returns the count of songs in the library.
* @return Flow emitting the current song count.
*/
fun getSongCountFlow(): Flow<Int>

/**
* Returns the count of cloud songs in the library.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,10 +310,6 @@ class MusicRepositoryImpl @Inject constructor(
return songRepository.getFavoriteSongCountFlow(storageFilter)
}

override fun getSongCountFlow(): Flow<Int> {
return musicDao.getSongCount().distinctUntilChanged()
}

override fun getCloudSongCountFlow(): Flow<Int> {
return musicDao.getCloudSongCount().distinctUntilChanged()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,6 @@ fun FullPlayerContent(
if (showFetchLyricsDialog) {
showFetchLyricsDialog = false
showLyricsSheet = true
playerViewModel.resetLyricsSearchState()
}
}
is LyricsSearchUiState.Error -> {
Expand Down
Loading
Loading