263 lines
11 KiB
Kotlin
263 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
|
|
|
|
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, 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) {
|
|
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),
|
|
DISAPPEARING(R.id.nav_disappearing, R.string.page_disappearing),
|
|
TEXT_CONTENT(R.id.nav_text_content, R.string.page_text_content),
|
|
IMAGE_CONTENT(R.id.nav_image_content, R.string.page_image_content),
|
|
EDITS(R.id.nav_edits, R.string.page_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
|
|
}
|
|
}
|