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" }