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:
OxygenCobalt 2021-08-16 20:25:20 -06:00
parent 5788fb8732
commit 73ec99a214
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
48 changed files with 310 additions and 326 deletions

View file

@ -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">

View file

@ -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)
} }
/** /**

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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)
} }
} }
} }

View file

@ -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)

View file

@ -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

View file

@ -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.

View file

@ -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 {

View file

@ -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]

View file

@ -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.

View file

@ -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.

View file

@ -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
} }

View file

@ -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)
} }
} }

View file

@ -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

View file

@ -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
} }
} }
} }

View file

@ -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)
} }

View file

@ -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 ->

View file

@ -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()

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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,

View file

@ -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,

View file

@ -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),

View file

@ -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) {

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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 ---

View file

@ -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].

View file

@ -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 {

View file

@ -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.

View file

@ -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

View file

@ -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
} }
} }

View file

@ -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.

View file

@ -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

View file

@ -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)
} }

View file

@ -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].

View file

@ -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

View file

@ -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
} }

View file

@ -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)

View file

@ -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 {

View file

@ -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"

View file

@ -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