diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index da9ec9033..96d8e64b8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,4 +1,4 @@ -name: "CodeQL Advanced" +name: "CodeQL" on: push: @@ -11,7 +11,7 @@ on: jobs: analyze: - name: Analyze + name: CodeQL Analysis if: github.actor != 'dependabot[bot]' runs-on: ubuntu-latest permissions: @@ -21,16 +21,19 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 # v4 is current stable; v5 is emerging + uses: actions/checkout@v6 - name: Set up JDK 21 uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '21' + cache: 'gradle' - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 + with: + cache-read-only: false - name: Initialize CodeQL uses: github/codeql-action/init@v4 @@ -39,10 +42,10 @@ jobs: build-mode: manual - name: Build with Gradle - run: ./gradlew assembleDebug # Use the wrapper for consistency + run: ./gradlew clean assembleDebug -x :app:validateSigningBenchmark -x :app:validateSigningRelease - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 with: - # Match the languages defined in the init step category: "/language:java-kotlin" + upload: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..1778e8632 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,13 @@ +name: "Lint" +on: [push, pull_request] +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '21' + cache: 'gradle' + - run: ./gradlew lint detekt diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..ec363a815 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,13 @@ +name: "Tests" +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '21' + cache: 'gradle' + - run: ./gradlew test --parallel diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 34dd762b1..076a27dac 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -130,7 +130,8 @@ android { } lint { - checkReleaseBuilds = false + checkReleaseBuilds = true + abortOnError = false // Keep it from failing the build entirely on CI if you have minor warnings } splits { @@ -196,52 +197,29 @@ dependencies { // AndroidX & Compose implementation(libs.androidx.core.ktx) - implementation(libs.androidx.lifecycle.runtime.ktx) - implementation(libs.androidx.lifecycle.runtime.compose) - implementation(libs.lifecycleprocess) + implementation(libs.bundles.lifecycle) implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) - implementation(libs.androidx.ui) - implementation(libs.androidx.ui.graphics) - implementation(libs.androidx.ui.tooling.preview) - implementation(libs.androidx.compose.material3) - implementation(libs.androidx.material.icons.core) - implementation(libs.androidx.material.icons.extended) - implementation(libs.androidx.constraintlayout.compose) - implementation(libs.androidx.foundation) - implementation(libs.androidx.animation) + implementation(libs.bundles.compose) implementation(libs.androidx.palette.ktx) implementation(libs.androidx.core.splashscreen) - implementation(libs.androidx.ui.text.google.fonts) implementation(libs.material) implementation(libs.androidx.appcompat) // DI & Navigation - implementation(libs.hilt.android) + implementation(libs.bundles.hilt) ksp(libs.hilt.android.compiler) - implementation(libs.androidx.hilt.navigation.compose) - implementation(libs.androidx.hilt.lifecycle.viewmodel.compose) - implementation(libs.androidx.hilt.work) ksp(libs.androidx.hilt.compiler) implementation(libs.androidx.navigation.compose) - implementation(libs.androidx.navigation.runtime.ktx) // Storage & Paging - implementation(libs.androidx.room.runtime) + implementation(libs.bundles.room) ksp(libs.androidx.room.compiler) - implementation(libs.androidx.room.ktx) - implementation(libs.androidx.room.paging) - implementation(libs.androidx.paging.runtime) - implementation(libs.androidx.paging.compose) - implementation(libs.androidx.paging.common) + implementation(libs.bundles.paging) // Media & Files - implementation(libs.androidx.media3.exoplayer) - implementation(libs.androidx.media3.ui) - implementation(libs.androidx.media3.session) + implementation(libs.bundles.media3) implementation(libs.androidx.media3.exoplayer.ffmpeg) - implementation(libs.androidx.media3.exoplayer.midi) - implementation(libs.androidx.media3.transformer) implementation(libs.androidx.mediarouter) implementation(libs.androidx.media) implementation(libs.coil.compose) @@ -252,10 +230,7 @@ dependencies { implementation(libs.androidx.graphics.shapes) // Networking & Serialization - implementation(libs.retrofit) - implementation(libs.converter.gson) - implementation(libs.okhttp) - implementation(libs.logging.interceptor) + implementation(libs.bundles.networking) implementation(libs.gson) implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.collections.immutable) diff --git a/app/src/main/baseline-prof.txt b/app/src/main/baseline-prof.txt index ff6b9d94e..8e7db006e 100644 --- a/app/src/main/baseline-prof.txt +++ b/app/src/main/baseline-prof.txt @@ -5997,7 +5997,7 @@ Landroidx/compose/material3/MinimumInteractiveModifierNode$$ExternalSyntheticLam SPLandroidx/compose/material3/MinimumInteractiveModifierNode$$ExternalSyntheticLambda0;->(ILandroidx/compose/ui/layout/Placeable;I)V PLandroidx/compose/material3/MinimumInteractiveModifierNode$$ExternalSyntheticLambda0;->invoke(Ljava/lang/Object;)Ljava/lang/Object; Landroidx/compose/material3/ModalBottomSheetKt; -HSPLandroidx/compose/material3/ModalBottomSheetKt;->rememberModalBottomSheetState(ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Landroidx/compose/material3/SheetState; +HSPLandroidx/compose/material3/ModalBottomSheetKt;->rememberBottomSheetState(ZLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Landroidx/compose/material3/SheetState; Landroidx/compose/material3/ModalBottomSheetKt$$ExternalSyntheticLambda3; SPLandroidx/compose/material3/ModalBottomSheetKt$$ExternalSyntheticLambda3;->()V Landroidx/compose/material3/MotionScheme; diff --git a/app/src/main/java/com/theveloper/pixelplay/data/database/PixelPlayDatabase.kt b/app/src/main/java/com/theveloper/pixelplay/data/database/PixelPlayDatabase.kt index 2f7cca438..c6cd4392b 100644 --- a/app/src/main/java/com/theveloper/pixelplay/data/database/PixelPlayDatabase.kt +++ b/app/src/main/java/com/theveloper/pixelplay/data/database/PixelPlayDatabase.kt @@ -291,19 +291,19 @@ abstract class PixelPlayDatabase : RoomDatabase() { } val MIGRATION_18_19 = object : Migration(18, 19) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( "CREATE TABLE IF NOT EXISTS `lyrics` (`songId` INTEGER NOT NULL, `content` TEXT NOT NULL, `isSynced` INTEGER NOT NULL DEFAULT 0, `source` TEXT, PRIMARY KEY(`songId`))" ) - database.execSQL( + db.execSQL( "INSERT INTO lyrics (songId, content) SELECT id, lyrics FROM songs WHERE lyrics IS NOT NULL AND lyrics != ''" ) } } val MIGRATION_14_15 = object : Migration(14, 15) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL( + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( "ALTER TABLE album_art_themes ADD COLUMN paletteStyle TEXT NOT NULL DEFAULT 'tonal_spot'" ) @@ -332,14 +332,14 @@ abstract class PixelPlayDatabase : RoomDatabase() { val prefixes = listOf("light_", "dark_") prefixes.forEach { prefix -> newRoleColumns.forEach { role -> - database.execSQL( + db.execSQL( "ALTER TABLE album_art_themes ADD COLUMN ${prefix}${role} TEXT NOT NULL DEFAULT '#00000000'" ) } } // The table is a cache; wipe stale rows so we always regenerate with full token data. - database.execSQL("DELETE FROM album_art_themes") + db.execSQL("DELETE FROM album_art_themes") } } diff --git a/app/src/main/java/com/theveloper/pixelplay/data/remote/qqmusic/QQSignGenerator.kt b/app/src/main/java/com/theveloper/pixelplay/data/remote/qqmusic/QQSignGenerator.kt index 3a1995d00..a8abfb35a 100644 --- a/app/src/main/java/com/theveloper/pixelplay/data/remote/qqmusic/QQSignGenerator.kt +++ b/app/src/main/java/com/theveloper/pixelplay/data/remote/qqmusic/QQSignGenerator.kt @@ -1,7 +1,6 @@ package com.theveloper.pixelplay.data.remote.qqmusic import android.content.Context -import android.os.Build import android.os.Handler import android.os.Looper import android.util.Base64 @@ -76,14 +75,12 @@ class QQSignGenerator(private val context: Context) { domStorageEnabled = true allowFileAccess = false allowContentAccess = false - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - allowFileAccessFromFileURLs = false - allowUniversalAccessFromFileURLs = false - } + @Suppress("DEPRECATION") + allowFileAccessFromFileURLs = false + @Suppress("DEPRECATION") + allowUniversalAccessFromFileURLs = false mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - safeBrowsingEnabled = true - } + safeBrowsingEnabled = true } webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { diff --git a/app/src/main/java/com/theveloper/pixelplay/data/repository/MediaStoreSongRepository.kt b/app/src/main/java/com/theveloper/pixelplay/data/repository/MediaStoreSongRepository.kt index 735a18c45..0367a785b 100644 --- a/app/src/main/java/com/theveloper/pixelplay/data/repository/MediaStoreSongRepository.kt +++ b/app/src/main/java/com/theveloper/pixelplay/data/repository/MediaStoreSongRepository.kt @@ -382,7 +382,7 @@ class MediaStoreSongRepository @Inject constructor( val minDuration = values[3] as Int Triple(allowedDirs, blockedDirs, minDuration) }.flatMapLatest { (allowedDirs, blockedDirs, minDuration) -> - val minDurationMs = minDuration as Int + val minDurationMs = minDuration val musicIds = getFilteredSongIds(allowedDirs.toList(), blockedDirs.toList(), minDurationMs) val genreMap = getSongIdToGenreMap(context.contentResolver) diff --git a/app/src/main/java/com/theveloper/pixelplay/data/repository/MusicRepositoryImpl.kt b/app/src/main/java/com/theveloper/pixelplay/data/repository/MusicRepositoryImpl.kt index 8ebd428cc..c90ca7220 100644 --- a/app/src/main/java/com/theveloper/pixelplay/data/repository/MusicRepositoryImpl.kt +++ b/app/src/main/java/com/theveloper/pixelplay/data/repository/MusicRepositoryImpl.kt @@ -1,42 +1,32 @@ package com.theveloper.pixelplay.data.repository -// import kotlinx.coroutines.withContext // May not be needed for Flow transformations - -// import kotlinx.coroutines.sync.withLock // May not be needed if directoryScanMutex logic changes - import android.content.Context import android.net.Uri import android.os.Environment import android.provider.MediaStore import android.util.Log - -import com.theveloper.pixelplay.data.model.Song -import com.theveloper.pixelplay.data.repository.ArtistImageRepository -import dagger.Lazy -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map -import javax.inject.Inject -import javax.inject.Singleton import androidx.core.net.toUri +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.filter +import androidx.paging.map import com.theveloper.pixelplay.data.database.FavoritesDao import com.theveloper.pixelplay.data.database.MusicDao import com.theveloper.pixelplay.data.database.SearchHistoryDao import com.theveloper.pixelplay.data.database.SearchHistoryEntity import com.theveloper.pixelplay.data.database.TelegramChannelEntity import com.theveloper.pixelplay.data.database.TelegramDao +import com.theveloper.pixelplay.data.database.TelegramTopicEntity import com.theveloper.pixelplay.data.database.toAlbum import com.theveloper.pixelplay.data.database.toArtist import com.theveloper.pixelplay.data.database.toSearchHistoryItem import com.theveloper.pixelplay.data.database.toSong import com.theveloper.pixelplay.data.database.toTelegramEntity import com.theveloper.pixelplay.data.database.toTelegramEntityWithThread -import com.theveloper.pixelplay.data.database.TelegramTopicEntity import com.theveloper.pixelplay.data.model.Album import com.theveloper.pixelplay.data.model.Artist +import com.theveloper.pixelplay.data.model.FolderSource import com.theveloper.pixelplay.data.model.Genre import com.theveloper.pixelplay.data.model.Lyrics import com.theveloper.pixelplay.data.model.LyricsSourcePreference @@ -45,8 +35,8 @@ import com.theveloper.pixelplay.data.model.Playlist import com.theveloper.pixelplay.data.model.SearchFilterType import com.theveloper.pixelplay.data.model.SearchHistoryItem import com.theveloper.pixelplay.data.model.SearchResultItem +import com.theveloper.pixelplay.data.model.Song import com.theveloper.pixelplay.data.model.SortOption -import com.theveloper.pixelplay.data.model.FolderSource import com.theveloper.pixelplay.data.model.StorageFilter import com.theveloper.pixelplay.data.preferences.PlaylistPreferencesRepository import com.theveloper.pixelplay.data.preferences.UserPreferencesRepository @@ -56,33 +46,36 @@ import com.theveloper.pixelplay.utils.LogUtils import com.theveloper.pixelplay.utils.StorageType import com.theveloper.pixelplay.utils.StorageUtils import com.theveloper.pixelplay.utils.toHexString +import dagger.Lazy +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import java.io.File -import androidx.paging.Pager -import androidx.paging.PagingConfig -import androidx.paging.PagingData -import androidx.paging.map -import androidx.paging.filter -import kotlinx.coroutines.flow.conflate -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.CoroutineScope +import javax.inject.Inject +import javax.inject.Singleton @OptIn(ExperimentalCoroutinesApi::class) @Singleton diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/AiMetadataSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/AiMetadataSheet.kt index 433ed3485..c4f96337c 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/AiMetadataSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/AiMetadataSheet.kt @@ -49,6 +49,7 @@ fun AiMetadataSheet( error: String?, onRetry: () -> Unit ) { + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val colors = MaterialTheme.colorScheme diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/AiPlaylistSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/AiPlaylistSheet.kt index 1566a6678..4a9ca46c1 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/AiPlaylistSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/AiPlaylistSheet.kt @@ -84,6 +84,7 @@ fun AiPlaylistSheet( var minLength by remember { mutableStateOf("5") } var maxLength by remember { mutableStateOf("15") } + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true, ) diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/AlbumMultiSelectionOptionSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/AlbumMultiSelectionOptionSheet.kt index 37cee5f1f..55bbc7035 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/AlbumMultiSelectionOptionSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/AlbumMultiSelectionOptionSheet.kt @@ -57,6 +57,7 @@ fun AlbumMultiSelectionOptionSheet( onPlayNext: () -> Unit, onAddToQueue: () -> Unit ) { + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) ModalBottomSheet( diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/CastBottomSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/CastBottomSheet.kt index e2a0a78d8..80a1673a4 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/CastBottomSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/CastBottomSheet.kt @@ -333,6 +333,7 @@ fun CastBottomSheet( bluetoothName = activeBluetoothName ) + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true, confirmValueChange = { true } diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/CustomPresetsSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/CustomPresetsSheet.kt index ab8e7a63b..c09461091 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/CustomPresetsSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/CustomPresetsSheet.kt @@ -51,6 +51,7 @@ fun CustomPresetsSheet( onDelete: (EqualizerPreset) -> Unit, onDismiss: () -> Unit ) { + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) ModalBottomSheet( diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/DailyMixMenu.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/DailyMixMenu.kt index f87192e7e..16ef74157 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/DailyMixMenu.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/DailyMixMenu.kt @@ -28,6 +28,7 @@ fun DailyMixMenu( onApplyPrompt: (String) -> Unit, isLoading: Boolean ) { + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState() var prompt by remember { mutableStateOf("") } diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/EditSongSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/EditSongSheet.kt index 6040ebc3a..dcf9ba755 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/EditSongSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/EditSongSheet.kt @@ -10,18 +10,15 @@ import androidx.compose.foundation.gestures.rememberTransformableState import androidx.compose.foundation.gestures.transformable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.rounded.Album import androidx.compose.material.icons.rounded.Category import androidx.compose.material.icons.rounded.FormatListNumbered import androidx.compose.material.icons.rounded.Info import androidx.compose.material.icons.rounded.Image import androidx.compose.material.icons.rounded.MusicNote -import androidx.compose.material.icons.rounded.Notes import androidx.compose.material.icons.rounded.Person import androidx.compose.material3.* import androidx.compose.material3.BasicAlertDialog @@ -72,16 +69,13 @@ import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape import dev.shreyaspatil.capturable.capturable import androidx.compose.ui.geometry.Offset import androidx.compose.material.icons.rounded.Restore -import androidx.compose.material.icons.rounded.Shuffle -import androidx.compose.material.icons.rounded.Timer import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.focus.focusModifier import androidx.compose.ui.text.font.FontWeight +import androidx.core.net.toUri import kotlin.math.roundToInt import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import androidx.media3.common.Player import com.theveloper.pixelplay.data.media.AudioMetadataReader import com.theveloper.pixelplay.data.media.CoverArtUpdate import dev.shreyaspatil.capturable.controller.rememberCaptureController @@ -623,7 +617,7 @@ private fun EditSongContent( val encodedTitle = URLEncoder.encode(title, "UTF-8") val encodedArtist = URLEncoder.encode(artist, "UTF-8") val url = "https://lrclib.net/search/$encodedTitle%20$encodedArtist" - val intent = Intent(Intent.ACTION_VIEW).setData(Uri.parse(url)) + val intent = Intent(Intent.ACTION_VIEW).setData(url.toUri()) context.startActivity(intent) }, ) { @@ -862,9 +856,11 @@ fun CoverArtCropperDialog( var isLoading by remember { mutableStateOf(true) } var loadError by remember { mutableStateOf(null) } var isSaving by remember { mutableStateOf(false) } - var scale by remember { mutableStateOf(1f) } + var scale by remember { mutableFloatStateOf(1f) } var offset by remember { mutableStateOf(Offset.Zero) } - var containerSize by remember { mutableStateOf(0f) } + var containerSize by remember { mutableFloatStateOf(0f) } + + val errorMsg = stringResource(R.string.edit_song_unable_to_load_image) LaunchedEffect(sourceUri) { isLoading = true @@ -873,7 +869,7 @@ fun CoverArtCropperDialog( if (bitmap != null) { loadedBitmap = bitmap.asImageBitmap() } else { - loadError = context.getString(R.string.edit_song_unable_to_load_image) + loadError = errorMsg } isLoading = false scale = 1f @@ -886,7 +882,7 @@ fun CoverArtCropperDialog( } } - val transformableState = rememberTransformableState { zoomChange, panChange, _ -> + val transformableState = rememberTransformableState { _, zoomChange, panChange, _ -> val newScale = (scale * zoomChange).coerceIn(1f, 4f) scale = newScale loadedBitmap?.let { bitmap -> @@ -1039,26 +1035,21 @@ fun CoverArtCropperDialog( Button( enabled = canConfirm && !isSaving, onClick = { - if (!canConfirm) return@Button dialogScope.launch { isSaving = true val captured = captureController.captureAsync().await() - if (captured != null) { - val bytes = withContext(Dispatchers.IO) { - imageBitmapToJpeg(captured) - } - if (bytes != null) { - onConfirm( - CoverArtCropResult( - preview = captured, - update = CoverArtUpdate(bytes, COVER_ART_MIME_TYPE) - ) + val bytes = withContext(Dispatchers.IO) { + imageBitmapToJpeg(captured) + } + if (bytes != null) { + onConfirm( + CoverArtCropResult( + preview = captured, + update = CoverArtUpdate(bytes, COVER_ART_MIME_TYPE) ) - } else { - Timber.w("Failed to convert captured cover art to JPEG") - } + ) } else { - Timber.w("CaptureController returned null bitmap") + Timber.w("Failed to convert captured cover art to JPEG") } isSaving = false } diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/GenreSortBottomSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/GenreSortBottomSheet.kt index 47efe56f0..1641b8139 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/GenreSortBottomSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/GenreSortBottomSheet.kt @@ -48,6 +48,7 @@ fun GenreSortBottomSheet( onShuffle: () -> Unit, headerContent: @Composable (() -> Unit)? = null ) { + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true ) diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/LibrarySortBottomSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/LibrarySortBottomSheet.kt index 5b2c33919..8370222d6 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/LibrarySortBottomSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/LibrarySortBottomSheet.kt @@ -77,6 +77,7 @@ fun LibrarySortBottomSheet( sourceToggleContent: (@Composable () -> Unit)? = null, extraContent: (@Composable () -> Unit)? = null ) { + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val selectedColor = MaterialTheme.colorScheme.secondaryContainer diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/LyricsSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/LyricsSheet.kt index 21bdf6273..d17f7ef96 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/LyricsSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/LyricsSheet.kt @@ -449,6 +449,7 @@ fun LyricsSheet( var immersiveMode by remember { mutableStateOf(false) } var lastInteractionTime by remember { mutableLongStateOf(System.currentTimeMillis()) } var showMoreSheet by remember { mutableStateOf(false) } + @Suppress("DEPRECATION") val moreSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true ) diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/MultiSelectionBottomSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/MultiSelectionBottomSheet.kt index 5fefda0b9..de8f99977 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/MultiSelectionBottomSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/MultiSelectionBottomSheet.kt @@ -106,6 +106,7 @@ fun MultiSelectionBottomSheet( onBatchEdit: () -> Unit ) { val context = LocalContext.current + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) // Compute if all selected songs are liked diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/PlaylistBottomSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/PlaylistBottomSheet.kt index 3c0a0c5a5..ae12f8d27 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/PlaylistBottomSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/PlaylistBottomSheet.kt @@ -64,6 +64,7 @@ fun PlaylistBottomSheet( ) { var showCreatePlaylistDialog by remember { mutableStateOf(false) } + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true, confirmValueChange = { true } diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/PlaylistMultiSelectionBottomSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/PlaylistMultiSelectionBottomSheet.kt index a9afbd594..eba556244 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/PlaylistMultiSelectionBottomSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/PlaylistMultiSelectionBottomSheet.kt @@ -84,6 +84,7 @@ fun PlaylistMultiSelectionBottomSheet( onMergeAll: () -> Unit, onShareAll: () -> Unit ) { + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val evenCornerRadius = 26.dp val buttonShape = AbsoluteSmoothCornerShape( diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/ReorderTabsSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/ReorderTabsSheet.kt index a24bd9ace..3f1fadea3 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/ReorderTabsSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/ReorderTabsSheet.kt @@ -65,6 +65,7 @@ import sh.calvin.reorderable.rememberReorderableLazyListState import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow +@Suppress("DEPRECATION") @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun ReorderTabsSheet( @@ -106,6 +107,7 @@ fun ReorderTabsSheet( ) } + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val scope = rememberCoroutineScope() val listState = rememberLazyListState() diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/RoundedParallaxCarousell.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/RoundedParallaxCarousell.kt index 92445df96..3aa790286 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/RoundedParallaxCarousell.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/RoundedParallaxCarousell.kt @@ -576,7 +576,7 @@ private class CarouselItemModifierNode( } // --- limitar además al propio layer (seguro) - val layerBounds = Rect(0f, 0f, size.width.toFloat(), size.height.toFloat()) + val layerBounds = Rect(0f, 0f, size.width, size.height) val maskRect = Rect(left, top, right, bottom).intersect(layerBounds) // --- actualizar info para la máscara (para MaskScope, etc.) diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/SongInfoBottomSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/SongInfoBottomSheet.kt index 566627f04..78bde78ce 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/SongInfoBottomSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/SongInfoBottomSheet.kt @@ -307,6 +307,7 @@ fun SongInfoBottomSheet( ) } + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true, confirmValueChange = { true } diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/SongPickerBottomSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/SongPickerBottomSheet.kt index 1c29515fd..9088da0de 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/SongPickerBottomSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/SongPickerBottomSheet.kt @@ -103,6 +103,7 @@ fun SongPickerBottomSheet( onConfirm: (Set) -> Unit, playerViewModel: PlayerViewModel = hiltViewModel() ) { + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val selectedSongIds = remember { mutableStateMapOf().apply { diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/TimerOptionsBottomSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/TimerOptionsBottomSheet.kt index e5aeb0abe..3b669ae5d 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/TimerOptionsBottomSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/TimerOptionsBottomSheet.kt @@ -75,6 +75,7 @@ fun TimerOptionsBottomSheet( var showCustomTimePicker by rememberSaveable { mutableStateOf(false) } val context = LocalContext.current var timerSliderPosition by remember { mutableStateOf(0f) } + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val isSwitchEnabled = isEndOfTrackTimerActive diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/components/player/FullPlayerContent.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/components/player/FullPlayerContent.kt index 0e84ec5b7..410020521 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/components/player/FullPlayerContent.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/components/player/FullPlayerContent.kt @@ -978,6 +978,7 @@ fun FullPlayerContent( ) } + @Suppress("DEPRECATION") val artistPickerSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) if (showArtistPicker && currentSongArtists.isNotEmpty()) { PlayerArtistPickerBottomSheet( diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/HomeScreen.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/HomeScreen.kt index 7fba291fa..7787898aa 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/HomeScreen.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/HomeScreen.kt @@ -252,7 +252,9 @@ fun HomeScreen( var showBetaInfoBottomSheet by remember { mutableStateOf(false) } var showStreamingProviderSheet by remember { mutableStateOf(false) } var cleanInstallDisclaimerDismissedThisSession by rememberSaveable { mutableStateOf(false) } + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState() + @Suppress("DEPRECATION") val betaSheetState = rememberModalBottomSheetState() val scope = rememberCoroutineScope() LocalContext.current diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/LibraryMediaTabs.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/LibraryMediaTabs.kt index d3991a9b3..5c049f320 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/LibraryMediaTabs.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/LibraryMediaTabs.kt @@ -214,7 +214,7 @@ fun LibraryAlbumsTab( when { refreshState is LoadState.Error && albums.itemCount == 0 -> { - val error = (refreshState as LoadState.Error).error + val error = refreshState.error Box( modifier = Modifier .fillMaxSize() @@ -524,7 +524,7 @@ fun LibraryArtistsTab( when { refreshState is LoadState.Error && artists.itemCount == 0 -> { - val error = (refreshState as LoadState.Error).error + val error = refreshState.error Box( modifier = Modifier .fillMaxSize() diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/LibraryScreen.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/LibraryScreen.kt index 8a4ff0a67..2b5849bb6 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/LibraryScreen.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/LibraryScreen.kt @@ -2644,6 +2644,7 @@ private fun LibraryTabSwitcherSheet( onEditClick: () -> Unit, onDismiss: () -> Unit ) { + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) ModalBottomSheet( diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/LibrarySongsTab.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/LibrarySongsTab.kt index 24e9b98f1..392410d4c 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/LibrarySongsTab.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/LibrarySongsTab.kt @@ -225,7 +225,7 @@ fun LibrarySongsTab( when { refreshState is LoadState.Error && songs.itemCount == 0 -> { - val error = (refreshState as LoadState.Error).error + val error = refreshState.error Box( modifier = Modifier .fillMaxSize() diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/MashupScreen.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/MashupScreen.kt index 19a7dd84f..7848406b1 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/MashupScreen.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/MashupScreen.kt @@ -66,6 +66,7 @@ fun MashupScreen( mashupViewModel: MashupViewModel = hiltViewModel() ) { val mashupUiState by mashupViewModel.uiState.collectAsStateWithLifecycle() + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState() val scope = rememberCoroutineScope() diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/PlaylistDetailScreen.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/PlaylistDetailScreen.kt index 778440c30..9f7666bea 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/PlaylistDetailScreen.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/PlaylistDetailScreen.kt @@ -696,6 +696,7 @@ fun PlaylistDetailScreen( ) } if (showPlaylistOptionsSheet && !isFolderPlaylist) { + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) ModalBottomSheet( diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/SettingsCategoryScreen.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/SettingsCategoryScreen.kt index e7bfaf99b..2a6f07699 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/screens/SettingsCategoryScreen.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/screens/SettingsCategoryScreen.kt @@ -276,6 +276,7 @@ fun SettingsCategoryScreen( var paletteBulkCompletedCount by remember { mutableStateOf(0) } var paletteBulkTotalCount by remember { mutableStateOf(0) } var paletteSongSearchQuery by remember { mutableStateOf("") } + @Suppress("DEPRECATION") val paletteRegenerateSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val isAnyPaletteRegenerateRunning = isPaletteRegenerateRunning || isPaletteBulkRegenerateRunning val filteredPaletteSongs = remember(paletteRegenerateTargets, paletteSongSearchQuery) { diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/telegram/channel/TelegramChannelSearchSheet.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/telegram/channel/TelegramChannelSearchSheet.kt index af55bacbf..8aafe856a 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/telegram/channel/TelegramChannelSearchSheet.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/telegram/channel/TelegramChannelSearchSheet.kt @@ -75,6 +75,7 @@ fun TelegramChannelSearchSheet( val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() val statusMessage by viewModel.statusMessage.collectAsStateWithLifecycle() val isOnline by viewModel.isOnline.collectAsStateWithLifecycle() + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val focusRequester = remember { FocusRequester() } diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/telegram/dashboard/TelegramDashboardScreen.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/telegram/dashboard/TelegramDashboardScreen.kt index a70f15746..6c9f446cb 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/telegram/dashboard/TelegramDashboardScreen.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/telegram/dashboard/TelegramDashboardScreen.kt @@ -703,6 +703,7 @@ private fun ChannelActionsBottomSheet( onSync: () -> Unit, onDelete: () -> Unit ) { + @Suppress("DEPRECATION") val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val publicChannelFallback = stringResource(R.string.presentation_batch_f_public_channel_fallback) val usernameLabel = remember(channel.username) { diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/AccountsViewModel.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/AccountsViewModel.kt index 354d92987..1f2ee30e6 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/AccountsViewModel.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/AccountsViewModel.kt @@ -114,12 +114,12 @@ class AccountsViewModel @Inject constructor( ) { it.toList() }, loggingOutServices ) { states, activeLogouts -> - val (telegramConnected, telegramChannelCount) = states[0] as Pair - val (gDriveConnected, gDriveFolderCount) = states[1] as Pair - val (neteaseConnected, neteasePlaylistCount) = states[2] as Pair - val (qqConnected, qqPlaylistCount) = states[3] as Pair - val (navidromeConnected, navidromePlaylistCount) = states[4] as Pair - val (jellyfinConnected, jellyfinPlaylistCount) = states[5] as Pair + val (telegramConnected, telegramChannelCount) = states[0] + val (gDriveConnected, gDriveFolderCount) = states[1] + val (neteaseConnected, neteasePlaylistCount) = states[2] + val (qqConnected, qqPlaylistCount) = states[3] + val (navidromeConnected, navidromePlaylistCount) = states[4] + val (jellyfinConnected, jellyfinPlaylistCount) = states[5] val connectedAccounts = buildList { if (telegramConnected) { diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/CastStateHolder.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/CastStateHolder.kt index a9633ec45..bd4b8a0cb 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/CastStateHolder.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/CastStateHolder.kt @@ -196,6 +196,7 @@ class CastStateHolder @Inject constructor( updateRoutes() syncSelectedRouteFromRouter(router) } + @Suppress("OVERRIDE_DEPRECATION") override fun onRouteSelected(router: MediaRouter, route: MediaRouter.RouteInfo) { updateRoutes() syncSelectedRouteFromRouter(router) diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/CastTransferStateHolder.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/CastTransferStateHolder.kt index 124740938..fafad7331 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/CastTransferStateHolder.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/CastTransferStateHolder.kt @@ -1027,13 +1027,14 @@ class CastTransferStateHolder @Inject constructor( ): Boolean = withContext(Dispatchers.IO) { var connection: HttpURLConnection? = null runCatching { - connection = (URL(endpoint).openConnection() as HttpURLConnection).apply { + val conn = (URL(endpoint).openConnection() as HttpURLConnection).apply { connectTimeout = 150 readTimeout = 150 instanceFollowRedirects = false requestMethod = method } - val code = connection?.responseCode ?: -1 + connection = conn + val code = conn.responseCode code in 200..299 }.getOrDefault(false).also { connection?.disconnect() diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/ConnectivityStateHolder.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/ConnectivityStateHolder.kt index f86f46be3..d86e8e32b 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/ConnectivityStateHolder.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/ConnectivityStateHolder.kt @@ -329,9 +329,9 @@ class ConnectivityStateHolder @Inject constructor( device.type == android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP || device.type == android.media.AudioDeviceInfo.TYPE_BLUETOOTH_SCO ) { - val name = device.productName?.toString()?.trim().orEmpty() + val name = device.productName.toString().trim() if (name.isNotEmpty() && !isOwnBluetoothDeviceName(name, localDeviceNames)) { - val address = device.address?.trim().orEmpty().takeIf { it.isNotEmpty() } + val address = device.address.trim().takeIf { it.isNotEmpty() } val key = bluetoothDeviceKey(address, name) connectedDevices[key] = BluetoothAudioDeviceState( name = name, @@ -506,9 +506,12 @@ class ConnectivityStateHolder @Inject constructor( private fun readConnectedWifiSsid(): String? { if (!hasFineLocationPermission()) return null + @Suppress("DEPRECATION") val info = wifiManager?.connectionInfo ?: return null + @Suppress("DEPRECATION") if (info.supplicantState != android.net.wifi.SupplicantState.COMPLETED) return null + @Suppress("DEPRECATION") var ssid = info.ssid if (ssid.startsWith("\"") && ssid.endsWith("\"")) { ssid = ssid.substring(1, ssid.length - 1) diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/GenreDetailViewModel.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/GenreDetailViewModel.kt index 3c5c821e6..8c8818197 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/GenreDetailViewModel.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/GenreDetailViewModel.kt @@ -158,7 +158,7 @@ class GenreDetailViewModel @Inject constructor( val sections = buildDisplaySections(songs, SortOption.ARTIST) val flattened = flattenSections(sections, artistMap) - val sorted = songs.sortedBy { it.artist ?: "Unknown Artist" } + val sorted = songs.sortedBy { it.artist } ProcessingResult(genre, songs, sorted, sections, flattened) } @@ -195,8 +195,8 @@ class GenreDetailViewModel @Inject constructor( val sections = buildDisplaySections(currentState.songs, newSort) val flattened = flattenSections(sections, artistMap) val sorted = when (newSort) { - SortOption.ARTIST -> currentState.songs.sortedBy { it.artist ?: "Unknown Artist" } - SortOption.ALBUM -> currentState.songs.sortedBy { it.album ?: "Unknown Album" } + SortOption.ARTIST -> currentState.songs.sortedBy { it.artist } + SortOption.ALBUM -> currentState.songs.sortedBy { it.album } SortOption.TITLE -> currentState.songs.sortedBy { it.title } } Triple(sections, flattened, sorted) @@ -278,10 +278,10 @@ class GenreDetailViewModel @Inject constructor( private fun buildDisplaySections(songs: List, sort: SortOption): List { return when (sort) { SortOption.ARTIST -> { - val sorted = songs.sortedBy { it.artist ?: "Unknown Artist" } - val grouped = sorted.groupBy { it.artist ?: "Unknown Artist" } + val sorted = songs.sortedBy { it.artist } + val grouped = sorted.groupBy { it.artist } grouped.map { (artist, artistSongs) -> - val albums = artistSongs.groupBy { it.album ?: "Unknown Album" }.map { (albumName, albumSongs) -> + val albums = artistSongs.groupBy { it.album }.map { (albumName, albumSongs) -> val sortedAlbumSongs = albumSongs.sortedWith( compareBy { it.discNumber ?: 1 } .thenBy { if (it.trackNumber > 0) it.trackNumber else Int.MAX_VALUE } @@ -293,8 +293,8 @@ class GenreDetailViewModel @Inject constructor( } } SortOption.ALBUM -> { - val sorted = songs.sortedBy { it.album ?: "Unknown Album" } - val grouped = sorted.groupBy { it.album ?: "Unknown Album" } + val sorted = songs.sortedBy { it.album } + val grouped = sorted.groupBy { it.album } grouped.map { (album, albumSongs) -> val sortedAlbumSongs = albumSongs.sortedWith( compareBy { it.discNumber ?: 1 } diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/PlaybackStateHolder.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/PlaybackStateHolder.kt index ebcdf502f..1a49ff1a1 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/PlaybackStateHolder.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/PlaybackStateHolder.kt @@ -694,7 +694,7 @@ class PlaybackStateHolder @Inject constructor( if (hasMediaMismatch) { Timber.tag(TAG).v( "Skipping local progress tick due media mismatch (visible=%s, player=%s)", - visibleSong?.id, + visibleSong.id, currentMediaId ) delay(tickMs) diff --git a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/PlayerViewModel.kt b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/PlayerViewModel.kt index 196cba271..00e829c58 100644 --- a/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/PlayerViewModel.kt +++ b/app/src/main/java/com/theveloper/pixelplay/presentation/viewmodel/PlayerViewModel.kt @@ -3970,9 +3970,7 @@ class PlayerViewModel @Inject constructor( _toastEvents.emit( context.getString(R.string.player_share_zip_failed_format, error.localizedMessage ?: ""), ) - println( - "Failed to share: ${error.localizedMessage}" - ) + Timber.e(error, "Failed to share") } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 10a198aa9..8eea83d14 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -242,7 +242,7 @@ (Mixed values) (Optional - leave empty to skip) Successfully updated %d songs - Updated %d of %d songs. Some files could not be edited. + Updated %1$d of %2$d songs. Some files could not be edited. Failed to update songs diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 756bee62c..04d0c255c 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -1,12 +1,15 @@ - + + + + 127.0.0.1 localhost - \ No newline at end of file + diff --git a/gradle.properties b/gradle.properties index 3456658ac..123fd60fb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,6 +3,7 @@ kotlin.code.style=official APP_VERSION_NAME=0.7.0-beta APP_VERSION_CODE=8 org.gradle.configuration-cache=true +org.gradle.caching=true # Adjusted to stay within 3GB limit and avoid thrashing # Increased heap to 4GB and Metaspace to 1024m to prevent memory exhaustion org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+ParallelRefParsingEnabled @@ -27,6 +28,9 @@ android.r8.strictFullModeForKeepRules=true android.builtInKotlin=true android.newDsl=true +android.nonTransitiveRClass=true +android.enableJetifier=false ksp.useKSP2=true +ksp.incremental=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1d8e45488..c9b84e4bf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ accompanistDrawablepainter = "0.37.3" agp = "9.2.1" app = "1.7.0" -googleGenai = "1.53.0" +googleGenai = "1.56.0" googlePlayServicesCast = "22.3.1" animation = "1.11.2" appcompat = "1.7.1" @@ -65,7 +65,7 @@ junit5 = "6.1.0" kuromoji = "0.9.0" pinyin4j = "2.5.1" securityCrypto = "1.1.0" -netty = "4.2.13.Final" +netty = "4.2.14.Final" bouncycastle = "1.84" commons-lang3 = "3.20.0" jdom2 = "2.0.6.1" @@ -77,7 +77,7 @@ dagger = "2.59.2" javax-inject = "1" # Annotations procesing -ksp = "2.3.8" +ksp = "2.3.9" smoothCornerRectAndroidCompose = "v1.0.0" spleeterAndroidIos = "1.0.2" tensorflowLite = "2.17.0" @@ -89,7 +89,7 @@ workRuntimeKtx = "2.11.2" composeTesting = "1.0.0-alpha03" timber = "5.0.1" generativeai = "0.9.0" -mockk = "1.14.9" +mockk = "1.14.11" turbine = "1.2.1" truth = "1.4.5" @@ -282,3 +282,62 @@ android-test = { id = "com.android.test", version.ref = "agp" } baselineprofile = { id = "androidx.baselineprofile", version.ref = "baselineprofile" } android-library = { id = "com.android.library", version.ref = "agp" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } + +[bundles] +compose = [ + "androidx-ui", + "androidx-ui-graphics", + "androidx-ui-tooling-preview", + "androidx-compose-material3", + "androidx-material-icons-core", + "androidx-material-icons-extended", + "androidx-constraintlayout-compose", + "androidx-foundation", + "androidx-animation", + "androidx-ui-text-google-fonts" +] +lifecycle = [ + "androidx-lifecycle-runtime-ktx", + "androidx-lifecycle-runtime-compose", + "lifecycleprocess" +] +room = [ + "androidx-room-runtime", + "androidx-room-ktx", + "androidx-room-paging" +] +paging = [ + "androidx-paging-runtime", + "androidx-paging-compose" +] +media3 = [ + "androidx-media3-exoplayer", + "androidx-media3-ui", + "androidx-media3-session", + "androidx-media3-exoplayer-midi", + "androidx-media3-transformer" +] +hilt = [ + "hilt-android", + "androidx-hilt-navigation-compose", + "androidx-hilt-lifecycle-viewmodel-compose", + "androidx-hilt-work" +] +networking = [ + "retrofit", + "converter-gson", + "okhttp", + "logging-interceptor" +] +wear = [ + "wear-compose-material", + "wear-compose-material3", + "wear-compose-foundation", + "wear-compose-navigation" +] +horologist = [ + "horologist-compose-layout", + "horologist-media-ui", + "horologist-audio-ui", + "horologist-composables" +] diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts index 947f59d97..d1929ba0d 100644 --- a/wear/build.gradle.kts +++ b/wear/build.gradle.kts @@ -32,13 +32,14 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) - lint { - abortOnError = false - checkReleaseBuilds = false - } } } + lint { + abortOnError = false + checkReleaseBuilds = true + } + buildFeatures { compose = true buildConfig = true @@ -60,63 +61,40 @@ kotlin { dependencies { implementation(project(":shared")) - // Wear OS Compose - implementation(libs.wear.compose.material) - implementation(libs.wear.compose.material3) - implementation(libs.wear.compose.foundation) - implementation(libs.wear.compose.navigation) - - // Horologist - implementation(libs.horologist.compose.layout) - implementation(libs.horologist.media.ui) - implementation(libs.horologist.audio.ui) - implementation(libs.horologist.composables) + // Wear OS & Horologist + implementation(libs.bundles.wear) + implementation(libs.bundles.horologist) // Data Layer implementation(libs.play.services.wearable) implementation(libs.kotlinx.coroutines.play.services) - // Hilt - implementation(libs.hilt.android) + // DI & Navigation + implementation(libs.bundles.hilt) ksp(libs.hilt.android.compiler) - implementation(libs.androidx.hilt.navigation.compose) - implementation(libs.androidx.hilt.lifecycle.viewmodel.compose) + ksp(libs.androidx.hilt.compiler) - // Compose core - implementation(platform(libs.androidx.compose.bom)) + // AndroidX & Compose + implementation(libs.androidx.core.ktx) + implementation(libs.bundles.lifecycle) implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.foundation) - - // Serialization - implementation(libs.kotlinx.serialization.json) - - // Image loading - implementation(libs.coil.compose) - - // Logging - implementation(libs.timber) - - // Core - implementation(libs.androidx.core.ktx) - - // Lifecycle - implementation(libs.androidx.lifecycle.runtime.ktx) - implementation(libs.androidx.lifecycle.runtime.compose) - - // Material icons for Wear implementation(libs.androidx.material.icons.core) implementation(libs.androidx.material.icons.extended) - // Room (local database for transferred songs) - implementation(libs.androidx.room.runtime) - implementation(libs.androidx.room.ktx) + // Storage & Media + implementation(libs.bundles.room) ksp(libs.androidx.room.compiler) - - // Media3 ExoPlayer (standalone local playback) implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.session) implementation(libs.androidx.mediarouter) + // UI Utilities + implementation(libs.coil.compose) + implementation(libs.timber) + implementation(libs.kotlinx.serialization.json) + constraints { // Fix vulnerabilities in transitive dependencies implementation(libs.netty.common)