ui: refactor utilities
Move the AndroidUtils file into the root Auxio directory, renaming it to AuxioUtils as well. This also changes some methods to be extension methods instead of argument functions.
This commit is contained in:
parent
5788fb8732
commit
73ec99a214
48 changed files with 310 additions and 326 deletions
|
@ -21,8 +21,6 @@ Auxio is a local music player with a fast, reliable UI/UX without the many usele
|
||||||
|
|
||||||
I primarily built Auxio for myself, but you can use it too, I guess.
|
I primarily built Auxio for myself, but you can use it too, I guess.
|
||||||
|
|
||||||
**Note: Auxio is in a point that I am largely satisfied with. The app is still maintained, but feature additions will be slow.**
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2021 Auxio Project
|
* Copyright (c) 2021 Auxio Project
|
||||||
* AndroidUtils.kt is part of Auxio.
|
* AuxioUtils.kt is part of Auxio.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.ui
|
package org.oxycblt.auxio
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
@ -46,9 +46,6 @@ import androidx.annotation.PluralsRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.MainActivity
|
|
||||||
import org.oxycblt.auxio.R
|
|
||||||
import org.oxycblt.auxio.logE
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
const val INTENT_REQUEST_CODE = 0xA0A0
|
const val INTENT_REQUEST_CODE = 0xA0A0
|
||||||
|
@ -60,7 +57,7 @@ const val INTENT_REQUEST_CODE = 0xA0A0
|
||||||
*/
|
*/
|
||||||
fun ImageButton.disable() {
|
fun ImageButton.disable() {
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
imageTintList = R.color.inactive.toStateList(context)
|
imageTintList = R.color.inactive.resolveStateList(context)
|
||||||
isEnabled = false
|
isEnabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,9 +66,24 @@ fun ImageButton.disable() {
|
||||||
* Set a [TextView] text color, without having to resolve the resource.
|
* Set a [TextView] text color, without having to resolve the resource.
|
||||||
*/
|
*/
|
||||||
fun TextView.setTextColorResource(@ColorRes color: Int) {
|
fun TextView.setTextColorResource(@ColorRes color: Int) {
|
||||||
setTextColor(color.toColor(context))
|
setTextColor(color.resolveColor(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the span count for most RecyclerViews. These probably work right on most displays. Trust me.
|
||||||
|
*/
|
||||||
|
val RecyclerView.spans: Int get() =
|
||||||
|
if (context.isLandscape()) {
|
||||||
|
if (context.isXLTablet()) 3 else 2
|
||||||
|
} else {
|
||||||
|
if (context.isXLTablet()) 2 else 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a recyclerview can scroll.
|
||||||
|
*/
|
||||||
|
fun RecyclerView.canScroll(): Boolean = computeVerticalScrollRange() > height
|
||||||
|
|
||||||
// --- CONVENIENCE ---
|
// --- CONVENIENCE ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,14 +92,12 @@ fun TextView.setTextColorResource(@ColorRes color: Int) {
|
||||||
val Context.inflater: LayoutInflater get() = LayoutInflater.from(this)
|
val Context.inflater: LayoutInflater get() = LayoutInflater.from(this)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a plural.
|
* Returns whether the current UI is in night mode or not. This will work if the theme is
|
||||||
* @param pluralsRes Resource for the plural
|
* automatic as well.
|
||||||
* @param value Int value for the plural.
|
|
||||||
* @return The formatted string requested
|
|
||||||
*/
|
*/
|
||||||
fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String {
|
val Context.isNight: Boolean get() =
|
||||||
return resources.getQuantityString(pluralsRes, value, value)
|
resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
|
||||||
}
|
Configuration.UI_MODE_NIGHT_YES
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for getting a system service without nullability issues.
|
* Convenience method for getting a system service without nullability issues.
|
||||||
|
@ -102,80 +112,6 @@ fun <T : Any> Context.getSystemServiceSafe(serviceClass: KClass<T>): T {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the current UI is in night mode or not. This will work if the theme is
|
|
||||||
* automatic as well.
|
|
||||||
*/
|
|
||||||
fun Context.isNight(): Boolean {
|
|
||||||
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
|
|
||||||
Configuration.UI_MODE_NIGHT_YES
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve a color.
|
|
||||||
* @param context [Context] required
|
|
||||||
* @return The resolved color, black if the resolving process failed.
|
|
||||||
*/
|
|
||||||
@ColorInt
|
|
||||||
fun @receiver:ColorRes Int.toColor(context: Context): Int {
|
|
||||||
return try {
|
|
||||||
ContextCompat.getColor(context, this)
|
|
||||||
} catch (e: Resources.NotFoundException) {
|
|
||||||
logE("Attempted color load failed: ${e.stackTraceToString()}")
|
|
||||||
|
|
||||||
// Default to the emergency color [Black] if the loading fails.
|
|
||||||
ContextCompat.getColor(context, android.R.color.black)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve a color and turn it into a [ColorStateList]
|
|
||||||
* @param context [Context] required
|
|
||||||
* @return The resolved color as a [ColorStateList]
|
|
||||||
* @see toColor
|
|
||||||
*/
|
|
||||||
fun @receiver:ColorRes Int.toStateList(context: Context) =
|
|
||||||
ColorStateList.valueOf(toColor(context))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve a drawable resource into a [Drawable]
|
|
||||||
*/
|
|
||||||
fun @receiver:DrawableRes Int.toDrawable(context: Context) =
|
|
||||||
ContextCompat.getDrawable(context, this)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve a drawable resource into an [AnimatedVectorDrawable]
|
|
||||||
* @see toDrawable
|
|
||||||
*/
|
|
||||||
fun @receiver:DrawableRes Int.toAnimDrawable(context: Context) =
|
|
||||||
toDrawable(context) as AnimatedVectorDrawable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve this int into a color as if it was an attribute
|
|
||||||
*/
|
|
||||||
@ColorInt
|
|
||||||
fun @receiver:AttrRes Int.resolveAttr(context: Context): Int {
|
|
||||||
// Convert the attribute into its color
|
|
||||||
val resolvedAttr = TypedValue()
|
|
||||||
context.theme.resolveAttribute(this, resolvedAttr, true)
|
|
||||||
|
|
||||||
// Then convert it to a proper color
|
|
||||||
val color = if (resolvedAttr.resourceId != 0) {
|
|
||||||
resolvedAttr.resourceId
|
|
||||||
} else {
|
|
||||||
resolvedAttr.data
|
|
||||||
}
|
|
||||||
|
|
||||||
return color.toColor(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a toast using the provided string resource.
|
|
||||||
*/
|
|
||||||
fun Context.showToast(@StringRes str: Int) {
|
|
||||||
Toast.makeText(applicationContext, getString(str), Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a broadcast [PendingIntent]
|
* Create a broadcast [PendingIntent]
|
||||||
*/
|
*/
|
||||||
|
@ -200,6 +136,81 @@ fun Context.newMainIntent(): PendingIntent {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a toast using the provided string resource.
|
||||||
|
*/
|
||||||
|
fun Context.showToast(@StringRes str: Int) {
|
||||||
|
Toast.makeText(applicationContext, getString(str), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method for getting a plural.
|
||||||
|
* @param pluralsRes Resource for the plural
|
||||||
|
* @param value Int value for the plural.
|
||||||
|
* @return The formatted string requested
|
||||||
|
*/
|
||||||
|
fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String {
|
||||||
|
return resources.getQuantityString(pluralsRes, value, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a color.
|
||||||
|
* @param context [Context] required
|
||||||
|
* @return The resolved color, black if the resolving process failed.
|
||||||
|
*/
|
||||||
|
@ColorInt
|
||||||
|
fun @receiver:ColorRes Int.resolveColor(context: Context): Int {
|
||||||
|
return try {
|
||||||
|
ContextCompat.getColor(context, this)
|
||||||
|
} catch (e: Resources.NotFoundException) {
|
||||||
|
logE("Attempted color load failed: ${e.stackTraceToString()}")
|
||||||
|
|
||||||
|
// Default to the emergency color [Black] if the loading fails.
|
||||||
|
ContextCompat.getColor(context, android.R.color.black)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a color and turn it into a [ColorStateList]
|
||||||
|
* @param context [Context] required
|
||||||
|
* @return The resolved color as a [ColorStateList]
|
||||||
|
* @see resolveColor
|
||||||
|
*/
|
||||||
|
fun @receiver:ColorRes Int.resolveStateList(context: Context) =
|
||||||
|
ColorStateList.valueOf(resolveColor(context))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a drawable resource into a [Drawable]
|
||||||
|
*/
|
||||||
|
fun @receiver:DrawableRes Int.resolveDrawable(context: Context) =
|
||||||
|
requireNotNull(ContextCompat.getDrawable(context, this))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a drawable resource into an [AnimatedVectorDrawable]
|
||||||
|
* @see resolveDrawable
|
||||||
|
*/
|
||||||
|
fun @receiver:DrawableRes Int.toAnimDrawable(context: Context) =
|
||||||
|
resolveDrawable(context) as AnimatedVectorDrawable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve this int into a color as if it was an attribute
|
||||||
|
*/
|
||||||
|
@ColorInt
|
||||||
|
fun @receiver:AttrRes Int.resolveAttr(context: Context): Int {
|
||||||
|
// Convert the attribute into its color
|
||||||
|
val resolvedAttr = TypedValue()
|
||||||
|
context.theme.resolveAttribute(this, resolvedAttr, true)
|
||||||
|
|
||||||
|
// Then convert it to a proper color
|
||||||
|
val color = if (resolvedAttr.resourceId != 0) {
|
||||||
|
resolvedAttr.resourceId
|
||||||
|
} else {
|
||||||
|
resolvedAttr.data
|
||||||
|
}
|
||||||
|
|
||||||
|
return color.resolveColor(context)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut for querying all items in a database and running [block] with the cursor returned.
|
* Shortcut for querying all items in a database and running [block] with the cursor returned.
|
||||||
* Will not run if the cursor is null.
|
* Will not run if the cursor is null.
|
||||||
|
@ -235,16 +246,15 @@ fun isEdgeOn(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the device is currently in landscape.
|
* Determine if the device is currently in landscape.
|
||||||
* @param resources [Resources] required
|
|
||||||
*/
|
*/
|
||||||
fun isLandscape(resources: Resources): Boolean {
|
fun Context.isLandscape(): Boolean {
|
||||||
return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if we are in tablet mode or not
|
* Determine if we are in tablet mode or not
|
||||||
*/
|
*/
|
||||||
fun isTablet(resources: Resources): Boolean {
|
fun Context.isTablet(): Boolean {
|
||||||
val layout = resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK
|
val layout = resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK
|
||||||
|
|
||||||
return layout == Configuration.SCREENLAYOUT_SIZE_XLARGE ||
|
return layout == Configuration.SCREENLAYOUT_SIZE_XLARGE ||
|
||||||
|
@ -254,35 +264,19 @@ fun isTablet(resources: Resources): Boolean {
|
||||||
/**
|
/**
|
||||||
* Determine if the tablet is XLARGE, ignoring normal tablets.
|
* Determine if the tablet is XLARGE, ignoring normal tablets.
|
||||||
*/
|
*/
|
||||||
fun isXLTablet(resources: Resources): Boolean {
|
fun Context.isXLTablet(): Boolean {
|
||||||
val layout = resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK
|
val layout = resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK
|
||||||
|
|
||||||
return layout == Configuration.SCREENLAYOUT_SIZE_XLARGE
|
return layout == Configuration.SCREENLAYOUT_SIZE_XLARGE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the span count for most RecyclerViews. These probably work right on most displays. Trust me.
|
|
||||||
*/
|
|
||||||
fun RecyclerView.getSpans(): Int {
|
|
||||||
return if (isLandscape(resources)) {
|
|
||||||
if (isXLTablet(resources)) 3 else 2
|
|
||||||
} else {
|
|
||||||
if (isXLTablet(resources)) 2 else 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether a recyclerview can scroll.
|
|
||||||
*/
|
|
||||||
fun RecyclerView.canScroll() = computeVerticalScrollRange() > height
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if we are in the "Irregular" landscape mode (e.g landscape, but nav bar is on the sides)
|
* Check if we are in the "Irregular" landscape mode (e.g landscape, but nav bar is on the sides)
|
||||||
* Used to disable most of edge-to-edge if that's the case, as I cant get it to work on this mode.
|
* Used to disable most of edge-to-edge if that's the case, as I cant get it to work on this mode.
|
||||||
* @return True if we are in the irregular landscape mode, false if not.
|
* @return True if we are in the irregular landscape mode, false if not.
|
||||||
*/
|
*/
|
||||||
fun Activity.isIrregularLandscape(): Boolean {
|
fun Activity.isIrregularLandscape(): Boolean {
|
||||||
return isLandscape(resources) && !isSystemBarOnBottom(this)
|
return isLandscape() && !isSystemBarOnBottom(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -27,13 +27,11 @@ import androidx.activity.viewModels
|
||||||
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 androidx.databinding.DataBindingUtil
|
||||||
|
import org.oxycblt.auxio.accent.Accent
|
||||||
import org.oxycblt.auxio.databinding.ActivityMainBinding
|
import org.oxycblt.auxio.databinding.ActivityMainBinding
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.playback.system.PlaybackService
|
import org.oxycblt.auxio.playback.system.PlaybackService
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.ui.Accent
|
|
||||||
import org.oxycblt.auxio.ui.isEdgeOn
|
|
||||||
import org.oxycblt.auxio.ui.isNight
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The single [AppCompatActivity] for Auxio.
|
* The single [AppCompatActivity] for Auxio.
|
||||||
|
@ -66,22 +64,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
onNewIntent(intent)
|
onNewIntent(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupTheme() {
|
|
||||||
// Update the current accent and theme
|
|
||||||
val settingsManager = SettingsManager.getInstance()
|
|
||||||
AppCompatDelegate.setDefaultNightMode(settingsManager.theme)
|
|
||||||
|
|
||||||
val newAccent = Accent.set(settingsManager.accent)
|
|
||||||
|
|
||||||
// The black theme has a completely separate set of styles since style attributes cannot
|
|
||||||
// be modified at runtime.
|
|
||||||
if (isNight() && settingsManager.useBlackTheme) {
|
|
||||||
setTheme(newAccent.blackTheme)
|
|
||||||
} else {
|
|
||||||
setTheme(newAccent.theme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent?) {
|
override fun onNewIntent(intent: Intent?) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
|
|
||||||
|
@ -102,6 +84,22 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupTheme() {
|
||||||
|
// Update the current accent and theme
|
||||||
|
val settingsManager = SettingsManager.getInstance()
|
||||||
|
AppCompatDelegate.setDefaultNightMode(settingsManager.theme)
|
||||||
|
|
||||||
|
val newAccent = Accent.set(settingsManager.accent)
|
||||||
|
|
||||||
|
// The black theme has a completely separate set of styles since style attributes cannot
|
||||||
|
// be modified at runtime.
|
||||||
|
if (isNight && settingsManager.useBlackTheme) {
|
||||||
|
setTheme(newAccent.blackTheme)
|
||||||
|
} else {
|
||||||
|
setTheme(newAccent.theme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupEdgeToEdge(binding: ActivityMainBinding) {
|
private fun setupEdgeToEdge(binding: ActivityMainBinding) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
// Do modern edge to edge, which happens to be around twice the size of the
|
// Do modern edge to edge, which happens to be around twice the size of the
|
||||||
|
|
|
@ -32,15 +32,11 @@ import androidx.navigation.NavOptions
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
|
import org.oxycblt.auxio.accent.Accent
|
||||||
import org.oxycblt.auxio.databinding.FragmentMainBinding
|
import org.oxycblt.auxio.databinding.FragmentMainBinding
|
||||||
import org.oxycblt.auxio.detail.DetailViewModel
|
import org.oxycblt.auxio.detail.DetailViewModel
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.ui.Accent
|
|
||||||
import org.oxycblt.auxio.ui.isLandscape
|
|
||||||
import org.oxycblt.auxio.ui.isTablet
|
|
||||||
import org.oxycblt.auxio.ui.resolveAttr
|
|
||||||
import org.oxycblt.auxio.ui.toColor
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The primary "Home" [Fragment] for Auxio.
|
* The primary "Home" [Fragment] for Auxio.
|
||||||
|
@ -56,7 +52,7 @@ class MainFragment : Fragment() {
|
||||||
): View {
|
): View {
|
||||||
val binding = FragmentMainBinding.inflate(inflater)
|
val binding = FragmentMainBinding.inflate(inflater)
|
||||||
|
|
||||||
val colorActive = Accent.get().color.toColor(requireContext())
|
val colorActive = Accent.get().color.resolveColor(requireContext())
|
||||||
val colorInactive = ColorUtils.setAlphaComponent(colorActive, 150)
|
val colorInactive = ColorUtils.setAlphaComponent(colorActive, 150)
|
||||||
|
|
||||||
// Set up the tints for the navigation icons + text
|
// Set up the tints for the navigation icons + text
|
||||||
|
@ -86,7 +82,7 @@ class MainFragment : Fragment() {
|
||||||
itemIconTintList = navTints
|
itemIconTintList = navTints
|
||||||
itemTextColor = navTints
|
itemTextColor = navTints
|
||||||
|
|
||||||
if (isTablet(resources) && !isLandscape(resources)) {
|
if (requireContext().isTablet() && !requireContext().isLandscape()) {
|
||||||
labelVisibilityMode = BottomNavigationView.LABEL_VISIBILITY_LABELED
|
labelVisibilityMode = BottomNavigationView.LABEL_VISIBILITY_LABELED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +156,7 @@ class MainFragment : Fragment() {
|
||||||
if (song == null) {
|
if (song == null) {
|
||||||
logD("Hiding CompactPlaybackFragment since no song is being played.")
|
logD("Hiding CompactPlaybackFragment since no song is being played.")
|
||||||
|
|
||||||
binding.compactPlayback.visibility = if (isLandscape(resources)) {
|
binding.compactPlayback.visibility = if (requireContext().isLandscape()) {
|
||||||
View.INVISIBLE
|
View.INVISIBLE
|
||||||
} else {
|
} else {
|
||||||
View.GONE
|
View.GONE
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.ui
|
package org.oxycblt.auxio.accent
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -27,6 +27,7 @@ import androidx.annotation.StringRes
|
||||||
import androidx.annotation.StyleRes
|
import androidx.annotation.StyleRes
|
||||||
import androidx.core.text.HtmlCompat
|
import androidx.core.text.HtmlCompat
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.resolveStateList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of all possible accents.
|
* A list of all possible accents.
|
||||||
|
@ -94,7 +95,7 @@ data class Accent(
|
||||||
/**
|
/**
|
||||||
* Get a [ColorStateList] of the accent
|
* Get a [ColorStateList] of the accent
|
||||||
*/
|
*/
|
||||||
fun getStateList(context: Context) = color.toStateList(context)
|
fun getStateList(context: Context) = color.resolveStateList(context)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name (in bold) and the hex value of a accent.
|
* Get the name (in bold) and the hex value of a accent.
|
|
@ -16,17 +16,15 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.settings.accent
|
package org.oxycblt.auxio.accent
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.TooltipCompat
|
import androidx.appcompat.widget.TooltipCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.ItemAccentBinding
|
import org.oxycblt.auxio.databinding.ItemAccentBinding
|
||||||
import org.oxycblt.auxio.ui.ACCENTS
|
import org.oxycblt.auxio.inflater
|
||||||
import org.oxycblt.auxio.ui.Accent
|
import org.oxycblt.auxio.resolveStateList
|
||||||
import org.oxycblt.auxio.ui.inflater
|
|
||||||
import org.oxycblt.auxio.ui.toStateList
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter that displays the list of all possible accents, and highlights the current one.
|
* An adapter that displays the list of all possible accents, and highlights the current one.
|
||||||
|
@ -84,9 +82,9 @@ class AccentAdapter(
|
||||||
selectedViewHolder?.setSelected(false)
|
selectedViewHolder?.setSelected(false)
|
||||||
selectedViewHolder = this
|
selectedViewHolder = this
|
||||||
|
|
||||||
R.color.surface.toStateList(context)
|
R.color.surface.resolveStateList(context)
|
||||||
} else {
|
} else {
|
||||||
android.R.color.transparent.toStateList(context)
|
android.R.color.transparent.resolveStateList(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.settings.accent
|
package org.oxycblt.auxio.accent
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -27,11 +27,9 @@ import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.DialogAccentBinding
|
import org.oxycblt.auxio.databinding.DialogAccentBinding
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
|
import org.oxycblt.auxio.resolveColor
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.settings.ui.LifecycleDialog
|
import org.oxycblt.auxio.settings.ui.LifecycleDialog
|
||||||
import org.oxycblt.auxio.ui.ACCENTS
|
|
||||||
import org.oxycblt.auxio.ui.Accent
|
|
||||||
import org.oxycblt.auxio.ui.toColor
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog responsible for showing the list of accents to select.
|
* Dialog responsible for showing the list of accents to select.
|
||||||
|
@ -93,7 +91,7 @@ class AccentDialog : LifecycleDialog() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateAccent() {
|
private fun updateAccent() {
|
||||||
val accentColor = pendingAccent.color.toColor(requireContext())
|
val accentColor = pendingAccent.color.resolveColor(requireContext())
|
||||||
|
|
||||||
(requireDialog() as AlertDialog).apply {
|
(requireDialog() as AlertDialog).apply {
|
||||||
getButton(AlertDialog.BUTTON_POSITIVE)?.setTextColor(accentColor)
|
getButton(AlertDialog.BUTTON_POSITIVE)?.setTextColor(accentColor)
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.settings.accent
|
package org.oxycblt.auxio.accent
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
|
@ -25,6 +25,7 @@ import android.view.ViewGroup
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.canScroll
|
||||||
import org.oxycblt.auxio.detail.adapters.AlbumDetailAdapter
|
import org.oxycblt.auxio.detail.adapters.AlbumDetailAdapter
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
|
@ -34,10 +35,9 @@ import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||||
import org.oxycblt.auxio.recycler.CenterSmoothScroller
|
import org.oxycblt.auxio.recycler.CenterSmoothScroller
|
||||||
|
import org.oxycblt.auxio.showToast
|
||||||
import org.oxycblt.auxio.ui.ActionMenu
|
import org.oxycblt.auxio.ui.ActionMenu
|
||||||
import org.oxycblt.auxio.ui.canScroll
|
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
import org.oxycblt.auxio.ui.showToast
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [DetailFragment] for an album.
|
* The [DetailFragment] for an album.
|
||||||
|
|
|
@ -28,8 +28,8 @@ import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.databinding.FragmentDetailBinding
|
import org.oxycblt.auxio.databinding.FragmentDetailBinding
|
||||||
|
import org.oxycblt.auxio.isLandscape
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.ui.isLandscape
|
|
||||||
import org.oxycblt.auxio.ui.memberBinding
|
import org.oxycblt.auxio.ui.memberBinding
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,7 +95,7 @@ abstract class DetailFragment : Fragment() {
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
|
|
||||||
// Set up a grid if the mode is landscape
|
// Set up a grid if the mode is landscape
|
||||||
if (isLandscape(resources)) {
|
if (requireContext().isLandscape()) {
|
||||||
layoutManager = GridLayoutManager(requireContext(), 2).also {
|
layoutManager = GridLayoutManager(requireContext(), 2).also {
|
||||||
it.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
it.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
||||||
override fun getSpanSize(position: Int): Int {
|
override fun getSpanSize(position: Int): Int {
|
||||||
|
|
|
@ -23,9 +23,12 @@ import android.view.ViewGroup
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.oxycblt.auxio.accent.Accent
|
||||||
import org.oxycblt.auxio.databinding.ItemAlbumHeaderBinding
|
import org.oxycblt.auxio.databinding.ItemAlbumHeaderBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
|
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
|
||||||
import org.oxycblt.auxio.detail.DetailViewModel
|
import org.oxycblt.auxio.detail.DetailViewModel
|
||||||
|
import org.oxycblt.auxio.disable
|
||||||
|
import org.oxycblt.auxio.inflater
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.BaseModel
|
import org.oxycblt.auxio.music.BaseModel
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
@ -33,10 +36,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.recycler.DiffCallback
|
import org.oxycblt.auxio.recycler.DiffCallback
|
||||||
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
||||||
import org.oxycblt.auxio.recycler.viewholders.Highlightable
|
import org.oxycblt.auxio.recycler.viewholders.Highlightable
|
||||||
import org.oxycblt.auxio.ui.Accent
|
import org.oxycblt.auxio.setTextColorResource
|
||||||
import org.oxycblt.auxio.ui.disable
|
|
||||||
import org.oxycblt.auxio.ui.inflater
|
|
||||||
import org.oxycblt.auxio.ui.setTextColorResource
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter for displaying the details and [Song]s of an [Album]
|
* An adapter for displaying the details and [Song]s of an [Album]
|
||||||
|
|
|
@ -22,11 +22,14 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.oxycblt.auxio.accent.Accent
|
||||||
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
|
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding
|
import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemArtistHeaderBinding
|
import org.oxycblt.auxio.databinding.ItemArtistHeaderBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemArtistSongBinding
|
import org.oxycblt.auxio.databinding.ItemArtistSongBinding
|
||||||
import org.oxycblt.auxio.detail.DetailViewModel
|
import org.oxycblt.auxio.detail.DetailViewModel
|
||||||
|
import org.oxycblt.auxio.disable
|
||||||
|
import org.oxycblt.auxio.inflater
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
|
@ -37,10 +40,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.recycler.DiffCallback
|
import org.oxycblt.auxio.recycler.DiffCallback
|
||||||
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
||||||
import org.oxycblt.auxio.recycler.viewholders.Highlightable
|
import org.oxycblt.auxio.recycler.viewholders.Highlightable
|
||||||
import org.oxycblt.auxio.ui.Accent
|
import org.oxycblt.auxio.setTextColorResource
|
||||||
import org.oxycblt.auxio.ui.disable
|
|
||||||
import org.oxycblt.auxio.ui.inflater
|
|
||||||
import org.oxycblt.auxio.ui.setTextColorResource
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter for displaying the [Album]s and [Song]s of an artist.
|
* An adapter for displaying the [Album]s and [Song]s of an artist.
|
||||||
|
|
|
@ -23,9 +23,12 @@ import android.view.ViewGroup
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.oxycblt.auxio.accent.Accent
|
||||||
import org.oxycblt.auxio.databinding.ItemGenreHeaderBinding
|
import org.oxycblt.auxio.databinding.ItemGenreHeaderBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemGenreSongBinding
|
import org.oxycblt.auxio.databinding.ItemGenreSongBinding
|
||||||
import org.oxycblt.auxio.detail.DetailViewModel
|
import org.oxycblt.auxio.detail.DetailViewModel
|
||||||
|
import org.oxycblt.auxio.disable
|
||||||
|
import org.oxycblt.auxio.inflater
|
||||||
import org.oxycblt.auxio.music.BaseModel
|
import org.oxycblt.auxio.music.BaseModel
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
@ -33,10 +36,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.recycler.DiffCallback
|
import org.oxycblt.auxio.recycler.DiffCallback
|
||||||
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
||||||
import org.oxycblt.auxio.recycler.viewholders.Highlightable
|
import org.oxycblt.auxio.recycler.viewholders.Highlightable
|
||||||
import org.oxycblt.auxio.ui.Accent
|
import org.oxycblt.auxio.setTextColorResource
|
||||||
import org.oxycblt.auxio.ui.disable
|
|
||||||
import org.oxycblt.auxio.ui.inflater
|
|
||||||
import org.oxycblt.auxio.ui.setTextColorResource
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter for displaying the [Song]s of a genre.
|
* An adapter for displaying the [Song]s of a genre.
|
||||||
|
|
|
@ -16,24 +16,24 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music
|
package org.oxycblt.auxio.excluded
|
||||||
|
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
import androidx.core.database.sqlite.transaction
|
import androidx.core.database.sqlite.transaction
|
||||||
|
import org.oxycblt.auxio.assertBackgroundThread
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.ui.assertBackgroundThread
|
import org.oxycblt.auxio.queryAll
|
||||||
import org.oxycblt.auxio.ui.queryAll
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database for storing blacklisted paths.
|
* Database for storing excluded directories.
|
||||||
* Note that the paths stored here will not work with MediaStore unless you append a "%" at the end.
|
* Note that the paths stored here will not work with MediaStore unless you append a "%" at the end.
|
||||||
* Yes. I know Room exists. But that would needlessly bloat my app and has crippling bugs.
|
* Yes. I know Room exists. But that would needlessly bloat my app and has crippling bugs.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class BlacklistDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
class ExcludedDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
||||||
override fun onCreate(db: SQLiteDatabase) {
|
override fun onCreate(db: SQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_NAME ($COLUMN_PATH TEXT NOT NULL)")
|
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_NAME ($COLUMN_PATH TEXT NOT NULL)")
|
||||||
}
|
}
|
||||||
|
@ -94,12 +94,12 @@ class BlacklistDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, n
|
||||||
const val COLUMN_PATH = "COLUMN_PATH"
|
const val COLUMN_PATH = "COLUMN_PATH"
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var INSTANCE: BlacklistDatabase? = null
|
private var INSTANCE: ExcludedDatabase? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get/Instantiate the single instance of [PlaybackStateDatabase].
|
* Get/Instantiate the single instance of [PlaybackStateDatabase].
|
||||||
*/
|
*/
|
||||||
fun getInstance(context: Context): BlacklistDatabase {
|
fun getInstance(context: Context): ExcludedDatabase {
|
||||||
val currentInstance = INSTANCE
|
val currentInstance = INSTANCE
|
||||||
|
|
||||||
if (currentInstance != null) {
|
if (currentInstance != null) {
|
||||||
|
@ -107,7 +107,7 @@ class BlacklistDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, n
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
val newInstance = BlacklistDatabase(context.applicationContext)
|
val newInstance = ExcludedDatabase(context.applicationContext)
|
||||||
INSTANCE = newInstance
|
INSTANCE = newInstance
|
||||||
return newInstance
|
return newInstance
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.settings.blacklist
|
package org.oxycblt.auxio.excluded
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -37,16 +37,16 @@ import org.oxycblt.auxio.databinding.DialogBlacklistBinding
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.settings.ui.LifecycleDialog
|
import org.oxycblt.auxio.settings.ui.LifecycleDialog
|
||||||
import org.oxycblt.auxio.ui.showToast
|
import org.oxycblt.auxio.showToast
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog that manages the currently excluded directories.
|
* Dialog that manages the currently excluded directories.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class BlacklistDialog : LifecycleDialog() {
|
class ExcludedDialog : LifecycleDialog() {
|
||||||
private val blacklistModel: BlacklistViewModel by viewModels {
|
private val blacklistModel: ExcludedViewModel by viewModels {
|
||||||
BlacklistViewModel.Factory(requireContext())
|
ExcludedViewModel.Factory(requireContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
|
@ -58,7 +58,7 @@ class BlacklistDialog : LifecycleDialog() {
|
||||||
): View {
|
): View {
|
||||||
val binding = DialogBlacklistBinding.inflate(inflater)
|
val binding = DialogBlacklistBinding.inflate(inflater)
|
||||||
|
|
||||||
val adapter = BlacklistEntryAdapter { path ->
|
val adapter = ExcludedEntryAdapter { path ->
|
||||||
blacklistModel.removePath(path)
|
blacklistModel.removePath(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +123,7 @@ class BlacklistDialog : LifecycleDialog() {
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
blacklistModel.addPath(path)
|
blacklistModel.addPath(path)
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: Tolerate this once the excluded system is modernized
|
||||||
requireContext().showToast(R.string.err_bad_dir)
|
requireContext().showToast(R.string.err_bad_dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,21 +16,21 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.settings.blacklist
|
package org.oxycblt.auxio.excluded
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.databinding.ItemBlacklistEntryBinding
|
import org.oxycblt.auxio.databinding.ItemBlacklistEntryBinding
|
||||||
import org.oxycblt.auxio.ui.inflater
|
import org.oxycblt.auxio.inflater
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter that shows the blacklist entries and their "Clear" button.
|
* Adapter that shows the blacklist entries and their "Clear" button.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class BlacklistEntryAdapter(
|
class ExcludedEntryAdapter(
|
||||||
private val onClear: (String) -> Unit
|
private val onClear: (String) -> Unit
|
||||||
) : RecyclerView.Adapter<BlacklistEntryAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<ExcludedEntryAdapter.ViewHolder>() {
|
||||||
private var paths = mutableListOf<String>()
|
private var paths = mutableListOf<String>()
|
||||||
|
|
||||||
override fun getItemCount() = paths.size
|
override fun getItemCount() = paths.size
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.settings.blacklist
|
package org.oxycblt.auxio.excluded
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
@ -27,18 +27,17 @@ import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.oxycblt.auxio.music.BlacklistDatabase
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewModel that acts as a wrapper around [BlacklistDatabase], allowing for the addition/removal
|
* ViewModel that acts as a wrapper around [ExcludedDatabase], allowing for the addition/removal
|
||||||
* of paths. Use [Factory] to instantiate this.
|
* of paths. Use [Factory] to instantiate this.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class BlacklistViewModel(context: Context) : ViewModel() {
|
class ExcludedViewModel(context: Context) : ViewModel() {
|
||||||
private val mPaths = MutableLiveData(mutableListOf<String>())
|
private val mPaths = MutableLiveData(mutableListOf<String>())
|
||||||
val paths: LiveData<MutableList<String>> get() = mPaths
|
val paths: LiveData<MutableList<String>> get() = mPaths
|
||||||
|
|
||||||
private val blacklistDatabase = BlacklistDatabase.getInstance(context)
|
private val blacklistDatabase = ExcludedDatabase.getInstance(context)
|
||||||
private var dbPaths = listOf<String>()
|
private var dbPaths = listOf<String>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -97,12 +96,12 @@ class BlacklistViewModel(context: Context) : ViewModel() {
|
||||||
|
|
||||||
class Factory(private val context: Context) : ViewModelProvider.Factory {
|
class Factory(private val context: Context) : ViewModelProvider.Factory {
|
||||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||||
check(modelClass.isAssignableFrom(BlacklistViewModel::class.java)) {
|
check(modelClass.isAssignableFrom(ExcludedViewModel::class.java)) {
|
||||||
"BlacklistViewModel.Factory does not support this class"
|
"BlacklistViewModel.Factory does not support this class"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
return BlacklistViewModel(context) as T
|
return ExcludedViewModel(context) as T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -36,7 +36,7 @@ import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Parent
|
import org.oxycblt.auxio.music.Parent
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.recycler.sliceArticle
|
import org.oxycblt.auxio.recycler.sliceArticle
|
||||||
import org.oxycblt.auxio.ui.getSpans
|
import org.oxycblt.auxio.spans
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,7 +78,6 @@ class LibraryFragment : Fragment() {
|
||||||
adapter = libraryAdapter
|
adapter = libraryAdapter
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
|
|
||||||
val spans = getSpans()
|
|
||||||
if (spans != 1) {
|
if (spans != 1) {
|
||||||
layoutManager = GridLayoutManager(requireContext(), spans)
|
layoutManager = GridLayoutManager(requireContext(), spans)
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,6 @@ import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentLoadingBinding
|
import org.oxycblt.auxio.databinding.FragmentLoadingBinding
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.ui.isLandscape
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment that handles what to display during the loading process.
|
* Fragment that handles what to display during the loading process.
|
||||||
|
@ -62,10 +61,6 @@ class LoadingFragment : Fragment() {
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
binding.loadingModel = loadingModel
|
binding.loadingModel = loadingModel
|
||||||
|
|
||||||
// The loading panel shouldn't fit the system window on landscape as that will cause it
|
|
||||||
// to be mis-aligned with the Auxio icon.
|
|
||||||
binding.loadingPanel.fitsSystemWindows = !isLandscape(resources)
|
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
loadingModel.doGrant.observe(viewLifecycleOwner) { doGrant ->
|
loadingModel.doGrant.observe(viewLifecycleOwner) { doGrant ->
|
||||||
|
|
|
@ -27,6 +27,7 @@ import android.provider.MediaStore.Audio.Genres
|
||||||
import android.provider.MediaStore.Audio.Media
|
import android.provider.MediaStore.Audio.Media
|
||||||
import androidx.core.database.getStringOrNull
|
import androidx.core.database.getStringOrNull
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.excluded.ExcludedDatabase
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,7 +70,7 @@ class MusicLoader(private val context: Context) {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
private fun buildSelector() {
|
private fun buildSelector() {
|
||||||
// TODO: Upgrade this to be compatible with Android Q.
|
// TODO: Upgrade this to be compatible with Android Q.
|
||||||
val blacklistDatabase = BlacklistDatabase.getInstance(context)
|
val blacklistDatabase = ExcludedDatabase.getInstance(context)
|
||||||
|
|
||||||
val paths = blacklistDatabase.readPaths()
|
val paths = blacklistDatabase.readPaths()
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ import android.widget.TextView
|
||||||
import androidx.core.text.isDigitsOnly
|
import androidx.core.text.isDigitsOnly
|
||||||
import androidx.databinding.BindingAdapter
|
import androidx.databinding.BindingAdapter
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.ui.getPlural
|
import org.oxycblt.auxio.getPlural
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A complete array of all the hardcoded genre values for ID3 <v3, contains standard genres and
|
* A complete array of all the hardcoded genre values for ID3 <v3, contains standard genres and
|
||||||
|
|
|
@ -26,7 +26,7 @@ import android.util.AttributeSet
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.widget.AppCompatImageButton
|
import androidx.appcompat.widget.AppCompatImageButton
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.ui.toAnimDrawable
|
import org.oxycblt.auxio.toAnimDrawable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom [AppCompatImageButton] that handles the animated play/pause icons.
|
* Custom [AppCompatImageButton] that handles the animated play/pause icons.
|
||||||
|
|
|
@ -28,14 +28,14 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.accent.Accent
|
||||||
import org.oxycblt.auxio.databinding.FragmentPlaybackBinding
|
import org.oxycblt.auxio.databinding.FragmentPlaybackBinding
|
||||||
import org.oxycblt.auxio.detail.DetailViewModel
|
import org.oxycblt.auxio.detail.DetailViewModel
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.playback.state.LoopMode
|
import org.oxycblt.auxio.playback.state.LoopMode
|
||||||
import org.oxycblt.auxio.ui.Accent
|
import org.oxycblt.auxio.resolveDrawable
|
||||||
|
import org.oxycblt.auxio.resolveStateList
|
||||||
import org.oxycblt.auxio.ui.memberBinding
|
import org.oxycblt.auxio.ui.memberBinding
|
||||||
import org.oxycblt.auxio.ui.toDrawable
|
|
||||||
import org.oxycblt.auxio.ui.toStateList
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [Fragment] that displays more information about the song, along with more media controls.
|
* A [Fragment] that displays more information about the song, along with more media controls.
|
||||||
|
@ -56,11 +56,11 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||||
): View {
|
): View {
|
||||||
val normalTextColor = binding.playbackDurationCurrent.currentTextColor
|
val normalTextColor = binding.playbackDurationCurrent.currentTextColor
|
||||||
val accentColor = Accent.get().getStateList(requireContext())
|
val accentColor = Accent.get().getStateList(requireContext())
|
||||||
val controlColor = R.color.control.toStateList(requireContext())
|
val controlColor = R.color.control.resolveStateList(requireContext())
|
||||||
|
|
||||||
// Can't set the tint of a MenuItem below Android 8, so use icons instead.
|
// Can't set the tint of a MenuItem below Android 8, so use icons instead.
|
||||||
val iconQueueActive = R.drawable.ic_queue.toDrawable(requireContext())
|
val iconQueueActive = R.drawable.ic_queue.resolveDrawable(requireContext())
|
||||||
val iconQueueInactive = R.drawable.ic_queue_inactive.toDrawable(requireContext())
|
val iconQueueInactive = R.drawable.ic_queue_inactive.resolveDrawable(requireContext())
|
||||||
|
|
||||||
val queueItem: MenuItem
|
val queueItem: MenuItem
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
|
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
|
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
|
||||||
|
import org.oxycblt.auxio.inflater
|
||||||
import org.oxycblt.auxio.logE
|
import org.oxycblt.auxio.logE
|
||||||
import org.oxycblt.auxio.music.BaseModel
|
import org.oxycblt.auxio.music.BaseModel
|
||||||
import org.oxycblt.auxio.music.Header
|
import org.oxycblt.auxio.music.Header
|
||||||
|
@ -36,7 +37,6 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.recycler.DiffCallback
|
import org.oxycblt.auxio.recycler.DiffCallback
|
||||||
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
||||||
import org.oxycblt.auxio.recycler.viewholders.HeaderViewHolder
|
import org.oxycblt.auxio.recycler.viewholders.HeaderViewHolder
|
||||||
import org.oxycblt.auxio.ui.inflater
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The single adapter for both the Next Queue and the User Queue.
|
* The single adapter for both the Next Queue and the User Queue.
|
||||||
|
|
|
@ -32,11 +32,11 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentQueueBinding
|
import org.oxycblt.auxio.databinding.FragmentQueueBinding
|
||||||
|
import org.oxycblt.auxio.isEdgeOn
|
||||||
|
import org.oxycblt.auxio.isIrregularLandscape
|
||||||
import org.oxycblt.auxio.music.BaseModel
|
import org.oxycblt.auxio.music.BaseModel
|
||||||
import org.oxycblt.auxio.music.Header
|
import org.oxycblt.auxio.music.Header
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.ui.isEdgeOn
|
|
||||||
import org.oxycblt.auxio.ui.isIrregularLandscape
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [Fragment] that contains both the user queue and the next queue, with the ability to
|
* A [Fragment] that contains both the user queue and the next queue, with the ability to
|
||||||
|
|
|
@ -26,7 +26,7 @@ package org.oxycblt.auxio.playback.state
|
||||||
* @property isUserQueue A bool for if this queue item is a user queue item or not
|
* @property isUserQueue A bool for if this queue item is a user queue item or not
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
data class QueueItem(
|
data class DatabaseQueueItem(
|
||||||
var id: Long = 0L,
|
var id: Long = 0L,
|
||||||
val songHash: Int,
|
val songHash: Int,
|
||||||
val albumHash: Int,
|
val albumHash: Int,
|
|
@ -30,7 +30,7 @@ package org.oxycblt.auxio.playback.state
|
||||||
* @property inUserQueue - A bool for if the state was currently playing from the user queue.
|
* @property inUserQueue - A bool for if the state was currently playing from the user queue.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
data class PlaybackState(
|
data class DatabaseState(
|
||||||
val id: Long = 0L,
|
val id: Long = 0L,
|
||||||
val songHash: Int,
|
val songHash: Int,
|
||||||
val position: Long,
|
val position: Long,
|
|
@ -23,9 +23,9 @@ import android.content.Context
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
import androidx.core.database.sqlite.transaction
|
import androidx.core.database.sqlite.transaction
|
||||||
|
import org.oxycblt.auxio.assertBackgroundThread
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.ui.assertBackgroundThread
|
import org.oxycblt.auxio.queryAll
|
||||||
import org.oxycblt.auxio.ui.queryAll
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A SQLite database for managing the persistent playback state and queue.
|
* A SQLite database for managing the persistent playback state and queue.
|
||||||
|
@ -72,30 +72,30 @@ class PlaybackStateDatabase(context: Context) :
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a [PlaybackState] table
|
* Construct a [DatabaseState] table
|
||||||
*/
|
*/
|
||||||
private fun constructStateTable(command: StringBuilder): StringBuilder {
|
private fun constructStateTable(command: StringBuilder): StringBuilder {
|
||||||
command.append("${PlaybackState.COLUMN_ID} LONG PRIMARY KEY,")
|
command.append("${DatabaseState.COLUMN_ID} LONG PRIMARY KEY,")
|
||||||
.append("${PlaybackState.COLUMN_SONG_HASH} INTEGER NOT NULL,")
|
.append("${DatabaseState.COLUMN_SONG_HASH} INTEGER NOT NULL,")
|
||||||
.append("${PlaybackState.COLUMN_POSITION} LONG NOT NULL,")
|
.append("${DatabaseState.COLUMN_POSITION} LONG NOT NULL,")
|
||||||
.append("${PlaybackState.COLUMN_PARENT_HASH} INTEGER NOT NULL,")
|
.append("${DatabaseState.COLUMN_PARENT_HASH} INTEGER NOT NULL,")
|
||||||
.append("${PlaybackState.COLUMN_INDEX} INTEGER NOT NULL,")
|
.append("${DatabaseState.COLUMN_INDEX} INTEGER NOT NULL,")
|
||||||
.append("${PlaybackState.COLUMN_MODE} INTEGER NOT NULL,")
|
.append("${DatabaseState.COLUMN_MODE} INTEGER NOT NULL,")
|
||||||
.append("${PlaybackState.COLUMN_IS_SHUFFLING} BOOLEAN NOT NULL,")
|
.append("${DatabaseState.COLUMN_IS_SHUFFLING} BOOLEAN NOT NULL,")
|
||||||
.append("${PlaybackState.COLUMN_LOOP_MODE} INTEGER NOT NULL,")
|
.append("${DatabaseState.COLUMN_LOOP_MODE} INTEGER NOT NULL,")
|
||||||
.append("${PlaybackState.COLUMN_IN_USER_QUEUE} BOOLEAN NOT NULL)")
|
.append("${DatabaseState.COLUMN_IN_USER_QUEUE} BOOLEAN NOT NULL)")
|
||||||
|
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a [QueueItem] table
|
* Construct a [DatabaseQueueItem] table
|
||||||
*/
|
*/
|
||||||
private fun constructQueueTable(command: StringBuilder): StringBuilder {
|
private fun constructQueueTable(command: StringBuilder): StringBuilder {
|
||||||
command.append("${QueueItem.COLUMN_ID} LONG PRIMARY KEY,")
|
command.append("${DatabaseQueueItem.COLUMN_ID} LONG PRIMARY KEY,")
|
||||||
.append("${QueueItem.COLUMN_SONG_HASH} INTEGER NOT NULL,")
|
.append("${DatabaseQueueItem.COLUMN_SONG_HASH} INTEGER NOT NULL,")
|
||||||
.append("${QueueItem.COLUMN_ALBUM_HASH} INTEGER NOT NULL,")
|
.append("${DatabaseQueueItem.COLUMN_ALBUM_HASH} INTEGER NOT NULL,")
|
||||||
.append("${QueueItem.COLUMN_IS_USER_QUEUE} BOOLEAN NOT NULL)")
|
.append("${DatabaseQueueItem.COLUMN_IS_USER_QUEUE} BOOLEAN NOT NULL)")
|
||||||
|
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
@ -103,9 +103,9 @@ class PlaybackStateDatabase(context: Context) :
|
||||||
// --- INTERFACE FUNCTIONS ---
|
// --- INTERFACE FUNCTIONS ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the previously written [PlaybackState] and write a new one.
|
* Clear the previously written [DatabaseState] and write a new one.
|
||||||
*/
|
*/
|
||||||
fun writeState(state: PlaybackState) {
|
fun writeState(state: DatabaseState) {
|
||||||
assertBackgroundThread()
|
assertBackgroundThread()
|
||||||
|
|
||||||
writableDatabase.transaction {
|
writableDatabase.transaction {
|
||||||
|
@ -114,15 +114,15 @@ class PlaybackStateDatabase(context: Context) :
|
||||||
this@PlaybackStateDatabase.logD("Wiped state db.")
|
this@PlaybackStateDatabase.logD("Wiped state db.")
|
||||||
|
|
||||||
val stateData = ContentValues(10).apply {
|
val stateData = ContentValues(10).apply {
|
||||||
put(PlaybackState.COLUMN_ID, state.id)
|
put(DatabaseState.COLUMN_ID, state.id)
|
||||||
put(PlaybackState.COLUMN_SONG_HASH, state.songHash)
|
put(DatabaseState.COLUMN_SONG_HASH, state.songHash)
|
||||||
put(PlaybackState.COLUMN_POSITION, state.position)
|
put(DatabaseState.COLUMN_POSITION, state.position)
|
||||||
put(PlaybackState.COLUMN_PARENT_HASH, state.parentHash)
|
put(DatabaseState.COLUMN_PARENT_HASH, state.parentHash)
|
||||||
put(PlaybackState.COLUMN_INDEX, state.index)
|
put(DatabaseState.COLUMN_INDEX, state.index)
|
||||||
put(PlaybackState.COLUMN_MODE, state.mode)
|
put(DatabaseState.COLUMN_MODE, state.mode)
|
||||||
put(PlaybackState.COLUMN_IS_SHUFFLING, state.isShuffling)
|
put(DatabaseState.COLUMN_IS_SHUFFLING, state.isShuffling)
|
||||||
put(PlaybackState.COLUMN_LOOP_MODE, state.loopMode)
|
put(DatabaseState.COLUMN_LOOP_MODE, state.loopMode)
|
||||||
put(PlaybackState.COLUMN_IN_USER_QUEUE, state.inUserQueue)
|
put(DatabaseState.COLUMN_IN_USER_QUEUE, state.inUserQueue)
|
||||||
}
|
}
|
||||||
|
|
||||||
insert(TABLE_NAME_STATE, null, stateData)
|
insert(TABLE_NAME_STATE, null, stateData)
|
||||||
|
@ -132,31 +132,31 @@ class PlaybackStateDatabase(context: Context) :
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the stored [PlaybackState] from the database, if there is one.
|
* Read the stored [DatabaseState] from the database, if there is one.
|
||||||
* @return The stored [PlaybackState], null if there isn't one.
|
* @return The stored [DatabaseState], null if there isn't one.
|
||||||
*/
|
*/
|
||||||
fun readState(): PlaybackState? {
|
fun readState(): DatabaseState? {
|
||||||
assertBackgroundThread()
|
assertBackgroundThread()
|
||||||
|
|
||||||
var state: PlaybackState? = null
|
var state: DatabaseState? = null
|
||||||
|
|
||||||
readableDatabase.queryAll(TABLE_NAME_STATE) { cursor ->
|
readableDatabase.queryAll(TABLE_NAME_STATE) { cursor ->
|
||||||
if (cursor.count == 0) return@queryAll
|
if (cursor.count == 0) return@queryAll
|
||||||
|
|
||||||
val songIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_SONG_HASH)
|
val songIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_SONG_HASH)
|
||||||
val posIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_POSITION)
|
val posIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_POSITION)
|
||||||
val parentIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_PARENT_HASH)
|
val parentIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_PARENT_HASH)
|
||||||
val indexIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_INDEX)
|
val indexIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_INDEX)
|
||||||
val modeIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_MODE)
|
val modeIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_MODE)
|
||||||
val shuffleIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_IS_SHUFFLING)
|
val shuffleIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_IS_SHUFFLING)
|
||||||
val loopModeIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_LOOP_MODE)
|
val loopModeIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_LOOP_MODE)
|
||||||
val inUserQueueIndex = cursor.getColumnIndexOrThrow(
|
val inUserQueueIndex = cursor.getColumnIndexOrThrow(
|
||||||
PlaybackState.COLUMN_IN_USER_QUEUE
|
DatabaseState.COLUMN_IN_USER_QUEUE
|
||||||
)
|
)
|
||||||
|
|
||||||
cursor.moveToFirst()
|
cursor.moveToFirst()
|
||||||
|
|
||||||
state = PlaybackState(
|
state = DatabaseState(
|
||||||
songHash = cursor.getInt(songIndex),
|
songHash = cursor.getInt(songIndex),
|
||||||
position = cursor.getLong(posIndex),
|
position = cursor.getLong(posIndex),
|
||||||
parentHash = cursor.getInt(parentIndex),
|
parentHash = cursor.getInt(parentIndex),
|
||||||
|
@ -174,7 +174,7 @@ class PlaybackStateDatabase(context: Context) :
|
||||||
/**
|
/**
|
||||||
* Write a list of [queueItems] to the database, clearing the previous queue present.
|
* Write a list of [queueItems] to the database, clearing the previous queue present.
|
||||||
*/
|
*/
|
||||||
fun writeQueue(queueItems: List<QueueItem>) {
|
fun writeQueue(queueItems: List<DatabaseQueueItem>) {
|
||||||
assertBackgroundThread()
|
assertBackgroundThread()
|
||||||
|
|
||||||
val database = writableDatabase
|
val database = writableDatabase
|
||||||
|
@ -197,10 +197,10 @@ class PlaybackStateDatabase(context: Context) :
|
||||||
i++
|
i++
|
||||||
|
|
||||||
val itemData = ContentValues(4).apply {
|
val itemData = ContentValues(4).apply {
|
||||||
put(QueueItem.COLUMN_ID, item.id)
|
put(DatabaseQueueItem.COLUMN_ID, item.id)
|
||||||
put(QueueItem.COLUMN_SONG_HASH, item.songHash)
|
put(DatabaseQueueItem.COLUMN_SONG_HASH, item.songHash)
|
||||||
put(QueueItem.COLUMN_ALBUM_HASH, item.albumHash)
|
put(DatabaseQueueItem.COLUMN_ALBUM_HASH, item.albumHash)
|
||||||
put(QueueItem.COLUMN_IS_USER_QUEUE, item.isUserQueue)
|
put(DatabaseQueueItem.COLUMN_IS_USER_QUEUE, item.isUserQueue)
|
||||||
}
|
}
|
||||||
|
|
||||||
insert(TABLE_NAME_QUEUE, null, itemData)
|
insert(TABLE_NAME_QUEUE, null, itemData)
|
||||||
|
@ -216,24 +216,24 @@ class PlaybackStateDatabase(context: Context) :
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the database for any [QueueItem]s.
|
* Read the database for any [DatabaseQueueItem]s.
|
||||||
* @return A list of any stored [QueueItem]s.
|
* @return A list of any stored [DatabaseQueueItem]s.
|
||||||
*/
|
*/
|
||||||
fun readQueue(): List<QueueItem> {
|
fun readQueue(): List<DatabaseQueueItem> {
|
||||||
assertBackgroundThread()
|
assertBackgroundThread()
|
||||||
|
|
||||||
val queueItems = mutableListOf<QueueItem>()
|
val queueItems = mutableListOf<DatabaseQueueItem>()
|
||||||
|
|
||||||
readableDatabase.queryAll(TABLE_NAME_QUEUE) { cursor ->
|
readableDatabase.queryAll(TABLE_NAME_QUEUE) { cursor ->
|
||||||
if (cursor.count == 0) return@queryAll
|
if (cursor.count == 0) return@queryAll
|
||||||
|
|
||||||
val idIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_ID)
|
val idIndex = cursor.getColumnIndexOrThrow(DatabaseQueueItem.COLUMN_ID)
|
||||||
val songIdIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_SONG_HASH)
|
val songIdIndex = cursor.getColumnIndexOrThrow(DatabaseQueueItem.COLUMN_SONG_HASH)
|
||||||
val albumIdIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_ALBUM_HASH)
|
val albumIdIndex = cursor.getColumnIndexOrThrow(DatabaseQueueItem.COLUMN_ALBUM_HASH)
|
||||||
val isUserQueueIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_IS_USER_QUEUE)
|
val isUserQueueIndex = cursor.getColumnIndexOrThrow(DatabaseQueueItem.COLUMN_IS_USER_QUEUE)
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
queueItems += QueueItem(
|
queueItems += DatabaseQueueItem(
|
||||||
id = cursor.getLong(idIndex),
|
id = cursor.getLong(idIndex),
|
||||||
songHash = cursor.getInt(songIdIndex),
|
songHash = cursor.getInt(songIdIndex),
|
||||||
albumHash = cursor.getInt(albumIdIndex),
|
albumHash = cursor.getInt(albumIdIndex),
|
||||||
|
|
|
@ -581,8 +581,8 @@ class PlaybackStateManager private constructor() {
|
||||||
logD("Getting state from DB.")
|
logD("Getting state from DB.")
|
||||||
|
|
||||||
val start: Long
|
val start: Long
|
||||||
val playbackState: PlaybackState?
|
val playbackState: DatabaseState?
|
||||||
val queueItems: List<QueueItem>
|
val queueItems: List<DatabaseQueueItem>
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
start = System.currentTimeMillis()
|
start = System.currentTimeMillis()
|
||||||
|
@ -609,11 +609,11 @@ class PlaybackStateManager private constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pack the current state into a [PlaybackState] to be saved.
|
* Pack the current state into a [DatabaseState] to be saved.
|
||||||
* @return A [PlaybackState] reflecting the current state.
|
* @return A [DatabaseState] reflecting the current state.
|
||||||
*/
|
*/
|
||||||
private fun packToPlaybackState(): PlaybackState {
|
private fun packToPlaybackState(): DatabaseState {
|
||||||
return PlaybackState(
|
return DatabaseState(
|
||||||
songHash = mSong?.hash ?: Int.MIN_VALUE,
|
songHash = mSong?.hash ?: Int.MIN_VALUE,
|
||||||
position = mPosition,
|
position = mPosition,
|
||||||
parentHash = mParent?.hash ?: Int.MIN_VALUE,
|
parentHash = mParent?.hash ?: Int.MIN_VALUE,
|
||||||
|
@ -628,7 +628,7 @@ class PlaybackStateManager private constructor() {
|
||||||
/**
|
/**
|
||||||
* Unpack a [playbackState] into this instance.
|
* Unpack a [playbackState] into this instance.
|
||||||
*/
|
*/
|
||||||
private fun unpackFromPlaybackState(playbackState: PlaybackState) {
|
private fun unpackFromPlaybackState(playbackState: DatabaseState) {
|
||||||
// Turn the simplified information from PlaybackState into usable data.
|
// Turn the simplified information from PlaybackState into usable data.
|
||||||
|
|
||||||
// Do queue setup first
|
// Do queue setup first
|
||||||
|
@ -646,21 +646,21 @@ class PlaybackStateManager private constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pack the queue into a list of [QueueItem]s to be saved.
|
* Pack the queue into a list of [DatabaseQueueItem]s to be saved.
|
||||||
* @return A list of packed queue items.
|
* @return A list of packed queue items.
|
||||||
*/
|
*/
|
||||||
private fun packQueue(): List<QueueItem> {
|
private fun packQueue(): List<DatabaseQueueItem> {
|
||||||
val unified = mutableListOf<QueueItem>()
|
val unified = mutableListOf<DatabaseQueueItem>()
|
||||||
|
|
||||||
var queueItemId = 0L
|
var queueItemId = 0L
|
||||||
|
|
||||||
mUserQueue.forEach { song ->
|
mUserQueue.forEach { song ->
|
||||||
unified.add(QueueItem(queueItemId, song.hash, song.album.hash, true))
|
unified.add(DatabaseQueueItem(queueItemId, song.hash, song.album.hash, true))
|
||||||
queueItemId++
|
queueItemId++
|
||||||
}
|
}
|
||||||
|
|
||||||
mQueue.forEach { song ->
|
mQueue.forEach { song ->
|
||||||
unified.add(QueueItem(queueItemId, song.hash, song.album.hash, false))
|
unified.add(DatabaseQueueItem(queueItemId, song.hash, song.album.hash, false))
|
||||||
queueItemId++
|
queueItemId++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -669,9 +669,9 @@ class PlaybackStateManager private constructor() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unpack a list of queue items into a queue & user queue.
|
* Unpack a list of queue items into a queue & user queue.
|
||||||
* @param queueItems The list of [QueueItem]s to unpack.
|
* @param queueItems The list of [DatabaseQueueItem]s to unpack.
|
||||||
*/
|
*/
|
||||||
private fun unpackQueue(queueItems: List<QueueItem>) {
|
private fun unpackQueue(queueItems: List<DatabaseQueueItem>) {
|
||||||
for (item in queueItems) {
|
for (item in queueItems) {
|
||||||
musicStore.findSongFast(item.songHash, item.albumHash)?.let { song ->
|
musicStore.findSongFast(item.songHash, item.albumHash)?.let { song ->
|
||||||
if (item.isUserQueue) {
|
if (item.isUserQueue) {
|
||||||
|
|
|
@ -25,10 +25,10 @@ import androidx.core.animation.addListener
|
||||||
import androidx.media.AudioFocusRequestCompat
|
import androidx.media.AudioFocusRequestCompat
|
||||||
import androidx.media.AudioManagerCompat
|
import androidx.media.AudioManagerCompat
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||||
|
import org.oxycblt.auxio.getSystemServiceSafe
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.ui.getSystemServiceSafe
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object that manages the AudioFocus state.
|
* Object that manages the AudioFocus state.
|
||||||
|
|
|
@ -31,10 +31,10 @@ import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.coil.loadBitmap
|
import org.oxycblt.auxio.coil.loadBitmap
|
||||||
import org.oxycblt.auxio.music.Parent
|
import org.oxycblt.auxio.music.Parent
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
import org.oxycblt.auxio.newBroadcastIntent
|
||||||
|
import org.oxycblt.auxio.newMainIntent
|
||||||
import org.oxycblt.auxio.playback.state.LoopMode
|
import org.oxycblt.auxio.playback.state.LoopMode
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.ui.newBroadcastIntent
|
|
||||||
import org.oxycblt.auxio.ui.newMainIntent
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The unified notification for [PlaybackService]. This is not self-sufficient, updates have
|
* The unified notification for [PlaybackService]. This is not self-sufficient, updates have
|
||||||
|
|
|
@ -52,6 +52,7 @@ import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.takeWhile
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
|
import org.oxycblt.auxio.getSystemServiceSafe
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.music.Parent
|
import org.oxycblt.auxio.music.Parent
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
@ -59,7 +60,6 @@ import org.oxycblt.auxio.music.toURI
|
||||||
import org.oxycblt.auxio.playback.state.LoopMode
|
import org.oxycblt.auxio.playback.state.LoopMode
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.ui.getSystemServiceSafe
|
|
||||||
import org.oxycblt.auxio.widgets.WidgetController
|
import org.oxycblt.auxio.widgets.WidgetController
|
||||||
import org.oxycblt.auxio.widgets.WidgetProvider
|
import org.oxycblt.auxio.widgets.WidgetProvider
|
||||||
|
|
||||||
|
|
|
@ -33,13 +33,13 @@ import androidx.dynamicanimation.animation.SpringAnimation
|
||||||
import androidx.dynamicanimation.animation.SpringForce
|
import androidx.dynamicanimation.animation.SpringForce
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.oxycblt.auxio.accent.Accent
|
||||||
|
import org.oxycblt.auxio.canScroll
|
||||||
import org.oxycblt.auxio.databinding.ViewFastScrollBinding
|
import org.oxycblt.auxio.databinding.ViewFastScrollBinding
|
||||||
|
import org.oxycblt.auxio.inflater
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.ui.Accent
|
import org.oxycblt.auxio.resolveAttr
|
||||||
import org.oxycblt.auxio.ui.canScroll
|
import org.oxycblt.auxio.resolveColor
|
||||||
import org.oxycblt.auxio.ui.inflater
|
|
||||||
import org.oxycblt.auxio.ui.resolveAttr
|
|
||||||
import org.oxycblt.auxio.ui.toColor
|
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
@ -49,6 +49,8 @@ import kotlin.math.roundToInt
|
||||||
* fast-scrollers, this one displays indicators and a thumb instead of simply a scroll bar.
|
* fast-scrollers, this one displays indicators and a thumb instead of simply a scroll bar.
|
||||||
* This code is fundamentally an adaptation of Reddit's IndicatorFastScroll, albeit specialized
|
* This code is fundamentally an adaptation of Reddit's IndicatorFastScroll, albeit specialized
|
||||||
* towards Auxio. The original library is here: https://github.com/reddit/IndicatorFastScroll/
|
* towards Auxio. The original library is here: https://github.com/reddit/IndicatorFastScroll/
|
||||||
|
* TODO: Replace this with something similar to AndroidFastScroll [but optimized for Auxio],
|
||||||
|
* since this thumb view is a blocker to a better sort system.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class FastScrollView @JvmOverloads constructor(
|
class FastScrollView @JvmOverloads constructor(
|
||||||
|
@ -97,7 +99,7 @@ class FastScrollView @JvmOverloads constructor(
|
||||||
private data class Indicator(val char: Char, val pos: Int)
|
private data class Indicator(val char: Char, val pos: Int)
|
||||||
|
|
||||||
private var indicators = listOf<Indicator>()
|
private var indicators = listOf<Indicator>()
|
||||||
private val activeColor = Accent.get().color.toColor(context)
|
private val activeColor = Accent.get().color.resolveColor(context)
|
||||||
private val inactiveColor = android.R.attr.textColorSecondary.resolveAttr(context)
|
private val inactiveColor = android.R.attr.textColorSecondary.resolveAttr(context)
|
||||||
|
|
||||||
// --- STATE ---
|
// --- STATE ---
|
||||||
|
|
|
@ -25,12 +25,12 @@ import org.oxycblt.auxio.databinding.ItemArtistBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemGenreBinding
|
import org.oxycblt.auxio.databinding.ItemGenreBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemHeaderBinding
|
import org.oxycblt.auxio.databinding.ItemHeaderBinding
|
||||||
import org.oxycblt.auxio.databinding.ItemSongBinding
|
import org.oxycblt.auxio.databinding.ItemSongBinding
|
||||||
|
import org.oxycblt.auxio.inflater
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Header
|
import org.oxycblt.auxio.music.Header
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.inflater
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Shared ViewHolder for a [Song]. Instantiation should be done with [from].
|
* The Shared ViewHolder for a [Song]. Instantiation should be done with [from].
|
||||||
|
|
|
@ -40,7 +40,7 @@ import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Header
|
import org.oxycblt.auxio.music.Header
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.ui.getSpans
|
import org.oxycblt.auxio.spans
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,7 +90,9 @@ class SearchFragment : Fragment() {
|
||||||
|
|
||||||
binding.searchRecycler.apply {
|
binding.searchRecycler.apply {
|
||||||
adapter = searchAdapter
|
adapter = searchAdapter
|
||||||
val spans = getSpans()
|
|
||||||
|
// It's expensive to calculate the spans for each position in the list, so cache it.
|
||||||
|
val spans = spans
|
||||||
|
|
||||||
if (spans != -1) {
|
if (spans != -1) {
|
||||||
layoutManager = GridLayoutManager(requireContext(), spans).apply {
|
layoutManager = GridLayoutManager(requireContext(), spans).apply {
|
||||||
|
|
|
@ -34,7 +34,7 @@ import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentAboutBinding
|
import org.oxycblt.auxio.databinding.FragmentAboutBinding
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.ui.showToast
|
import org.oxycblt.auxio.showToast
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [BottomSheetDialogFragment] that shows Auxio's about screen.
|
* A [BottomSheetDialogFragment] that shows Auxio's about screen.
|
||||||
|
|
|
@ -21,10 +21,10 @@ package org.oxycblt.auxio.settings
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
import org.oxycblt.auxio.accent.ACCENTS
|
||||||
|
import org.oxycblt.auxio.accent.Accent
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||||
import org.oxycblt.auxio.recycler.DisplayMode
|
import org.oxycblt.auxio.recycler.DisplayMode
|
||||||
import org.oxycblt.auxio.ui.ACCENTS
|
|
||||||
import org.oxycblt.auxio.ui.Accent
|
|
||||||
|
|
||||||
// A couple of utils for migrating from old settings values to the new
|
// A couple of utils for migrating from old settings values to the new
|
||||||
// formats used in 1.3.2 & 1.4.0
|
// formats used in 1.3.2 & 1.4.0
|
||||||
|
|
|
@ -28,16 +28,16 @@ import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.children
|
import androidx.preference.children
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.accent.Accent
|
||||||
|
import org.oxycblt.auxio.accent.AccentDialog
|
||||||
|
import org.oxycblt.auxio.excluded.ExcludedDialog
|
||||||
|
import org.oxycblt.auxio.isNight
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.recycler.DisplayMode
|
import org.oxycblt.auxio.recycler.DisplayMode
|
||||||
import org.oxycblt.auxio.settings.accent.AccentDialog
|
|
||||||
import org.oxycblt.auxio.settings.blacklist.BlacklistDialog
|
|
||||||
import org.oxycblt.auxio.settings.ui.IntListPrefDialog
|
import org.oxycblt.auxio.settings.ui.IntListPrefDialog
|
||||||
import org.oxycblt.auxio.settings.ui.IntListPreference
|
import org.oxycblt.auxio.settings.ui.IntListPreference
|
||||||
import org.oxycblt.auxio.ui.Accent
|
import org.oxycblt.auxio.showToast
|
||||||
import org.oxycblt.auxio.ui.isNight
|
|
||||||
import org.oxycblt.auxio.ui.showToast
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The actual fragment containing the settings menu. Inherits [PreferenceFragmentCompat].
|
* The actual fragment containing the settings menu. Inherits [PreferenceFragmentCompat].
|
||||||
|
@ -103,7 +103,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
SettingsManager.KEY_BLACK_THEME -> {
|
SettingsManager.KEY_BLACK_THEME -> {
|
||||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
if (requireContext().isNight()) {
|
if (requireContext().isNight) {
|
||||||
requireActivity().recreate()
|
requireActivity().recreate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
SettingsManager.KEY_BLACKLIST -> {
|
SettingsManager.KEY_BLACKLIST -> {
|
||||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
BlacklistDialog().show(childFragmentManager, BlacklistDialog.TAG)
|
ExcludedDialog().show(childFragmentManager, ExcludedDialog.TAG)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,11 @@ import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import org.oxycblt.auxio.accent.ACCENTS
|
||||||
|
import org.oxycblt.auxio.accent.Accent
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||||
import org.oxycblt.auxio.recycler.DisplayMode
|
import org.oxycblt.auxio.recycler.DisplayMode
|
||||||
import org.oxycblt.auxio.recycler.SortMode
|
import org.oxycblt.auxio.recycler.SortMode
|
||||||
import org.oxycblt.auxio.ui.ACCENTS
|
|
||||||
import org.oxycblt.auxio.ui.Accent
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around the [SharedPreferences] class that writes & reads values without a context.
|
* Wrapper around the [SharedPreferences] class that writes & reads values without a context.
|
||||||
|
|
|
@ -27,7 +27,7 @@ import androidx.core.graphics.drawable.toDrawable
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.ui.resolveAttr
|
import org.oxycblt.auxio.resolveAttr
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper around [DialogFragment] that allows the usage of the standard Auxio lifecycle
|
* A wrapper around [DialogFragment] that allows the usage of the standard Auxio lifecycle
|
||||||
|
|
|
@ -31,7 +31,7 @@ import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.recycler.sliceArticle
|
import org.oxycblt.auxio.recycler.sliceArticle
|
||||||
import org.oxycblt.auxio.ui.getSpans
|
import org.oxycblt.auxio.spans
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,7 +66,6 @@ class SongsFragment : Fragment() {
|
||||||
adapter = songAdapter
|
adapter = songAdapter
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
|
|
||||||
val spans = getSpans()
|
|
||||||
if (spans != 1) {
|
if (spans != 1) {
|
||||||
layoutManager = GridLayoutManager(requireContext(), spans)
|
layoutManager = GridLayoutManager(requireContext(), spans)
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.oxycblt.auxio.music.BaseModel
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
|
import org.oxycblt.auxio.showToast
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extension method for creating and showing a new [ActionMenu].
|
* Extension method for creating and showing a new [ActionMenu].
|
||||||
|
|
|
@ -26,6 +26,8 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleObserver
|
import androidx.lifecycle.LifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.OnLifecycleEvent
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
|
import org.oxycblt.auxio.assertMainThread
|
||||||
|
import org.oxycblt.auxio.inflater
|
||||||
import kotlin.properties.ReadOnlyProperty
|
import kotlin.properties.ReadOnlyProperty
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ class SlideLinearLayout @JvmOverloads constructor(
|
||||||
it.isAccessible = true
|
it.isAccessible = true
|
||||||
}
|
}
|
||||||
} catch (e: NoSuchFieldException) {
|
} catch (e: NoSuchFieldException) {
|
||||||
logE("Could not get mDisappearingChildren.")
|
logE("Could not get mDisappearingChildren. This is very ungood.")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,10 @@ import android.content.Context
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.annotation.LayoutRes
|
import androidx.annotation.LayoutRes
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.newBroadcastIntent
|
||||||
|
import org.oxycblt.auxio.newMainIntent
|
||||||
import org.oxycblt.auxio.playback.state.LoopMode
|
import org.oxycblt.auxio.playback.state.LoopMode
|
||||||
import org.oxycblt.auxio.playback.system.PlaybackService
|
import org.oxycblt.auxio.playback.system.PlaybackService
|
||||||
import org.oxycblt.auxio.ui.newBroadcastIntent
|
|
||||||
import org.oxycblt.auxio.ui.newMainIntent
|
|
||||||
|
|
||||||
private fun createViews(
|
private fun createViews(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
@ -41,7 +41,7 @@ private fun createViews(
|
||||||
return views
|
return views
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RemoteViews.applyMeta(context: Context, state: WidgetState) {
|
private fun RemoteViews.applyMeta(state: WidgetState) {
|
||||||
setTextViewText(R.id.widget_song, state.song.name)
|
setTextViewText(R.id.widget_song, state.song.name)
|
||||||
setTextViewText(R.id.widget_artist, state.song.album.artist.name)
|
setTextViewText(R.id.widget_artist, state.song.album.artist.name)
|
||||||
}
|
}
|
||||||
|
@ -96,20 +96,20 @@ fun createDefaultWidget(context: Context): RemoteViews {
|
||||||
|
|
||||||
fun createMiniWidget(context: Context, state: WidgetState): RemoteViews {
|
fun createMiniWidget(context: Context, state: WidgetState): RemoteViews {
|
||||||
val views = createViews(context, R.layout.widget_mini)
|
val views = createViews(context, R.layout.widget_mini)
|
||||||
views.applyMeta(context, state)
|
views.applyMeta(state)
|
||||||
return views
|
return views
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createCompactWidget(context: Context, state: WidgetState): RemoteViews {
|
fun createCompactWidget(context: Context, state: WidgetState): RemoteViews {
|
||||||
val views = createViews(context, R.layout.widget_compact)
|
val views = createViews(context, R.layout.widget_compact)
|
||||||
views.applyMeta(context, state)
|
views.applyMeta(state)
|
||||||
views.applyCover(context, state)
|
views.applyCover(context, state)
|
||||||
return views
|
return views
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
|
fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
|
||||||
val views = createViews(context, R.layout.widget_small)
|
val views = createViews(context, R.layout.widget_small)
|
||||||
views.applyMeta(context, state)
|
views.applyMeta(state)
|
||||||
views.applyCover(context, state)
|
views.applyCover(context, state)
|
||||||
views.applyControls(context, state)
|
views.applyControls(context, state)
|
||||||
return views
|
return views
|
||||||
|
@ -117,7 +117,7 @@ fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
|
||||||
|
|
||||||
fun createFullWidget(context: Context, state: WidgetState): RemoteViews {
|
fun createFullWidget(context: Context, state: WidgetState): RemoteViews {
|
||||||
val views = createViews(context, R.layout.widget_full)
|
val views = createViews(context, R.layout.widget_full)
|
||||||
views.applyMeta(context, state)
|
views.applyMeta(state)
|
||||||
views.applyCover(context, state)
|
views.applyCover(context, state)
|
||||||
views.applyControls(context, state)
|
views.applyControls(context, state)
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,9 @@ import android.util.SizeF
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.coil.loadBitmap
|
import org.oxycblt.auxio.coil.loadBitmap
|
||||||
|
import org.oxycblt.auxio.isLandscape
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.ui.isLandscape
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auxio's one and only appwidget. This widget follows a more unorthodox approach, effectively
|
* Auxio's one and only appwidget. This widget follows a more unorthodox approach, effectively
|
||||||
|
@ -167,7 +167,7 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
var height: Int
|
var height: Int
|
||||||
|
|
||||||
// Landscape/Portrait modes use different dimen bounds
|
// Landscape/Portrait modes use different dimen bounds
|
||||||
if (isLandscape(context.resources)) {
|
if (context.isLandscape()) {
|
||||||
width = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)
|
width = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)
|
||||||
height = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)
|
height = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:overScrollMode="never"
|
android:overScrollMode="never"
|
||||||
android:paddingTop="@dimen/spacing_small"
|
android:paddingTop="@dimen/spacing_small"
|
||||||
app:layoutManager="org.oxycblt.auxio.settings.accent.AutoGridLayoutManager"
|
app:layoutManager=".accent.AutoGridLayoutManager"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/accent_cancel"
|
app:layout_constraintBottom_toTopOf="@+id/accent_cancel"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/accent_header"
|
app:layout_constraintTop_toBottomOf="@+id/accent_header"
|
||||||
tools:itemCount="18"
|
tools:itemCount="18"
|
||||||
|
|
|
@ -87,8 +87,10 @@ To prevent any strange bugs, all integer representations must be unique. A table
|
||||||
Auxio's package structure is mostly based around the features, and then any sub-features or components involved with that. There are some shared packages however. A diagram of the package structure is shown below:
|
Auxio's package structure is mostly based around the features, and then any sub-features or components involved with that. There are some shared packages however. A diagram of the package structure is shown below:
|
||||||
|
|
||||||
```
|
```
|
||||||
org.oxycblt.auxio # Main UI's and logging utilities
|
org.oxycblt.auxio # Main UI's and utilities
|
||||||
|
├──.accent # Accent UI + Systems
|
||||||
├──.coil # Fetchers and utilities for Coil, contains binding adapters than be used in the user interface.
|
├──.coil # Fetchers and utilities for Coil, contains binding adapters than be used in the user interface.
|
||||||
|
├──.blacklist # Excluded Directories UI/Systems
|
||||||
├──.database # Databases and their items for Auxio
|
├──.database # Databases and their items for Auxio
|
||||||
├──.detail # UIs for more album/artist/genre details
|
├──.detail # UIs for more album/artist/genre details
|
||||||
│ └──.adapters # RecyclerView adapters for the detail UIs, which display the header information and items
|
│ └──.adapters # RecyclerView adapters for the detail UIs, which display the header information and items
|
||||||
|
@ -103,8 +105,6 @@ org.oxycblt.auxio # Main UI's and logging utilities
|
||||||
│ └──.viewholders # Shared ViewHolders and ViewHolder utilities
|
│ └──.viewholders # Shared ViewHolders and ViewHolder utilities
|
||||||
├──.search # Search UI
|
├──.search # Search UI
|
||||||
├──.settings # Settings UI and systems
|
├──.settings # Settings UI and systems
|
||||||
│ ├──.blacklist # Excluded Directories UI/Systems
|
|
||||||
│ ├──.accent # Accent UI + Systems
|
|
||||||
│ └──.ui # Settings-Related UIs
|
│ └──.ui # Settings-Related UIs
|
||||||
├──.songs # Songs UI
|
├──.songs # Songs UI
|
||||||
├──.ui # Shared user interface utilities
|
├──.ui # Shared user interface utilities
|
||||||
|
|
Loading…
Reference in a new issue