From 3aaa2ab0e07ab0b4c23ae4e88c27cd24a8b20f29 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Tue, 22 Feb 2022 17:08:57 -0700 Subject: [PATCH] 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. --- .../java/org/oxycblt/auxio/MainActivity.kt | 16 +-- .../java/org/oxycblt/auxio/MainFragment.kt | 5 +- .../java/org/oxycblt/auxio/accent/Accent.kt | 35 +----- .../org/oxycblt/auxio/accent/AccentAdapter.kt | 2 - ...centDialog.kt => AccentCustomizeDialog.kt} | 12 +- ...tManager.kt => AccentGridLayoutManager.kt} | 2 +- .../org/oxycblt/auxio/coil/AuxioFetcher.kt | 12 +- .../java/org/oxycblt/auxio/coil/Fetchers.kt | 1 - .../auxio/detail/AlbumDetailFragment.kt | 13 +- .../auxio/detail/ArtistDetailFragment.kt | 29 +++-- .../auxio/detail/DetailAppBarLayout.kt | 23 ++-- .../oxycblt/auxio/detail/DetailFragment.kt | 6 +- .../oxycblt/auxio/detail/DetailViewModel.kt | 17 +-- .../auxio/detail/GenreDetailFragment.kt | 38 +++--- .../detail/recycler/AlbumDetailAdapter.kt | 3 - .../detail/recycler/ArtistDetailAdapter.kt | 10 +- .../detail/recycler/GenreDetailAdapter.kt | 10 +- .../auxio/excluded/ExcludedDatabase.kt | 8 +- .../oxycblt/auxio/excluded/ExcludedDialog.kt | 9 +- .../auxio/excluded/ExcludedViewModel.kt | 11 +- .../home/AdaptiveFloatingActionButton.kt | 5 +- .../oxycblt/auxio/home/AdaptiveTabStrategy.kt | 14 ++- .../org/oxycblt/auxio/home/HomeFragment.kt | 23 ++-- .../org/oxycblt/auxio/home/HomeViewModel.kt | 4 +- .../java/org/oxycblt/auxio/home/tabs/Tab.kt | 2 +- .../auxio/home/tabs/TabCustomizeDialog.kt | 7 +- .../java/org/oxycblt/auxio/music/Models.kt | 9 +- .../org/oxycblt/auxio/music/MusicLoader.kt | 31 +++-- .../org/oxycblt/auxio/music/MusicStore.kt | 10 +- .../org/oxycblt/auxio/music/MusicUtils.kt | 52 +++++++- .../org/oxycblt/auxio/music/MusicViewModel.kt | 7 +- .../auxio/playback/PlaybackFragment.kt | 7 +- .../oxycblt/auxio/playback/PlaybackLayout.kt | 13 +- .../oxycblt/auxio/playback/PlaybackSeekBar.kt | 2 + .../auxio/playback/PlaybackViewModel.kt | 20 ++- .../auxio/playback/queue/QueueAdapter.kt | 8 +- .../auxio/playback/queue/QueueDragCallback.kt | 7 +- .../auxio/playback/queue/QueueFragment.kt | 5 +- .../playback/state/PlaybackStateDatabase.kt | 117 +++++++++--------- .../playback/state/PlaybackStateManager.kt | 29 ++--- .../auxio/playback/system/AudioReactor.kt | 10 +- .../playback/system/MediaButtonReceiver.kt | 2 + .../auxio/playback/system/PlaybackService.kt | 17 ++- .../system/PlaybackSessionConnector.kt | 3 + .../org/oxycblt/auxio/search/SearchAdapter.kt | 3 +- .../oxycblt/auxio/search/SearchFragment.kt | 3 +- .../oxycblt/auxio/search/SearchViewModel.kt | 8 +- .../oxycblt/auxio/settings/AboutFragment.kt | 4 +- .../oxycblt/auxio/settings/SettingsCompat.kt | 3 +- .../auxio/settings/SettingsListFragment.kt | 7 +- .../oxycblt/auxio/settings/SettingsManager.kt | 2 +- .../org/oxycblt/auxio/ui/EdgeAppBarLayout.kt | 9 +- .../java/org/oxycblt/auxio/ui/MemberBinder.kt | 2 +- .../org/oxycblt/auxio/util/ContextUtil.kt | 14 +-- .../java/org/oxycblt/auxio/util/DbUtil.kt | 2 +- .../java/org/oxycblt/auxio/util/LogUtil.kt | 19 +++ .../java/org/oxycblt/auxio/util/ViewUtil.kt | 1 + .../oxycblt/auxio/widgets/WidgetController.kt | 3 + .../oxycblt/auxio/widgets/WidgetProvider.kt | 9 +- .../res/layout-sw640dp/view_playback_bar.xml | 2 +- .../res/layout-w600dp/view_playback_bar.xml | 2 +- app/src/main/res/layout/dialog_accent.xml | 2 +- app/src/main/res/layout/item_album.xml | 2 +- app/src/main/res/layout/item_artist.xml | 2 +- app/src/main/res/layout/item_genre.xml | 2 +- app/src/main/res/layout/item_genre_song.xml | 2 +- app/src/main/res/layout/item_queue_song.xml | 2 +- app/src/main/res/layout/item_song.xml | 2 +- app/src/main/res/layout/view_playback_bar.xml | 2 +- 69 files changed, 436 insertions(+), 339 deletions(-) rename app/src/main/java/org/oxycblt/auxio/accent/{AccentDialog.kt => AccentCustomizeDialog.kt} (90%) rename app/src/main/java/org/oxycblt/auxio/accent/{AutoGridLayoutManager.kt => AccentGridLayoutManager.kt} (98%) diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt index 8cfc2b966..4c05f784d 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt @@ -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 { diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 0da9ef221..a19d45c0a 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt b/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt index ccec41a24..97205cb44 100644 --- a/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt @@ -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 - } - } } diff --git a/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt b/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt index f42ecb3c9..0eaa56242 100644 --- a/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/accent/AccentDialog.kt b/app/src/main/java/org/oxycblt/auxio/accent/AccentCustomizeDialog.kt similarity index 90% rename from app/src/main/java/org/oxycblt/auxio/accent/AccentDialog.kt rename to app/src/main/java/org/oxycblt/auxio/accent/AccentCustomizeDialog.kt index 3a6df6535..6c2321e5a 100644 --- a/app/src/main/java/org/oxycblt/auxio/accent/AccentDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/AccentCustomizeDialog.kt @@ -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() } diff --git a/app/src/main/java/org/oxycblt/auxio/accent/AutoGridLayoutManager.kt b/app/src/main/java/org/oxycblt/auxio/accent/AccentGridLayoutManager.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/accent/AutoGridLayoutManager.kt rename to app/src/main/java/org/oxycblt/auxio/accent/AccentGridLayoutManager.kt index 4d371927e..48ed253ee 100644 --- a/app/src/main/java/org/oxycblt/auxio/accent/AutoGridLayoutManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/AccentGridLayoutManager.kt @@ -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, diff --git a/app/src/main/java/org/oxycblt/auxio/coil/AuxioFetcher.kt b/app/src/main/java/org/oxycblt/auxio/coil/AuxioFetcher.kt index 910e4c43c..266227ae2 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/AuxioFetcher.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/AuxioFetcher.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt b/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt index 418083170..f3119c7b7 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt @@ -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) } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 689f8113d..18dcc05f3 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index eaa875e0b..60f112b4c 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt index 516eff0d6..6a073aff6 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt @@ -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(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() diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt index b085c946f..a29a934f3 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt @@ -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) diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index 3c1db8a1b..fed77ca96 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -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(curGenre.value!!) + logD("Refreshing genre data") + val genre = requireNotNull(curGenre.value) + val data = mutableListOf(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(artist) data.add( @@ -206,7 +207,9 @@ class DetailViewModel : ViewModel() { } private fun refreshAlbumData() { - val data = mutableListOf(curAlbum.value!!) + logD("Refreshing album data") + val album = requireNotNull(curAlbum.value) + val data = mutableListOf(album) data.add( ActionHeader( diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index 8bb6d9ac9..a14d4378e 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt index c48a069d3..f09acf501 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt @@ -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) } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt index de8c0a32c..614522ca7 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt @@ -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) diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt index 9f0e8366c..804b33561 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt @@ -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 { diff --git a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDatabase.kt b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDatabase.kt index a5d434f95..b3562d563 100644 --- a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDatabase.kt +++ b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDatabase.kt @@ -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() - 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" diff --git a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt index bb339023d..c5b9be463 100644 --- a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt @@ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedViewModel.kt b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedViewModel.kt index 504cb4f73..8de9ccc9b 100644 --- a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedViewModel.kt @@ -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" + ) } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt index 434820910..f6b405332 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveFloatingActionButton.kt @@ -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) } diff --git a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveTabStrategy.kt b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveTabStrategy.kt index d81da4d20..e76f5941a 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveTabStrategy.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveTabStrategy.kt @@ -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) + } } } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 7143169f9..b5b447ad4 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index 0765d3ee1..9089b23f4 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt index a4166a1ec..3f8a30686 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt @@ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt index 5dfa61a1f..93b478f7d 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt @@ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/music/Models.kt b/app/src/main/java/org/oxycblt/auxio/music/Models.kt index 8f35bf4ac..d4f7e296c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Models.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Models.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt index c39f267a5..286cb13b1 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt index 22addc747..7cc5782bc 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt @@ -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" } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt index cc3d87b49..0dc9c2d29 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt @@ -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) +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt index a71e76c19..b91adb12f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt @@ -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(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) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt index 92ae2cd93..b9864f665 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt index 027ccbf00..f5d984841 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt @@ -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) ) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt index f2e517cd0..274995005 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index a741adafe..1fcb41a35 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -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) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt index d61af2e03..6c6970662 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt @@ -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) { if (data != newData) { data = newData - listDiffer.submitList(newData) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt index 3b80f863e..3478f6a9e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt @@ -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) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt index cc9d2bb27..8765c4d98 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -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) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt index 5ed2a2b35..3450096f3 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt @@ -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 { + assertBackgroundThread() + + val queue = mutableListOf() + + 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 { - assertBackgroundThread() - - val queue = mutableListOf() - - 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, diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index fea3fd451..50ce3a643 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -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 @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt index d450a40c2..23b7c3a16 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt @@ -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 { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt index 0173fc7b5..8cde108b2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt @@ -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) } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 78cbb9a2f..d9842ffa9 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -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) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt index 336667cf3..0c211c683 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt @@ -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) diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt index 8104c55a0..b7ea8a1ec 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt @@ -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") } } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt index b39647326..f83e3b566 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt index 4f9604edc..6c84a9157 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -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() @@ -127,6 +131,8 @@ class SearchViewModel : ViewModel() { else -> null } + logD("Updating filter mode to $mFilterMode") + settingsManager.searchFilterMode = mFilterMode search(mLastQuery) diff --git a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt index 64cb1bf26..f24110e70 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt @@ -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 ) diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt index 7c678052e..8ee87c17e 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt @@ -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)) { diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt index e20a184fb..525d5654d 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt @@ -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 } } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt index c2e3c311b..8557cabc2 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt @@ -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") } } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt index 60f6ff74c..8ecaf814a 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt @@ -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 } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt b/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt index b460b7290..811f8d2e1 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt @@ -73,7 +73,7 @@ class MemberBinder( 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. diff --git a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt index 148b00110..77ca4c7b2 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt @@ -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 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 } /** diff --git a/app/src/main/java/org/oxycblt/auxio/util/DbUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/DbUtil.kt index 8895960a6..5d09b5096 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/DbUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/DbUtil.kt @@ -34,7 +34,7 @@ fun 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" } } diff --git a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt index fc5bdb105..500b65df9 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt @@ -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" diff --git a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt index 5ca64eacc..7f1c9773e 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetController.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetController.kt index c28a5b560..10bd5026c 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetController.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetController.kt @@ -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) diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt index dad9770de..96dbf54ed 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt @@ -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 diff --git a/app/src/main/res/layout-sw640dp/view_playback_bar.xml b/app/src/main/res/layout-sw640dp/view_playback_bar.xml index d2238a9fd..19356f679 100644 --- a/app/src/main/res/layout-sw640dp/view_playback_bar.xml +++ b/app/src/main/res/layout-sw640dp/view_playback_bar.xml @@ -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" diff --git a/app/src/main/res/layout-w600dp/view_playback_bar.xml b/app/src/main/res/layout-w600dp/view_playback_bar.xml index 94a1484f3..63dbed1bc 100644 --- a/app/src/main/res/layout-w600dp/view_playback_bar.xml +++ b/app/src/main/res/layout-w600dp/view_playback_bar.xml @@ -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" diff --git a/app/src/main/res/layout/dialog_accent.xml b/app/src/main/res/layout/dialog_accent.xml index 31af1a5a7..68c6f1609 100644 --- a/app/src/main/res/layout/dialog_accent.xml +++ b/app/src/main/res/layout/dialog_accent.xml @@ -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" diff --git a/app/src/main/res/layout/item_album.xml b/app/src/main/res/layout/item_album.xml index 898936cac..66eb8006f 100644 --- a/app/src/main/res/layout/item_album.xml +++ b/app/src/main/res/layout/item_album.xml @@ -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" diff --git a/app/src/main/res/layout/item_artist.xml b/app/src/main/res/layout/item_artist.xml index c4e4c138b..81ce94239 100644 --- a/app/src/main/res/layout/item_artist.xml +++ b/app/src/main/res/layout/item_artist.xml @@ -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" diff --git a/app/src/main/res/layout/item_genre.xml b/app/src/main/res/layout/item_genre.xml index 8fb90cb02..5d2109065 100644 --- a/app/src/main/res/layout/item_genre.xml +++ b/app/src/main/res/layout/item_genre.xml @@ -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" diff --git a/app/src/main/res/layout/item_genre_song.xml b/app/src/main/res/layout/item_genre_song.xml index 7d6278c9d..4070e6393 100644 --- a/app/src/main/res/layout/item_genre_song.xml +++ b/app/src/main/res/layout/item_genre_song.xml @@ -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" diff --git a/app/src/main/res/layout/item_queue_song.xml b/app/src/main/res/layout/item_queue_song.xml index cf9580e97..2f12c6da8 100644 --- a/app/src/main/res/layout/item_queue_song.xml +++ b/app/src/main/res/layout/item_queue_song.xml @@ -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" diff --git a/app/src/main/res/layout/item_song.xml b/app/src/main/res/layout/item_song.xml index 1082b050f..3e4313525 100644 --- a/app/src/main/res/layout/item_song.xml +++ b/app/src/main/res/layout/item_song.xml @@ -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" diff --git a/app/src/main/res/layout/view_playback_bar.xml b/app/src/main/res/layout/view_playback_bar.xml index d9034843e..e05fb7eab 100644 --- a/app/src/main/res/layout/view_playback_bar.xml +++ b/app/src/main/res/layout/view_playback_bar.xml @@ -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"