Add edge-to-edge
Add [somewhat basic] edge-to-edge functionality for android O and up, along with the ability to enable/disable it.
This commit is contained in:
parent
59036a2747
commit
12c14eeda6
9 changed files with 143 additions and 44 deletions
|
@ -97,8 +97,8 @@ dependencies {
|
||||||
// Dialogs
|
// Dialogs
|
||||||
implementation 'com.afollestad.material-dialogs:core:3.3.0'
|
implementation 'com.afollestad.material-dialogs:core:3.3.0'
|
||||||
|
|
||||||
// Edge-To-Edge
|
// Edge-To-Edge insets
|
||||||
implementation 'de.halfbit:edge-to-edge:0.10'
|
implementation "dev.chrisbanes:insetter-ktx:0.3.1"
|
||||||
|
|
||||||
// --- DEV ---
|
// --- DEV ---
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,34 @@
|
||||||
package org.oxycblt.auxio
|
package org.oxycblt.auxio
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.AttributeSet
|
import android.graphics.Color
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.WindowInsets
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import dev.chrisbanes.insetter.applySystemWindowInsetsToMargin
|
||||||
|
import org.oxycblt.auxio.databinding.ActivityMainBinding
|
||||||
import org.oxycblt.auxio.playback.PlaybackService
|
import org.oxycblt.auxio.playback.PlaybackService
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.ui.accent
|
import org.oxycblt.auxio.ui.accent
|
||||||
|
import org.oxycblt.auxio.ui.handleTransparentSystemBars
|
||||||
|
|
||||||
// FIXME: Fix bug where fast navigation will break the animations and
|
// FIXME: Fix bug where fast navigation will break the animations and
|
||||||
// lead to nothing being displayed [Possibly Un-fixable]
|
// lead to nothing being displayed [Possibly Un-fixable]
|
||||||
// TODO: Landscape UI layouts
|
// TODO: Landscape UI layouts
|
||||||
// FIXME: Compat issue with Versions 5 that leads to progress bar looking off
|
// FIXME: Compat issue with Versions 5 that leads to progress bar looking off
|
||||||
class MainActivity : AppCompatActivity(R.layout.activity_main) {
|
class MainActivity : AppCompatActivity() {
|
||||||
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
|
||||||
|
this, R.layout.activity_main
|
||||||
|
)
|
||||||
|
|
||||||
val settingsManager = SettingsManager.init(applicationContext)
|
val settingsManager = SettingsManager.init(applicationContext)
|
||||||
|
|
||||||
AppCompatDelegate.setDefaultNightMode(
|
AppCompatDelegate.setDefaultNightMode(
|
||||||
|
@ -27,7 +40,41 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
|
||||||
// Apply the theme
|
// Apply the theme
|
||||||
setTheme(accent.second)
|
setTheme(accent.second)
|
||||||
|
|
||||||
return super.onCreateView(name, context, attrs)
|
// If enabled and possible, go through a stupidly long & complicated process
|
||||||
|
// just to get edge-to-edge to work.
|
||||||
|
// TODO: Make the navigation bar fully transparent
|
||||||
|
if (settingsManager.getEdgeToEdge() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
|
window?.apply {
|
||||||
|
statusBarColor = Color.TRANSPARENT
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
Log.d(this::class.simpleName, "Doing R+ edge-to-edge.")
|
||||||
|
|
||||||
|
setDecorFitsSystemWindows(false)
|
||||||
|
|
||||||
|
binding.root.setOnApplyWindowInsetsListener { v, insets ->
|
||||||
|
WindowInsets.Builder()
|
||||||
|
.setInsets(
|
||||||
|
WindowInsets.Type.systemBars(),
|
||||||
|
insets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(this::class.simpleName, "Doing deprec edge-to-edge.")
|
||||||
|
binding.root.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
navigationBarColor = Color.TRANSPARENT
|
||||||
|
|
||||||
|
handleTransparentSystemBars(resources.configuration)
|
||||||
|
|
||||||
|
// I barely know how insets work so here's another third party library
|
||||||
|
// that I think does things
|
||||||
|
binding.root.applySystemWindowInsetsToMargin(top = false, bottom = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
|
@ -41,8 +88,4 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
|
||||||
fun doThemeRecreate(newTheme: Int) {
|
fun doThemeRecreate(newTheme: Int) {
|
||||||
AppCompatDelegate.setDefaultNightMode(newTheme)
|
AppCompatDelegate.setDefaultNightMode(newTheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun doAccentRecreate() {
|
|
||||||
recreate()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,9 @@ class NoLeakThumbView @JvmOverloads constructor(
|
||||||
textView = thumbView.findViewById(R.id.fast_scroller_thumb_text)
|
textView = thumbView.findViewById(R.id.fast_scroller_thumb_text)
|
||||||
iconView = thumbView.findViewById(R.id.fast_scroller_thumb_icon)
|
iconView = thumbView.findViewById(R.id.fast_scroller_thumb_icon)
|
||||||
|
|
||||||
|
isActivated = false
|
||||||
|
isVisible = false
|
||||||
|
|
||||||
applyStyle()
|
applyStyle()
|
||||||
|
|
||||||
thumbAnimation = SpringAnimation(thumbView, DynamicAnimation.TRANSLATION_Y).apply {
|
thumbAnimation = SpringAnimation(thumbView, DynamicAnimation.TRANSLATION_Y).apply {
|
||||||
|
@ -87,10 +90,12 @@ class NoLeakThumbView @JvmOverloads constructor(
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
isActivated = false
|
isActivated = false
|
||||||
|
isVisible = true
|
||||||
return@setOnTouchListener true
|
return@setOnTouchListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
isActivated = isPointerOnItem(fastScrollerView, event.y.toInt())
|
isActivated = isPointerOnItem(fastScrollerView, event.y.toInt())
|
||||||
|
isVisible = true
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
view.apply {
|
// --- PREFERENCE ITEM SETUP ---
|
||||||
}
|
|
||||||
|
|
||||||
val themePref = findPreference<Preference>(SettingsManager.Keys.KEY_THEME)?.apply {
|
val themePref = findPreference<Preference>(SettingsManager.Keys.KEY_THEME)?.apply {
|
||||||
setIcon(
|
setIcon(
|
||||||
|
@ -53,6 +52,8 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
summary = getDetailedAccentSummary(requireActivity(), accent)
|
summary = getDetailedAccentSummary(requireActivity(), accent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
settingsModel.theme.observe(viewLifecycleOwner) {
|
settingsModel.theme.observe(viewLifecycleOwner) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
themePref?.setIcon(
|
themePref?.setIcon(
|
||||||
|
@ -75,12 +76,20 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
accentPref?.summary = getDetailedAccentSummary(requireActivity(), it)
|
accentPref?.summary = getDetailedAccentSummary(requireActivity(), it)
|
||||||
|
|
||||||
(requireActivity() as MainActivity).doAccentRecreate()
|
requireActivity().recreate()
|
||||||
|
|
||||||
settingsModel.doneWithAccentUpdate()
|
settingsModel.doneWithAccentUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settingsModel.edge.observe(viewLifecycleOwner) {
|
||||||
|
if (it != null) {
|
||||||
|
requireActivity().recreate()
|
||||||
|
|
||||||
|
settingsModel.doneWithEdgeUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Log.d(this::class.simpleName, "Fragment created.")
|
Log.d(this::class.simpleName, "Fragment created.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,10 @@ class SettingsManager private constructor(context: Context) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getEdgeToEdge(): Boolean {
|
||||||
|
return sharedPrefs.getBoolean(Keys.KEY_EDGE_TO_EDGE, false)
|
||||||
|
}
|
||||||
|
|
||||||
fun setLibrarySortMode(sortMode: SortMode) {
|
fun setLibrarySortMode(sortMode: SortMode) {
|
||||||
sharedPrefs.edit()
|
sharedPrefs.edit()
|
||||||
.putInt(Keys.KEY_LIBRARY_SORT_MODE, sortMode.toInt())
|
.putInt(Keys.KEY_LIBRARY_SORT_MODE, sortMode.toInt())
|
||||||
|
@ -80,10 +84,6 @@ class SettingsManager private constructor(context: Context) :
|
||||||
) ?: SortMode.ALPHA_DOWN
|
) ?: SortMode.ALPHA_DOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getEdgeToEdge(): Boolean {
|
|
||||||
return sharedPrefs.getBoolean(Keys.KEY_EDGE_TO_EDGE, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- OVERRIDES ---
|
// --- OVERRIDES ---
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
|
@ -93,6 +93,12 @@ class SettingsManager private constructor(context: Context) :
|
||||||
it.onThemeUpdate(getTheme())
|
it.onThemeUpdate(getTheme())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Keys.KEY_EDGE_TO_EDGE -> {
|
||||||
|
callbacks.forEach {
|
||||||
|
it.onEdgeToEdgeUpdate(getEdgeToEdge())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,9 @@ class SettingsViewModel : ViewModel(), SettingsManager.Callback {
|
||||||
private val mAccent = MutableLiveData<Pair<Int, Int>?>()
|
private val mAccent = MutableLiveData<Pair<Int, Int>?>()
|
||||||
val accent: LiveData<Pair<Int, Int>?> get() = mAccent
|
val accent: LiveData<Pair<Int, Int>?> get() = mAccent
|
||||||
|
|
||||||
|
private val mEdge = MutableLiveData<Boolean?>()
|
||||||
|
val edge: LiveData<Boolean?> = mEdge
|
||||||
|
|
||||||
private val settingsManager = SettingsManager.getInstance()
|
private val settingsManager = SettingsManager.getInstance()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -25,18 +28,22 @@ class SettingsViewModel : ViewModel(), SettingsManager.Callback {
|
||||||
mAccent.value = null
|
mAccent.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onThemeUpdate(newTheme: Int) {
|
fun doneWithEdgeUpdate() {
|
||||||
super.onThemeUpdate(newTheme)
|
mEdge.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onThemeUpdate(newTheme: Int) {
|
||||||
mTheme.value = newTheme
|
mTheme.value = newTheme
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAccentUpdate(newAccent: Pair<Int, Int>) {
|
override fun onAccentUpdate(newAccent: Pair<Int, Int>) {
|
||||||
super.onAccentUpdate(newAccent)
|
|
||||||
|
|
||||||
mAccent.value = newAccent
|
mAccent.value = newAccent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onEdgeToEdgeUpdate(isEdgeToEdge: Boolean) {
|
||||||
|
mEdge.value = isEdgeToEdge
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,20 @@ package org.oxycblt.auxio.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.os.Build
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.Window
|
||||||
|
import android.view.WindowInsetsController
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.MenuRes
|
import androidx.annotation.MenuRes
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.text.HtmlCompat
|
import androidx.core.text.HtmlCompat
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
@ -66,6 +72,38 @@ fun Spanned.render(): Spanned {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle transparent system bars on light mode. Adapted from Music Player GO
|
||||||
|
* (https://github.com/enricocid/Music-Player-GO)
|
||||||
|
*/
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O_MR1)
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
fun Window.handleTransparentSystemBars(config: Configuration) {
|
||||||
|
fun isNight() = config.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
insetsController?.let { controller ->
|
||||||
|
val appearance = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS or
|
||||||
|
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
|
||||||
|
|
||||||
|
val mask = if (isNight()) 0 else appearance
|
||||||
|
|
||||||
|
controller.setSystemBarsAppearance(appearance, mask)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val flags = decorView.systemUiVisibility
|
||||||
|
|
||||||
|
decorView.systemUiVisibility =
|
||||||
|
if (isNight()) {
|
||||||
|
flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() and
|
||||||
|
View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
|
||||||
|
} else {
|
||||||
|
flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or
|
||||||
|
View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show actions for a song item, such as the ones found in [org.oxycblt.auxio.songs.SongsFragment]
|
* Show actions for a song item, such as the ones found in [org.oxycblt.auxio.songs.SongsFragment]
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host"
|
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
|
||||||
app:defaultNavHost="true"
|
|
||||||
app:navGraph="@navigation/nav_main"
|
|
||||||
tools:context=".MainActivity"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools" />
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/nav_host"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
|
app:defaultNavHost="true"
|
||||||
|
app:navGraph="@navigation/nav_main" />
|
||||||
|
</layout>
|
|
@ -8,6 +8,7 @@
|
||||||
<item name="android:textCursorDrawable">@drawable/ui_cursor</item>
|
<item name="android:textCursorDrawable">@drawable/ui_cursor</item>
|
||||||
<item name="android:fitsSystemWindows">true</item>
|
<item name="android:fitsSystemWindows">true</item>
|
||||||
<item name="android:scrollbars">none</item>
|
<item name="android:scrollbars">none</item>
|
||||||
|
<item name="android:windowIsFloating">false</item>
|
||||||
|
|
||||||
<item name="popupMenuStyle">@style/Widget.CustomPopup</item>
|
<item name="popupMenuStyle">@style/Widget.CustomPopup</item>
|
||||||
<item name="colorControlNormal">@color/control_color</item>
|
<item name="colorControlNormal">@color/control_color</item>
|
||||||
|
@ -15,9 +16,7 @@
|
||||||
<item name="indicatorFastScrollerStyle">@style/FastScrollTheme</item>
|
<item name="indicatorFastScrollerStyle">@style/FastScrollTheme</item>
|
||||||
<item name="colorControlActivated">?attr/colorPrimary</item>
|
<item name="colorControlActivated">?attr/colorPrimary</item>
|
||||||
<item name="colorControlHighlight">?attr/colorPrimary</item>
|
<item name="colorControlHighlight">?attr/colorPrimary</item>
|
||||||
<item name="preferenceTheme">@style/Theme.Preference</item>
|
|
||||||
|
|
||||||
<item name="md_divider_color">@android:color/transparent</item>
|
|
||||||
<item name="md_background_color">@color/background</item>
|
<item name="md_background_color">@color/background</item>
|
||||||
<item name="md_corner_radius">0dp</item>
|
<item name="md_corner_radius">0dp</item>
|
||||||
<item name="md_color_button_text">@color/control_color</item>
|
<item name="md_color_button_text">@color/control_color</item>
|
||||||
|
@ -88,20 +87,9 @@
|
||||||
<item name="android:textSize">18sp</item>
|
<item name="android:textSize">18sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Preference" parent="PreferenceThemeOverlay">
|
|
||||||
<item name="rippleColor">@color/selection_color</item>
|
|
||||||
<item name="itemRippleColor">@color/selection_color</item>
|
|
||||||
<item name="tabRippleColor">@color/selection_color</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Fix to get QueueFragment to not overlap the Status Bar or Navigation Bar [Currently unused but still here]
|
|
||||||
https://stackoverflow.com/a/57790787/14143986
|
|
||||||
|
|
||||||
<style name="Theme.BottomSheetFix" parent="@style/Theme.Design.BottomSheetDialog">
|
<style name="Theme.BottomSheetFix" parent="@style/Theme.Design.BottomSheetDialog">
|
||||||
<item name="android:windowIsFloating">false</item>
|
<item name="android:windowIsFloating">false</item>
|
||||||
<item name="android:navigationBarColor">@color/background</item>
|
<item name="android:navigationBarColor">@color/background</item>
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
</style>
|
</style>
|
||||||
-->
|
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue