From fcc28f41db18c49a46711ece9a5c61963a0682ed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=B3=E5=BE=B7=E7=94=9F?=
<111033412+KoukeNeko@users.noreply.github.com>
Date: Sun, 14 Dec 2025 11:47:36 +0800
Subject: [PATCH 01/15] Refactor: Extract PolicyAccessDialog into its own file
The `PolicyAccessDialog` `DialogFragment` has been moved from `MainActivity.kt` into its own dedicated file, `PolicyAccessDialog.kt`, to improve code organization.
In `MainActivity.kt`, `enableEdgeToEdge()` is now used, and the insets handling method has been renamed from `handleEdgeToEdgeInsets` to `setupWindowInsets` for clarity.
---
.../com/klee/volumelockr/ui/MainActivity.kt | 53 ++++++++++---------
.../klee/volumelockr/ui/PolicyAccessDialog.kt | 27 ++++++++++
2 files changed, 54 insertions(+), 26 deletions(-)
create mode 100644 app/src/main/java/com/klee/volumelockr/ui/PolicyAccessDialog.kt
diff --git a/app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt b/app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt
index 1c882c1..eabb90d 100644
--- a/app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt
+++ b/app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt
@@ -1,17 +1,16 @@
package com.klee.volumelockr.ui
-import android.app.AlertDialog
-import android.app.Dialog
import android.app.NotificationManager
-import android.content.Intent
import android.os.Build
import android.os.Bundle
-import android.provider.Settings
+import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
-import androidx.fragment.app.DialogFragment
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.ui.AppBarConfiguration
+import androidx.navigation.ui.setupWithNavController
import com.klee.volumelockr.R
import com.klee.volumelockr.databinding.ActivityMainBinding
@@ -20,10 +19,12 @@ class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
- handleEdgeToEdgeInsets()
+ setupToolbar()
+ setupWindowInsets()
}
override fun onResume() {
@@ -34,6 +35,15 @@ class MainActivity : AppCompatActivity() {
}
}
+ private fun setupToolbar() {
+ setSupportActionBar(binding.toolbar)
+ val navHostFragment = supportFragmentManager
+ .findFragmentById(R.id.fragment_container_view) as NavHostFragment
+ val navController = navHostFragment.navController
+ val appBarConfiguration = AppBarConfiguration(navController.graph)
+ binding.toolbar.setupWithNavController(navController, appBarConfiguration)
+ }
+
@RequiresApi(Build.VERSION_CODES.M)
private fun checkDoNotDisturbPermission() {
val notificationManager =
@@ -44,31 +54,22 @@ class MainActivity : AppCompatActivity() {
}
}
- class PolicyAccessDialog : DialogFragment() {
- companion object {
- const val TAG = "PolicyAccessDialog"
- }
-
- @RequiresApi(Build.VERSION_CODES.M)
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
- AlertDialog.Builder(requireContext())
- .setMessage(getString(R.string.dialog_policy_access_title))
- .setCancelable(false)
- .setPositiveButton(getString(R.string.dialog_allow)) { _, _ ->
- startActivity(Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS))
- }
- .create()
- }
-
- private fun handleEdgeToEdgeInsets() {
- ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets ->
+ private fun setupWindowInsets() {
+ ViewCompat.setOnApplyWindowInsetsListener(binding.appBarLayout) { v, windowInsets ->
val bars = windowInsets.getInsets(
WindowInsetsCompat.Type.systemBars()
or WindowInsetsCompat.Type.displayCutout()
)
+ v.setPadding(bars.left, bars.top, bars.right, 0)
+ WindowInsetsCompat.CONSUMED
+ }
- v.setPadding(bars.left, bars.top, bars.right, bars.bottom)
-
+ ViewCompat.setOnApplyWindowInsetsListener(binding.fragmentContainerView) { v, windowInsets ->
+ val bars = windowInsets.getInsets(
+ WindowInsetsCompat.Type.systemBars()
+ or WindowInsetsCompat.Type.displayCutout()
+ )
+ v.setPadding(bars.left, 0, bars.right, bars.bottom)
WindowInsetsCompat.CONSUMED
}
}
diff --git a/app/src/main/java/com/klee/volumelockr/ui/PolicyAccessDialog.kt b/app/src/main/java/com/klee/volumelockr/ui/PolicyAccessDialog.kt
new file mode 100644
index 0000000..4664c0e
--- /dev/null
+++ b/app/src/main/java/com/klee/volumelockr/ui/PolicyAccessDialog.kt
@@ -0,0 +1,27 @@
+package com.klee.volumelockr.ui
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.provider.Settings
+import androidx.annotation.RequiresApi
+import androidx.fragment.app.DialogFragment
+import com.klee.volumelockr.R
+
+class PolicyAccessDialog : DialogFragment() {
+ companion object {
+ const val TAG = "PolicyAccessDialog"
+ }
+
+ @RequiresApi(Build.VERSION_CODES.M)
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
+ AlertDialog.Builder(requireContext())
+ .setMessage(getString(R.string.dialog_policy_access_title))
+ .setCancelable(false)
+ .setPositiveButton(getString(R.string.dialog_allow)) { _, _ ->
+ startActivity(Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS))
+ }
+ .create()
+}
From 6fbb698865c86da8bd488ea9fcb1aae1592f47e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=B3=E5=BE=B7=E7=94=9F?=
<111033412+KoukeNeko@users.noreply.github.com>
Date: Sun, 14 Dec 2025 11:58:17 +0800
Subject: [PATCH 02/15] feat: Add bottom navigation
Adds a `BottomNavigationView` to the main screen for easier navigation between the main volume sliders, settings, and the about page.
This includes:
- Adding a `MaterialToolbar` and `BottomNavigationView` to `activity_main.xml`.
- Creating a new `bottom_nav_menu.xml` with icons for home, settings, and info.
- Updating `MainActivity.kt` to set up the toolbar and bottom navigation with the navigation controller.
- Removing the now-redundant options menu from `VolumeSliderFragment`.
- Applying a `NoActionBar` theme to the activity.
---
.../com/klee/volumelockr/ui/MainActivity.kt | 11 ++--
.../volumelockr/ui/VolumeSliderFragment.kt | 50 -------------------
app/src/main/res/drawable/ic_home.xml | 9 ++++
app/src/main/res/drawable/ic_info.xml | 9 ++++
app/src/main/res/drawable/ic_settings.xml | 9 ++++
app/src/main/res/layout/activity_main.xml | 30 ++++++++++-
app/src/main/res/menu/bottom_nav_menu.xml | 17 +++++++
app/src/main/res/values/themes.xml | 3 +-
8 files changed, 81 insertions(+), 57 deletions(-)
create mode 100644 app/src/main/res/drawable/ic_home.xml
create mode 100644 app/src/main/res/drawable/ic_info.xml
create mode 100644 app/src/main/res/drawable/ic_settings.xml
create mode 100644 app/src/main/res/menu/bottom_nav_menu.xml
diff --git a/app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt b/app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt
index eabb90d..ff6c0a1 100644
--- a/app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt
+++ b/app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt
@@ -23,7 +23,7 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
- setupToolbar()
+ setupNavigation()
setupWindowInsets()
}
@@ -35,13 +35,16 @@ class MainActivity : AppCompatActivity() {
}
}
- private fun setupToolbar() {
+ private fun setupNavigation() {
setSupportActionBar(binding.toolbar)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.fragment_container_view) as NavHostFragment
val navController = navHostFragment.navController
- val appBarConfiguration = AppBarConfiguration(navController.graph)
+ val appBarConfiguration = AppBarConfiguration(
+ setOf(R.id.volumeSliderFragment, R.id.settingsFragment, R.id.about_libraries)
+ )
binding.toolbar.setupWithNavController(navController, appBarConfiguration)
+ binding.bottomNavigation.setupWithNavController(navController)
}
@RequiresApi(Build.VERSION_CODES.M)
@@ -64,7 +67,7 @@ class MainActivity : AppCompatActivity() {
WindowInsetsCompat.CONSUMED
}
- ViewCompat.setOnApplyWindowInsetsListener(binding.fragmentContainerView) { v, windowInsets ->
+ ViewCompat.setOnApplyWindowInsetsListener(binding.bottomNavigation) { v, windowInsets ->
val bars = windowInsets.getInsets(
WindowInsetsCompat.Type.systemBars()
or WindowInsetsCompat.Type.displayCutout()
diff --git a/app/src/main/java/com/klee/volumelockr/ui/VolumeSliderFragment.kt b/app/src/main/java/com/klee/volumelockr/ui/VolumeSliderFragment.kt
index 9b60e35..fe2fd36 100644
--- a/app/src/main/java/com/klee/volumelockr/ui/VolumeSliderFragment.kt
+++ b/app/src/main/java/com/klee/volumelockr/ui/VolumeSliderFragment.kt
@@ -9,17 +9,10 @@ import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.view.LayoutInflater
-import android.view.Menu
-import android.view.MenuInflater
-import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
-import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
-import com.klee.volumelockr.R
import com.klee.volumelockr.databinding.FragmentVolumeSliderBinding
import com.klee.volumelockr.service.VolumeService
@@ -36,16 +29,10 @@ class VolumeSliderFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
- setHasOptionsMenu(true)
_binding = FragmentVolumeSliderBinding.inflate(inflater, container, false)
return binding.root
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- handleEdgeToEdgeInsets()
- }
-
override fun onResume() {
super.onResume()
mService?.let {
@@ -67,49 +54,12 @@ class VolumeSliderFragment : Fragment() {
super.onDestroyView()
}
- override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
- val inflater: MenuInflater = menuInflater
- inflater.inflate(R.menu.options, menu)
- super.onCreateOptionsMenu(menu, menuInflater)
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.about -> findNavController().navigate(R.id.action_x_to_about_libs)
- R.id.options -> findNavController().navigate(R.id.action_sliders_to_settings)
- }
- return true
- }
-
private fun setupRecyclerView(service: VolumeService) {
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
mAdapter = VolumeAdapter(service.getVolumes(), service, requireContext())
binding.recyclerView.adapter = mAdapter
}
- private fun handleEdgeToEdgeInsets() {
- val recyclerView = binding.recyclerView
- val startPadding = recyclerView.paddingLeft
- val topPadding = recyclerView.paddingTop
- val endPadding = recyclerView.paddingRight
- val bottomPadding = recyclerView.paddingBottom
-
- ViewCompat.setOnApplyWindowInsetsListener(recyclerView) { v, insets ->
- val bars = insets.getInsets(
- WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
- )
-
- v.setPadding(
- startPadding + bars.left,
- topPadding + bars.top,
- endPadding + bars.right,
- bottomPadding + bars.bottom
- )
-
- WindowInsetsCompat.CONSUMED
- }
- }
-
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName?, service: IBinder?) {
diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml
new file mode 100644
index 0000000..58cc391
--- /dev/null
+++ b/app/src/main/res/drawable/ic_home.xml
@@ -0,0 +1,9 @@
+