diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 79ba1435c..6221bc037 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -9,6 +9,11 @@ on: - synchronize - ready_for_review +permissions: + contents: read + pull-requests: write + checks: write + env: GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-XX:+HeapDumpOnOutOfMemoryError" @@ -37,20 +42,18 @@ jobs: run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 + cache: 'gradle' - name: Setup Gradle - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v4 - name: "Lint Project" run: "support/scripts/lint" - - name: "Gradle cache" - uses: gradle/gradle-build-action@v2 - - name: "Build and Run Unit Tests" run: "support/scripts/unit-test" diff --git a/.github/workflows/continuous_deployment.yml b/.github/workflows/continuous_deployment.yml index 0ab4c870d..2dbc1322a 100644 --- a/.github/workflows/continuous_deployment.yml +++ b/.github/workflows/continuous_deployment.yml @@ -4,8 +4,11 @@ on: push: branches: [main] +permissions: + contents: read + concurrency: - group: cd-${{ github.head_ref }} + group: cd-${{ github.ref }} cancel-in-progress: true env: @@ -31,17 +34,18 @@ jobs: run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 + cache: 'gradle' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 - name: "Lint Project" run: "support/scripts/lint" - - name: "Gradle cache" - uses: gradle/gradle-build-action@v2 - - name: "Build and Run Unit Tests" run: "support/scripts/unit-test" @@ -80,12 +84,13 @@ jobs: uses: actions/download-artifact@v4 with: name: app-bundle + path: . - name: "Upload bundle to Play Store" - uses: r0adkll/upload-google-play@v1 + uses: r0adkll/upload-google-play@v1.1.3 with: serviceAccountJsonPlainText: ${{ secrets.GOOGLE_SERVICE_ACCOUNT }} packageName: com.simplecityapps.shuttle - releaseFiles: bundle/release/app-release.aab + releaseFiles: android/app/build/outputs/bundle/release/app-release.aab track: internal status: completed diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index 8adf0d5cc..47f576863 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -6,14 +6,18 @@ on: - '.github/workflows/shellcheck.yml' - 'support/scripts/**' +permissions: + contents: read + pull-requests: write + jobs: shellcheck: name: ShellCheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: ShellCheck Support Scripts - uses: reviewdog/action-shellcheck@v1.15 + uses: reviewdog/action-shellcheck@v1.27 with: reporter: github-pr-review path: "support/scripts" diff --git a/.github/workflows/yamllint.yml b/.github/workflows/yamllint.yml index 95b936f2d..9014c4dcf 100644 --- a/.github/workflows/yamllint.yml +++ b/.github/workflows/yamllint.yml @@ -1,17 +1,22 @@ name: Yaml Lint + on: pull_request: branches: [main] paths: - '**.yml' +permissions: + contents: read + pull-requests: write + jobs: yamllint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: yaml-lint - uses: reviewdog/action-yamllint@v1.6 + uses: reviewdog/action-yamllint@v1.20 with: level: error reporter: github-pr-review diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml deleted file mode 100644 index 2159e8536..000000000 --- a/.idea/deploymentTargetSelector.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.kotlin/sessions/kotlin-compiler-18148379643065932919.salive b/.kotlin/sessions/kotlin-compiler-18148379643065932919.salive deleted file mode 100644 index e69de29bb..000000000 diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 5b3cf8fba..cfc9710b9 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -13,12 +13,12 @@ plugins { android { - compileSdk = 35 + compileSdk = 36 defaultConfig { applicationId = "com.simplecityapps.shuttle" - minSdk = 21 - targetSdk = 35 + minSdk = 23 + targetSdk = 36 versionName = versionName() versionCode = versionCode() vectorDrawables.useSupportLibrary = true @@ -79,7 +79,10 @@ android { kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() - freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn" + freeCompilerArgs = freeCompilerArgs + listOf( + "-Xopt-in=kotlin.RequiresOptIn", + "-Xopt-in=kotlin.time.ExperimentalTime" + ) } lint { abortOnError = false @@ -99,7 +102,9 @@ android { implementation(composeBom) androidTestImplementation(composeBom) implementation(libs.kotlinx.collections.immutable) + implementation(libs.kotlinx.datetime) implementation(libs.androidx.material3) + implementation("androidx.compose.material:material-icons-extended") // Android Studio Preview support implementation(libs.androidx.ui.tooling.preview) diff --git a/android/app/src/main/java/com/simplecityapps/shuttle/appinitializers/CrashReportingInitializer.kt b/android/app/src/main/java/com/simplecityapps/shuttle/appinitializers/CrashReportingInitializer.kt index 19bcd20ef..9c5027913 100644 --- a/android/app/src/main/java/com/simplecityapps/shuttle/appinitializers/CrashReportingInitializer.kt +++ b/android/app/src/main/java/com/simplecityapps/shuttle/appinitializers/CrashReportingInitializer.kt @@ -8,8 +8,8 @@ import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager -import com.google.firebase.crashlytics.ktx.crashlytics -import com.google.firebase.ktx.Firebase +import com.google.firebase.Firebase +import com.google.firebase.crashlytics.crashlytics import com.simplecityapps.shuttle.BuildConfig import com.simplecityapps.shuttle.persistence.GeneralPreferenceManager import com.simplecityapps.shuttle.ui.common.ActivityLifecycleCallbacksAdapter diff --git a/android/app/src/main/java/com/simplecityapps/shuttle/appinitializers/TrialInitializer.kt b/android/app/src/main/java/com/simplecityapps/shuttle/appinitializers/TrialInitializer.kt index a863a7601..4227af07e 100644 --- a/android/app/src/main/java/com/simplecityapps/shuttle/appinitializers/TrialInitializer.kt +++ b/android/app/src/main/java/com/simplecityapps/shuttle/appinitializers/TrialInitializer.kt @@ -28,7 +28,7 @@ constructor( Timber.v("Billing client available") billingManager.queryPurchases() coroutineScope.launch { - billingManager.querySkuDetails() + billingManager.queryProductDetails() } } } diff --git a/android/app/src/main/java/com/simplecityapps/shuttle/ui/common/dialog/TagEditorPresenter.kt b/android/app/src/main/java/com/simplecityapps/shuttle/ui/common/dialog/TagEditorPresenter.kt index 935d4aecd..ce28207d0 100644 --- a/android/app/src/main/java/com/simplecityapps/shuttle/ui/common/dialog/TagEditorPresenter.kt +++ b/android/app/src/main/java/com/simplecityapps/shuttle/ui/common/dialog/TagEditorPresenter.kt @@ -264,7 +264,7 @@ constructor( withContext(Dispatchers.Main) { view?.setLoading(TagEditorContract.LoadingState.WritingTags(index, editables.size)) } - return@mapIndexed song to kTagLib.writeMetadata(pfd.detachFd(), metadata) + return@mapIndexed song to kTagLib.writeMetadata(pfd.detachFd(), metadata, uri.lastPathSegment) } } catch (e: IllegalStateException) { Timber.e(e, "Failed to update tags") diff --git a/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/home/HomeFragment.kt b/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/home/HomeFragment.kt index e5ec19c95..fa5e7dad7 100644 --- a/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/home/HomeFragment.kt +++ b/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/home/HomeFragment.kt @@ -45,12 +45,12 @@ import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint import java.util.Calendar import javax.inject.Inject +import kotlin.time.Instant.Companion.fromEpochMilliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch -import kotlinx.datetime.Instant.Companion.fromEpochMilliseconds @AndroidEntryPoint class HomeFragment : diff --git a/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/main/MainPresenter.kt b/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/main/MainPresenter.kt index a50f6b871..19dc004d2 100644 --- a/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/main/MainPresenter.kt +++ b/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/main/MainPresenter.kt @@ -1,7 +1,7 @@ package com.simplecityapps.shuttle.ui.screens.main -import com.google.firebase.crashlytics.ktx.crashlytics -import com.google.firebase.ktx.Firebase +import com.google.firebase.Firebase +import com.google.firebase.crashlytics.crashlytics import com.simplecityapps.playback.queue.QueueChangeCallback import com.simplecityapps.playback.queue.QueueManager import com.simplecityapps.playback.queue.QueueWatcher diff --git a/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/trial/PurchaseDialogFragment.kt b/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/trial/PurchaseDialogFragment.kt index 883955ddd..69c4cb260 100644 --- a/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/trial/PurchaseDialogFragment.kt +++ b/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/trial/PurchaseDialogFragment.kt @@ -7,7 +7,7 @@ import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView -import com.android.billingclient.api.SkuDetails +import com.android.billingclient.api.ProductDetails import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.simplecityapps.adapter.RecyclerAdapter @@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.onEach @AndroidEntryPoint class PurchaseDialogFragment : DialogFragment() { - private var enabledSkus = + private var enabledProductIds = listOf( "s2_subscription_full_version_monthly", "s2_subscription_full_version_yearly", @@ -39,7 +39,7 @@ class PurchaseDialogFragment : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { if (firebaseConfig.getString("pricing_tier") == "low") { - enabledSkus = + enabledProductIds = listOf( "s2_subscription_full_version_yearly_low", "s2_iap_full_version_low" @@ -48,17 +48,17 @@ class PurchaseDialogFragment : DialogFragment() { adapter = RecyclerAdapter(lifecycleScope) - billingManager.skuDetails + billingManager.productDetails .onEach { detailsList -> adapter.update( detailsList - .filter { enabledSkus.contains(it.sku) } - .map { skuDetails -> + .filter { enabledProductIds.contains(it.productId) } + .map { productDetails -> SkuBinder( - skuDetails, + productDetails, object : SkuBinder.Listener { - override fun onClick(skuDetails: SkuDetails) { - billingManager.launchPurchaseFlow(requireActivity(), skuDetails) + override fun onClick(productDetails: ProductDetails) { + billingManager.launchPurchaseFlow(requireActivity(), productDetails) dismiss() } } diff --git a/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/trial/SkuBinder.kt b/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/trial/SkuBinder.kt index 4dfeeb34a..c117479fb 100644 --- a/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/trial/SkuBinder.kt +++ b/android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/trial/SkuBinder.kt @@ -7,14 +7,14 @@ import android.widget.Button import android.widget.TextView import androidx.core.view.isInvisible import com.android.billingclient.api.BillingClient -import com.android.billingclient.api.SkuDetails +import com.android.billingclient.api.ProductDetails import com.simplecityapps.adapter.ViewBinder import com.simplecityapps.shuttle.R import com.simplecityapps.shuttle.ui.common.recyclerview.ViewTypes -class SkuBinder(val skuDetails: SkuDetails, val litener: Listener) : ViewBinder { +class SkuBinder(val productDetails: ProductDetails, val litener: Listener) : ViewBinder { interface Listener { - fun onClick(skuDetails: SkuDetails) + fun onClick(productDetails: ProductDetails) } override fun createViewHolder(parent: ViewGroup): ViewHolder = ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item_sku, parent, false)) @@ -29,7 +29,7 @@ class SkuBinder(val skuDetails: SkuDetails, val litener: Listener) : ViewBinder init { itemView.setOnClickListener { - viewBinder?.litener?.onClick(viewBinder!!.skuDetails) + viewBinder?.litener?.onClick(viewBinder!!.productDetails) } } @@ -39,12 +39,15 @@ class SkuBinder(val skuDetails: SkuDetails, val litener: Listener) : ViewBinder ) { super.bind(viewBinder, isPartial) - title.text = viewBinder.skuDetails.title.substringBefore('(') - subtitle.text = viewBinder.skuDetails.description - price.text = viewBinder.skuDetails.price - priceOutlined.text = viewBinder.skuDetails.price - price.isInvisible = viewBinder.skuDetails.type == BillingClient.SkuType.SUBS - priceOutlined.isInvisible = viewBinder.skuDetails.type == BillingClient.SkuType.INAPP + title.text = viewBinder.productDetails.name.substringBefore('(') + subtitle.text = viewBinder.productDetails.description + val formattedPrice = viewBinder.productDetails.oneTimePurchaseOfferDetails?.formattedPrice + ?: viewBinder.productDetails.subscriptionOfferDetails?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.formattedPrice + ?: "" + price.text = formattedPrice + priceOutlined.text = formattedPrice + price.isInvisible = viewBinder.productDetails.productType == BillingClient.ProductType.SUBS + priceOutlined.isInvisible = viewBinder.productDetails.productType == BillingClient.ProductType.INAPP } } } diff --git a/android/core/build.gradle b/android/core/build.gradle index 45d956290..97b556b14 100644 --- a/android/core/build.gradle +++ b/android/core/build.gradle @@ -4,11 +4,11 @@ apply plugin: 'com.google.devtools.ksp' apply plugin: 'dagger.hilt.android.plugin' android { - compileSdk 35 + compileSdk 36 defaultConfig { minSdkVersion 21 - targetSdkVersion 35 + targetSdkVersion 36 buildConfigField("String", "PROXY_ADDR", "\"192.168.0.14\"") buildConfigField("Integer", "PROXY_PORT", "8888") } diff --git a/android/data/build.gradle.kts b/android/data/build.gradle.kts index 7701c614a..c4e61b130 100644 --- a/android/data/build.gradle.kts +++ b/android/data/build.gradle.kts @@ -5,10 +5,10 @@ plugins { } android { - compileSdk = 35 + compileSdk = 36 defaultConfig { - minSdk = 21 + minSdk = 23 } namespace = "com.simplecityapps.shuttle.data" @@ -20,8 +20,14 @@ android { kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } +} - dependencies { - implementation(libs.kotlinx.datetime) +kotlin { + compilerOptions { + optIn.add("kotlin.time.ExperimentalTime") } } + +dependencies { + implementation(libs.kotlinx.datetime) +} diff --git a/android/data/src/main/kotlin/com/simplecityapps/shuttle/query/SongQuery.kt b/android/data/src/main/kotlin/com/simplecityapps/shuttle/query/SongQuery.kt index 84cc025dc..132804728 100644 --- a/android/data/src/main/kotlin/com/simplecityapps/shuttle/query/SongQuery.kt +++ b/android/data/src/main/kotlin/com/simplecityapps/shuttle/query/SongQuery.kt @@ -6,9 +6,9 @@ import com.simplecityapps.shuttle.model.MediaProviderType import com.simplecityapps.shuttle.model.Song import com.simplecityapps.shuttle.parcel.InstantParceler import com.simplecityapps.shuttle.sorting.SongSortOrder +import kotlin.time.Clock import kotlin.time.Duration.Companion.days import kotlin.time.ExperimentalTime -import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.parcelize.Parcelize import kotlinx.parcelize.TypeParceler diff --git a/android/imageloader/build.gradle b/android/imageloader/build.gradle index 7747ddcf1..f0ea6afb5 100644 --- a/android/imageloader/build.gradle +++ b/android/imageloader/build.gradle @@ -4,11 +4,11 @@ apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'com.google.devtools.ksp' android { - compileSdk 35 + compileSdk 36 defaultConfig { minSdkVersion 21 - targetSdkVersion 35 + targetSdkVersion 36 consumerProguardFiles "consumer-rules.pro" } diff --git a/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/DirectoryAlbumArtistLocalArtworkModelLoader.kt b/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/DirectoryAlbumArtistLocalArtworkModelLoader.kt index 77886bc2e..854934113 100644 --- a/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/DirectoryAlbumArtistLocalArtworkModelLoader.kt +++ b/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/DirectoryAlbumArtistLocalArtworkModelLoader.kt @@ -89,7 +89,7 @@ class DirectoryAlbumArtistLocalArtworkModelLoader( } companion object { - private val pattern by lazy { Pattern.compile("(\.?)artist.*\\.(jpg|jpeg|png|webp)", Pattern.CASE_INSENSITIVE) } + private val pattern by lazy { Pattern.compile("(\\.?)artist.*\\.(jpg|jpeg|png|webp)", Pattern.CASE_INSENSITIVE) } } } } diff --git a/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/DirectoryAlbumLocalArtworkModelLoader.kt b/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/DirectoryAlbumLocalArtworkModelLoader.kt index 0ac66e1ba..c01e327ac 100644 --- a/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/DirectoryAlbumLocalArtworkModelLoader.kt +++ b/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/DirectoryAlbumLocalArtworkModelLoader.kt @@ -81,7 +81,7 @@ class DirectoryAlbumLocalArtworkModelLoader( } companion object { - private val pattern by lazy { Pattern.compile("(\.?(folder|cover|album)).*\\.(jpg|jpeg|png|webp)", Pattern.CASE_INSENSITIVE) } + private val pattern by lazy { Pattern.compile("(\\.?(folder|cover|album)).*\\.(jpg|jpeg|png|webp)", Pattern.CASE_INSENSITIVE) } } } } diff --git a/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/DirectorySongLocalArtworkModelLoader.kt b/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/DirectorySongLocalArtworkModelLoader.kt index 7ea1f922b..e536eb964 100644 --- a/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/DirectorySongLocalArtworkModelLoader.kt +++ b/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/DirectorySongLocalArtworkModelLoader.kt @@ -67,7 +67,7 @@ class DirectorySongLocalArtworkModelLoader( } companion object { - private val pattern by lazy { Pattern.compile("(\.?(folder|cover|album)).*\\.(jpg|jpeg|png|webp)", Pattern.CASE_INSENSITIVE) } + private val pattern by lazy { Pattern.compile("(\\.?(folder|cover|album)).*\\.(jpg|jpeg|png|webp)", Pattern.CASE_INSENSITIVE) } } } } diff --git a/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/TagLibAlbumLocalArtworkModelLoader.kt b/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/TagLibAlbumLocalArtworkModelLoader.kt index 46196abdb..d0a74e0f6 100644 --- a/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/TagLibAlbumLocalArtworkModelLoader.kt +++ b/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/TagLibAlbumLocalArtworkModelLoader.kt @@ -64,7 +64,7 @@ class TagLibAlbumLocalArtworkModelLoader( } try { context.contentResolver.openFileDescriptor(uri, "r")?.use { pfd -> - kTagLib.getArtwork(pfd.detachFd())?.inputStream() + kTagLib.getArtwork(pfd.detachFd(), uri.lastPathSegment)?.inputStream() } } catch (e: SecurityException) { Timber.v("Failed to retrieve artwork (permission denial)") diff --git a/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/TagLibSongLocalArtworkModelLoader.kt b/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/TagLibSongLocalArtworkModelLoader.kt index 6d9808e02..743d9dc10 100644 --- a/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/TagLibSongLocalArtworkModelLoader.kt +++ b/android/imageloader/src/main/java/au/com/simplecityapps/shuttle/imageloading/glide/loader/local/TagLibSongLocalArtworkModelLoader.kt @@ -67,7 +67,7 @@ class TagLibSongLocalArtworkModelLoader( } try { context.contentResolver.openFileDescriptor(uri, "r")?.use { pfd -> - return kTagLib.getArtwork(pfd.detachFd())?.inputStream() + return kTagLib.getArtwork(pfd.detachFd(), uri.lastPathSegment)?.inputStream() } } catch (e: SecurityException) { Timber.v("Failed to retrieve artwork (permission denial)") diff --git a/android/mediaprovider/core/build.gradle b/android/mediaprovider/core/build.gradle index 8bd2b2e2a..018c827ec 100644 --- a/android/mediaprovider/core/build.gradle +++ b/android/mediaprovider/core/build.gradle @@ -5,11 +5,11 @@ apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'com.google.devtools.ksp' android { - compileSdk 35 + compileSdk 36 defaultConfig { - minSdkVersion 21 - targetSdkVersion 35 + minSdkVersion 23 + targetSdkVersion 36 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary true @@ -31,6 +31,12 @@ android { namespace 'com.simplecityapps.mediaprovider' } +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class).configureEach { + compilerOptions { + optIn.add('kotlin.time.ExperimentalTime') + } +} + dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) diff --git a/android/mediaprovider/emby/build.gradle b/android/mediaprovider/emby/build.gradle index c74502271..804599adf 100644 --- a/android/mediaprovider/emby/build.gradle +++ b/android/mediaprovider/emby/build.gradle @@ -5,11 +5,11 @@ plugins { } android { - compileSdk 35 + compileSdk 36 defaultConfig { - minSdkVersion 21 - targetSdkVersion 35 + minSdkVersion 23 + targetSdkVersion 36 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -27,6 +27,12 @@ android { buildFeatures.buildConfig true } +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class).configureEach { + compilerOptions { + optIn.add('kotlin.time.ExperimentalTime') + } +} + dependencies { implementation libs.androidx.appcompat diff --git a/android/mediaprovider/emby/src/main/java/com/simplecityapps/provider/emby/EmbyMediaProvider.kt b/android/mediaprovider/emby/src/main/java/com/simplecityapps/provider/emby/EmbyMediaProvider.kt index a2a84aaee..b4d282046 100644 --- a/android/mediaprovider/emby/src/main/java/com/simplecityapps/provider/emby/EmbyMediaProvider.kt +++ b/android/mediaprovider/emby/src/main/java/com/simplecityapps/provider/emby/EmbyMediaProvider.kt @@ -20,6 +20,7 @@ import com.simplecityapps.shuttle.model.MediaProviderType import com.simplecityapps.shuttle.model.Playlist import com.simplecityapps.shuttle.model.Song import kotlin.math.min +import kotlin.time.Clock import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow @@ -30,7 +31,6 @@ import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import kotlinx.datetime.Clock import kotlinx.datetime.LocalDate import timber.log.Timber diff --git a/android/mediaprovider/jellyfin/build.gradle b/android/mediaprovider/jellyfin/build.gradle index 48d2930a4..916a7b23b 100644 --- a/android/mediaprovider/jellyfin/build.gradle +++ b/android/mediaprovider/jellyfin/build.gradle @@ -5,11 +5,11 @@ plugins { } android { - compileSdk 35 + compileSdk 36 defaultConfig { - minSdkVersion 21 - targetSdkVersion 35 + minSdkVersion 23 + targetSdkVersion 36 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -26,6 +26,12 @@ android { buildFeatures.buildConfig true } +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class).configureEach { + compilerOptions { + optIn.add('kotlin.time.ExperimentalTime') + } +} + dependencies { implementation libs.androidx.appcompat diff --git a/android/mediaprovider/jellyfin/src/main/java/com/simplecityapps/provider/jellyfin/JellyfinMediaProvider.kt b/android/mediaprovider/jellyfin/src/main/java/com/simplecityapps/provider/jellyfin/JellyfinMediaProvider.kt index e66991be0..cd5278e1f 100644 --- a/android/mediaprovider/jellyfin/src/main/java/com/simplecityapps/provider/jellyfin/JellyfinMediaProvider.kt +++ b/android/mediaprovider/jellyfin/src/main/java/com/simplecityapps/provider/jellyfin/JellyfinMediaProvider.kt @@ -20,6 +20,7 @@ import com.simplecityapps.shuttle.model.MediaProviderType import com.simplecityapps.shuttle.model.Playlist import com.simplecityapps.shuttle.model.Song import kotlin.math.min +import kotlin.time.Clock import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow @@ -30,7 +31,6 @@ import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import kotlinx.datetime.Clock import kotlinx.datetime.LocalDate import timber.log.Timber diff --git a/android/mediaprovider/local/build.gradle b/android/mediaprovider/local/build.gradle index e8fc9cfd7..10c162495 100644 --- a/android/mediaprovider/local/build.gradle +++ b/android/mediaprovider/local/build.gradle @@ -3,11 +3,11 @@ apply plugin: 'kotlin-android' apply plugin: 'com.google.devtools.ksp' android { - compileSdk 35 + compileSdk 36 defaultConfig { - minSdkVersion 21 - targetSdkVersion 35 + minSdkVersion 23 + targetSdkVersion 36 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -35,6 +35,12 @@ android { buildFeatures.buildConfig true } +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class).configureEach { + compilerOptions { + optIn.add('kotlin.time.ExperimentalTime') + } +} + dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) diff --git a/android/mediaprovider/local/src/main/java/com/simplecityapps/localmediaprovider/local/data/room/dao/PlaylistDataDao.kt b/android/mediaprovider/local/src/main/java/com/simplecityapps/localmediaprovider/local/data/room/dao/PlaylistDataDao.kt index 5c266263f..9b8646ffd 100644 --- a/android/mediaprovider/local/src/main/java/com/simplecityapps/localmediaprovider/local/data/room/dao/PlaylistDataDao.kt +++ b/android/mediaprovider/local/src/main/java/com/simplecityapps/localmediaprovider/local/data/room/dao/PlaylistDataDao.kt @@ -66,7 +66,7 @@ data class PlaylistEntity( val id: Long, val name: String, val songCount: Int, - val duration: Int, + val duration: Int?, val sortOrder: PlaylistSongSortOrder, val mediaProvider: MediaProviderType, val externalId: String? @@ -76,7 +76,7 @@ fun PlaylistEntity.toPlaylist(): Playlist = Playlist( id = id, name = name, songCount = songCount, - duration = duration, + duration = duration ?: 0, sortOrder = sortOrder, mediaProvider = mediaProvider, externalId = externalId diff --git a/android/mediaprovider/plex/build.gradle b/android/mediaprovider/plex/build.gradle index 7ad251d7f..4729733d9 100644 --- a/android/mediaprovider/plex/build.gradle +++ b/android/mediaprovider/plex/build.gradle @@ -5,11 +5,11 @@ plugins { } android { - compileSdk 35 + compileSdk 36 defaultConfig { - minSdkVersion 21 - targetSdkVersion 35 + minSdkVersion 23 + targetSdkVersion 36 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -24,6 +24,12 @@ android { namespace 'com.simplecityapps.provider.plex' } +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class).configureEach { + compilerOptions { + optIn.add('kotlin.time.ExperimentalTime') + } +} + dependencies { implementation libs.androidx.appcompat diff --git a/android/mediaprovider/plex/src/main/java/com/simplecityapps/provider/plex/PlexMediaProvider.kt b/android/mediaprovider/plex/src/main/java/com/simplecityapps/provider/plex/PlexMediaProvider.kt index 2ff4ae0d3..a0cef6fec 100644 --- a/android/mediaprovider/plex/src/main/java/com/simplecityapps/provider/plex/PlexMediaProvider.kt +++ b/android/mediaprovider/plex/src/main/java/com/simplecityapps/provider/plex/PlexMediaProvider.kt @@ -13,10 +13,10 @@ import com.simplecityapps.provider.plex.http.sections import com.simplecityapps.shuttle.model.MediaProviderType import com.simplecityapps.shuttle.model.Playlist import com.simplecityapps.shuttle.model.Song +import kotlin.time.Clock import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf -import kotlinx.datetime.Clock import kotlinx.datetime.LocalDate import timber.log.Timber diff --git a/android/networking/build.gradle b/android/networking/build.gradle index a196b2305..aebcd56cf 100644 --- a/android/networking/build.gradle +++ b/android/networking/build.gradle @@ -5,11 +5,11 @@ plugins { } android { - compileSdk 35 + compileSdk 36 defaultConfig { minSdkVersion 21 - targetSdkVersion 35 + targetSdkVersion 36 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" diff --git a/android/playback/build.gradle b/android/playback/build.gradle index 3688dac04..72a8308dd 100644 --- a/android/playback/build.gradle +++ b/android/playback/build.gradle @@ -4,11 +4,11 @@ apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'com.google.devtools.ksp' android { - compileSdk 35 + compileSdk 36 defaultConfig { minSdkVersion 21 - targetSdkVersion 35 + targetSdkVersion 36 consumerProguardFiles("consumer-rules.pro") } compileOptions { diff --git a/android/recyclerview-adapter/build.gradle b/android/recyclerview-adapter/build.gradle index 2b232efee..288c53a08 100644 --- a/android/recyclerview-adapter/build.gradle +++ b/android/recyclerview-adapter/build.gradle @@ -2,11 +2,11 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdk 35 + compileSdk 36 defaultConfig { minSdkVersion 21 - targetSdkVersion 35 + targetSdkVersion 36 consumerProguardFiles("consumer-rules.pro") } compileOptions { diff --git a/android/remote-config/build.gradle.kts b/android/remote-config/build.gradle.kts index b024a6226..0212e16a1 100644 --- a/android/remote-config/build.gradle.kts +++ b/android/remote-config/build.gradle.kts @@ -6,10 +6,10 @@ plugins { } android { - compileSdk = 35 + compileSdk = 36 defaultConfig { - minSdk = 21 + minSdk = 23 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") diff --git a/android/remote-config/src/main/java/com/simplecityapps/shuttle/remote_config/RemoteConfigModule.kt b/android/remote-config/src/main/java/com/simplecityapps/shuttle/remote_config/RemoteConfigModule.kt index 1f21f49e0..2dbc28412 100644 --- a/android/remote-config/src/main/java/com/simplecityapps/shuttle/remote_config/RemoteConfigModule.kt +++ b/android/remote-config/src/main/java/com/simplecityapps/shuttle/remote_config/RemoteConfigModule.kt @@ -1,9 +1,9 @@ package com.simplecityapps.shuttle.remote_config -import com.google.firebase.ktx.Firebase +import com.google.firebase.Firebase import com.google.firebase.remoteconfig.FirebaseRemoteConfig -import com.google.firebase.remoteconfig.ktx.remoteConfig -import com.google.firebase.remoteconfig.ktx.remoteConfigSettings +import com.google.firebase.remoteconfig.remoteConfig +import com.google.firebase.remoteconfig.remoteConfigSettings import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/android/saf/build.gradle b/android/saf/build.gradle index 125c8d190..98c363df7 100644 --- a/android/saf/build.gradle +++ b/android/saf/build.gradle @@ -4,11 +4,11 @@ plugins { } android { - compileSdk 35 + compileSdk 36 defaultConfig { minSdkVersion 21 - targetSdkVersion 35 + targetSdkVersion 36 consumerProguardFiles 'consumer-rules.pro' } diff --git a/android/trial/build.gradle b/android/trial/build.gradle index 4df12b7f6..baa596ecf 100644 --- a/android/trial/build.gradle +++ b/android/trial/build.gradle @@ -5,11 +5,11 @@ plugins { } android { - compileSdk 35 + compileSdk 36 defaultConfig { - minSdkVersion 21 - targetSdkVersion 35 + minSdkVersion 23 + targetSdkVersion 36 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -26,6 +26,12 @@ android { buildFeatures.buildConfig true } +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class).configureEach { + compilerOptions { + optIn.add('kotlin.time.ExperimentalTime') + } +} + dependencies { implementation libs.kotlin.stdlib diff --git a/android/trial/src/main/java/com/simplecityapps/trial/BillingManager.kt b/android/trial/src/main/java/com/simplecityapps/trial/BillingManager.kt index 214740d3c..555450354 100644 --- a/android/trial/src/main/java/com/simplecityapps/trial/BillingManager.kt +++ b/android/trial/src/main/java/com/simplecityapps/trial/BillingManager.kt @@ -7,12 +7,14 @@ import com.android.billingclient.api.BillingClient import com.android.billingclient.api.BillingClientStateListener import com.android.billingclient.api.BillingFlowParams import com.android.billingclient.api.BillingResult +import com.android.billingclient.api.PendingPurchasesParams +import com.android.billingclient.api.ProductDetails import com.android.billingclient.api.Purchase import com.android.billingclient.api.PurchasesResponseListener import com.android.billingclient.api.PurchasesUpdatedListener -import com.android.billingclient.api.SkuDetails -import com.android.billingclient.api.SkuDetailsParams -import com.android.billingclient.api.querySkuDetails +import com.android.billingclient.api.QueryProductDetailsParams +import com.android.billingclient.api.QueryPurchasesParams +import com.android.billingclient.api.queryProductDetails import com.simplecityapps.shuttle.di.AppCoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay @@ -50,7 +52,7 @@ class BillingManager( "s2_iap_full_version_low" ) - val skuDetails: MutableStateFlow> = MutableStateFlow(emptySet()) + val productDetails: MutableStateFlow> = MutableStateFlow(emptySet()) val billingState: MutableStateFlow = MutableStateFlow(BillingState.Unknown) @@ -103,7 +105,11 @@ class BillingManager( private val billingClient = BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) - .enablePendingPurchases() + .enablePendingPurchases( + PendingPurchasesParams.newBuilder() + .enableOneTimeProducts() + .build() + ) .build() fun start() { @@ -112,49 +118,65 @@ class BillingManager( fun launchPurchaseFlow( activity: FragmentActivity, - skuDetails: SkuDetails + productDetails: ProductDetails ) { if (!billingClient.isReady) { Timber.e("Failed to launch purchase flow: BillingClient not ready") return } + + val productDetailsParamsList = listOf( + BillingFlowParams.ProductDetailsParams.newBuilder() + .setProductDetails(productDetails) + .build() + ) + billingClient.launchBillingFlow( activity, - BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build() + BillingFlowParams.newBuilder() + .setProductDetailsParamsList(productDetailsParamsList) + .build() ) } - suspend fun querySkuDetails() { - val inAppPurchaseDetails = - SkuDetailsParams.newBuilder() - .setSkusList( - listOf( - "s2_iap_full_version", - "s2_iap_full_version_low" - ) - ) - .setType(BillingClient.SkuType.INAPP) + suspend fun queryProductDetails() { + val inAppProductList = listOf( + QueryProductDetailsParams.Product.newBuilder() + .setProductId("s2_iap_full_version") + .setProductType(BillingClient.ProductType.INAPP) + .build(), + QueryProductDetailsParams.Product.newBuilder() + .setProductId("s2_iap_full_version_low") + .setProductType(BillingClient.ProductType.INAPP) .build() + ) - val subscriptionDetails = - SkuDetailsParams.newBuilder() - .setSkusList( - listOf( - "s2_subscription_full_version_monthly", - "s2_subscription_full_version_yearly", - "s2_subscription_full_version_yearly_low" - ) - ) - .setType(BillingClient.SkuType.SUBS) + val subscriptionProductList = listOf( + QueryProductDetailsParams.Product.newBuilder() + .setProductId("s2_subscription_full_version_monthly") + .setProductType(BillingClient.ProductType.SUBS) + .build(), + QueryProductDetailsParams.Product.newBuilder() + .setProductId("s2_subscription_full_version_yearly") + .setProductType(BillingClient.ProductType.SUBS) + .build(), + QueryProductDetailsParams.Product.newBuilder() + .setProductId("s2_subscription_full_version_yearly_low") + .setProductType(BillingClient.ProductType.SUBS) .build() + ) - listOf(inAppPurchaseDetails, subscriptionDetails).forEach { skuDetailsParams -> - val skuDetailsResult = billingClient.querySkuDetails(skuDetailsParams) - when (skuDetailsResult.billingResult.responseCode) { + listOf(inAppProductList, subscriptionProductList).forEach { productList -> + val params = QueryProductDetailsParams.newBuilder() + .setProductList(productList) + .build() + + val productDetailsResult = billingClient.queryProductDetails(params) + when (productDetailsResult.billingResult.responseCode) { BillingClient.BillingResponseCode.OK -> { - skuDetails.value = - (skuDetails.value + skuDetailsResult.skuDetailsList.orEmpty()) - .sortedByDescending { skuDetails -> skuDetails.type } // Show subs first + productDetails.value = + (productDetails.value + productDetailsResult.productDetailsList.orEmpty()) + .sortedByDescending { it.productType } // Show subs first .toSet() } BillingClient.BillingResponseCode.SERVICE_DISCONNECTED, @@ -164,7 +186,7 @@ class BillingManager( BillingClient.BillingResponseCode.DEVELOPER_ERROR, BillingClient.BillingResponseCode.ERROR -> { - Timber.e("onSkuDetailsResponse: ${skuDetailsResult.billingResult.responseCode} ${skuDetailsResult.billingResult.debugMessage}") + Timber.e("onProductDetailsResponse: ${productDetailsResult.billingResult.responseCode} ${productDetailsResult.billingResult.debugMessage}") } BillingClient.BillingResponseCode.USER_CANCELED, BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED, @@ -172,7 +194,7 @@ class BillingManager( BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> { // These response codes are not expected. - Timber.e("onSkuDetailsResponse: ${skuDetailsResult.billingResult.responseCode} ${skuDetailsResult.billingResult.debugMessage}") + Timber.e("onProductDetailsResponse: ${productDetailsResult.billingResult.responseCode} ${productDetailsResult.billingResult.debugMessage}") } } } @@ -190,14 +212,22 @@ class BillingManager( processPurchases(purchases) } } - billingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, purchaseResponseListener) - billingClient.queryPurchasesAsync(BillingClient.SkuType.SUBS, purchaseResponseListener) + + val inAppParams = QueryPurchasesParams.newBuilder() + .setProductType(BillingClient.ProductType.INAPP) + .build() + billingClient.queryPurchasesAsync(inAppParams, purchaseResponseListener) + + val subsParams = QueryPurchasesParams.newBuilder() + .setProductType(BillingClient.ProductType.SUBS) + .build() + billingClient.queryPurchasesAsync(subsParams, purchaseResponseListener) } @Synchronized private fun processPurchases(purchases: List) { if (billingState.value == BillingState.Paid || - paidVersionSkus.intersect(purchases.flatMap { purchase -> purchase.skus }) + paidVersionSkus.intersect(purchases.flatMap { purchase -> purchase.products }) .isNotEmpty() ) { billingState.value = BillingState.Paid diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index face0589a..1ef3d70c8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,75 +1,75 @@ [versions] -aboutlibraries = "10.9.2" -android-device-names = "2.0.0" +aboutlibraries = "13.1.0" +android-device-names = "2.1.1" annotation = "1.9.1" -appcompat = "1.7.0" -agp = "8.10.1" -billing-ktx = "7.1.1" +appcompat = "1.7.1" +agp = "8.13.0" +billing-ktx = "8.0.0" circleindicator = "2.1.6" -compose = "2025.01.00" +compose = "2025.10.01" compose-lint-checks = "1.4.2" -detekt = "1.23.7" +detekt = "1.23.8" kotlinx-collections-immutable = "0.4.0" -ksp = "2.1.0-1.0.29" -constraintlayout = "2.2.0" -converter-moshi = "2.11.0" -core-ktx-version = "1.6.1" -desugar-jdk-libs = "2.1.4" -documentfile = "1.0.1" +ksp = "2.2.20-2.0.3" +constraintlayout = "2.2.1" +converter-moshi = "3.0.0" +core-ktx-version = "1.7.0" +desugar-jdk-libs = "2.1.5" +documentfile = "1.1.0" drawerlayout = "1.2.0" -espresso-core = "3.6.1" +espresso-core = "3.7.0" exoplayer-shuttle = "2.14.2-shuttle" -firebase-bom = "33.8.0" -firebase-crashlytics = "3.0.2" -fluent-system-icons = "1.1.137" -fragment-ktx = "1.8.5" -glide = "4.16.0" -google-services = "4.4.2" -hamcrest-library = "1.3" -hilt-android = "2.55" -hilt-compiler = "1.2.0" -hilt-work = "1.2.0" +firebase-bom = "34.5.0" +firebase-crashlytics = "3.0.6" +fluent-system-icons = "1.1.311" +fragment-ktx = "1.8.9" +glide = "5.0.5" +google-services = "4.4.4" +hamcrest-library = "3.0" +hilt-android = "2.57.2" +hilt-compiler = "1.3.0" +hilt-work = "1.3.0" junit = "4.13.2" -junit-version = "1.2.1" -kotlin = "2.1.0" -kotlinx-coroutines-android = "1.8.1" -kotlinx-coroutines-core = "1.8.1" -kotlinx-coroutines-play-services = "1.8.1" -kotlinx-datetime = "0.4.1" -ktaglib = "1.5" -leakcanary-android = "2.10" -lifecycle-common-java8 = "2.8.7" +junit-version = "1.3.0" +kotlin = "2.2.21" +kotlinx-coroutines-android = "1.10.2" +kotlinx-coroutines-core = "1.10.2" +kotlinx-coroutines-play-services = "1.10.2" +kotlinx-datetime = "0.7.1" +ktaglib = "1.6.1" +leakcanary-android = "2.14" +lifecycle-common-java8 = "2.9.4" lifecycle-extensions = "2.2.0" -lifecycle-runtime-ktx = "2.8.7" -lifecycle-viewmodel-compose = "2.8.7" -logging-interceptor = "4.12.0" -material = "1.12.0" -media = "1.7.0" -moshi = "1.15.1" -moshi-adapters = "1.15.0" -moshi-kotlin = "1.15.0" -moshi-kotlin-codegen = "1.15.0" +lifecycle-runtime-ktx = "2.9.4" +lifecycle-viewmodel-compose = "2.9.4" +logging-interceptor = "5.3.0" +material = "1.13.0" +media = "1.7.1" +moshi = "1.15.2" +moshi-adapters = "1.15.2" +moshi-kotlin = "1.15.2" +moshi-kotlin-codegen = "1.15.2" mpandroidchart = "3.1.0" nanohttpd-webserver = "2.3.1" -navigation-safe-args-gradle-plugin = "2.8.5" -navigation-ui-ktx = "2.8.5" -noise = "2.0.0" -okhttp = "4.12.0" -okhttp3-integration = "4.12.0" +navigation-safe-args-gradle-plugin = "2.9.5" +navigation-ui-ktx = "2.9.5" +noise = "3.0.3" +okhttp = "5.3.0" +okhttp3-integration = "5.0.5" palette-ktx = "1.0.0" phrase = "master-SNAPSHOT" -play-services-cast-framework = "22.0.0" +play-services-cast-framework = "22.2.0" preference-ktx = "1.2.1" recyclerview = "1.4.0" recyclerview-fastscroll = "2.0.1" review = "2.0.2" -room-compiler = "2.6.1" -runner = "1.6.2" -security-crypto = "1.1.0-alpha06" +room-compiler = "2.8.3" +runner = "1.7.0" +security-crypto = "1.1.0" semver4j = "3.1.0" -timber = "4.7.1" +timber = "5.0.1" viewpager2 = "1.1.0" -work-runtime-ktx = "2.10.0" +work-runtime-ktx = "2.11.0" [libraries] androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } @@ -117,9 +117,9 @@ exoplayer-core = { module = "com.github.timusus.exoplayer:exoplayer-core", versi exoplayer-extensionFlac = { module = "com.github.timusus.exoplayer:extension-flac", version.ref = "exoplayer-shuttle" } exoplayer-extensionOpus = { module = "com.github.timusus.exoplayer:extension-opus", version.ref = "exoplayer-shuttle" } exoplayer-hls = { module = "com.github.timusus.exoplayer:exoplayer-hls", version.ref = "exoplayer-shuttle" } -firebase-analytics-ktx = { module = "com.google.firebase:firebase-analytics-ktx" } +firebase-analytics-ktx = { module = "com.google.firebase:firebase-analytics" } firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" } -firebase-config-ktx = { module = "com.google.firebase:firebase-config-ktx" } +firebase-config-ktx = { module = "com.google.firebase:firebase-config" } firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" } glide-annotations = { module = "com.github.bumptech.glide:annotations", version.ref = "glide" } @@ -150,7 +150,7 @@ moshi-kotlinCodegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", vers nanohttpd-webserver = { module = "org.nanohttpd:nanohttpd-webserver", version.ref = "nanohttpd-webserver" } okhttp3-loggingInterceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "logging-interceptor" } okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } -paramsen-noise = { module = "com.github.paramsen:noise", version.ref = "noise" } +paramsen-noise = { module = "com.github.timusus:noise", version.ref = "noise" } philjay-mpAndroidChart = { module = "com.github.PhilJay:MPAndroidChart", version.ref = "mpandroidchart" } relex-circleindicator = { module = "me.relex:circleindicator", version.ref = "circleindicator" } retrofit2-converterMoshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "converter-moshi" }