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.
This commit is contained in:
darken
2025-07-03 00:14:58 +02:00
committed by Matthias Urhahn
parent 6b2073eff2
commit c97c8f4852
12 changed files with 75 additions and 32 deletions
@@ -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?) {
+4 -4
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()
} }
} }
@@ -85,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)
} }
} }
@@ -97,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)
} }
@@ -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"