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.
**Note: Auxio is in a point that I am largely satisfied with. The app is still maintained, but feature additions will be slow.**
## Screenshots
<p align="center">

View file

@ -1,6 +1,6 @@
/*
* 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
* 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/>.
*/
package org.oxycblt.auxio.ui
package org.oxycblt.auxio
import android.app.Activity
import android.app.PendingIntent
@ -46,9 +46,6 @@ import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.MainActivity
import org.oxycblt.auxio.R
import org.oxycblt.auxio.logE
import kotlin.reflect.KClass
const val INTENT_REQUEST_CODE = 0xA0A0
@ -60,7 +57,7 @@ const val INTENT_REQUEST_CODE = 0xA0A0
*/
fun ImageButton.disable() {
if (isEnabled) {
imageTintList = R.color.inactive.toStateList(context)
imageTintList = R.color.inactive.resolveStateList(context)
isEnabled = false
}
}
@ -69,9 +66,24 @@ fun ImageButton.disable() {
* Set a [TextView] text color, without having to resolve the resource.
*/
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 ---
/**
@ -80,14 +92,12 @@ fun TextView.setTextColorResource(@ColorRes color: Int) {
val Context.inflater: LayoutInflater get() = LayoutInflater.from(this)
/**
* Convenience method for getting a plural.
* @param pluralsRes Resource for the plural
* @param value Int value for the plural.
* @return The formatted string requested
* Returns whether the current UI is in night mode or not. This will work if the theme is
* automatic as well.
*/
fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String {
return resources.getQuantityString(pluralsRes, value, value)
}
val Context.isNight: Boolean get() =
resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
Configuration.UI_MODE_NIGHT_YES
/**
* 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]
*/
@ -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.
* 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.
* @param resources [Resources] required
*/
fun isLandscape(resources: Resources): Boolean {
fun Context.isLandscape(): Boolean {
return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
}
/**
* 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
return layout == Configuration.SCREENLAYOUT_SIZE_XLARGE ||
@ -254,35 +264,19 @@ fun isTablet(resources: Resources): Boolean {
/**
* 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
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)
* 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.
*/
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.AppCompatDelegate
import androidx.databinding.DataBindingUtil
import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.databinding.ActivityMainBinding
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.system.PlaybackService
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.
@ -66,22 +64,6 @@ class MainActivity : AppCompatActivity() {
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?) {
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) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// 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.findNavController
import com.google.android.material.bottomnavigation.BottomNavigationView
import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.databinding.FragmentMainBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.music.Song
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.
@ -56,7 +52,7 @@ class MainFragment : Fragment() {
): View {
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)
// Set up the tints for the navigation icons + text
@ -86,7 +82,7 @@ class MainFragment : Fragment() {
itemIconTintList = navTints
itemTextColor = navTints
if (isTablet(resources) && !isLandscape(resources)) {
if (requireContext().isTablet() && !requireContext().isLandscape()) {
labelVisibilityMode = BottomNavigationView.LABEL_VISIBILITY_LABELED
}
@ -160,7 +156,7 @@ class MainFragment : Fragment() {
if (song == null) {
logD("Hiding CompactPlaybackFragment since no song is being played.")
binding.compactPlayback.visibility = if (isLandscape(resources)) {
binding.compactPlayback.visibility = if (requireContext().isLandscape()) {
View.INVISIBLE
} else {
View.GONE

View file

@ -16,7 +16,7 @@
* 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.content.Context
@ -27,6 +27,7 @@ import androidx.annotation.StringRes
import androidx.annotation.StyleRes
import androidx.core.text.HtmlCompat
import org.oxycblt.auxio.R
import org.oxycblt.auxio.resolveStateList
/**
* A list of all possible accents.
@ -94,7 +95,7 @@ data class 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.

View file

@ -16,17 +16,15 @@
* 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 androidx.appcompat.widget.TooltipCompat
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemAccentBinding
import org.oxycblt.auxio.ui.ACCENTS
import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.inflater
import org.oxycblt.auxio.ui.toStateList
import org.oxycblt.auxio.inflater
import org.oxycblt.auxio.resolveStateList
/**
* 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 = this
R.color.surface.toStateList(context)
R.color.surface.resolveStateList(context)
} 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/>.
*/
package org.oxycblt.auxio.settings.accent
package org.oxycblt.auxio.accent
import android.os.Bundle
import android.view.LayoutInflater
@ -27,11 +27,9 @@ import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogAccentBinding
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.resolveColor
import org.oxycblt.auxio.settings.SettingsManager
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.
@ -93,7 +91,7 @@ class AccentDialog : LifecycleDialog() {
}
private fun updateAccent() {
val accentColor = pendingAccent.color.toColor(requireContext())
val accentColor = pendingAccent.color.resolveColor(requireContext())
(requireDialog() as AlertDialog).apply {
getButton(AlertDialog.BUTTON_POSITIVE)?.setTextColor(accentColor)

View file

@ -16,7 +16,7 @@
* 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.util.AttributeSet

View file

@ -25,6 +25,7 @@ import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.R
import org.oxycblt.auxio.canScroll
import org.oxycblt.auxio.detail.adapters.AlbumDetailAdapter
import org.oxycblt.auxio.logD
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.playback.state.PlaybackMode
import org.oxycblt.auxio.recycler.CenterSmoothScroller
import org.oxycblt.auxio.showToast
import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.canScroll
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.ui.showToast
/**
* 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.RecyclerView
import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.isLandscape
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.isLandscape
import org.oxycblt.auxio.ui.memberBinding
/**
@ -95,7 +95,7 @@ abstract class DetailFragment : Fragment() {
setHasFixedSize(true)
// Set up a grid if the mode is landscape
if (isLandscape(resources)) {
if (requireContext().isLandscape()) {
layoutManager = GridLayoutManager(requireContext(), 2).also {
it.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {

View file

@ -23,9 +23,12 @@ import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.databinding.ItemAlbumHeaderBinding
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
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.BaseModel
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.viewholders.BaseViewHolder
import org.oxycblt.auxio.recycler.viewholders.Highlightable
import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.disable
import org.oxycblt.auxio.ui.inflater
import org.oxycblt.auxio.ui.setTextColorResource
import org.oxycblt.auxio.setTextColorResource
/**
* 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 androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistHeaderBinding
import org.oxycblt.auxio.databinding.ItemArtistSongBinding
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.music.Album
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.viewholders.BaseViewHolder
import org.oxycblt.auxio.recycler.viewholders.Highlightable
import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.disable
import org.oxycblt.auxio.ui.inflater
import org.oxycblt.auxio.ui.setTextColorResource
import org.oxycblt.auxio.setTextColorResource
/**
* 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.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.databinding.ItemGenreHeaderBinding
import org.oxycblt.auxio.databinding.ItemGenreSongBinding
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.Genre
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.viewholders.BaseViewHolder
import org.oxycblt.auxio.recycler.viewholders.Highlightable
import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.disable
import org.oxycblt.auxio.ui.inflater
import org.oxycblt.auxio.ui.setTextColorResource
import org.oxycblt.auxio.setTextColorResource
/**
* 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/>.
*/
package org.oxycblt.auxio.music
package org.oxycblt.auxio.excluded
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import androidx.core.database.sqlite.transaction
import org.oxycblt.auxio.assertBackgroundThread
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.ui.assertBackgroundThread
import org.oxycblt.auxio.ui.queryAll
import org.oxycblt.auxio.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.
* Yes. I know Room exists. But that would needlessly bloat my app and has crippling bugs.
* @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) {
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"
@Volatile
private var INSTANCE: BlacklistDatabase? = null
private var INSTANCE: ExcludedDatabase? = null
/**
* Get/Instantiate the single instance of [PlaybackStateDatabase].
*/
fun getInstance(context: Context): BlacklistDatabase {
fun getInstance(context: Context): ExcludedDatabase {
val currentInstance = INSTANCE
if (currentInstance != null) {
@ -107,7 +107,7 @@ class BlacklistDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, n
}
synchronized(this) {
val newInstance = BlacklistDatabase(context.applicationContext)
val newInstance = ExcludedDatabase(context.applicationContext)
INSTANCE = newInstance
return newInstance
}

View file

@ -16,7 +16,7 @@
* 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.net.Uri
@ -37,16 +37,16 @@ import org.oxycblt.auxio.databinding.DialogBlacklistBinding
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.settings.ui.LifecycleDialog
import org.oxycblt.auxio.ui.showToast
import org.oxycblt.auxio.showToast
import kotlin.system.exitProcess
/**
* Dialog that manages the currently excluded directories.
* @author OxygenCobalt
*/
class BlacklistDialog : LifecycleDialog() {
private val blacklistModel: BlacklistViewModel by viewModels {
BlacklistViewModel.Factory(requireContext())
class ExcludedDialog : LifecycleDialog() {
private val blacklistModel: ExcludedViewModel by viewModels {
ExcludedViewModel.Factory(requireContext())
}
private val playbackModel: PlaybackViewModel by activityViewModels()
@ -58,7 +58,7 @@ class BlacklistDialog : LifecycleDialog() {
): View {
val binding = DialogBlacklistBinding.inflate(inflater)
val adapter = BlacklistEntryAdapter { path ->
val adapter = ExcludedEntryAdapter { path ->
blacklistModel.removePath(path)
}
@ -123,6 +123,7 @@ class BlacklistDialog : LifecycleDialog() {
if (path != null) {
blacklistModel.addPath(path)
} else {
// TODO: Tolerate this once the excluded system is modernized
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/>.
*/
package org.oxycblt.auxio.settings.blacklist
package org.oxycblt.auxio.excluded
import android.annotation.SuppressLint
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
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.
* @author OxygenCobalt
*/
class BlacklistEntryAdapter(
class ExcludedEntryAdapter(
private val onClear: (String) -> Unit
) : RecyclerView.Adapter<BlacklistEntryAdapter.ViewHolder>() {
) : RecyclerView.Adapter<ExcludedEntryAdapter.ViewHolder>() {
private var paths = mutableListOf<String>()
override fun getItemCount() = paths.size

View file

@ -16,7 +16,7 @@
* 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 androidx.lifecycle.LiveData
@ -27,18 +27,17 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
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.
* @author OxygenCobalt
*/
class BlacklistViewModel(context: Context) : ViewModel() {
class ExcludedViewModel(context: Context) : ViewModel() {
private val mPaths = MutableLiveData(mutableListOf<String>())
val paths: LiveData<MutableList<String>> get() = mPaths
private val blacklistDatabase = BlacklistDatabase.getInstance(context)
private val blacklistDatabase = ExcludedDatabase.getInstance(context)
private var dbPaths = listOf<String>()
init {
@ -97,12 +96,12 @@ class BlacklistViewModel(context: Context) : ViewModel() {
class Factory(private val context: Context) : ViewModelProvider.Factory {
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"
}
@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.Song
import org.oxycblt.auxio.recycler.sliceArticle
import org.oxycblt.auxio.ui.getSpans
import org.oxycblt.auxio.spans
import org.oxycblt.auxio.ui.newMenu
/**
@ -78,7 +78,6 @@ class LibraryFragment : Fragment() {
adapter = libraryAdapter
setHasFixedSize(true)
val spans = getSpans()
if (spans != 1) {
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.logD
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.ui.isLandscape
/**
* Fragment that handles what to display during the loading process.
@ -62,10 +61,6 @@ class LoadingFragment : Fragment() {
binding.lifecycleOwner = viewLifecycleOwner
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 ---
loadingModel.doGrant.observe(viewLifecycleOwner) { doGrant ->

View file

@ -27,6 +27,7 @@ import android.provider.MediaStore.Audio.Genres
import android.provider.MediaStore.Audio.Media
import androidx.core.database.getStringOrNull
import org.oxycblt.auxio.R
import org.oxycblt.auxio.excluded.ExcludedDatabase
import org.oxycblt.auxio.logD
/**
@ -69,7 +70,7 @@ class MusicLoader(private val context: Context) {
@Suppress("DEPRECATION")
private fun buildSelector() {
// TODO: Upgrade this to be compatible with Android Q.
val blacklistDatabase = BlacklistDatabase.getInstance(context)
val blacklistDatabase = ExcludedDatabase.getInstance(context)
val paths = blacklistDatabase.readPaths()

View file

@ -27,7 +27,7 @@ import android.widget.TextView
import androidx.core.text.isDigitsOnly
import androidx.databinding.BindingAdapter
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

View file

@ -26,7 +26,7 @@ import android.util.AttributeSet
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.AppCompatImageButton
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.

View file

@ -28,14 +28,14 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.R
import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.databinding.FragmentPlaybackBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.logD
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.toDrawable
import org.oxycblt.auxio.ui.toStateList
/**
* A [Fragment] that displays more information about the song, along with more media controls.
@ -56,11 +56,11 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
): View {
val normalTextColor = binding.playbackDurationCurrent.currentTextColor
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.
val iconQueueActive = R.drawable.ic_queue.toDrawable(requireContext())
val iconQueueInactive = R.drawable.ic_queue_inactive.toDrawable(requireContext())
val iconQueueActive = R.drawable.ic_queue.resolveDrawable(requireContext())
val iconQueueInactive = R.drawable.ic_queue_inactive.resolveDrawable(requireContext())
val queueItem: MenuItem

View file

@ -28,6 +28,7 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
import org.oxycblt.auxio.inflater
import org.oxycblt.auxio.logE
import org.oxycblt.auxio.music.BaseModel
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.viewholders.BaseViewHolder
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.

View file

@ -32,11 +32,11 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
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.Header
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

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
* @author OxygenCobalt
*/
data class QueueItem(
data class DatabaseQueueItem(
var id: Long = 0L,
val songHash: 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.
* @author OxygenCobalt
*/
data class PlaybackState(
data class DatabaseState(
val id: Long = 0L,
val songHash: Int,
val position: Long,

View file

@ -23,9 +23,9 @@ import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import androidx.core.database.sqlite.transaction
import org.oxycblt.auxio.assertBackgroundThread
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.ui.assertBackgroundThread
import org.oxycblt.auxio.ui.queryAll
import org.oxycblt.auxio.queryAll
/**
* 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 {
command.append("${PlaybackState.COLUMN_ID} LONG PRIMARY KEY,")
.append("${PlaybackState.COLUMN_SONG_HASH} INTEGER NOT NULL,")
.append("${PlaybackState.COLUMN_POSITION} LONG NOT NULL,")
.append("${PlaybackState.COLUMN_PARENT_HASH} INTEGER NOT NULL,")
.append("${PlaybackState.COLUMN_INDEX} INTEGER NOT NULL,")
.append("${PlaybackState.COLUMN_MODE} INTEGER NOT NULL,")
.append("${PlaybackState.COLUMN_IS_SHUFFLING} BOOLEAN NOT NULL,")
.append("${PlaybackState.COLUMN_LOOP_MODE} INTEGER NOT NULL,")
.append("${PlaybackState.COLUMN_IN_USER_QUEUE} BOOLEAN NOT NULL)")
command.append("${DatabaseState.COLUMN_ID} LONG PRIMARY KEY,")
.append("${DatabaseState.COLUMN_SONG_HASH} INTEGER NOT NULL,")
.append("${DatabaseState.COLUMN_POSITION} LONG NOT NULL,")
.append("${DatabaseState.COLUMN_PARENT_HASH} INTEGER NOT NULL,")
.append("${DatabaseState.COLUMN_INDEX} INTEGER NOT NULL,")
.append("${DatabaseState.COLUMN_MODE} INTEGER NOT NULL,")
.append("${DatabaseState.COLUMN_IS_SHUFFLING} BOOLEAN NOT NULL,")
.append("${DatabaseState.COLUMN_LOOP_MODE} INTEGER NOT NULL,")
.append("${DatabaseState.COLUMN_IN_USER_QUEUE} BOOLEAN NOT NULL)")
return command
}
/**
* Construct a [QueueItem] table
* Construct a [DatabaseQueueItem] table
*/
private fun constructQueueTable(command: StringBuilder): StringBuilder {
command.append("${QueueItem.COLUMN_ID} LONG PRIMARY KEY,")
.append("${QueueItem.COLUMN_SONG_HASH} INTEGER NOT NULL,")
.append("${QueueItem.COLUMN_ALBUM_HASH} INTEGER NOT NULL,")
.append("${QueueItem.COLUMN_IS_USER_QUEUE} BOOLEAN NOT NULL)")
command.append("${DatabaseQueueItem.COLUMN_ID} LONG PRIMARY KEY,")
.append("${DatabaseQueueItem.COLUMN_SONG_HASH} INTEGER NOT NULL,")
.append("${DatabaseQueueItem.COLUMN_ALBUM_HASH} INTEGER NOT NULL,")
.append("${DatabaseQueueItem.COLUMN_IS_USER_QUEUE} BOOLEAN NOT NULL)")
return command
}
@ -103,9 +103,9 @@ class PlaybackStateDatabase(context: Context) :
// --- 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()
writableDatabase.transaction {
@ -114,15 +114,15 @@ class PlaybackStateDatabase(context: Context) :
this@PlaybackStateDatabase.logD("Wiped state db.")
val stateData = ContentValues(10).apply {
put(PlaybackState.COLUMN_ID, state.id)
put(PlaybackState.COLUMN_SONG_HASH, state.songHash)
put(PlaybackState.COLUMN_POSITION, state.position)
put(PlaybackState.COLUMN_PARENT_HASH, state.parentHash)
put(PlaybackState.COLUMN_INDEX, state.index)
put(PlaybackState.COLUMN_MODE, state.mode)
put(PlaybackState.COLUMN_IS_SHUFFLING, state.isShuffling)
put(PlaybackState.COLUMN_LOOP_MODE, state.loopMode)
put(PlaybackState.COLUMN_IN_USER_QUEUE, state.inUserQueue)
put(DatabaseState.COLUMN_ID, state.id)
put(DatabaseState.COLUMN_SONG_HASH, state.songHash)
put(DatabaseState.COLUMN_POSITION, state.position)
put(DatabaseState.COLUMN_PARENT_HASH, state.parentHash)
put(DatabaseState.COLUMN_INDEX, state.index)
put(DatabaseState.COLUMN_MODE, state.mode)
put(DatabaseState.COLUMN_IS_SHUFFLING, state.isShuffling)
put(DatabaseState.COLUMN_LOOP_MODE, state.loopMode)
put(DatabaseState.COLUMN_IN_USER_QUEUE, state.inUserQueue)
}
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.
* @return The stored [PlaybackState], null if there isn't one.
* Read the stored [DatabaseState] from the database, if there is one.
* @return The stored [DatabaseState], null if there isn't one.
*/
fun readState(): PlaybackState? {
fun readState(): DatabaseState? {
assertBackgroundThread()
var state: PlaybackState? = null
var state: DatabaseState? = null
readableDatabase.queryAll(TABLE_NAME_STATE) { cursor ->
if (cursor.count == 0) return@queryAll
val songIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_SONG_HASH)
val posIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_POSITION)
val parentIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_PARENT_HASH)
val indexIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_INDEX)
val modeIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_MODE)
val shuffleIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_IS_SHUFFLING)
val loopModeIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_LOOP_MODE)
val songIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_SONG_HASH)
val posIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_POSITION)
val parentIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_PARENT_HASH)
val indexIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_INDEX)
val modeIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_MODE)
val shuffleIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_IS_SHUFFLING)
val loopModeIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_LOOP_MODE)
val inUserQueueIndex = cursor.getColumnIndexOrThrow(
PlaybackState.COLUMN_IN_USER_QUEUE
DatabaseState.COLUMN_IN_USER_QUEUE
)
cursor.moveToFirst()
state = PlaybackState(
state = DatabaseState(
songHash = cursor.getInt(songIndex),
position = cursor.getLong(posIndex),
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.
*/
fun writeQueue(queueItems: List<QueueItem>) {
fun writeQueue(queueItems: List<DatabaseQueueItem>) {
assertBackgroundThread()
val database = writableDatabase
@ -197,10 +197,10 @@ class PlaybackStateDatabase(context: Context) :
i++
val itemData = ContentValues(4).apply {
put(QueueItem.COLUMN_ID, item.id)
put(QueueItem.COLUMN_SONG_HASH, item.songHash)
put(QueueItem.COLUMN_ALBUM_HASH, item.albumHash)
put(QueueItem.COLUMN_IS_USER_QUEUE, item.isUserQueue)
put(DatabaseQueueItem.COLUMN_ID, item.id)
put(DatabaseQueueItem.COLUMN_SONG_HASH, item.songHash)
put(DatabaseQueueItem.COLUMN_ALBUM_HASH, item.albumHash)
put(DatabaseQueueItem.COLUMN_IS_USER_QUEUE, item.isUserQueue)
}
insert(TABLE_NAME_QUEUE, null, itemData)
@ -216,24 +216,24 @@ class PlaybackStateDatabase(context: Context) :
}
/**
* Read the database for any [QueueItem]s.
* @return A list of any stored [QueueItem]s.
* Read the database for any [DatabaseQueueItem]s.
* @return A list of any stored [DatabaseQueueItem]s.
*/
fun readQueue(): List<QueueItem> {
fun readQueue(): List<DatabaseQueueItem> {
assertBackgroundThread()
val queueItems = mutableListOf<QueueItem>()
val queueItems = mutableListOf<DatabaseQueueItem>()
readableDatabase.queryAll(TABLE_NAME_QUEUE) { cursor ->
if (cursor.count == 0) return@queryAll
val idIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_ID)
val songIdIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_SONG_HASH)
val albumIdIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_ALBUM_HASH)
val isUserQueueIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_IS_USER_QUEUE)
val idIndex = cursor.getColumnIndexOrThrow(DatabaseQueueItem.COLUMN_ID)
val songIdIndex = cursor.getColumnIndexOrThrow(DatabaseQueueItem.COLUMN_SONG_HASH)
val albumIdIndex = cursor.getColumnIndexOrThrow(DatabaseQueueItem.COLUMN_ALBUM_HASH)
val isUserQueueIndex = cursor.getColumnIndexOrThrow(DatabaseQueueItem.COLUMN_IS_USER_QUEUE)
while (cursor.moveToNext()) {
queueItems += QueueItem(
queueItems += DatabaseQueueItem(
id = cursor.getLong(idIndex),
songHash = cursor.getInt(songIdIndex),
albumHash = cursor.getInt(albumIdIndex),

View file

@ -581,8 +581,8 @@ class PlaybackStateManager private constructor() {
logD("Getting state from DB.")
val start: Long
val playbackState: PlaybackState?
val queueItems: List<QueueItem>
val playbackState: DatabaseState?
val queueItems: List<DatabaseQueueItem>
withContext(Dispatchers.IO) {
start = System.currentTimeMillis()
@ -609,11 +609,11 @@ class PlaybackStateManager private constructor() {
}
/**
* Pack the current state into a [PlaybackState] to be saved.
* @return A [PlaybackState] reflecting the current state.
* Pack the current state into a [DatabaseState] to be saved.
* @return A [DatabaseState] reflecting the current state.
*/
private fun packToPlaybackState(): PlaybackState {
return PlaybackState(
private fun packToPlaybackState(): DatabaseState {
return DatabaseState(
songHash = mSong?.hash ?: Int.MIN_VALUE,
position = mPosition,
parentHash = mParent?.hash ?: Int.MIN_VALUE,
@ -628,7 +628,7 @@ class PlaybackStateManager private constructor() {
/**
* 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.
// 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.
*/
private fun packQueue(): List<QueueItem> {
val unified = mutableListOf<QueueItem>()
private fun packQueue(): List<DatabaseQueueItem> {
val unified = mutableListOf<DatabaseQueueItem>()
var queueItemId = 0L
mUserQueue.forEach { song ->
unified.add(QueueItem(queueItemId, song.hash, song.album.hash, true))
unified.add(DatabaseQueueItem(queueItemId, song.hash, song.album.hash, true))
queueItemId++
}
mQueue.forEach { song ->
unified.add(QueueItem(queueItemId, song.hash, song.album.hash, false))
unified.add(DatabaseQueueItem(queueItemId, song.hash, song.album.hash, false))
queueItemId++
}
@ -669,9 +669,9 @@ class PlaybackStateManager private constructor() {
/**
* 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) {
musicStore.findSongFast(item.songHash, item.albumHash)?.let { song ->
if (item.isUserQueue) {

View file

@ -25,10 +25,10 @@ import androidx.core.animation.addListener
import androidx.media.AudioFocusRequestCompat
import androidx.media.AudioManagerCompat
import com.google.android.exoplayer2.SimpleExoPlayer
import org.oxycblt.auxio.getSystemServiceSafe
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.getSystemServiceSafe
/**
* 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.music.Parent
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.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

View file

@ -52,6 +52,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.getSystemServiceSafe
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.Parent
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.PlaybackStateManager
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.getSystemServiceSafe
import org.oxycblt.auxio.widgets.WidgetController
import org.oxycblt.auxio.widgets.WidgetProvider

View file

@ -33,13 +33,13 @@ import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import androidx.recyclerview.widget.LinearLayoutManager
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.inflater
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.canScroll
import org.oxycblt.auxio.ui.inflater
import org.oxycblt.auxio.ui.resolveAttr
import org.oxycblt.auxio.ui.toColor
import org.oxycblt.auxio.resolveAttr
import org.oxycblt.auxio.resolveColor
import kotlin.math.ceil
import kotlin.math.min
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.
* This code is fundamentally an adaptation of Reddit's IndicatorFastScroll, albeit specialized
* 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
*/
class FastScrollView @JvmOverloads constructor(
@ -97,7 +99,7 @@ class FastScrollView @JvmOverloads constructor(
private data class Indicator(val char: Char, val pos: Int)
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)
// --- STATE ---

View file

@ -25,12 +25,12 @@ import org.oxycblt.auxio.databinding.ItemArtistBinding
import org.oxycblt.auxio.databinding.ItemGenreBinding
import org.oxycblt.auxio.databinding.ItemHeaderBinding
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.inflater
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.inflater
/**
* 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.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.getSpans
import org.oxycblt.auxio.spans
import org.oxycblt.auxio.ui.newMenu
/**
@ -90,7 +90,9 @@ class SearchFragment : Fragment() {
binding.searchRecycler.apply {
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) {
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.logD
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.

View file

@ -21,10 +21,10 @@ package org.oxycblt.auxio.settings
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatDelegate
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.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
// formats used in 1.3.2 & 1.4.0

View file

@ -28,16 +28,16 @@ import androidx.preference.PreferenceFragmentCompat
import androidx.preference.children
import coil.Coil
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.playback.PlaybackViewModel
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.IntListPreference
import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.isNight
import org.oxycblt.auxio.ui.showToast
import org.oxycblt.auxio.showToast
/**
* The actual fragment containing the settings menu. Inherits [PreferenceFragmentCompat].
@ -103,7 +103,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
SettingsManager.KEY_BLACK_THEME -> {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
if (requireContext().isNight()) {
if (requireContext().isNight) {
requireActivity().recreate()
}
@ -168,7 +168,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
SettingsManager.KEY_BLACKLIST -> {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
BlacklistDialog().show(childFragmentManager, BlacklistDialog.TAG)
ExcludedDialog().show(childFragmentManager, ExcludedDialog.TAG)
true
}
}

View file

@ -22,11 +22,11 @@ import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
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.recycler.DisplayMode
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.

View file

@ -27,7 +27,7 @@ import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
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

View file

@ -31,7 +31,7 @@ import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.sliceArticle
import org.oxycblt.auxio.ui.getSpans
import org.oxycblt.auxio.spans
import org.oxycblt.auxio.ui.newMenu
/**
@ -66,7 +66,6 @@ class SongsFragment : Fragment() {
adapter = songAdapter
setHasFixedSize(true)
val spans = getSpans()
if (spans != 1) {
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.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.showToast
/**
* 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.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import org.oxycblt.auxio.assertMainThread
import org.oxycblt.auxio.inflater
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

View file

@ -55,7 +55,7 @@ class SlideLinearLayout @JvmOverloads constructor(
it.isAccessible = true
}
} catch (e: NoSuchFieldException) {
logE("Could not get mDisappearingChildren.")
logE("Could not get mDisappearingChildren. This is very ungood.")
null
}

View file

@ -22,10 +22,10 @@ import android.content.Context
import android.widget.RemoteViews
import androidx.annotation.LayoutRes
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.system.PlaybackService
import org.oxycblt.auxio.ui.newBroadcastIntent
import org.oxycblt.auxio.ui.newMainIntent
private fun createViews(
context: Context,
@ -41,7 +41,7 @@ private fun createViews(
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_artist, state.song.album.artist.name)
}
@ -96,20 +96,20 @@ fun createDefaultWidget(context: Context): RemoteViews {
fun createMiniWidget(context: Context, state: WidgetState): RemoteViews {
val views = createViews(context, R.layout.widget_mini)
views.applyMeta(context, state)
views.applyMeta(state)
return views
}
fun createCompactWidget(context: Context, state: WidgetState): RemoteViews {
val views = createViews(context, R.layout.widget_compact)
views.applyMeta(context, state)
views.applyMeta(state)
views.applyCover(context, state)
return views
}
fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
val views = createViews(context, R.layout.widget_small)
views.applyMeta(context, state)
views.applyMeta(state)
views.applyCover(context, state)
views.applyControls(context, state)
return views
@ -117,7 +117,7 @@ fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
fun createFullWidget(context: Context, state: WidgetState): RemoteViews {
val views = createViews(context, R.layout.widget_full)
views.applyMeta(context, state)
views.applyMeta(state)
views.applyCover(context, state)
views.applyControls(context, state)

View file

@ -30,9 +30,9 @@ import android.util.SizeF
import android.widget.RemoteViews
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.coil.loadBitmap
import org.oxycblt.auxio.isLandscape
import org.oxycblt.auxio.logD
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
@ -167,7 +167,7 @@ class WidgetProvider : AppWidgetProvider() {
var height: Int
// Landscape/Portrait modes use different dimen bounds
if (isLandscape(context.resources)) {
if (context.isLandscape()) {
width = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)
height = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)
} else {

View file

@ -9,7 +9,7 @@
android:layout_height="match_parent"
android:overScrollMode="never"
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_constraintTop_toBottomOf="@+id/accent_header"
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:
```
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.
├──.blacklist # Excluded Directories UI/Systems
├──.database # Databases and their items for Auxio
├──.detail # UIs for more album/artist/genre details
│ └──.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
├──.search # Search UI
├──.settings # Settings UI and systems
│ ├──.blacklist # Excluded Directories UI/Systems
│ ├──.accent # Accent UI + Systems
│ └──.ui # Settings-Related UIs
├──.songs # Songs UI
├──.ui # Shared user interface utilities