Compare commits

...
Author SHA1 Message Date
darken 65f9e1d9db Release: 2.17.6-rc0 2025-07-03 00:19:40 +02:00
darken c97c8f4852 Refactor: Introduce EdgeToEdgeHelper for consistent inset handling
This commit introduces an `EdgeToEdgeHelper` class to centralize and simplify the application of window insets for edge-to-edge display.

Key changes:
- Created `EdgeToEdgeHelper` to manage padding based on system bar insets.
- Migrated various Fragments (`OverviewFragment`, `OnboardingFragment`, `SettingsFragment`, `TroubleShooterFragment`) to use `EdgeToEdgeHelper` for consistent padding.
- Removed manual inset handling from `Activity2` and `Fragment2` as `EdgeToEdgeHelper` now manages this.
- Enabled edge-to-edge display in `MainActivity`.
- Minor updates to `BillingClientConnection` for product details fetching and `build.gradle.kts` for configuration.
2025-07-03 00:14:58 +02:00
darken 6b2073eff2 Update AGP, SDK, and BillingClient
This commit updates:
- Android Gradle Plugin to 8.11.0
- Compile/Target SDK to 36
- Google Play Billing Library to 8.0.0

The `BillingClient` is updated to use the new `PendingPurchasesParams` and `QueryPurchasesParams` APIs.
2025-07-03 00:14:58 +02:00
19 changed files with 100 additions and 42 deletions
+1 -1
View File
@@ -1 +1 @@
2.17.5-rc0 21705000 2.17.6-rc0 21706000
@@ -3,7 +3,6 @@ package eu.darken.capod.common.uix
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.log
@@ -16,8 +15,6 @@ abstract class Activity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
log(tag, VERBOSE) { "onCreate(savedInstanceState=$savedInstanceState)" } log(tag, VERBOSE) { "onCreate(savedInstanceState=$savedInstanceState)" }
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows( window, false )
} }
override fun onResume() { override fun onResume() {
@@ -7,8 +7,6 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.log
@@ -42,17 +40,6 @@ abstract class Fragment2(@LayoutRes val layoutRes: Int?) : Fragment(layoutRes ?:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
log(tag, VERBOSE) { "onViewCreated(view=$view, savedInstanceState=$savedInstanceState)" } log(tag, VERBOSE) { "onViewCreated(view=$view, savedInstanceState=$savedInstanceState)" }
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
val systemWindowInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(
v.paddingLeft,
systemWindowInsets.top,
v.paddingRight,
0
)
insets
}
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
+6 -6
View File
@@ -10,7 +10,7 @@ apply(plugin = "androidx.navigation.safeargs.kotlin")
android { android {
compileSdk = ProjectConfig.compileSdk compileSdk = ProjectConfig.compileSdk
namespace = "${ProjectConfig.packageName}" namespace = ProjectConfig.packageName
defaultConfig { defaultConfig {
applicationId = ProjectConfig.packageName applicationId = ProjectConfig.packageName
@@ -122,9 +122,9 @@ android {
unitTests { unitTests {
isIncludeAndroidResources = true isIncludeAndroidResources = true
} }
tasks.withType<Test> { }
useJUnitPlatform() tasks.withType<Test> {
} useJUnitPlatform()
} }
} }
@@ -151,6 +151,6 @@ dependencies {
addTesting() addTesting()
"gplayImplementation"("com.android.billingclient:billing:7.1.1") "gplayImplementation"("com.android.billingclient:billing:8.0.0")
"gplayImplementation"("com.android.billingclient:billing-ktx:7.1.1") "gplayImplementation"("com.android.billingclient:billing-ktx:8.0.0")
} }
@@ -8,6 +8,7 @@ import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ProductDetails import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.Purchase import com.android.billingclient.api.Purchase
import com.android.billingclient.api.QueryProductDetailsParams import com.android.billingclient.api.QueryProductDetailsParams
import com.android.billingclient.api.QueryPurchasesParams
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.log
@@ -39,8 +40,12 @@ data class BillingClientConnection(
.setupCommonEventHandlers(TAG) { "purchases" } .setupCommonEventHandlers(TAG) { "purchases" }
suspend fun queryPurchases(): Collection<Purchase> { suspend fun queryPurchases(): Collection<Purchase> {
val params = QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.INAPP)
.build()
val (result: BillingResult, purchases) = suspendCoroutine<Pair<BillingResult, Collection<Purchase>?>> { continuation -> val (result: BillingResult, purchases) = suspendCoroutine<Pair<BillingResult, Collection<Purchase>?>> { continuation ->
client.queryPurchasesAsync(BillingClient.SkuType.INAPP) { result, purchases -> client.queryPurchasesAsync(params) { result, purchases ->
continuation.resume(result to purchases) continuation.resume(result to purchases)
} }
} }
@@ -80,9 +85,10 @@ data class BillingClientConnection(
val params = QueryProductDetailsParams.newBuilder().setProductList(listOf(productDetails)).build() val params = QueryProductDetailsParams.newBuilder().setProductList(listOf(productDetails)).build()
val (result, details) = suspendCoroutine<Pair<BillingResult, Collection<ProductDetails>?>> { continuation -> val (result, details) = suspendCoroutine<Pair<BillingResult, List<ProductDetails>>> { continuation ->
client.queryProductDetailsAsync(params) { result, skuDetails -> client.queryProductDetailsAsync(params) { billingResult, queryResult ->
continuation.resume(result to skuDetails) val productDetailsList = queryResult.productDetailsList ?: emptyList()
continuation.resume(billingResult to productDetailsList)
} }
} }
@@ -92,7 +98,7 @@ data class BillingClientConnection(
if (!result.isSuccess) throw BillingResultException(result) if (!result.isSuccess) throw BillingResultException(result)
if (details.isNullOrEmpty()) throw IllegalStateException("Unknown SKU, no details available.") if (details.isEmpty()) throw IllegalStateException("Unknown SKU, no details available.")
return Sku.Details(sku, details) return Sku.Details(sku, details)
} }
@@ -5,6 +5,7 @@ import com.android.billingclient.api.BillingClient.BillingResponseCode
import com.android.billingclient.api.BillingClient.newBuilder import com.android.billingclient.api.BillingClient.newBuilder
import com.android.billingclient.api.BillingClientStateListener import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingResult import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.PendingPurchasesParams
import com.android.billingclient.api.Purchase import com.android.billingclient.api.Purchase
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.common.debug.logging.Logging.Priority.* import eu.darken.capod.common.debug.logging.Logging.Priority.*
@@ -33,7 +34,11 @@ class BillingClientConnectionProvider @Inject constructor(
val purchasePublisher = MutableStateFlow<Collection<Purchase>>(emptySet()) val purchasePublisher = MutableStateFlow<Collection<Purchase>>(emptySet())
val client = newBuilder(context).apply { val client = newBuilder(context).apply {
enablePendingPurchases() enablePendingPurchases(
PendingPurchasesParams.newBuilder()
.enableOneTimeProducts()
.build()
)
setListener { result, purchases -> setListener { result, purchases ->
if (result.isSuccess) { if (result.isSuccess) {
log(TAG) { log(TAG) {
@@ -0,0 +1,33 @@
package eu.darken.capod.common
import android.app.Activity
import android.view.View
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import eu.darken.capod.common.debug.logging.logTag
class EdgeToEdgeHelper(activity: Activity) {
private val tag = logTag("EdgeToEdge", "$activity")
fun insetsPadding(
view: View,
left: Boolean = false,
top: Boolean = false,
right: Boolean = false,
bottom: Boolean = false,
) {
ViewCompat.setOnApplyWindowInsetsListener(view) { v: View, insets: WindowInsetsCompat ->
val systemBars: Insets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(
if (left) systemBars.left else v.paddingLeft,
if (top) systemBars.top else v.paddingTop,
if (right) systemBars.right else v.paddingRight,
if (bottom) systemBars.bottom else v.paddingBottom,
)
insets
}
}
}
@@ -1,6 +1,7 @@
package eu.darken.capod.main.ui package eu.darken.capod.main.ui
import android.os.Bundle import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@@ -17,9 +18,9 @@ class MainActivity : Activity2() {
private val navController by lazy { supportFragmentManager.findNavController(R.id.nav_host) } private val navController by lazy { supportFragmentManager.findNavController(R.id.nav_host) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
installSplashScreen()
enableEdgeToEdge()
ui = MainActivityBinding.inflate(layoutInflater) ui = MainActivityBinding.inflate(layoutInflater)
setContentView(ui.root) setContentView(ui.root)
@@ -5,6 +5,7 @@ import android.view.View
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import eu.darken.capod.R import eu.darken.capod.R
import eu.darken.capod.common.EdgeToEdgeHelper
import eu.darken.capod.common.PrivacyPolicy import eu.darken.capod.common.PrivacyPolicy
import eu.darken.capod.common.WebpageTool import eu.darken.capod.common.WebpageTool
import eu.darken.capod.common.uix.Fragment3 import eu.darken.capod.common.uix.Fragment3
@@ -22,6 +23,9 @@ class OnboardingFragment : Fragment3(R.layout.onboarding_fragment) {
@Inject lateinit var webpageTool: WebpageTool @Inject lateinit var webpageTool: WebpageTool
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
EdgeToEdgeHelper(requireActivity()).apply {
insetsPadding(ui.root, left = true, right = true, top = true, bottom = true)
}
ui.goPrivacyPolicy.setOnClickListener { webpageTool.open(PrivacyPolicy.URL) } ui.goPrivacyPolicy.setOnClickListener { webpageTool.open(PrivacyPolicy.URL) }
ui.continueAction.setOnClickListener { vm.finishOnboarding() } ui.continueAction.setOnClickListener { vm.finishOnboarding() }
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@@ -8,12 +8,11 @@ import android.text.SpannableStringBuilder
import android.view.View import android.view.View
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import eu.darken.capod.BuildConfig import eu.darken.capod.BuildConfig
import eu.darken.capod.R import eu.darken.capod.R
import eu.darken.capod.common.EdgeToEdgeHelper
import eu.darken.capod.common.colorString import eu.darken.capod.common.colorString
import eu.darken.capod.common.debug.logging.log import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.lists.differ.update import eu.darken.capod.common.lists.differ.update
@@ -49,6 +48,11 @@ class OverviewFragment : Fragment3(R.layout.main_fragment) {
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
EdgeToEdgeHelper(requireActivity()).apply {
insetsPadding(ui.root, left = true, right = true)
insetsPadding(ui.toolbar, top = true)
insetsPadding(ui.list, bottom = false)
}
ui.apply { ui.apply {
list.setupDefaults(adapter, dividers = false) list.setupDefaults(adapter, dividers = false)
} }
@@ -60,14 +64,17 @@ class OverviewFragment : Fragment3(R.layout.main_fragment) {
vm.goToSettings() vm.goToSettings()
true true
} }
R.id.menu_item_donate -> { R.id.menu_item_donate -> {
vm.onUpgrade() vm.onUpgrade()
true true
} }
R.id.menu_item_upgrade -> { R.id.menu_item_upgrade -> {
vm.onUpgrade() vm.onUpgrade()
true true
} }
else -> false else -> false
} }
} }
@@ -93,6 +100,7 @@ class OverviewFragment : Fragment3(R.layout.main_fragment) {
) )
) )
} }
Permission.SYSTEM_ALERT_WINDOW -> { Permission.SYSTEM_ALERT_WINDOW -> {
awaitingPermission = true awaitingPermission = true
startActivity( startActivity(
@@ -102,6 +110,7 @@ class OverviewFragment : Fragment3(R.layout.main_fragment) {
) )
) )
} }
else -> { else -> {
permissionLauncher.launch(it.permissionId) permissionLauncher.launch(it.permissionId)
} }
@@ -123,6 +132,7 @@ class OverviewFragment : Fragment3(R.layout.main_fragment) {
getString(eu.darken.capod.common.R.string.app_name) getString(eu.darken.capod.common.R.string.app_name)
} }
} }
UpgradeRepo.Type.FOSS -> { UpgradeRepo.Type.FOSS -> {
if (info.isPro) { if (info.isPro) {
getString(eu.darken.capod.common.R.string.app_name_foss) getString(eu.darken.capod.common.R.string.app_name_foss)
@@ -2,10 +2,7 @@ package eu.darken.capod.main.ui.settings
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.ListView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.preference.Preference import androidx.preference.Preference
@@ -13,6 +10,7 @@ import androidx.preference.PreferenceFragmentCompat
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import eu.darken.capod.R import eu.darken.capod.R
import eu.darken.capod.common.BuildConfigWrap import eu.darken.capod.common.BuildConfigWrap
import eu.darken.capod.common.EdgeToEdgeHelper
import eu.darken.capod.common.uix.Fragment2 import eu.darken.capod.common.uix.Fragment2
import eu.darken.capod.common.viewbinding.viewBinding import eu.darken.capod.common.viewbinding.viewBinding
import eu.darken.capod.databinding.SettingsFragmentBinding import eu.darken.capod.databinding.SettingsFragmentBinding
@@ -38,6 +36,11 @@ class SettingsFragment : Fragment2(R.layout.settings_fragment),
) : Parcelable ) : Parcelable
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
EdgeToEdgeHelper(requireActivity()).apply {
insetsPadding(ui.root, left = true, right = true)
insetsPadding(ui.toolbar, top = true)
insetsPadding(ui.contentFrame, bottom = true)
}
childFragmentManager.addOnBackStackChangedListener { childFragmentManager.addOnBackStackChangedListener {
val backStackCnt = childFragmentManager.backStackEntryCount val backStackCnt = childFragmentManager.backStackEntryCount
val newScreenInfo = when { val newScreenInfo = when {
@@ -8,6 +8,7 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import eu.darken.capod.R import eu.darken.capod.R
import eu.darken.capod.common.EdgeToEdgeHelper
import eu.darken.capod.common.WebpageTool import eu.darken.capod.common.WebpageTool
import eu.darken.capod.common.navigation.popBackStack import eu.darken.capod.common.navigation.popBackStack
import eu.darken.capod.common.uix.Fragment3 import eu.darken.capod.common.uix.Fragment3
@@ -26,6 +27,11 @@ class TroubleShooterFragment : Fragment3(R.layout.troubleshooter_fragment) {
@Inject lateinit var webpageTool: WebpageTool @Inject lateinit var webpageTool: WebpageTool
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
EdgeToEdgeHelper(requireActivity()).apply {
insetsPadding(ui.root, left = true, right = true)
insetsPadding(ui.toolbar, top = true)
insetsPadding(ui.scrollView, bottom = true)
}
ui.toolbar.apply { ui.toolbar.apply {
setupWithNavController(findNavController()) setupWithNavController(findNavController())
} }
@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="32dp"> android:layout_margin="32dp">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -18,6 +18,7 @@
<ScrollView <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:id="@+id/scrollView"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
+1 -1
View File
@@ -8,7 +8,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath("com.android.tools.build:gradle:8.10.1") classpath("com.android.tools.build:gradle:8.11.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.Kotlin.core}") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.Kotlin.core}")
classpath("com.google.dagger:hilt-android-gradle-plugin:${Versions.Dagger.core}") classpath("com.google.dagger:hilt-android-gradle-plugin:${Versions.Dagger.core}")
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.AndroidX.Navigation.core}") classpath("androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.AndroidX.Navigation.core}")
+1 -1
View File
@@ -8,7 +8,7 @@ repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
implementation("com.android.tools.build:gradle:8.10.1") implementation("com.android.tools.build:gradle:8.11.0")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.21") implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.21")
implementation("com.squareup:javapoet:1.13.0") implementation("com.squareup:javapoet:1.13.0")
} }
+2 -2
View File
@@ -11,8 +11,8 @@ object ProjectConfig {
const val packageName = "eu.darken.capod" const val packageName = "eu.darken.capod"
const val minSdk = 26 const val minSdk = 26
const val compileSdk = 34 const val compileSdk = 36
const val targetSdk = 34 const val targetSdk = 36
object Version { object Version {
val versionProperties = Properties().apply { val versionProperties = Properties().apply {
@@ -0,0 +1,5 @@
🐛 Bug fixes, 🚀 performance boosts, maybe even ✨ new features.
Changelog: https://capod.darken.eu/changelog
FYI: Its just me here — thanks for understanding if replies take a bit. ¯\_(ツ)_/¯
+1 -1
View File
@@ -1,6 +1,6 @@
### Updated by release.sh ### ### Updated by release.sh ###
project.versioning.major=2 project.versioning.major=2
project.versioning.minor=17 project.versioning.minor=17
project.versioning.patch=5 project.versioning.patch=6
project.versioning.build=0 project.versioning.build=0
############################# #############################