all: rework logging

Rework logging to be clearer and more standardized.

Rework all usages of lossing to follow a single unified style,
introducing a new "warn" option alongside this.
This commit is contained in:
OxygenCobalt 2022-02-22 17:08:57 -07:00
parent e1dbe6c40c
commit 3aaa2ab0e0
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
69 changed files with 436 additions and 339 deletions

View file

@ -29,7 +29,6 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.updatePadding
import androidx.databinding.DataBindingUtil
import androidx.viewbinding.ViewBinding
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
@ -56,7 +55,7 @@ class MainActivity : AppCompatActivity() {
applyEdgeToEdgeWindow(binding)
logD("Activity created.")
logD("Activity created")
}
override fun onStart() {
@ -94,26 +93,29 @@ class MainActivity : AppCompatActivity() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Android 12, let dynamic colors be our accent and only enable the black theme option
if (isNight && settingsManager.useBlackTheme) {
logD("Applying black theme [dynamic colors]")
setTheme(R.style.Theme_Auxio_Black)
}
} else {
// Below android 12, load the accent and enable theme customization
AppCompatDelegate.setDefaultNightMode(settingsManager.theme)
val newAccent = Accent.set(settingsManager.accent)
val accent = 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)
logD("Applying black theme [with accent $accent]")
setTheme(accent.blackTheme)
} else {
setTheme(newAccent.theme)
logD("Applying normal theme [with accent $accent]")
setTheme(accent.theme)
}
}
}
private fun applyEdgeToEdgeWindow(binding: ViewBinding) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
logD("Doing R+ edge-to-edge.")
logD("Doing R+ edge-to-edge")
window?.setDecorFitsSystemWindows(false)
@ -136,7 +138,7 @@ class MainActivity : AppCompatActivity() {
}
} else {
// Do old edge-to-edge otherwise.
logD("Doing legacy edge-to-edge.")
logD("Doing legacy edge-to-edge")
@Suppress("DEPRECATION")
binding.root.apply {

View file

@ -36,6 +36,7 @@ import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
/**
* A wrapper around the home fragment that shows the playback fragment and controls
@ -110,7 +111,7 @@ class MainFragment : Fragment() {
// Error, show the error to the user
is MusicStore.Response.Err -> {
logD("Received Error")
logW("Received Error")
val errorRes = when (response.kind) {
MusicStore.ErrorKind.NO_MUSIC -> R.string.err_no_music
@ -142,7 +143,7 @@ class MainFragment : Fragment() {
}
}
logD("Fragment Created.")
logD("Fragment Created")
return binding.root
}

View file

@ -100,6 +100,9 @@ private val ACCENT_PRIMARY_COLORS = arrayOf(
/**
* The data object for an accent. In the UI this is known as a "Color Scheme."
* This can be nominally used to gleam some attributes about a given color scheme, but this
* is not recommended. Attributes are usually the better option in nearly all cases.
*
* @property name The name of this accent
* @property theme The theme resource for this accent
* @property blackTheme The black theme resource for this accent
@ -111,36 +114,4 @@ data class Accent(val index: Int) {
val theme: Int get() = ACCENT_THEMES[index]
val blackTheme: Int get() = ACCENT_BLACK_THEMES[index]
val primary: Int get() = ACCENT_PRIMARY_COLORS[index]
companion object {
@Volatile
private var CURRENT: Accent? = null
/**
* Get the current accent.
* @return The current accent
* @throws IllegalStateException When the accent has not been set.
*/
fun get(): Accent {
val cur = CURRENT
if (cur != null) {
return cur
}
error("Accent must be set before retrieving it.")
}
/**
* Set the current accent.
* @return The new accent
*/
fun set(accent: Accent): Accent {
synchronized(this) {
CURRENT = accent
}
return accent
}
}
}

View file

