android: Add navigation rail

This commit is contained in:
Charles Lombardo 2023-05-01 02:26:47 -04:00 committed by bunnei
parent 6df030998a
commit 9f6f21946c
14 changed files with 208 additions and 93 deletions

View file

@ -15,6 +15,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.updatePadding
import com.google.android.material.color.MaterialColors
import org.yuzu.yuzu_emu.NativeLibrary
@ -52,12 +53,14 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
setSupportActionBar(binding.toolbarSettings)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) {
binding.navigationBarShade.setBackgroundColor(
ThemeHelper.getColorWithOpacity(
MaterialColors.getColor(binding.navigationBarShade, R.attr.colorSurface),
ThemeHelper.SYSTEM_BAR_ALPHA
)
)
}
setInsets()
}
@ -164,12 +167,20 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(binding.frameContent) { view: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updatePadding(left = insets.left, right = insets.right)
InsetsHelper.insetAppBar(insets, binding.appbarSettings)
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
view.updatePadding(
left = barInsets.left + cutoutInsets.left,
right = barInsets.right + cutoutInsets.right
)
val mlpShade = binding.navigationBarShade.layoutParams as ViewGroup.MarginLayoutParams
mlpShade.height = insets.bottom
val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
mlpAppBar.leftMargin = barInsets.left + cutoutInsets.left
mlpAppBar.rightMargin = barInsets.right + cutoutInsets.right
binding.appbarSettings.layoutParams = mlpAppBar
val mlpShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
mlpShade.height = barInsets.bottom
binding.navigationBarShade.layoutParams = mlpShade
windowInsets

View file

@ -20,6 +20,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager
@ -107,26 +108,30 @@ class HomeSettingsFragment : Fragment() {
try {
startActivity(getFileManagerIntentOnDocumentProvider(Intent.ACTION_VIEW))
return
} catch (_: ActivityNotFoundException) {}
} catch (_: ActivityNotFoundException) {
}
try {
startActivity(getFileManagerIntentOnDocumentProvider("android.provider.action.BROWSE"))
return
} catch (_: ActivityNotFoundException) {}
} catch (_: ActivityNotFoundException) {
}
// Just try to open the file manager, try the package name used on "normal" phones
try {
startActivity(getFileManagerIntent("com.google.android.documentsui"))
showNoLinkNotification()
return
} catch (_: ActivityNotFoundException) {}
} catch (_: ActivityNotFoundException) {
}
try {
// Next, try the AOSP package name
startActivity(getFileManagerIntent("com.android.documentsui"))
showNoLinkNotification()
return
} catch (_: ActivityNotFoundException) {}
} catch (_: ActivityNotFoundException) {
}
Toast.makeText(
requireContext(),
@ -153,7 +158,10 @@ class HomeSettingsFragment : Fragment() {
}
private fun showNoLinkNotification() {
val builder = NotificationCompat.Builder(requireContext(), getString(R.string.notice_notification_channel_id))
val builder = NotificationCompat.Builder(
requireContext(),
getString(R.string.notice_notification_channel_id)
)
.setSmallIcon(R.drawable.ic_stat_notification_logo)
.setContentTitle(getString(R.string.notification_no_directory_link))
.setContentText(getString(R.string.notification_no_directory_link_description))
@ -204,14 +212,28 @@ class HomeSettingsFragment : Fragment() {
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(binding.scrollViewSettings) { view: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.setPadding(
insets.left,
insets.top,
insets.right,
insets.bottom + resources.getDimensionPixelSize(R.dimen.spacing_navigation)
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
val spacingNavigationRail =
resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
binding.scrollViewSettings.setPadding(
barInsets.left + cutoutInsets.left,
barInsets.top,
barInsets.right + cutoutInsets.right,
barInsets.bottom
)
binding.linearLayoutSettings.updatePadding(bottom = spacingNavigation)
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
binding.linearLayoutSettings.updatePadding(left = spacingNavigationRail)
} else {
binding.linearLayoutSettings.updatePadding(right = spacingNavigationRail)
}
windowInsets
}
}

View file

@ -141,7 +141,8 @@ class SearchFragment : Fragment() {
}
if (binding.searchText.text.toString().isEmpty()
&& binding.chipGroup.checkedChipId != View.NO_ID) {
&& binding.chipGroup.checkedChipId != View.NO_ID
) {
gamesViewModel.setSearchedGames(filteredList)
return
}
@ -182,40 +183,50 @@ class SearchFragment : Fragment() {
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
val navigationSpacing = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
val spacingNavigationRail =
resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
val chipSpacing = resources.getDimensionPixelSize(R.dimen.spacing_chip)
binding.frameSearch.updatePadding(
left = insets.left,
top = insets.top,
right = insets.right
binding.constraintSearch.updatePadding(
left = barInsets.left + cutoutInsets.left,
top = barInsets.top,
right = barInsets.right + cutoutInsets.right
)
binding.gridGamesSearch.setPadding(
insets.left,
extraListSpacing,
insets.right,
insets.bottom + resources.getDimensionPixelSize(R.dimen.spacing_navigation) + extraListSpacing
)
binding.noResultsView.updatePadding(
left = insets.left,
right = insets.right,
bottom = insets.bottom + navigationSpacing
binding.gridGamesSearch.updatePadding(
top = extraListSpacing,
bottom = barInsets.bottom + spacingNavigation + extraListSpacing
)
binding.noResultsView.updatePadding(bottom = spacingNavigation + barInsets.bottom)
val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
mlpDivider.leftMargin = insets.left + chipSpacing
mlpDivider.rightMargin = insets.right + chipSpacing
binding.divider.layoutParams = mlpDivider
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
binding.frameSearch.updatePadding(left = spacingNavigationRail)
binding.gridGamesSearch.updatePadding(left = spacingNavigationRail)
binding.noResultsView.updatePadding(left = spacingNavigationRail)
binding.chipGroup.updatePadding(
left = insets.left + chipSpacing,
right = insets.right + chipSpacing
left = chipSpacing + spacingNavigationRail,
right = chipSpacing
)
mlpDivider.leftMargin = chipSpacing + spacingNavigationRail
mlpDivider.rightMargin = chipSpacing
} else {
binding.frameSearch.updatePadding(right = spacingNavigationRail)
binding.gridGamesSearch.updatePadding(right = spacingNavigationRail)
binding.noResultsView.updatePadding(right = spacingNavigationRail)
binding.chipGroup.updatePadding(
left = chipSpacing,
right = chipSpacing + spacingNavigationRail
)
mlpDivider.leftMargin = chipSpacing
mlpDivider.rightMargin = chipSpacing + spacingNavigationRail
}
binding.divider.layoutParams = mlpDivider
windowInsets
}

View file

@ -8,27 +8,20 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.color.MaterialColors
import com.google.android.material.search.SearchView
import com.google.android.material.search.SearchView.TransitionState
import com.google.android.material.transition.MaterialFadeThrough
import info.debatty.java.stringsimilarity.Jaccard
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.GameAdapter
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import java.util.Locale
class GamesFragment : Fragment() {
private var _binding: FragmentGamesBinding? = null
@ -127,24 +120,37 @@ class GamesFragment : Fragment() {
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large)
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
val spacingNavigationRail =
resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
binding.gridGames.updatePadding(
top = insets.top + extraListSpacing,
bottom = insets.bottom + resources.getDimensionPixelSize(R.dimen.spacing_navigation) + extraListSpacing
top = barInsets.top + extraListSpacing,
bottom = barInsets.bottom + spacingNavigation + extraListSpacing
)
binding.swipeRefresh.setProgressViewEndTarget(
false,
insets.top + resources.getDimensionPixelSize(R.dimen.spacing_refresh_end)
barInsets.top + resources.getDimensionPixelSize(R.dimen.spacing_refresh_end)
)
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpSwipe = binding.swipeRefresh.layoutParams as MarginLayoutParams
mlpSwipe.rightMargin = insets.right
mlpSwipe.leftMargin = insets.left
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
mlpSwipe.leftMargin = leftInsets + spacingNavigationRail
mlpSwipe.rightMargin = rightInsets
} else {
mlpSwipe.leftMargin = leftInsets
mlpSwipe.rightMargin = rightInsets + spacingNavigationRail
}
binding.swipeRefresh.layoutParams = mlpSwipe
binding.noticeText.updatePadding(bottom = spacingNavigation)
windowInsets
}
}

View file

@ -67,16 +67,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
ContextCompat.getColor(applicationContext, android.R.color.transparent)
ThemeHelper.setNavigationBarColor(
this,
ElevationOverlayProvider(binding.navigationBar.context).compositeOverlay(
MaterialColors.getColor(binding.navigationBar, R.attr.colorSurface),
binding.navigationBar.elevation
ElevationOverlayProvider(binding.navigationView.context).compositeOverlay(
MaterialColors.getColor(binding.navigationView, R.attr.colorSurface),
binding.navigationView.elevation
)
)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
setUpNavigation(navHostFragment.navController)
(binding.navigationBar as NavigationBarView).setOnItemReselectedListener {
(binding.navigationView as NavigationBarView).setOnItemReselectedListener {
when (it.itemId) {
R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
@ -95,7 +95,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
// Prevents navigation from being drawn for a short time on recreation if set to hidden
if (!homeViewModel.navigationVisible.value?.first!!) {
binding.navigationBar.visibility = View.INVISIBLE
binding.navigationView.visibility = View.INVISIBLE
binding.statusBarShade.visibility = View.INVISIBLE
}
@ -114,14 +114,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
fun finishSetup(navController: NavController) {
navController.navigate(R.id.action_firstTimeSetupFragment_to_gamesFragment)
binding.navigationBar.setupWithNavController(navController)
(binding.navigationView as NavigationBarView).setupWithNavController(navController)
showNavigation(visible = true, animated = true)
ThemeHelper.setNavigationBarColor(
this,
ElevationOverlayProvider(binding.navigationBar.context).compositeOverlay(
MaterialColors.getColor(binding.navigationBar, R.attr.colorSurface),
binding.navigationBar.elevation
ElevationOverlayProvider(binding.navigationView.context).compositeOverlay(
MaterialColors.getColor(binding.navigationView, R.attr.colorSurface),
binding.navigationView.elevation
)
)
}
@ -134,35 +134,35 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
navController.navigate(R.id.firstTimeSetupFragment)
homeViewModel.navigatedToSetup = true
} else {
binding.navigationBar.setupWithNavController(navController)
(binding.navigationView as NavigationBarView).setupWithNavController(navController)
}
}
private fun showNavigation(visible: Boolean, animated: Boolean) {
if (!animated) {
if (visible) {
binding.navigationBar.visibility = View.VISIBLE
binding.navigationView.visibility = View.VISIBLE
} else {
binding.navigationBar.visibility = View.INVISIBLE
binding.navigationView.visibility = View.INVISIBLE
}
return
}
binding.navigationBar.animate().apply {
binding.navigationView.animate().apply {
if (visible) {
binding.navigationBar.visibility = View.VISIBLE
binding.navigationBar.translationY = binding.navigationBar.height.toFloat() * 2
binding.navigationView.visibility = View.VISIBLE
binding.navigationView.translationY = binding.navigationView.height.toFloat() * 2
duration = 300
translationY(0f)
interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f)
} else {
duration = 300
translationY(binding.navigationBar.height.toFloat() * 2)
translationY(binding.navigationView.height.toFloat() * 2)
interpolator = PathInterpolator(0.3f, 0f, 0.8f, 0.15f)
}
}.withEndAction {
if (!visible) {
binding.navigationBar.visibility = View.INVISIBLE
binding.navigationView.visibility = View.INVISIBLE
}
}.start()
}
@ -177,7 +177,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f)
} else {
duration = 300
translationY(binding.navigationBar.height.toFloat() * -2)
translationY(binding.navigationView.height.toFloat() * -2)
interpolator = PathInterpolator(0.3f, 0f, 0.8f, 0.15f)
}
}.withEndAction {

View file

@ -4,22 +4,12 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.graphics.Rect
import android.view.ViewGroup.MarginLayoutParams
import androidx.core.graphics.Insets
import com.google.android.material.appbar.AppBarLayout
object InsetsHelper {
const val THREE_BUTTON_NAVIGATION = 0
const val TWO_BUTTON_NAVIGATION = 1
const val GESTURE_NAVIGATION = 2
fun insetAppBar(insets: Insets, appBarLayout: AppBarLayout) {
val mlpAppBar = appBarLayout.layoutParams as MarginLayoutParams
mlpAppBar.leftMargin = insets.left
mlpAppBar.rightMargin = insets.right
appBarLayout.layoutParams = mlpAppBar
}
@SuppressLint("DiscouragedApi")
fun getSystemGestureType(context: Context): Int {
val resources = context.resources

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinator_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/home_navigation"
tools:layout="@layout/fragment_games" />
<com.google.android.material.navigationrail.NavigationRailView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:labelVisibilityMode="selected"
app:menu="@menu/menu_navigation"
tools:visibility="visible" />
<View
android:id="@+id/status_bar_shade"
android:layout_width="0dp"
android:layout_height="1px"
android:background="@android:color/transparent"
android:clickable="false"
android:focusable="false"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -21,7 +21,7 @@
tools:layout="@layout/fragment_games" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/navigation_bar"
android:id="@+id/navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible"

View file

@ -8,6 +8,7 @@
android:clipToPadding="false">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/linear_layout_settings"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"

View file

@ -3,9 +3,11 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraint_search"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
android:background="?attr/colorSurface"
android:clipToPadding="false">
<RelativeLayout
android:layout_width="0dp"
@ -52,7 +54,8 @@
android:id="@+id/frame_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:layout_marginTop="12dp"
android:layout_marginHorizontal="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/homeSettingsFragment"
android:icon="@drawable/ic_settings"
android:title="@string/home_settings" />
<item
android:id="@+id/searchFragment"
android:icon="@drawable/ic_search"
android:title="@string/home_search" />
<item
android:id="@+id/gamesFragment"
android:icon="@drawable/ic_controller"
android:title="@string/home_games" />
</menu>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="spacing_navigation">0dp</dimen>
<dimen name="spacing_navigation_rail">80dp</dimen>
</resources>

View file

@ -7,6 +7,7 @@
<dimen name="spacing_list">64dp</dimen>
<dimen name="spacing_chip">20dp</dimen>
<dimen name="spacing_navigation">80dp</dimen>
<dimen name="spacing_navigation_rail">0dp</dimen>
<dimen name="spacing_search">128dp</dimen>
<dimen name="spacing_refresh_end">72dp</dimen>
<dimen name="menu_width">256dp</dimen>

View file

@ -42,7 +42,7 @@
<item name="sliderStyle">@style/YuzuSlider</item>
<item name="materialAlertDialogTheme">@style/YuzuMaterialDialog</item>
<item name="android:windowLayoutInDisplayCutoutMode">default</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:enforceStatusBarContrast">false</item>
<item name="android:enforceNavigationBarContrast">false</item>