Files
notifications-master/app/src/main/java/se/ajpanton/notificationlog/MainActivity.kt
T

275 lines
11 KiB
Kotlin

package se.ajpanton.notificationlog
import android.os.Bundle
import android.content.res.Configuration
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.Insets
import androidx.core.view.GravityCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.commit
import com.google.android.material.navigation.NavigationView
import se.ajpanton.notificationlog.databinding.ActivityMainBinding
import se.ajpanton.notificationlog.settings.LoggingType
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
private lateinit var binding: ActivityMainBinding
private lateinit var drawerToggle: ActionBarDrawerToggle
private var permanentSidebar = false
private var currentItemId = R.id.nav_view_logs
private var drawerNavigationBasePaddingLeft: Int? = null
private var permanentNavigationBasePaddingLeft: Int? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Keep every app surface below the system bars rather than relying on edge-to-edge drawing.
WindowCompat.setDecorFitsSystemWindows(window, true)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val isNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
Configuration.UI_MODE_NIGHT_YES
WindowInsetsControllerCompat(window, binding.root).isAppearanceLightNavigationBars = !isNightMode
setSupportActionBar(binding.toolbar)
setupNavigationView(binding.navViewDrawer)
setupNavigationView(binding.navViewPermanent)
drawerToggle = ActionBarDrawerToggle(
this,
binding.drawerLayout,
binding.toolbar,
R.string.navigation_open,
R.string.navigation_close,
)
binding.drawerLayout.addDrawerListener(drawerToggle)
binding.toolbar.setNavigationOnClickListener {
if (!permanentSidebar) {
binding.drawerLayout.openDrawer(GravityCompat.START)
}
}
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (!permanentSidebar && binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
return
}
isEnabled = false
onBackPressedDispatcher.onBackPressed()
isEnabled = true
}
})
currentItemId = savedInstanceState?.getInt(STATE_CURRENT_ITEM_ID) ?: R.id.nav_view_logs
navigateTo(currentItemId)
applyNavigationDrawerInsets()
syncDrawerToggle()
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(STATE_CURRENT_ITEM_ID, currentItemId)
super.onSaveInstanceState(outState)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.nav_settings_header) {
return false
}
navigateTo(item.itemId)
if (!permanentSidebar) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
}
return true
}
private fun navigateTo(itemId: Int) {
currentItemId = itemId
syncDrawerSelection(itemId)
val page = Page.fromMenuItem(itemId)
supportFragmentManager.commit {
replace(
R.id.content_frame,
when {
page == Page.VIEW_LOGS -> ViewLogsFragment()
page.loggingType != null -> EventSettingsFragment.newInstance(page.titleRes, page.loggingType)
else -> PageFragment.newInstance(page.titleRes)
},
)
runOnCommit(::applyPageTitleVisibility)
}
title = getString(page.titleRes)
}
private fun setupNavigationView(navigationView: NavigationView) {
navigationView.setNavigationItemSelectedListener(this)
navigationView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
updateNavigationHeaderHeight(navigationView)
}
}
private fun syncDrawerSelection(itemId: Int) {
syncNavigationSelection(binding.navViewDrawer, itemId)
syncNavigationSelection(binding.navViewPermanent, itemId)
}
private fun syncNavigationSelection(navigationView: NavigationView, itemId: Int) {
val menu = navigationView.menu ?: return
for (index in 0 until menu.size()) {
val item = menu.getItem(index)
if (item.itemId == R.id.nav_settings_header) {
item.isCheckable = false
} else {
item.isCheckable = true
item.isChecked = item.itemId == itemId
}
}
navigationView.invalidate()
}
private fun applyNavigationDrawerInsets() {
binding.drawerLayout.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
applyNavigationDrawerMarginsFromRootInsets()
}
binding.drawerLayout.post(::applyNavigationDrawerMarginsFromRootInsets)
}
private fun applyNavigationDrawerMarginsFromRootInsets() {
val rootInsets = ViewCompat.getRootWindowInsets(binding.drawerLayout)
val safeInsets = rootInsets?.getInsetsIgnoringVisibility(
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout(),
) ?: Insets.NONE
applyNavigationDrawerSafeArea(safeInsets.left, safeInsets.right)
}
private fun applyNavigationDrawerSafeArea(leftInset: Int, rightInset: Int) {
val safeLeft = maxOf(0, leftInset, binding.drawerLayout.paddingLeft)
val safeRight = maxOf(0, rightInset, binding.drawerLayout.paddingRight)
binding.drawerLayout.setDrawerSafeInsets(safeLeft, safeRight)
drawerNavigationBasePaddingLeft = applyNavigationLeftPadding(
binding.navViewDrawer,
drawerNavigationBasePaddingLeft,
safeLeft,
)
permanentNavigationBasePaddingLeft = applyNavigationLeftPadding(
binding.navViewPermanent,
permanentNavigationBasePaddingLeft,
0,
)
updatePermanentSidebarMode()
}
private fun applyNavigationLeftPadding(
navigationView: NavigationView,
basePaddingLeft: Int?,
safeLeft: Int,
): Int {
val base = basePaddingLeft ?: navigationView.paddingLeft
val leftPadding = base + safeLeft
if (navigationView.paddingLeft != leftPadding || navigationView.translationX != 0f) {
navigationView.translationX = 0f
navigationView.setPadding(
leftPadding,
navigationView.paddingTop,
navigationView.paddingRight,
navigationView.paddingBottom,
)
navigationView.requestLayout()
}
return base
}
private fun updatePermanentSidebarMode() {
val rootWidth = binding.drawerLayout.width
if (rootWidth <= 0) {
return
}
val drawerWidth = resources.getDimensionPixelSize(R.dimen.navigation_drawer_width)
val foldedPortraitWidth = resources.getDimensionPixelSize(R.dimen.folded_portrait_width)
val shouldUsePermanentSidebar = rootWidth >= drawerWidth +
(foldedPortraitWidth * PERMANENT_SIDEBAR_FOLDED_WIDTH_MULTIPLIER).toInt()
if (permanentSidebar != shouldUsePermanentSidebar) {
permanentSidebar = shouldUsePermanentSidebar
drawerToggle.isDrawerIndicatorEnabled = !permanentSidebar
binding.toolbar.visibility = if (permanentSidebar) View.GONE else View.VISIBLE
if (permanentSidebar) {
binding.navViewPermanent.visibility = View.VISIBLE
binding.drawerLayout.closeDrawer(GravityCompat.START, false)
} else {
binding.drawerLayout.closeDrawer(GravityCompat.START, false)
binding.navViewPermanent.visibility = View.GONE
}
syncDrawerToggle()
}
if (permanentSidebar) {
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START)
binding.navViewDrawer.visibility = View.GONE
} else {
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START)
binding.navViewDrawer.visibility = View.VISIBLE
}
updateNavigationHeaderHeight(binding.navViewDrawer)
updateNavigationHeaderHeight(binding.navViewPermanent)
applyPageTitleVisibility()
}
private fun updateNavigationHeaderHeight(navigationView: NavigationView) {
if (navigationView.headerCount == 0 || navigationView.height <= 0) {
return
}
val menuRows = countVisibleMenuRows(navigationView.menu)
val menuHeight = menuRows * resources.getDimensionPixelSize(R.dimen.navigation_menu_item_height)
val desiredHeaderHeight = (navigationView.height - menuHeight).coerceIn(
resources.getDimensionPixelSize(R.dimen.drawer_header_min_height),
resources.getDimensionPixelSize(R.dimen.drawer_header_max_height),
)
val header = navigationView.getHeaderView(0) ?: return
if (header.layoutParams.height != desiredHeaderHeight) {
header.layoutParams.height = desiredHeaderHeight
header.requestLayout()
}
}
private fun countVisibleMenuRows(menu: Menu): Int = (0 until menu.size()).count { menu.getItem(it).isVisible }
private fun applyPageTitleVisibility() {
(supportFragmentManager.findFragmentById(R.id.content_frame) as? PageFragment)
?.setPageTitleVisible(permanentSidebar)
}
private fun syncDrawerToggle() {
drawerToggle.syncState()
drawerToggle.drawerArrowDrawable.color = getColor(R.color.on_primary)
}
private enum class Page(
val menuId: Int,
val titleRes: Int,
val loggingType: LoggingType? = null,
) {
VIEW_LOGS(R.id.nav_view_logs, R.string.page_view_logs),
SETTINGS(R.id.nav_settings, R.string.page_settings),
APPEARING(R.id.nav_appearing, R.string.page_appearing, LoggingType.APPEARING),
DISAPPEARING(R.id.nav_disappearing, R.string.page_disappearing, LoggingType.DISAPPEARING),
TEXT_CONTENT(R.id.nav_text_content, R.string.page_text_content, LoggingType.TEXT_CONTENT),
IMAGE_CONTENT(R.id.nav_image_content, R.string.page_image_content, LoggingType.IMAGE_CONTENT),
EDITS(R.id.nav_edits, R.string.page_edits, LoggingType.EDITS),
;
companion object {
fun fromMenuItem(menuId: Int): Page = entries.firstOrNull { it.menuId == menuId } ?: VIEW_LOGS
}
}
private companion object {
const val STATE_CURRENT_ITEM_ID = "current_item_id"
const val PERMANENT_SIDEBAR_FOLDED_WIDTH_MULTIPLIER = 1.1f
}
}