@ -77,12 +77,10 @@ class AccentAdapter(
val context = binding.accent.context
binding.accent.isEnabled = !isSelected
binding.accent.imageTintList = if (isSelected) {
// Switch out the currently selected ViewHolder with this one.
selectedViewHolder?.setSelected(false)
selectedViewHolder = this
context.getAttrColorSafe(R.attr.colorSurface).stateList
} else {
context.getColorSafe(android.R.color.transparent).stateList

View file

@ -34,9 +34,9 @@ import org.oxycblt.auxio.util.logD
* Dialog responsible for showing the list of accents to select.
* @author OxygenCobalt
*/
class AccentDialog : LifecycleDialog() {
class AccentCustomizeDialog : LifecycleDialog() {
private val settingsManager = SettingsManager.getInstance()
private var pendingAccent = Accent.get()
private var pendingAccent = settingsManager.accent
override fun onCreateView(
inflater: LayoutInflater,
@ -53,18 +53,18 @@ class AccentDialog : LifecycleDialog() {
binding.accentRecycler.apply {
adapter = AccentAdapter(pendingAccent) { accent ->
logD("Switching selected accent to $accent")
pendingAccent = accent
}
}
logD("Dialog created.")
logD("Dialog created")
return binding.root
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(KEY_PENDING_ACCENT, pendingAccent.index)
}
@ -72,9 +72,9 @@ class AccentDialog : LifecycleDialog() {
builder.setTitle(R.string.set_accent)
builder.setPositiveButton(android.R.string.ok) { _, _ ->
if (pendingAccent != Accent.get()) {
if (pendingAccent != settingsManager.accent) {
logD("Applying new accent")
settingsManager.accent = pendingAccent
requireActivity().recreate()
}

View file

@ -30,7 +30,7 @@ import kotlin.math.max
* of the RecyclerView.
* Adapted from this StackOverflow answer: https://stackoverflow.com/a/30256880/14143986
*/
class AutoGridLayoutManager(
class AccentGridLayoutManager(
context: Context,
attrs: AttributeSet,
defStyleAttr: Int,

View file

@ -27,6 +27,7 @@ import okio.source
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
import java.io.ByteArrayInputStream
import java.io.InputStream
import android.util.Size as AndroidSize
@ -55,6 +56,7 @@ abstract class AuxioFetcher : Fetcher {
fetchMediaStoreCovers(context, album)
}
} catch (e: Exception) {
logW("Unable to extract album art due to an error")
null
}
}
@ -80,7 +82,6 @@ abstract class AuxioFetcher : Fetcher {
// music app which relies on proprietary OneUI extensions instead of AOSP. That means
// we have to have another layer of redundancy to retain quality. Thanks samsung. Prick.
val result = fetchAospMetadataCovers(context, album)
if (result != null) {
return result
}
@ -88,7 +89,6 @@ abstract class AuxioFetcher : Fetcher {
// Our next fallback is to rely on ExoPlayer's largely half-baked and undocumented
// metadata system.
val exoResult = fetchExoplayerCover(context, album)
if (exoResult != null) {
return exoResult
}
@ -97,7 +97,6 @@ abstract class AuxioFetcher : Fetcher {
// going against the point of this setting. The previous two calls are just too unreliable
// and we can't do any filesystem traversing due to scoped storage.
val mediaStoreResult = fetchMediaStoreCovers(context, album)
if (mediaStoreResult != null) {
return mediaStoreResult
}
@ -192,7 +191,7 @@ abstract class AuxioFetcher : Fetcher {
} else if (stream != null) {
// In the case a front cover is not found, use the first image in the tag instead.
// This can be corrected later on if a front cover frame is found.
logD("No front cover image, using image of type $type instead")
logW("No front cover image, using image of type $type instead")
stream = ByteArrayInputStream(pic)
}
@ -223,9 +222,10 @@ abstract class AuxioFetcher : Fetcher {
val increment = AndroidSize(mosaicSize.width / 2, mosaicSize.height / 2)
val mosaicBitmap = Bitmap.createBitmap(
mosaicSize.width, mosaicSize.height, Bitmap.Config.ARGB_8888
mosaicSize.width,
mosaicSize.height,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(mosaicBitmap)
var x = 0

View file

@ -79,7 +79,6 @@ class ArtistImageFetcher private constructor(
override suspend fun fetch(): FetchResult? {
val albums = Sort.ByName(true)
.sortAlbums(artist.albums)
val results = albums.mapAtMost(4) { album ->
fetchArt(context, album)
}

View file

@ -40,6 +40,7 @@ import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.util.canScroll
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
import org.oxycblt.auxio.util.showToast
/**
@ -111,10 +112,11 @@ class AlbumDetailFragment : DetailFragment() {
// fragment should be launched otherwise.
is Song -> {
if (detailModel.curAlbum.value!!.id == item.album.id) {
logD("Navigating to a song in this album")
scrollToItem(item.id, detailAdapter)
detailModel.finishNavToItem()
} else {
logD("Navigating to another album")
findNavController().navigate(
AlbumDetailFragmentDirections.actionShowAlbum(item.album.id)
)
@ -125,9 +127,11 @@ class AlbumDetailFragment : DetailFragment() {
// detail fragment.
is Album -> {
if (detailModel.curAlbum.value!!.id == item.id) {
logD("Navigating to the top of this album")
binding.detailRecycler.scrollToPosition(0)
detailModel.finishNavToItem()
} else {
logD("Navigating to another album")
findNavController().navigate(
AlbumDetailFragmentDirections.actionShowAlbum(item.id)
)
@ -136,13 +140,14 @@ class AlbumDetailFragment : DetailFragment() {
// Always launch a new ArtistDetailFragment.
is Artist -> {
logD("Navigating to another artist")
findNavController().navigate(
AlbumDetailFragmentDirections.actionShowArtist(item.id)
)
}
else -> {
}
null -> {}
else -> logW("Unsupported navigation item ${item::class.java}")
}
}
@ -161,7 +166,7 @@ class AlbumDetailFragment : DetailFragment() {
}
}
logD("Fragment created.")
logD("Fragment created")
return binding.root
}

View file

@ -35,6 +35,7 @@ import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
/**
* The [DetailFragment] for an artist.
@ -98,25 +99,33 @@ class ArtistDetailFragment : DetailFragment() {
when (item) {
is Artist -> {
if (item.id == detailModel.curArtist.value?.id) {
logD("Navigating to the top of this artist")
binding.detailRecycler.scrollToPosition(0)
detailModel.finishNavToItem()
} else {
logD("Navigating to another artist")
findNavController().navigate(
ArtistDetailFragmentDirections.actionShowArtist(item.id)
)
}
}
is Album -> findNavController().navigate(
ArtistDetailFragmentDirections.actionShowAlbum(item.id)
)
is Song -> findNavController().navigate(
ArtistDetailFragmentDirections.actionShowAlbum(item.album.id)
)
else -> {
is Album -> {
logD("Navigating to another album")
findNavController().navigate(
ArtistDetailFragmentDirections.actionShowAlbum(item.id)
)
}
is Song -> {
logD("Navigating to another album")
findNavController().navigate(
ArtistDetailFragmentDirections.actionShowAlbum(item.album.id)
)
}
null -> {}
else -> logW("Unsupported navigation item ${item::class.java}")
}
}
@ -141,7 +150,7 @@ class ArtistDetailFragment : DetailFragment() {
}
}
logD("Fragment created.")
logD("Fragment created")
return binding.root
}

View file

@ -14,6 +14,9 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.AppBarLayout
import org.oxycblt.auxio.R
import org.oxycblt.auxio.ui.EdgeAppBarLayout
import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.logTraceOrThrow
import java.lang.Exception
/**
* An [EdgeAppBarLayout] variant that also shows the name of the toolbar whenever the detail
@ -39,9 +42,8 @@ class DetailAppBarLayout @JvmOverloads constructor(
(layoutParams as CoordinatorLayout.LayoutParams).behavior = Behavior(context)
}
private fun findTitleView(): AppCompatTextView {
private fun findTitleView(): AppCompatTextView? {
val titleView = mTitleView
if (titleView != null) {
return titleView
}
@ -49,13 +51,18 @@ class DetailAppBarLayout @JvmOverloads constructor(
val toolbar = findViewById<Toolbar>(R.id.detail_toolbar)
// Reflect to get the actual title view to do transformations on
val newTitleView = Toolbar::class.java.getDeclaredField("mTitleTextView").run {
isAccessible = true
get(toolbar) as AppCompatTextView
val newTitleView = try {
Toolbar::class.java.getDeclaredField("mTitleTextView").run {
isAccessible = true
get(toolbar) as AppCompatTextView
}
} catch (e: Exception) {
logE("Could not get toolbar title view (likely an internal code change)")
e.logTraceOrThrow()
return null
}
newTitleView.alpha = 0f
mTitleView = newTitleView
return newTitleView
}
@ -95,11 +102,11 @@ class DetailAppBarLayout @JvmOverloads constructor(
to = 0f
}
if (titleView.alpha == to) return
if (titleView?.alpha == to) return
mTitleAnimator = ValueAnimator.ofFloat(from, to).apply {
addUpdateListener {
titleView.alpha = it.animatedValue as Float
titleView?.alpha = it.animatedValue as Float
}
duration = resources.getInteger(R.integer.app_bar_elevation_anim_duration).toLong()

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.memberBinding
import org.oxycblt.auxio.util.applySpans
import org.oxycblt.auxio.util.logD
/**
* A Base [Fragment] implementing the base features shared across all detail fragments.
@ -43,13 +44,11 @@ abstract class DetailFragment : Fragment() {
override fun onResume() {
super.onResume()
detailModel.setNavigating(false)
}
override fun onStop() {
super.onStop()
// Cancel all pending menus when this fragment stops to prevent bugs/crashes
detailModel.finishShowMenu(null)
}
@ -94,7 +93,6 @@ abstract class DetailFragment : Fragment() {
binding.detailRecycler.apply {
adapter = detailAdapter
setHasFixedSize(true)
applySpans(gridLookup)
}
}
@ -105,6 +103,8 @@ abstract class DetailFragment : Fragment() {
* @param showItem Which menu items to keep
*/
protected fun showMenu(config: DetailViewModel.MenuConfig, showItem: ((Int) -> Boolean)? = null) {
logD("Launching menu [$config]")
PopupMenu(config.anchor.context, config.anchor).apply {
inflate(R.menu.menu_detail_sort)

View file

@ -33,6 +33,7 @@ import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.util.logD
/**
* ViewModel that stores data for the [DetailFragment]s. This includes:
@ -77,12 +78,10 @@ class DetailViewModel : ViewModel() {
private set
private var currentMenuContext: DisplayMode? = null
private val settingsManager = SettingsManager.getInstance()
fun setGenre(id: Long) {
if (mCurGenre.value?.id == id) return
val musicStore = MusicStore.requireInstance()
mCurGenre.value = musicStore.genres.find { it.id == id }
refreshGenreData()
@ -90,7 +89,6 @@ class DetailViewModel : ViewModel() {
fun setArtist(id: Long) {
if (mCurArtist.value?.id == id) return
val musicStore = MusicStore.requireInstance()
mCurArtist.value = musicStore.artists.find { it.id == id }
refreshArtistData()
@ -98,7 +96,6 @@ class DetailViewModel : ViewModel() {
fun setAlbum(id: Long) {
if (mCurAlbum.value?.id == id) return
val musicStore = MusicStore.requireInstance()
mCurAlbum.value = musicStore.albums.find { it.id == id }
refreshAlbumData()
@ -112,6 +109,7 @@ class DetailViewModel : ViewModel() {
mShowMenu.value = null
if (newMode != null) {
logD("Applying new sort mode")
when (currentMenuContext) {
DisplayMode.SHOW_ALBUMS -> {
settingsManager.detailAlbumSort = newMode
@ -154,7 +152,9 @@ class DetailViewModel : ViewModel() {
}
private fun refreshGenreData() {
val data = mutableListOf<BaseModel>(curGenre.value!!)
logD("Refreshing genre data")
val genre = requireNotNull(curGenre.value)
val data = mutableListOf<BaseModel>(genre)
data.add(
ActionHeader(
@ -175,7 +175,8 @@ class DetailViewModel : ViewModel() {
}
private fun refreshArtistData() {
val artist = curArtist.value!!
logD("Refreshing artist data")
val artist = requireNotNull(curArtist.value)
val data = mutableListOf<BaseModel>(artist)
data.add(
@ -206,7 +207,9 @@ class DetailViewModel : ViewModel() {
}
private fun refreshAlbumData() {
val data = mutableListOf<BaseModel>(curAlbum.value!!)
logD("Refreshing album data")
val album = requireNotNull(curAlbum.value)
val data = mutableListOf<BaseModel>(album)
data.add(
ActionHeader(

View file

@ -35,6 +35,7 @@ import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
/**
* The [DetailFragment] for a genre.
@ -79,20 +80,29 @@ class GenreDetailFragment : DetailFragment() {
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
when (item) {
// All items will launch new detail fragments.
is Artist -> findNavController().navigate(
GenreDetailFragmentDirections.actionShowArtist(item.id)
)
is Album -> findNavController().navigate(
GenreDetailFragmentDirections.actionShowAlbum(item.id)
)
is Song -> findNavController().navigate(
GenreDetailFragmentDirections.actionShowAlbum(item.album.id)
)
else -> {
is Artist -> {
logD("Navigating to another artist")
findNavController().navigate(
GenreDetailFragmentDirections.actionShowArtist(item.id)
)
}
is Album -> {
logD("Navigating to another album")
findNavController().navigate(
GenreDetailFragmentDirections.actionShowAlbum(item.id)
)
}
is Song -> {
logD("Navigating to another song")
findNavController().navigate(
GenreDetailFragmentDirections.actionShowAlbum(item.album.id)
)
}
null -> {}
else -> logW("Unsupported navigation command ${item::class.java}")
}
}
@ -115,7 +125,7 @@ class GenreDetailFragment : DetailFragment() {
}
}
logD("Fragment created.")
logD("Fragment created")
return binding.root
}

View file

@ -58,7 +58,6 @@ class AlbumDetailAdapter(
is Album -> ALBUM_DETAIL_ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
is Song -> ALBUM_SONG_ITEM_TYPE
else -> -1
}
}
@ -86,7 +85,6 @@ class AlbumDetailAdapter(
is Album -> (holder as AlbumDetailViewHolder).bind(item)
is Song -> (holder as AlbumSongViewHolder).bind(item)
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
else -> {
}
}
@ -127,7 +125,6 @@ class AlbumDetailAdapter(
recycler.layoutManager?.findViewByPosition(pos)?.let { child ->
recycler.getChildViewHolder(child)?.let {
currentHolder = it as Highlightable
currentHolder?.setHighlighted(true)
}
}

View file

@ -33,12 +33,12 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.bindArtistInfo
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.ActionHeaderViewHolder
import org.oxycblt.auxio.ui.BaseViewHolder
import org.oxycblt.auxio.ui.DiffCallback
import org.oxycblt.auxio.ui.HeaderViewHolder
import org.oxycblt.auxio.util.getPluralSafe
import org.oxycblt.auxio.util.inflater
/**
@ -64,7 +64,6 @@ class ArtistDetailAdapter(
is Song -> ARTIST_SONG_ITEM_TYPE
is Header -> HeaderViewHolder.ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
else -> -1
}
}
@ -174,7 +173,6 @@ class ArtistDetailAdapter(
recycler.layoutManager?.findViewByPosition(pos)?.let { child ->
recycler.getChildViewHolder(child)?.let {
currentSongHolder = it as Highlightable
currentSongHolder?.setHighlighted(true)
}
}
@ -205,11 +203,7 @@ class ArtistDetailAdapter(
.entries.maxByOrNull { it.value.size }
?.key ?: context.getString(R.string.def_genre)
binding.detailInfo.text = context.getString(
R.string.fmt_counts,
context.getPluralSafe(R.plurals.fmt_album_count, data.albums.size),
context.getPluralSafe(R.plurals.fmt_song_count, data.songs.size)
)
binding.detailInfo.bindArtistInfo(data)
binding.detailPlayButton.setOnClickListener {
playbackModel.playArtist(data, false)

View file

@ -30,11 +30,11 @@ import org.oxycblt.auxio.music.ActionHeader
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.bindGenreInfo
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.ActionHeaderViewHolder
import org.oxycblt.auxio.ui.BaseViewHolder
import org.oxycblt.auxio.ui.DiffCallback
import org.oxycblt.auxio.util.getPluralSafe
import org.oxycblt.auxio.util.inflater
/**
@ -54,7 +54,6 @@ class GenreDetailAdapter(
is Genre -> GENRE_DETAIL_ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
is Song -> GENRE_SONG_ITEM_TYPE
else -> -1
}
}
@ -121,7 +120,6 @@ class GenreDetailAdapter(
recycler.layoutManager?.findViewByPosition(pos)?.let { child ->
recycler.getChildViewHolder(child)?.let {
currentHolder = it as Highlightable
currentHolder?.setHighlighted(true)
}
}
@ -143,11 +141,7 @@ class GenreDetailAdapter(
}
binding.detailName.text = data.resolvedName
binding.detailSubhead.apply {
text = context.getPluralSafe(R.plurals.fmt_song_count, data.songs.size)
}
binding.detailSubhead.bindGenreInfo(data)
binding.detailInfo.text = data.totalDuration
binding.detailPlayButton.setOnClickListener {

View file

@ -55,7 +55,6 @@ class ExcludedDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, nu
writableDatabase.transaction {
delete(TABLE_NAME, null, null)
logD("Deleted paths db")
for (path in paths) {
@ -66,6 +65,8 @@ class ExcludedDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, nu
}
)
}
logD("Successfully wrote ${paths.size} paths to db")
}
}
@ -76,17 +77,20 @@ class ExcludedDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, nu
assertBackgroundThread()
val paths = mutableListOf<String>()
readableDatabase.queryAll(TABLE_NAME) { cursor ->
while (cursor.moveToNext()) {
paths.add(cursor.getString(0))
}
}
logD("Successfully read ${paths.size} paths from db")
return paths
}
companion object {
// Blacklist is still used here for compatibility reasons, please don't get
// your pants in a twist about it.
const val DB_VERSION = 1
const val DB_NAME = "auxio_blacklist_database.db"

View file

@ -77,13 +77,16 @@ class ExcludedDialog : LifecycleDialog() {
dialog.setOnShowListener {
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.setOnClickListener {
logD("Opening launcher")
launcher.launch(null)
}
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
if (excludedModel.isModified) {
logD("Committing changes")
saveAndRestart()
} else {
logD("Dropping changes")
dismiss()
}
}
@ -93,11 +96,10 @@ class ExcludedDialog : LifecycleDialog() {
excludedModel.paths.observe(viewLifecycleOwner) { paths ->
adapter.submitList(paths)
binding.excludedEmpty.isVisible = paths.isEmpty()
}
logD("Dialog created.")
logD("Dialog created")
return binding.root
}
@ -114,6 +116,7 @@ class ExcludedDialog : LifecycleDialog() {
private fun addDocTreePath(uri: Uri?) {
// A null URI means that the user left the file picker without picking a directory
if (uri == null) {
logD("No URI given (user closed the dialog)")
return
}
@ -142,6 +145,7 @@ class ExcludedDialog : LifecycleDialog() {
return getRootPath() + "/" + typeAndPath.last()
}
logD("Unsupported volume ${typeAndPath[0]}")
return null
}
@ -156,7 +160,6 @@ class ExcludedDialog : LifecycleDialog() {
/**
* Get *just* the root path, nothing else is really needed.
*/
@Suppress("DEPRECATION")
private fun getRootPath(): String {
return Environment.getExternalStorageDirectory().absolutePath
}

View file

@ -27,6 +27,7 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.oxycblt.auxio.util.logD
/**
* ViewModel that acts as a wrapper around [ExcludedDatabase], allowing for the addition/removal
@ -73,10 +74,13 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo
*/
fun save(onDone: () -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
val start = System.currentTimeMillis()
excludedDatabase.writePaths(mPaths.value!!)
dbPaths = mPaths.value!!
onDone()
this@ExcludedViewModel.logD(
"Path save completed successfully in ${System.currentTimeMillis() - start}ms"
)
}
}
@ -85,11 +89,14 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo
*/
private fun loadDatabasePaths() {
viewModelScope.launch(Dispatchers.IO) {
val start = System.currentTimeMillis()
dbPaths = excludedDatabase.readPaths()
withContext(Dispatchers.Main) {
mPaths.value = dbPaths.toMutableList()
}
this@ExcludedViewModel.logD(
"Path load completed successfully in ${System.currentTimeMillis() - start}ms"
)
}
}

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.util.AttributeSet
import com.google.android.material.floatingactionbutton.FloatingActionButton
import org.oxycblt.auxio.util.getDimenSizeSafe
import org.oxycblt.auxio.util.logD
import com.google.android.material.R as MaterialR
/**
@ -20,7 +21,10 @@ class AdaptiveFloatingActionButton @JvmOverloads constructor(
init {
size = SIZE_NORMAL
// Use a large FAB on large screens, as it makes it easier to touch.
if (resources.configuration.smallestScreenWidthDp >= 640) {
logD("Using large FAB configuration")
val largeFabSize = context.getDimenSizeSafe(
MaterialR.dimen.m3_large_fab_size
)
@ -29,7 +33,6 @@ class AdaptiveFloatingActionButton @JvmOverloads constructor(
MaterialR.dimen.m3_large_fab_max_image_size
)
// Use a large FAB on large screens, as it makes it easier to touch.
customSize = largeFabSize
setMaxImageSize(largeImageSize)
}

View file

@ -3,6 +3,7 @@ package org.oxycblt.auxio.home
import android.content.Context
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.oxycblt.auxio.util.logD
/**
* A tag configuration strategy that automatically adapts the tab layout to the screen size.
@ -20,15 +21,22 @@ class AdaptiveTabStrategy(
val tabMode = homeModel.tabs[position]
when {
width < 370 ->
width < 370 -> {
logD("Using icon-only configuration")
tab.setIcon(tabMode.icon)
.setContentDescription(tabMode.string)
}
width < 640 -> tab.setText(tabMode.string)
width < 640 -> {
logD("Using text-only configuration")
tab.setText(tabMode.string)
}
else ->
else -> {
logD("Using icon-and-text configuration")
tab.setIcon(tabMode.icon)
.setText(tabMode.string)
}
}
}
}

View file

@ -49,6 +49,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.logTraceOrThrow
/**
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail
@ -77,16 +78,19 @@ class HomeFragment : Fragment() {
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_search -> {
logD("Navigating to search")
findNavController().navigate(HomeFragmentDirections.actionShowSearch())
}
R.id.action_settings -> {
logD("Navigating to settings")
parentFragment?.parentFragment?.findNavController()?.navigate(
MainFragmentDirections.actionShowSettings()
)
}
R.id.action_about -> {
logD("Navigating to about")
parentFragment?.parentFragment?.findNavController()?.navigate(
MainFragmentDirections.actionShowAbout()
)
@ -96,20 +100,16 @@ class HomeFragment : Fragment() {
R.id.option_sort_asc -> {
item.isChecked = !item.isChecked
val new = homeModel.getSortForDisplay(homeModel.curTab.value!!)
.ascending(item.isChecked)
homeModel.updateCurrentSort(new)
}
// Sorting option was selected, mark it as selected and update the mode
else -> {
item.isChecked = true
val new = homeModel.getSortForDisplay(homeModel.curTab.value!!)
.assignId(item.itemId)
homeModel.updateCurrentSort(requireNotNull(new))
}
}
@ -141,8 +141,8 @@ class HomeFragment : Fragment() {
set(recycler, slop * 3) // 3x seems to be the best fit here
}
} catch (e: Exception) {
logE("Unable to reduce ViewPager sensitivity")
logE(e.stackTraceToString())
logE("Unable to reduce ViewPager sensitivity (likely an internal code change)")
e.logTraceOrThrow()
}
// We know that there will only be a fixed amount of tabs, so we manually set this
@ -174,7 +174,7 @@ class HomeFragment : Fragment() {
is MusicStore.Response.Ok -> binding.homeFab.show()
// While loading or during an error, make sure we keep the shuffle fab hidden so
// that any kind of loading is impossible. PlaybackStateManager also relies on this
// that any kind of playback is impossible. PlaybackStateManager also relies on this
// invariant, so please don't change it.
else -> binding.homeFab.hide()
}
@ -207,7 +207,7 @@ class HomeFragment : Fragment() {
homeModel.curTab.observe(viewLifecycleOwner) { t ->
val tab = requireNotNull(t)
// Make sure that we update the scrolling view and allowed menu items before whenever
// Make sure that we update the scrolling view and allowed menu items whenever
// the tab changes.
when (tab) {
DisplayMode.SHOW_SONGS -> updateSortMenu(sortItem, tab)
@ -229,8 +229,9 @@ class HomeFragment : Fragment() {
}
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
// The AppBarLayout gets confused and collapses when we navigate too fast, wait for it
// to draw before we continue.
// The AppBarLayout gets confused when we navigate too fast, wait for it to draw
// before we navigate.
// This is only here just in case a collapsing toolbar is re-added.
binding.homeAppbar.post {
when (item) {
is Song -> findNavController().navigate(
@ -255,7 +256,7 @@ class HomeFragment : Fragment() {
}
}
logD("Fragment Created.")
logD("Fragment Created")
return binding.root
}

View file

@ -32,6 +32,7 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.util.logD
/**
* The ViewModel for managing [HomeFragment]'s data, sorting modes, and tab state.
@ -78,7 +79,6 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback {
viewModelScope.launch {
val musicStore = MusicStore.awaitInstance()
mSongs.value = settingsManager.libSongSort.sortSongs(musicStore.songs)
mAlbums.value = settingsManager.libAlbumSort.sortAlbums(musicStore.albums)
mArtists.value = settingsManager.libArtistSort.sortParents(musicStore.artists)
@ -90,6 +90,7 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback {
* Update the current tab based off of the new ViewPager position.
*/
fun updateCurrentTab(pos: Int) {
logD("Updating current tab to ${tabs[pos]}")
mCurTab.value = tabs[pos]
}
@ -110,6 +111,7 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback {
* Update the currently displayed item's [Sort].
*/
fun updateCurrentSort(sort: Sort) {
logD("Updating ${mCurTab.value} sort to $sort")
when (mCurTab.value) {
DisplayMode.SHOW_SONGS -> {
settingsManager.libSongSort = sort

View file

@ -109,7 +109,7 @@ sealed class Tab(open val mode: DisplayMode) {
// For safety, return null if we have an empty or larger-than-expected tab array.
if (distinct.isEmpty() || distinct.size < SEQUENCE_LEN) {
logE("Sequence size was ${distinct.size}, which is invalid.")
logE("Sequence size was ${distinct.size}, which is invalid")
return null
}

View file

@ -29,6 +29,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogTabsBinding
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.LifecycleDialog
import org.oxycblt.auxio.util.logD
/**
* The dialog for customizing library tabs. This dialog does not rely on any specific ViewModel
@ -49,7 +50,6 @@ class TabCustomizeDialog : LifecycleDialog() {
if (savedInstanceState != null) {
// Restore any pending tab configurations
val tabs = Tab.fromSequence(savedInstanceState.getInt(KEY_TABS))
if (tabs != null) {
pendingTabs = tabs
}
@ -66,10 +66,9 @@ class TabCustomizeDialog : LifecycleDialog() {
// of how ViewHolders are bound], but instead simply look for the mode in
// the list of pending tabs and update that instead.
val index = pendingTabs.indexOfFirst { it.mode == tab.mode }
if (index != -1) {
val curTab = pendingTabs[index]
logD("Updating tab $curTab to $tab")
pendingTabs[index] = when (curTab) {
is Tab.Visible -> Tab.Invisible(curTab.mode)
is Tab.Invisible -> Tab.Visible(curTab.mode)
@ -93,7 +92,6 @@ class TabCustomizeDialog : LifecycleDialog() {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(KEY_TABS, Tab.toSequence(pendingTabs))
}
@ -101,6 +99,7 @@ class TabCustomizeDialog : LifecycleDialog() {
builder.setTitle(R.string.set_lib_tabs)
builder.setPositiveButton(android.R.string.ok) { _, _ ->
logD("Committing tab changes")
settingsManager.libTabs = pendingTabs
}

View file

@ -124,8 +124,12 @@ data class Song(
val internalGroupingArtistName: String get() = internalMediaStoreAlbumArtistName
?: internalMediaStoreArtistName ?: MediaStore.UNKNOWN_STRING
/** Internal field. Do not use. */
val internalIsMissingAlbum: Boolean get() = mAlbum == null
/** Internal field. Do not use. */
val internalIsMissingArtist: Boolean get() = mAlbum?.internalIsMissingArtist ?: true
/** Internal field. Do not use. **/
val internalMissingGenre: Boolean get() = mGenre == null
val internalIsMissingGenre: Boolean get() = mGenre == null
/** Internal method. Do not use. */
fun internalLinkAlbum(album: Album) {
@ -180,6 +184,9 @@ data class Album(
val resolvedArtistName: String get() =
artist.resolvedName
/** Internal field. Do not use. */
val internalIsMissingArtist: Boolean = mArtist != null
/** Internal method. Do not use. */
fun internalLinkArtist(artist: Artist) {
mArtist = artist

View file

@ -7,8 +7,8 @@ import android.provider.MediaStore
import androidx.core.database.getStringOrNull
import org.oxycblt.auxio.R
import org.oxycblt.auxio.excluded.ExcludedDatabase
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE
import java.lang.Exception
/**
* This class acts as the base for most the black magic required to get a remotely sensible music
@ -26,7 +26,7 @@ import java.lang.Exception
* have to query for each genre, query all the songs in each genre, and then iterate through those
* songs to link every song with their genre. This is not documented anywhere, and the
* O(mom im scared) algorithm you have to run to get it working single-handedly DOUBLES Auxio's
* loading times. At no point have the devs considered that this column is absolutely insane, and
* loading times. At no point have the devs considered that this system is absolutely insane, and
* instead focused on adding infuriat- I mean nice proprietary extensions to MediaStore for their
* own Google Play Music, and of course every Google Play Music user knew how great that turned
* out!
@ -88,14 +88,14 @@ class MusicLoader {
val artists = buildArtists(context, albums)
val genres = readGenres(context, songs)
// Sanity check: Ensure that all songs are well-formed.
// Sanity check: Ensure that all songs are linked up to albums/artists/genres.
for (song in songs) {
try {
song.album.artist
song.genre
} catch (e: Exception) {
if (song.internalIsMissingAlbum ||
song.internalIsMissingArtist ||
song.internalIsMissingGenre
) {
logE("Found malformed song: ${song.name}")
throw e
throw IllegalStateException()
}
}
@ -204,6 +204,8 @@ class MusicLoader {
it.internalMediaStoreAlbumArtistName to it.track to it.duration
}.toMutableList()
logD("Successfully loaded ${songs.size} songs")
return songs
}
@ -247,6 +249,8 @@ class MusicLoader {
)
}
logD("Successfully built ${albums.size} albums")
return albums
}
@ -264,14 +268,15 @@ class MusicLoader {
// Album deduplication does not eliminate every case of fragmented artists, do
// we deduplicate in the artist creation step as well.
// Note that we actually don't do this in groupBy. This is generally because we
// only want to default to a lowercase artist name when we have no other choice.
// Note that we actually don't do this in groupBy. This is generally because using
// a template song may not result in the best possible artist name in all cases.
val previousArtistIndex = artists.indexOfFirst { artist ->
artist.name.lowercase() == artistName.lowercase()
}
if (previousArtistIndex > -1) {
val previousArtist = artists[previousArtistIndex]
logD("Merging duplicate artist into pre-existing artist ${previousArtist.name}")
artists[previousArtistIndex] = Artist(
previousArtist.name,
previousArtist.resolvedName,
@ -288,6 +293,8 @@ class MusicLoader {
}
}
logD("Successfully built ${artists.size} artists")
return artists
}
@ -327,7 +334,7 @@ class MusicLoader {
}
}
val songsWithoutGenres = songs.filter { it.internalMissingGenre }
val songsWithoutGenres = songs.filter { it.internalIsMissingGenre }
if (songsWithoutGenres.isNotEmpty()) {
// Songs that don't have a genre will be thrown into an unknown genre.
@ -340,6 +347,8 @@ class MusicLoader {
genres.add(unknownGenre)
}
logD("Successfully loaded ${genres.size} genres")
return genres
}

View file

@ -55,7 +55,7 @@ class MusicStore private constructor() {
* Load/Sort the entire music library. Should always be ran on a coroutine.
*/
private fun load(context: Context): Response {
logD("Starting initial music load...")
logD("Starting initial music load")
val notGranted = ContextCompat.checkSelfPermission(
context, Manifest.permission.READ_EXTERNAL_STORAGE
@ -76,11 +76,10 @@ class MusicStore private constructor() {
mArtists = library.artists
mGenres = library.genres
logD("Music load completed successfully in ${System.currentTimeMillis() - start}ms.")
logD("Music load completed successfully in ${System.currentTimeMillis() - start}ms")
} catch (e: Exception) {
logE("Something went horribly wrong.")
logE("Something went horribly wrong")
logE(e.stackTraceToString())
return Response.Err(ErrorKind.FAILED)
}
@ -117,6 +116,7 @@ class MusicStore private constructor() {
/**
* A response that [MusicStore] returns when loading music.
* And before you ask, yes, I do like rust.
* TODO: Replace this with the kotlin builtin
*/
sealed class Response {
class Ok(val musicStore: MusicStore) : Response()
@ -201,7 +201,7 @@ class MusicStore private constructor() {
*/
fun requireInstance(): MusicStore {
return requireNotNull(maybeGetInstance()) {
"Required MusicStore instance was not available."
"Required MusicStore instance was not available"
}
}

View file

@ -25,6 +25,8 @@ import androidx.core.text.isDigitsOnly
import androidx.databinding.BindingAdapter
import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.getPluralSafe
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
/**
* A complete array of all the hardcoded genre values for ID3(v2), contains standard genres and
@ -98,6 +100,7 @@ fun String.getGenreNameCompat(): String? {
*/
fun Long.toDuration(isElapsed: Boolean): String {
if (!isElapsed && this == 0L) {
logD("Non-elapsed duration is zero, using --:--")
return "--:--"
}
@ -121,14 +124,53 @@ fun Int.toDate(context: Context): String {
// --- BINDING ADAPTERS ---
/**
* Bind the album + song counts for an artist
*/
@BindingAdapter("artistCounts")
fun TextView.bindArtistCounts(artist: Artist) {
@BindingAdapter("songInfo")
fun TextView.bindSongInfo(song: Song?) {
if (song == null) {
logW("Song was null, not applying info")
return
}
text = context.getString(
R.string.fmt_two,
song.resolvedArtistName,
song.resolvedAlbumName
)
}
@BindingAdapter("albumInfo")
fun TextView.bindAlbumInfo(album: Album?) {
if (album == null) {
logW("Album was null, not applying info")
return
}
text = context.getString(
R.string.fmt_two, album.resolvedArtistName,
context.getPluralSafe(R.plurals.fmt_song_count, album.songs.size)
)
}
@BindingAdapter("artistInfo")
fun TextView.bindArtistInfo(artist: Artist?) {
if (artist == null) {
logW("Artist was null, not applying info")
return
}
text = context.getString(
R.string.fmt_counts,
context.getPluralSafe(R.plurals.fmt_album_count, artist.albums.size),
context.getPluralSafe(R.plurals.fmt_song_count, artist.songs.size)
)
}
@BindingAdapter("genreInfo")
fun TextView.bindGenreInfo(genre: Genre?) {
if (genre == null) {
logW("Genre was null, not applying info")
return
}
text = context.getPluralSafe(R.plurals.fmt_song_count, genre.songs.size)
}

View file

@ -24,6 +24,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.oxycblt.auxio.util.logD
class MusicViewModel : ViewModel() {
private val mLoaderResponse = MutableLiveData<MusicStore.Response?>(null)
@ -37,6 +38,7 @@ class MusicViewModel : ViewModel() {
*/
fun loadMusic(context: Context) {
if (mLoaderResponse.value != null || isBusy) {
logD("Loader is busy/already completed, not reloading")
return
}
@ -45,15 +47,14 @@ class MusicViewModel : ViewModel() {
viewModelScope.launch {
val result = MusicStore.initInstance(context)
isBusy = false
mLoaderResponse.value = result
isBusy = false
}
}
fun reloadMusic(context: Context) {
logD("Reloading music library")
mLoaderResponse.value = null
loadMusic(context)
}
}

View file

@ -92,7 +92,6 @@ class PlaybackFragment : Fragment() {
// Make marquee of song title work
binding.playbackSong.isSelected = true
binding.playbackSeekBar.onConfirmListener = playbackModel::setPosition
binding.playbackPlayPause.post {
binding.playbackPlayPause.stateListAnimator = null
}
@ -101,11 +100,11 @@ class PlaybackFragment : Fragment() {
playbackModel.song.observe(viewLifecycleOwner) { song ->
if (song != null) {
logD("Updating song display to ${song.name}.")
logD("Updating song display to ${song.name}")
binding.song = song
binding.playbackSeekBar.setDuration(song.seconds)
} else {
logD("No song is being played, leaving.")
logD("No song is being played, leaving")
findNavController().navigateUp()
}
}
@ -152,7 +151,7 @@ class PlaybackFragment : Fragment() {
}
}
logD("Fragment Created.")
logD("Fragment Created")
return binding.root
}

View file

@ -27,6 +27,7 @@ import org.oxycblt.auxio.util.disableDropShadowCompat
import org.oxycblt.auxio.util.getAttrColorSafe
import org.oxycblt.auxio.util.getDimenSafe
import org.oxycblt.auxio.util.getDrawableSafe
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.pxOfDp
import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
import org.oxycblt.auxio.util.stateList
@ -225,6 +226,8 @@ class PlaybackLayout @JvmOverloads constructor(
}
private fun applyState(state: PanelState) {
logD("Applying panel state $state")
// Dragging events are really complex and we don't want to mess up the state
// while we are in one.
if (state == panelState || panelState == PanelState.DRAGGING) {
@ -357,10 +360,8 @@ class PlaybackLayout @JvmOverloads constructor(
// bottom navigation is consumed by a bar. To fix this, we modify the bottom insets
// to reflect the presence of the panel [at least in it's collapsed state]
playbackContainerView.dispatchApplyWindowInsets(insets)
lastInsets = insets
applyContentWindowInsets()
return insets
}
@ -370,7 +371,6 @@ class PlaybackLayout @JvmOverloads constructor(
*/
private fun applyContentWindowInsets() {
val insets = lastInsets
if (insets != null) {
contentView.dispatchApplyWindowInsets(adjustInsets(insets))
}
@ -386,8 +386,9 @@ class PlaybackLayout @JvmOverloads constructor(
val bars = insets.systemBarInsetsCompat
val consumedByPanel = computePanelTopPosition(panelOffset) - measuredHeight
val adjustedBottomInset = (consumedByPanel + bars.bottom).coerceAtLeast(0)
return insets.replaceSystemBarInsetsCompat(bars.left, bars.top, bars.right, adjustedBottomInset)
return insets.replaceSystemBarInsetsCompat(
bars.left, bars.top, bars.right, adjustedBottomInset
)
}
override fun onSaveInstanceState(): Parcelable = Bundle().apply {
@ -586,6 +587,8 @@ class PlaybackLayout @JvmOverloads constructor(
(computePanelTopPosition(0f) - topPosition).toFloat() / panelRange
private fun smoothSlideTo(offset: Float) {
logD("Smooth sliding to $offset")
val okay = dragHelper.smoothSlideViewTo(
playbackContainerView, playbackContainerView.left, computePanelTopPosition(offset)
)

View file

@ -29,6 +29,7 @@ import org.oxycblt.auxio.databinding.ViewSeekBarBinding
import org.oxycblt.auxio.music.toDuration
import org.oxycblt.auxio.util.getAttrColorSafe
import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.stateList
/**
@ -73,6 +74,7 @@ class PlaybackSeekBar @JvmOverloads constructor(
// - The duration of the song was so low as to be rounded to zero when converted
// to seconds.
// In either of these cases, the seekbar is more or less useless. Disable it.
logD("Duration is 0, entering disabled state")
binding.seekBar.apply {
valueTo = 1f
isEnabled = false

View file

@ -111,7 +111,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
*/
fun playAlbum(album: Album, shuffled: Boolean) {
if (album.songs.isEmpty()) {
logE("Album is empty, Not playing.")
logE("Album is empty, Not playing")
return
}
@ -125,7 +125,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
*/
fun playArtist(artist: Artist, shuffled: Boolean) {
if (artist.songs.isEmpty()) {
logE("Artist is empty, Not playing.")
logE("Artist is empty, Not playing")
return
}
@ -139,7 +139,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
*/
fun playGenre(genre: Genre, shuffled: Boolean) {
if (genre.songs.isEmpty()) {
logE("Genre is empty, Not playing.")
logE("Genre is empty, Not playing")
return
}
@ -156,7 +156,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
if (playbackManager.isRestored && MusicStore.loaded()) {
playWithUriInternal(uri, context)
} else {
logD("Cant play this URI right now, waiting...")
logD("Cant play this URI right now, waiting")
mIntentUri = uri
}
@ -213,12 +213,10 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
* [apply] is called just before the change is committed so that the adapter can be updated.
*/
fun removeQueueDataItem(adapterIndex: Int, apply: () -> Unit) {
val adjusted = adapterIndex + (playbackManager.queue.size - mNextUp.value!!.size)
logD("$adjusted")
if (adjusted in playbackManager.queue.indices) {
val index = adapterIndex + (playbackManager.queue.size - mNextUp.value!!.size)
if (index in playbackManager.queue.indices) {
apply()
playbackManager.removeQueueItem(adjusted)
playbackManager.removeQueueItem(index)
}
}
/**
@ -227,10 +225,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
*/
fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int, apply: () -> Unit): Boolean {
val delta = (playbackManager.queue.size - mNextUp.value!!.size)
val from = adapterFrom + delta
val to = adapterTo + delta
if (from in playbackManager.queue.indices && to in playbackManager.queue.indices) {
apply()
playbackManager.moveQueueItems(from, to)
@ -332,7 +328,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
* [PlaybackStateManager] instance.
*/
private fun restorePlaybackState() {
logD("Attempting to restore playback state.")
logD("Attempting to restore playback state")
onSongUpdate(playbackManager.song)
onPositionUpdate(playbackManager.position)

View file

@ -70,11 +70,9 @@ class QueueAdapter(
QUEUE_SONG_ITEM_TYPE -> QueueSongViewHolder(
ItemQueueSongBinding.inflate(parent.context.inflater)
)
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
else -> error("Invalid ViewHolder item type $viewType.")
else -> error("Invalid ViewHolder item type $viewType")
}
}
@ -83,8 +81,7 @@ class QueueAdapter(
is Song -> (holder as QueueSongViewHolder).bind(item)
is Header -> (holder as HeaderViewHolder).bind(item)
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
else -> logE("Bad data given to QueueAdapter.")
else -> logE("Bad data given to QueueAdapter")
}
}
@ -95,7 +92,6 @@ class QueueAdapter(
fun submitList(newData: MutableList<BaseModel>) {
if (data != newData) {
data = newData
listDiffer.submitList(newData)
}
}

View file

@ -27,6 +27,7 @@ import com.google.android.material.shape.MaterialShapeDrawable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.getDimenSafe
import org.oxycblt.auxio.util.logD
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@ -89,9 +90,10 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
val holder = viewHolder as QueueAdapter.QueueSongViewHolder
if (shouldLift && isCurrentlyActive && actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
logD("Lifting queue item")
val bg = holder.bodyView.background as MaterialShapeDrawable
val elevation = recyclerView.context.getDimenSafe(R.dimen.elevation_small)
holder.itemView.animate()
.translationZ(elevation)
.setDuration(100)
@ -127,8 +129,9 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
val holder = viewHolder as QueueAdapter.QueueSongViewHolder
if (holder.itemView.translationZ != 0.0f) {
val bg = holder.bodyView.background as MaterialShapeDrawable
logD("Dropping queue item")
val bg = holder.bodyView.background as MaterialShapeDrawable
holder.itemView.animate()
.translationZ(0.0f)
.setDuration(100)

View file

@ -28,6 +28,7 @@ import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper
import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.logD
/**
* A [Fragment] that shows the queue and enables editing as well.
@ -77,9 +78,11 @@ class QueueFragment : Fragment() {
}
playbackModel.isShuffling.observe(viewLifecycleOwner) { isShuffling ->
// Try to prevent the queue adapter from going spastic during reshuffle events
// by just scrolling back to the top.
if (isShuffling != lastShuffle) {
logD("Reshuffle event, scrolling to top")
lastShuffle = isShuffling
binding.queueRecycler.scrollToPosition(0)
}
}

View file

@ -48,10 +48,10 @@ class PlaybackStateDatabase(context: Context) :
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = nuke(db)
private fun nuke(db: SQLiteDatabase) {
logD("Nuking database")
db.apply {
execSQL("DROP TABLE IF EXISTS $TABLE_NAME_STATE")
execSQL("DROP TABLE IF EXISTS $TABLE_NAME_QUEUE")
onCreate(this)
}
}
@ -103,34 +103,6 @@ class PlaybackStateDatabase(context: Context) :
// --- INTERFACE FUNCTIONS ---
/**
* Clear the previously written [SavedState] and write a new one.
*/
fun writeState(state: SavedState) {
assertBackgroundThread()
writableDatabase.transaction {
delete(TABLE_NAME_STATE, null, null)
this@PlaybackStateDatabase.logD("Wiped state db.")
val stateData = ContentValues(10).apply {
put(StateColumns.COLUMN_ID, 0)
put(StateColumns.COLUMN_SONG_HASH, state.song?.id)
put(StateColumns.COLUMN_POSITION, state.position)
put(StateColumns.COLUMN_PARENT_HASH, state.parent?.id)
put(StateColumns.COLUMN_QUEUE_INDEX, state.queueIndex)
put(StateColumns.COLUMN_PLAYBACK_MODE, state.playbackMode.toInt())
put(StateColumns.COLUMN_IS_SHUFFLING, state.isShuffling)
put(StateColumns.COLUMN_LOOP_MODE, state.loopMode.toInt())
}
insert(TABLE_NAME_STATE, null, stateData)
}
logD("Wrote state to database.")
}
/**
* Read the stored [SavedState] from the database, if there is one.
* @param musicStore Required to transform database songs/parents into actual instances
@ -178,11 +150,69 @@ class PlaybackStateDatabase(context: Context) :
isShuffling = cursor.getInt(shuffleIndex) == 1,
loopMode = LoopMode.fromInt(cursor.getInt(loopModeIndex)) ?: LoopMode.NONE,
)
logD("Successfully read playback state: $state")
}
return state
}
/**
* Clear the previously written [SavedState] and write a new one.
*/
fun writeState(state: SavedState) {
assertBackgroundThread()
writableDatabase.transaction {
delete(TABLE_NAME_STATE, null, null)
this@PlaybackStateDatabase.logD("Wiped state db")
val stateData = ContentValues(10).apply {
put(StateColumns.COLUMN_ID, 0)
put(StateColumns.COLUMN_SONG_HASH, state.song?.id)
put(StateColumns.COLUMN_POSITION, state.position)
put(StateColumns.COLUMN_PARENT_HASH, state.parent?.id)
put(StateColumns.COLUMN_QUEUE_INDEX, state.queueIndex)
put(StateColumns.COLUMN_PLAYBACK_MODE, state.playbackMode.toInt())
put(StateColumns.COLUMN_IS_SHUFFLING, state.isShuffling)
put(StateColumns.COLUMN_LOOP_MODE, state.loopMode.toInt())
}
insert(TABLE_NAME_STATE, null, stateData)
}
logD("Wrote state to database")
}
/**
* Read a list of queue items from this database.
* @param musicStore Required to transform database songs into actual song instances
*/
fun readQueue(musicStore: MusicStore): MutableList<Song> {
assertBackgroundThread()
val queue = mutableListOf<Song>()
readableDatabase.queryAll(TABLE_NAME_QUEUE) { cursor ->
if (cursor.count == 0) return@queryAll
val songIndex = cursor.getColumnIndexOrThrow(QueueColumns.SONG_HASH)
val albumIndex = cursor.getColumnIndexOrThrow(QueueColumns.ALBUM_HASH)
while (cursor.moveToNext()) {
musicStore.findSongFast(cursor.getLong(songIndex), cursor.getLong(albumIndex))
?.let { song ->
queue.add(song)
}
}
}
logD("Successfully read queue of ${queue.size} songs")
return queue
}
/**
* Write a queue to the database.
*/
@ -190,12 +220,11 @@ class PlaybackStateDatabase(context: Context) :
assertBackgroundThread()
val database = writableDatabase
database.transaction {
delete(TABLE_NAME_QUEUE, null, null)
}
logD("Wiped queue db.")
logD("Wiped queue db")
writeQueueBatch(queue, queue.size)
}
@ -232,32 +261,6 @@ class PlaybackStateDatabase(context: Context) :
}
}
/**
* Read a list of queue items from this database.
* @param musicStore Required to transform database songs into actual song instances
*/
fun readQueue(musicStore: MusicStore): MutableList<Song> {
assertBackgroundThread()
val queue = mutableListOf<Song>()
readableDatabase.queryAll(TABLE_NAME_QUEUE) { cursor ->
if (cursor.count == 0) return@queryAll
val songIndex = cursor.getColumnIndexOrThrow(QueueColumns.SONG_HASH)
val albumIndex = cursor.getColumnIndexOrThrow(QueueColumns.ALBUM_HASH)
while (cursor.moveToNext()) {
musicStore.findSongFast(cursor.getLong(songIndex), cursor.getLong(albumIndex))
?.let { song ->
queue.add(song)
}
}
}
return queue
}
data class SavedState(
val song: Song?,
val position: Long,

View file

@ -224,7 +224,6 @@ class PlaybackStateManager private constructor() {
private fun updatePlayback(song: Song, shouldPlay: Boolean = true) {
mSong = song
mPosition = 0
setPlaying(shouldPlay)
}
@ -271,18 +270,14 @@ class PlaybackStateManager private constructor() {
* Remove a queue item at [index]. Will ignore invalid indexes.
*/
fun removeQueueItem(index: Int): Boolean {
logD("Removing item ${mQueue[index].name}.")
if (index > mQueue.size || index < 0) {
logE("Index is out of bounds, did not remove queue item.")
logE("Index is out of bounds, did not remove queue item")
return false
}
logD("Removing item ${mQueue[index].name}")
mQueue.removeAt(index)
pushQueueUpdate()
return true
}
@ -292,15 +287,12 @@ class PlaybackStateManager private constructor() {
fun moveQueueItems(from: Int, to: Int): Boolean {
if (from > mQueue.size || from < 0 || to > mQueue.size || to < 0) {
logE("Indices were out of bounds, did not move queue item")
return false
}
val item = mQueue.removeAt(from)
mQueue.add(to, item)
logD("Moving item $from to position $to")
mQueue.add(to, mQueue.removeAt(from))
pushQueueUpdate()
return true
}
@ -501,7 +493,7 @@ class PlaybackStateManager private constructor() {
* @param context [Context] required
*/
suspend fun saveStateToDatabase(context: Context) {
logD("Saving state to DB.")
logD("Saving state to DB")
// Pack the entire state and save it to the database.
withContext(Dispatchers.IO) {
@ -519,7 +511,7 @@ class PlaybackStateManager private constructor() {
database.writeQueue(mQueue)
this@PlaybackStateManager.logD(
"Save finished in ${System.currentTimeMillis() - start}ms"
"State save completed successfully in ${System.currentTimeMillis() - start}ms"
)
}
}
@ -529,10 +521,9 @@ class PlaybackStateManager private constructor() {
* @param context [Context] required.
*/
suspend fun restoreFromDatabase(context: Context) {
logD("Getting state from DB.")
logD("Getting state from DB")
val musicStore = MusicStore.maybeGetInstance() ?: return
val start: Long
val playbackState: PlaybackStateDatabase.SavedState?
val queue: MutableList<Song>
@ -549,15 +540,13 @@ class PlaybackStateManager private constructor() {
// Get off the IO coroutine since it will cause LiveData updates to throw an exception
if (playbackState != null) {
logD("Found playback state $playbackState")
unpackFromPlaybackState(playbackState)
unpackQueue(queue)
doParentSanityCheck()
doIndexSanityCheck()
}
logD("Restore finished in ${System.currentTimeMillis() - start}ms")
logD("State load completed successfully in ${System.currentTimeMillis() - start}ms")
markRestored()
}
@ -592,7 +581,7 @@ class PlaybackStateManager private constructor() {
private fun doParentSanityCheck() {
// Check if the parent was lost while in the DB.
if (mSong != null && mParent == null && mPlaybackMode != PlaybackMode.ALL_SONGS) {
logD("Parent lost, attempting restore.")
logD("Parent lost, attempting restore")
mParent = when (mPlaybackMode) {
PlaybackMode.IN_ALBUM -> mQueue.firstOrNull()?.album

View file

@ -33,6 +33,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.getSystemServiceSafe
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
import kotlin.math.pow
/**
@ -85,6 +86,7 @@ class AudioReactor(
* Request the android system for audio focus
*/
fun requestFocus() {
logD("Requesting audio focus")
AudioManagerCompat.requestAudioFocus(audioManager, request)
}
@ -94,7 +96,7 @@ class AudioReactor(
*/
fun applyReplayGain(metadata: Metadata?) {
if (metadata == null) {
logD("No metadata.")
logW("No metadata could be extracted from this track")
volume = 1f
return
}
@ -102,7 +104,7 @@ class AudioReactor(
// ReplayGain is configurable, so determine what to do based off of the mode.
val useAlbumGain: (Gain) -> Boolean = when (settingsManager.replayGainMode) {
ReplayGainMode.OFF -> {
logD("ReplayGain is off.")
logD("ReplayGain is off")
volume = 1f
return
}
@ -132,10 +134,10 @@ class AudioReactor(
val adjust = if (gain != null) {
if (useAlbumGain(gain)) {
logD("Using album gain.")
logD("Using album gain")
gain.album
} else {
logD("Using track gain.")
logD("Using track gain")
gain.track
}
} else {

View file

@ -5,6 +5,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat
import org.oxycblt.auxio.util.logD
/**
* Some apps like to party like it's 2011 and just blindly query for the ACTION_MEDIA_BUTTON
@ -20,6 +21,7 @@ import androidx.core.content.ContextCompat
class MediaButtonReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_MEDIA_BUTTON) {
logD("Received external media button intent")
intent.component = ComponentName(context, PlaybackService::class.java)
ContextCompat.startForegroundService(context, intent)
}

View file

@ -180,7 +180,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
settingsManager.addCallback(this)
logD("Service created.")
logD("Service created")
}
override fun onDestroy() {
@ -207,7 +207,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
serviceJob.cancel()
}
logD("Service destroyed.")
logD("Service destroyed")
}
// --- PLAYER EVENT LISTENER OVERRIDES ---
@ -260,22 +260,21 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
override fun onSongUpdate(song: Song?) {
if (song != null) {
logD("Setting player to ${song.name}")
player.setMediaItem(MediaItem.fromUri(song.uri))
player.prepare()
notification.setMetadata(song, ::startForegroundOrNotify)
return
}
// Clear if there's nothing to play.
logD("Nothing playing, stopping playback")
player.stop()
stopForegroundAndNotification()
}
override fun onParentUpdate(parent: MusicParent?) {
notification.setParent(parent)
startForegroundOrNotify()
}
@ -295,7 +294,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
override fun onLoopUpdate(loopMode: LoopMode) {
if (!settingsManager.useAltNotifAction) {
notification.setLoop(loopMode)
startForegroundOrNotify()
}
}
@ -303,7 +301,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
override fun onShuffleUpdate(isShuffling: Boolean) {
if (settingsManager.useAltNotifAction) {
notification.setShuffle(isShuffling)
startForegroundOrNotify()
}
}
@ -334,7 +331,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
override fun onShowCoverUpdate(showCovers: Boolean) {
playbackManager.song?.let { song ->
connector.onSongUpdate(song)
notification.setMetadata(song, ::startForegroundOrNotify)
}
}
@ -449,6 +445,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
/**
* A [BroadcastReceiver] for receiving general playback events from the system.
* TODO: Don't fire when the service initially starts?
*/
private inner class PlaybackReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
@ -501,7 +498,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
*/
private fun resumeFromPlug() {
if (playbackManager.song != null && settingsManager.doPlugMgt) {
logD("Device connected, resuming...")
logD("Device connected, resuming")
playbackManager.setPlaying(true)
}
}
@ -511,7 +508,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
*/
private fun pauseFromPlug() {
if (playbackManager.song != null && settingsManager.doPlugMgt) {
logD("Device disconnected, pausing...")
logD("Device disconnected, pausing")
playbackManager.setPlaying(false)
}
}

View file

@ -29,6 +29,7 @@ import org.oxycblt.auxio.coil.loadBitmap
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.util.logD
/**
* Nightmarish class that coordinates communication between [MediaSessionCompat], [Player],
@ -158,6 +159,8 @@ class PlaybackSessionConnector(
// --- MISC ---
private fun invalidateSessionState() {
logD("Updating media session state")
// Position updates arrive faster when you upload STATE_PAUSED for some insane reason.
val state = PlaybackStateCompat.Builder()
.setActions(ACTIONS)

View file

@ -52,7 +52,6 @@ class SearchAdapter(
is Album -> AlbumViewHolder.ITEM_TYPE
is Song -> SongViewHolder.ITEM_TYPE
is Header -> HeaderViewHolder.ITEM_TYPE
else -> -1
}
}
@ -77,7 +76,7 @@ class SearchAdapter(
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
else -> error("Invalid ViewHolder item type.")
else -> error("Invalid ViewHolder item type")
}
}

View file

@ -114,7 +114,6 @@ class SearchFragment : Fragment() {
if (!launchedKeyboard) {
// Auto-open the keyboard when this view is shown
requestFocus()
postDelayed(200) {
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}
@ -162,7 +161,7 @@ class SearchFragment : Fragment() {
imm.hide()
}
logD("Fragment created.")
logD("Fragment created")
return binding.root
}

View file

@ -33,6 +33,7 @@ import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.util.logD
import java.text.Normalizer
/**
@ -70,11 +71,14 @@ class SearchViewModel : ViewModel() {
mLastQuery = query
if (query.isEmpty() || musicStore == null) {
logD("No music/query, ignoring search")
mSearchResults.value = listOf()
return
}
// Searching can be quite expensive, so hop on a co-routine
logD("Performing search for $query")
// Searching can be quite expensive, so get on a co-routine
viewModelScope.launch {
val sort = Sort.ByName(true)
val results = mutableListOf<BaseModel>()
@ -127,6 +131,8 @@ class SearchViewModel : ViewModel() {
else -> null
}
logD("Updating filter mode to $mFilterMode")
settingsManager.searchFilterMode = mFilterMode
search(mLastQuery)

View file

@ -74,7 +74,7 @@ class AboutFragment : Fragment() {
)
}
logD("Dialog created.")
logD("Dialog created")
return binding.root
}
@ -83,6 +83,8 @@ class AboutFragment : Fragment() {
* Go through the process of opening a [link] in a browser.
*/
private fun openLinkInBrowser(link: String) {
logD("Opening $link")
val browserIntent = Intent(Intent.ACTION_VIEW, link.toUri()).setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
)

View file

@ -22,8 +22,7 @@ import android.content.SharedPreferences
import androidx.core.content.edit
import org.oxycblt.auxio.accent.Accent
// A couple of utils for migrating from old settings values to the new
// formats used in 1.3.2 & 1.4.0
// A couple of utils for migrating from old settings values to the new formats
fun handleAccentCompat(prefs: SharedPreferences): Accent {
if (prefs.contains(OldKeys.KEY_ACCENT2)) {

View file

@ -31,7 +31,7 @@ import androidx.preference.children
import androidx.recyclerview.widget.RecyclerView
import coil.Coil
import org.oxycblt.auxio.R
import org.oxycblt.auxio.accent.AccentDialog
import org.oxycblt.auxio.accent.AccentCustomizeDialog
import org.oxycblt.auxio.excluded.ExcludedDialog
import org.oxycblt.auxio.home.tabs.TabCustomizeDialog
import org.oxycblt.auxio.playback.PlaybackViewModel
@ -68,7 +68,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
}
}
logD("Fragment created.")
logD("Fragment created")
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@ -119,7 +119,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
SettingsManager.KEY_ACCENT -> {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
AccentDialog().show(childFragmentManager, AccentDialog.TAG)
AccentCustomizeDialog().show(childFragmentManager, AccentCustomizeDialog.TAG)
true
}
@ -182,7 +182,6 @@ class SettingsListFragment : PreferenceFragmentCompat() {
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -> R.drawable.ic_auto
AppCompatDelegate.MODE_NIGHT_NO -> R.drawable.ic_day
AppCompatDelegate.MODE_NIGHT_YES -> R.drawable.ic_night
else -> R.drawable.ic_auto
}
}

View file

@ -331,7 +331,7 @@ class SettingsManager private constructor(context: Context) :
return instance
}
error("SettingsManager must be initialized with init() before getting its instance.")
error("SettingsManager must be initialized with init() before getting its instance")
}
}
}

View file

@ -29,7 +29,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.updatePadding
import com.google.android.material.appbar.AppBarLayout
import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.logW
import org.oxycblt.auxio.util.systemBarInsetsCompat
/**
@ -51,7 +51,6 @@ open class EdgeAppBarLayout @JvmOverloads constructor(
if (child != null) {
val coordinator = parent as CoordinatorLayout
(layoutParams as CoordinatorLayout.LayoutParams).behavior?.onNestedPreScroll(
coordinator, this, coordinator, 0, 0, tConsumed, 0
)
@ -66,15 +65,12 @@ open class EdgeAppBarLayout @JvmOverloads constructor(
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
super.onApplyWindowInsets(insets)
updatePadding(top = insets.systemBarInsetsCompat.top)
return insets
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
viewTreeObserver.removeOnPreDrawListener(onPreDraw)
}
@ -94,9 +90,10 @@ open class EdgeAppBarLayout @JvmOverloads constructor(
if (liftOnScrollTargetViewId != ResourcesCompat.ID_NULL) {
scrollingChild = (parent as ViewGroup).findViewById(liftOnScrollTargetViewId)
} else {
logE("liftOnScrollTargetViewId was not specified. ignoring scroll events.")
logW("liftOnScrollTargetViewId was not specified. ignoring scroll events")
}
}
return scrollingChild
}
}

View file

@ -73,7 +73,7 @@ class MemberBinder<T : ViewDataBinding>(
val lifecycle = fragment.viewLifecycleOwner.lifecycle
check(lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
"Fragment views are destroyed."
"Fragment views are destroyed"
}
// Otherwise create the binding and return that.

View file

@ -39,7 +39,6 @@ import androidx.annotation.PluralsRes
import androidx.annotation.Px
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.MainActivity
import kotlin.reflect.KClass
import kotlin.system.exitProcess
@ -190,16 +189,9 @@ fun Context.pxOfDp(@Dimension dp: Float): Int {
}
private fun <T> Context.handleResourceFailure(e: Exception, what: String, default: T): T {
logE("$what load failed.")
if (BuildConfig.DEBUG) {
// I'd rather be aware of a sudden crash when debugging.
throw e
} else {
// Not so much when the app is in production.
logE(e.stackTraceToString())
return default
}
logE("$what load failed")
e.logTraceOrThrow()
return default
}
/**

View file

@ -34,7 +34,7 @@ fun <R> SQLiteDatabase.queryAll(tableName: String, block: (Cursor) -> R) =
*/
fun assertBackgroundThread() {
check(Looper.myLooper() != Looper.getMainLooper()) {
"This operation must be ran on a background thread."
"This operation must be ran on a background thread"
}
}

View file

@ -41,6 +41,13 @@ fun Any.logD(msg: String) {
}
}
/**
* Shortcut method for logging [msg] as a warning to the console. Handles anonymous objects
*/
fun Any.logW(msg: String) {
Log.w(getName(), msg)
}
/**
* Shortcut method for logging [msg] as an error to the console. Handles anonymous objects
*/
@ -48,6 +55,18 @@ fun Any.logE(msg: String) {
Log.e(getName(), msg)
}
/**
* Logs an error in production while still throwing it in debug mode. This is useful for
* non-showstopper bugs that I would still prefer to be caught in debug mode.
*/
fun Throwable.logTraceOrThrow() {
if (BuildConfig.DEBUG) {
throw this
} else {
logE(stackTraceToString())
}
}
/**
* Get a non-nullable name, used so that logs will always show up by Auxio
* @return The name of the object, otherwise "Anonymous Object"

View file

@ -69,6 +69,7 @@ fun RecyclerView.canScroll(): Boolean = computeVerticalScrollRange() > height
*/
fun View.disableDropShadowCompat() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
logD("Disabling drop shadows")
val transparent = context.getColorSafe(android.R.color.transparent)
outlineAmbientShadowColor = transparent
outlineSpotShadowColor = transparent

View file

@ -23,6 +23,7 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.logD
/**
* A wrapper around each [WidgetProvider] that plugs into the main Auxio process and updates the
@ -53,6 +54,8 @@ class WidgetController(private val context: Context) :
* Release this instance, removing the callbacks and resetting all widgets
*/
fun release() {
logD("Releasing instance")
widget.reset(context)
playbackManager.removeCallback(this)
settingsManager.removeCallback(this)

View file

@ -40,6 +40,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.util.getDimenSizeSafe
import org.oxycblt.auxio.util.isLandscape
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
import kotlin.math.min
/**
@ -87,6 +88,10 @@ class WidgetProvider : AppWidgetProvider() {
}
}
/**
* Custom function for loading bitmaps to the widget in a way that works with the
* widget ImageView instances.
*/
private fun loadWidgetBitmap(context: Context, song: Song, onDone: (Bitmap?) -> Unit) {
val coverRequest = ImageRequest.Builder(context)
.data(song.album)
@ -152,6 +157,8 @@ class WidgetProvider : AppWidgetProvider() {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
logD("Requesting new view from PlaybackService")
// We can't resize the widget until we can generate the views, so request an update
// from PlaybackService.
requestUpdate(context)
@ -234,7 +241,7 @@ class WidgetProvider : AppWidgetProvider() {
continue
} else {
// Default to the smallest view if no layout fits
logD("No widget layout found")
logW("No good widget layout found")
val minimum = requireNotNull(
views.minByOrNull { it.key.width * it.key.height }?.value

View file

@ -52,7 +52,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_small"
android:ellipsize="end"
android:text="@{@string/fmt_two(song.resolvedArtistName, song.resolvedAlbumName)}"
app:songInfo="@{song}"
app:layout_constraintBottom_toBottomOf="@+id/playback_cover"
app:layout_constraintEnd_toEndOf="@+id/playback_song"
app:layout_constraintStart_toEndOf="@+id/playback_cover"

View file

@ -50,7 +50,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_small"
android:ellipsize="end"
android:text="@{@string/fmt_two(song.resolvedArtistName, song.resolvedAlbumName)}"
app:songInfo="@{song}"
app:layout_constraintBottom_toBottomOf="@+id/playback_cover"
app:layout_constraintEnd_toEndOf="@+id/playback_song"
app:layout_constraintStart_toEndOf="@+id/playback_cover"

View file

@ -12,7 +12,7 @@
android:paddingTop="@dimen/spacing_medium"
android:paddingEnd="@dimen/spacing_medium"
android:paddingBottom="@dimen/spacing_small"
app:layoutManager="org.oxycblt.auxio.accent.AutoGridLayoutManager"
app:layoutManager="org.oxycblt.auxio.accent.AccentGridLayoutManager"
app:layout_constraintBottom_toTopOf="@+id/accent_cancel"
app:layout_constraintTop_toBottomOf="@+id/accent_header"
tools:itemCount="18"

View file

@ -41,7 +41,7 @@
style="@style/Widget.Auxio.TextView.Item.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{@string/fmt_two(album.resolvedArtistName, @plurals/fmt_song_count(album.songs.size, album.songs.size))}"
app:albumInfo="@{album}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_cover"

View file

@ -41,7 +41,7 @@
style="@style/Widget.Auxio.TextView.Item.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:artistCounts="@{artist}"
app:artistInfo="@{artist}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/artist_image"

View file

@ -41,7 +41,7 @@
style="@style/Widget.Auxio.TextView.Item.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{@plurals/fmt_song_count(genre.songs.size(), genre.songs.size())}"
app:genreInfo="@{genre}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/genre_image"

View file

@ -44,7 +44,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{@string/fmt_two(song.resolvedArtistName, song.resolvedAlbumName)}"
app:songInfo="@{song}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/song_duration"
app:layout_constraintStart_toEndOf="@+id/album_cover"

View file

@ -69,7 +69,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{@string/fmt_two(song.resolvedArtistName, song.resolvedAlbumName)}"
app:songInfo="@{song}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/song_drag_handle"
app:layout_constraintStart_toEndOf="@+id/album_cover"

View file

@ -42,7 +42,7 @@
style="@style/Widget.Auxio.TextView.Item.Secondary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{@string/fmt_two(song.resolvedArtistName, song.resolvedAlbumName)}"
app:songInfo="@{song}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_cover"

View file

@ -51,7 +51,7 @@
android:layout_marginStart="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small"
android:ellipsize="end"
android:text="@{@string/fmt_two(song.resolvedArtistName, song.resolvedAlbumName)}"
app:songInfo="@{song}"
app:layout_constraintBottom_toBottomOf="@+id/playback_cover"
app:layout_constraintEnd_toStartOf="@+id/playback_play_pause"
app:layout_constraintStart_toEndOf="@+id/playback_cover"