From d79592e0299a3199a6b7884ba26957baa0fe38a2 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Wed, 23 Mar 2022 10:53:47 -0600 Subject: [PATCH] all: remove databinding Remove databinding entirely. Databinding was a terrible idea for Auxio. I rarely leveraged it, and when I did, it produced messy code and bloated build times. Dumpster it for just viewbinding, which is good. This reduces building times by nearly 2/3, and generally makes the codebase more coherent and usable. --- app/build.gradle | 2 +- .../java/org/oxycblt/auxio/MainActivity.kt | 25 +- .../java/org/oxycblt/auxio/coil/CoilUtils.kt | 14 +- .../detail/recycler/AlbumDetailAdapter.kt | 35 +- .../detail/recycler/ArtistDetailAdapter.kt | 43 ++- .../detail/recycler/GenreDetailAdapter.kt | 36 +- .../home/fastscroll/FastScrollRecyclerView.kt | 2 + .../org/oxycblt/auxio/music/MusicUtils.kt | 55 --- .../music/excluded/ExcludedEntryAdapter.kt | 4 +- .../auxio/playback/PlaybackBarFragment.kt | 11 +- .../auxio/playback/PlaybackPanelFragment.kt | 17 +- .../auxio/playback/PlaybackViewModel.kt | 1 + .../auxio/playback/queue/QueueAdapter.kt | 6 +- .../oxycblt/auxio/settings/AboutFragment.kt | 5 +- .../org/oxycblt/auxio/ui/BottomSheetLayout.kt | 4 +- .../java/org/oxycblt/auxio/ui/ViewHolders.kt | 70 ++-- .../java/org/oxycblt/auxio/util/ViewUtil.kt | 78 +++-- .../main/res/layout-h600dp/item_detail.xml | 11 +- .../layout-land/fragment_playback_panel.xml | 330 +++++++++--------- app/src/main/res/layout-land/item_detail.xml | 11 +- .../fragment_playback_panel.xml | 20 +- .../fragment_playback_panel.xml | 22 +- .../main/res/layout-sw600dp/item_detail.xml | 14 +- .../layout-sw640dp/fragment_playback_bar.xml | 12 +- .../main/res/layout-sw840dp/item_detail.xml | 11 +- .../fragment_playback_panel.xml | 22 +- .../layout-w600dp/fragment_playback_bar.xml | 20 +- app/src/main/res/layout/activity_main.xml | 20 +- app/src/main/res/layout/dialog_accent.xml | 34 +- app/src/main/res/layout/dialog_excluded.xml | 54 ++- app/src/main/res/layout/dialog_tabs.xml | 28 +- app/src/main/res/layout/fragment_about.xml | 307 ++++++++-------- app/src/main/res/layout/fragment_detail.xml | 54 ++- app/src/main/res/layout/fragment_home.xml | 10 +- .../main/res/layout/fragment_home_list.xml | 8 +- app/src/main/res/layout/fragment_main.xml | 17 +- .../main/res/layout/fragment_playback_bar.xml | 20 +- .../res/layout/fragment_playback_panel.xml | 20 +- app/src/main/res/layout/fragment_queue.xml | 22 +- app/src/main/res/layout/fragment_search.xml | 9 +- app/src/main/res/layout/fragment_settings.xml | 8 +- app/src/main/res/layout/item_accent.xml | 38 +- .../main/res/layout/item_action_header.xml | 32 +- app/src/main/res/layout/item_album.xml | 51 --- app/src/main/res/layout/item_album_song.xml | 44 +-- app/src/main/res/layout/item_artist.xml | 45 +-- app/src/main/res/layout/item_artist_album.xml | 52 --- app/src/main/res/layout/item_artist_song.xml | 69 ---- app/src/main/res/layout/item_detail.xml | 10 +- app/src/main/res/layout/item_excluded_dir.xml | 10 +- app/src/main/res/layout/item_genre.xml | 52 --- app/src/main/res/layout/item_genre_song.xml | 69 ---- app/src/main/res/layout/item_header.xml | 20 +- app/src/main/res/layout/item_parent.xml | 38 ++ app/src/main/res/layout/item_queue_song.xml | 30 +- app/src/main/res/layout/item_song.xml | 77 ++-- app/src/main/res/layout/item_tab.xml | 12 +- app/src/main/res/layout/view_seek_bar.xml | 11 +- app/src/main/res/values/dimens.xml | 1 - app/src/main/res/values/donottranslate.xml | 2 +- info/ARCHITECTURE.md | 13 +- 61 files changed, 861 insertions(+), 1307 deletions(-) delete mode 100644 app/src/main/res/layout/item_album.xml delete mode 100644 app/src/main/res/layout/item_artist_album.xml delete mode 100644 app/src/main/res/layout/item_artist_song.xml delete mode 100644 app/src/main/res/layout/item_genre.xml delete mode 100644 app/src/main/res/layout/item_genre_song.xml create mode 100644 app/src/main/res/layout/item_parent.xml diff --git a/app/build.gradle b/app/build.gradle index b76c5a506..276e0eb9c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,7 +17,7 @@ android { targetSdkVersion 32 buildFeatures { - dataBinding true + viewBinding true } } diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt index b75e62ede..95ab4f9d4 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt @@ -26,8 +26,6 @@ import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.core.view.updatePadding -import androidx.databinding.DataBindingUtil -import androidx.viewbinding.ViewBinding import org.oxycblt.auxio.databinding.ActivityMainBinding import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.system.PlaybackService @@ -54,10 +52,9 @@ class MainActivity : AppCompatActivity() { setupTheme() - val binding = - DataBindingUtil.setContentView(this, R.layout.activity_main) - - applyEdgeToEdgeWindow(binding) + val binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + applyEdgeToEdgeWindow(binding.root) logD("Activity created") } @@ -114,7 +111,7 @@ class MainActivity : AppCompatActivity() { } } - private fun applyEdgeToEdgeWindow(binding: ViewBinding) { + private fun applyEdgeToEdgeWindow(contentView: View) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { logD("Doing R+ edge-to-edge") @@ -124,7 +121,7 @@ class MainActivity : AppCompatActivity() { // the R+ SDK decides to make you specify the insets yourself with a barely // documented API that isn't even mentioned in any of the edge-to-edge // tutorials. Thanks android, very cool! - binding.root.setOnApplyWindowInsetsListener { _, insets -> + contentView.setOnApplyWindowInsetsListener { view, insets -> WindowInsets.Builder() .setInsets( WindowInsets.Type.systemBars(), @@ -133,27 +130,25 @@ class MainActivity : AppCompatActivity() { WindowInsets.Type.systemGestures(), insets.getInsetsIgnoringVisibility(WindowInsets.Type.systemGestures())) .build() - .applyLeftRightInsets(binding) + .applyLeftRightInsets(view) } } else { // Do old edge-to-edge otherwise. logD("Doing legacy edge-to-edge") @Suppress("DEPRECATION") - binding.root.apply { + contentView.apply { systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE - setOnApplyWindowInsetsListener { _, insets -> insets.applyLeftRightInsets(binding) } + setOnApplyWindowInsetsListener { view, insets -> insets.applyLeftRightInsets(view) } } } } - private fun WindowInsets.applyLeftRightInsets(binding: ViewBinding): WindowInsets { + private fun WindowInsets.applyLeftRightInsets(contentView: View): WindowInsets { val bars = systemBarInsetsCompat - - binding.root.updatePadding(left = bars.left, right = bars.right) - + contentView.updatePadding(left = bars.left, right = bars.right) return replaceSystemBarInsetsCompat(0, bars.top, 0, bars.bottom) } diff --git a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt index b995a994a..d2a502659 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt @@ -23,7 +23,6 @@ import android.widget.ImageView import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.graphics.drawable.toBitmap -import androidx.databinding.BindingAdapter import coil.dispose import coil.imageLoader import coil.load @@ -39,28 +38,25 @@ import org.oxycblt.auxio.music.Song // --- BINDING ADAPTERS --- /** Bind the album cover for a [song]. */ -@BindingAdapter("albumArt") -fun ImageView.bindAlbumCover(song: Song?) = +fun ImageView.applyAlbumCover(song: Song?) = load(song, R.drawable.ic_album, R.string.desc_album_cover) /** Bind the album cover for an [album]. */ -@BindingAdapter("albumArt") -fun ImageView.bindAlbumCover(album: Album?) = +fun ImageView.applyAlbumCover(album: Album?) = load(album, R.drawable.ic_album, R.string.desc_album_cover) /** Bind the image for an [artist] */ -@BindingAdapter("artistImage") -fun ImageView.bindArtistImage(artist: Artist?) = +fun ImageView.applyArtistImage(artist: Artist?) = load(artist, R.drawable.ic_artist, R.string.desc_artist_image) /** Bind the image for a [genre] */ -@BindingAdapter("genreImage") -fun ImageView.bindGenreImage(genre: Genre?) = +fun ImageView.applyGenreImage(genre: Genre?) = load(genre, R.drawable.ic_genre, R.string.desc_genre_image) fun ImageView.load(music: T?, @DrawableRes error: Int, @StringRes desc: Int) { contentDescription = context.getString(desc, music?.resolvedName) dispose() + scaleType = ImageView.ScaleType.FIT_CENTER load(music) { error(error) transformations(SquareFrameTransform.INSTANCE) 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 1facb2423..a613799d1 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 @@ -24,7 +24,7 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R -import org.oxycblt.auxio.coil.bindAlbumCover +import org.oxycblt.auxio.coil.applyAlbumCover import org.oxycblt.auxio.databinding.ItemAlbumSongBinding import org.oxycblt.auxio.databinding.ItemDetailBinding import org.oxycblt.auxio.detail.DetailViewModel @@ -32,12 +32,14 @@ import org.oxycblt.auxio.music.ActionHeader import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Item import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.toDuration 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 +import org.oxycblt.auxio.util.textSafe /** * An adapter for displaying the details and [Song]s of an [Album] @@ -127,14 +129,14 @@ class AlbumDetailAdapter( override fun onBind(data: Album) { binding.detailCover.apply { - bindAlbumCover(data) + applyAlbumCover(data) contentDescription = context.getString(R.string.desc_album_cover, data.resolvedName) } - binding.detailName.text = data.resolvedName + binding.detailName.textSafe = data.resolvedName binding.detailSubhead.apply { - text = data.artist.resolvedName + textSafe = data.artist.resolvedName setOnClickListener { detailModel.navToItem(data.artist) } } @@ -157,14 +159,25 @@ class AlbumDetailAdapter( private val binding: ItemAlbumSongBinding, ) : BaseViewHolder(binding, doOnClick, doOnLongClick), Highlightable { override fun onBind(data: Song) { - binding.song = data - binding.songName.requestLayout() + // Hide the track number view if the song does not have a track. + if (data.track != null) { + binding.songTrack.apply { + textSafe = context.getString(R.string.fmt_number, data.track) + isInvisible = false + } - // Hide the track number view if the track is zero, as generally a track number of - // zero implies that the song does not have a track number. - val usePlaceholder = data.track == null - binding.songTrack.isInvisible = usePlaceholder - binding.songTrackPlaceholder.isInvisible = !usePlaceholder + binding.songTrackPlaceholder.isInvisible = true + } else { + binding.songTrack.apply { + textSafe = "" + isInvisible = true + } + + binding.songTrackPlaceholder.isInvisible = false + } + + binding.songName.textSafe = data.resolvedName + binding.songDuration.textSafe = data.seconds.toDuration(false) } override fun setHighlighted(isHighlighted: Boolean) { 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 556f8657c..9c67ec5a6 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 @@ -23,23 +23,26 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R -import org.oxycblt.auxio.coil.bindArtistImage -import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding -import org.oxycblt.auxio.databinding.ItemArtistSongBinding +import org.oxycblt.auxio.coil.applyAlbumCover +import org.oxycblt.auxio.coil.applyArtistImage import org.oxycblt.auxio.databinding.ItemDetailBinding +import org.oxycblt.auxio.databinding.ItemParentBinding +import org.oxycblt.auxio.databinding.ItemSongBinding import org.oxycblt.auxio.music.ActionHeader import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.Item 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.context +import org.oxycblt.auxio.util.getPluralSafe import org.oxycblt.auxio.util.inflater +import org.oxycblt.auxio.util.textSafe /** * An adapter for displaying the [Album]s and [Song]s of an artist. @@ -73,9 +76,9 @@ class ArtistDetailAdapter( IntegerTable.ITEM_TYPE_ARTIST_DETAIL -> ArtistDetailViewHolder(ItemDetailBinding.inflate(parent.context.inflater)) IntegerTable.ITEM_TYPE_ARTIST_ALBUM -> - ArtistAlbumViewHolder(ItemArtistAlbumBinding.inflate(parent.context.inflater)) + ArtistAlbumViewHolder(ItemParentBinding.inflate(parent.context.inflater)) IntegerTable.ITEM_TYPE_ARTIST_SONG -> - ArtistSongViewHolder(ItemArtistSongBinding.inflate(parent.context.inflater)) + ArtistSongViewHolder(ItemSongBinding.inflate(parent.context.inflater)) IntegerTable.ITEM_TYPE_HEADER -> HeaderViewHolder.from(parent.context) IntegerTable.ITEM_TYPE_ACTION_HEADER -> ActionHeaderViewHolder.from(parent.context) else -> error("Invalid ViewHolder item type $viewType") @@ -173,16 +176,16 @@ class ArtistDetailAdapter( val context = binding.root.context binding.detailCover.apply { - bindArtistImage(data) + applyArtistImage(data) contentDescription = context.getString(R.string.desc_artist_image, data.resolvedName) } - binding.detailName.text = data.resolvedName + binding.detailName.textSafe = data.resolvedName // Get the genre that corresponds to the most songs in this artist, which would be // the most "Prominent" genre. - binding.detailSubhead.text = + binding.detailSubhead.textSafe = data.songs .groupBy { it.genre.resolvedName } .entries @@ -190,7 +193,11 @@ class ArtistDetailAdapter( ?.key ?: context.getString(R.string.def_genre) - binding.detailInfo.bindArtistInfo(data) + binding.detailInfo.textSafe = + binding.context.getString( + R.string.fmt_two, + binding.context.getPluralSafe(R.plurals.fmt_album_count, data.albums.size), + binding.context.getPluralSafe(R.plurals.fmt_song_count, data.songs.size)) binding.detailPlayButton.setOnClickListener { playbackModel.playArtist(data, false) } @@ -199,24 +206,26 @@ class ArtistDetailAdapter( } inner class ArtistAlbumViewHolder( - private val binding: ItemArtistAlbumBinding, + private val binding: ItemParentBinding, ) : BaseViewHolder(binding, doOnClick, doOnLongClick), Highlightable { override fun onBind(data: Album) { - binding.album = data - binding.albumName.requestLayout() + binding.parentImage.applyAlbumCover(data) + binding.parentName.textSafe = data.resolvedName + binding.parentInfo.textSafe = binding.context.getString(R.string.fmt_number, data.year) } override fun setHighlighted(isHighlighted: Boolean) { - binding.albumName.isActivated = isHighlighted + binding.parentName.isActivated = isHighlighted } } inner class ArtistSongViewHolder( - private val binding: ItemArtistSongBinding, + private val binding: ItemSongBinding, ) : BaseViewHolder(binding, doOnSongClick, doOnLongClick), Highlightable { override fun onBind(data: Song) { - binding.song = data - binding.songName.requestLayout() + binding.songAlbumCover.applyAlbumCover(data) + binding.songName.textSafe = data.resolvedName + binding.songInfo.textSafe = data.resolvedAlbumName } override fun setHighlighted(isHighlighted: Boolean) { 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 f8932023b..73d912b79 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 @@ -23,19 +23,22 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R -import org.oxycblt.auxio.coil.bindGenreImage +import org.oxycblt.auxio.coil.applyAlbumCover +import org.oxycblt.auxio.coil.applyGenreImage import org.oxycblt.auxio.databinding.ItemDetailBinding -import org.oxycblt.auxio.databinding.ItemGenreSongBinding +import org.oxycblt.auxio.databinding.ItemSongBinding import org.oxycblt.auxio.music.ActionHeader import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Item 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.context +import org.oxycblt.auxio.util.getPluralSafe import org.oxycblt.auxio.util.inflater +import org.oxycblt.auxio.util.textSafe /** * An adapter for displaying the [Song]s of a genre. @@ -64,9 +67,7 @@ class GenreDetailAdapter( GenreDetailViewHolder(ItemDetailBinding.inflate(parent.context.inflater)) IntegerTable.ITEM_TYPE_ACTION_HEADER -> ActionHeaderViewHolder.from(parent.context) IntegerTable.ITEM_TYPE_GENRE_SONG -> - GenreSongViewHolder( - ItemGenreSongBinding.inflate(parent.context.inflater), - ) + GenreSongViewHolder(ItemSongBinding.inflate(parent.context.inflater)) else -> error("Bad ViewHolder item type $viewType") } } @@ -127,13 +128,14 @@ class GenreDetailAdapter( val context = binding.root.context binding.detailCover.apply { - bindGenreImage(data) + applyGenreImage(data) contentDescription = context.getString(R.string.desc_genre_image, data.resolvedName) } - binding.detailName.text = data.resolvedName - binding.detailSubhead.bindGenreInfo(data) - binding.detailInfo.text = data.totalDuration + binding.detailName.textSafe = data.resolvedName + binding.detailSubhead.textSafe = + binding.context.getPluralSafe(R.plurals.fmt_song_count, data.songs.size) + binding.detailInfo.textSafe = data.totalDuration binding.detailPlayButton.setOnClickListener { playbackModel.playGenre(data, false) } @@ -141,14 +143,16 @@ class GenreDetailAdapter( } } - inner class GenreSongViewHolder( - private val binding: ItemGenreSongBinding, + /** The Shared ViewHolder for a [Song]. Instantiation should be done with [from]. */ + inner class GenreSongViewHolder + constructor( + private val binding: ItemSongBinding, ) : BaseViewHolder(binding, doOnClick, doOnLongClick), Highlightable { - override fun onBind(data: Song) { - binding.song = data - binding.songName.requestLayout() - binding.songInfo.requestLayout() + override fun onBind(data: Song) { + binding.songAlbumCover.applyAlbumCover(data) + binding.songName.textSafe = data.resolvedName + binding.songInfo.textSafe = data.resolvedArtistName } override fun setHighlighted(isHighlighted: Boolean) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt index a9f63fc9a..69110800a 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt @@ -186,6 +186,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr // --- RECYCLERVIEW EVENT MANAGEMENT --- private fun onPreDraw() { + // FIXME: Make the way we lay out views less of a hacky mess. Perhaps consider + // overlaying views or turning this into a ViewGroup. updateScrollbarState() thumbView.layoutDirection = layoutDirection 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 ac7631505..0be39f9d8 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt @@ -18,12 +18,7 @@ package org.oxycblt.auxio.music import android.text.format.DateUtils -import android.widget.TextView -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 // --- EXTENSION FUNCTIONS --- @@ -47,53 +42,3 @@ fun Long.toDuration(isElapsed: Boolean): String { return durationString } - -// --- BINDING ADAPTERS --- - -@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_two, - 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/excluded/ExcludedEntryAdapter.kt b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedEntryAdapter.kt index 66ea7bfff..7da9ff4f8 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedEntryAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedEntryAdapter.kt @@ -22,6 +22,7 @@ import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.ItemExcludedDirBinding import org.oxycblt.auxio.util.inflater +import org.oxycblt.auxio.util.textSafe /** * Adapter that shows the excluded directories and their "Clear" button. @@ -56,8 +57,7 @@ class ExcludedEntryAdapter(private val onClear: (String) -> Unit) : } fun bind(path: String) { - binding.excludedPath.text = path - binding.excludedPath.requestLayout() + binding.excludedPath.textSafe = path binding.excludedClear.setOnClickListener { onClear(path) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt index b609887fd..069df0c67 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt @@ -25,14 +25,14 @@ import androidx.core.view.updatePadding import androidx.fragment.app.activityViewModels import com.google.android.material.color.MaterialColors import org.oxycblt.auxio.R -import org.oxycblt.auxio.coil.bindAlbumCover +import org.oxycblt.auxio.coil.applyAlbumCover import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding import org.oxycblt.auxio.detail.DetailViewModel -import org.oxycblt.auxio.music.bindSongInfo import org.oxycblt.auxio.ui.BottomSheetLayout import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.util.getAttrColorSafe import org.oxycblt.auxio.util.systemBarInsetsCompat +import org.oxycblt.auxio.util.textSafe class PlaybackBarFragment : ViewBindingFragment() { private val playbackModel: PlaybackViewModel by activityViewModels() @@ -99,9 +99,10 @@ class PlaybackBarFragment : ViewBindingFragment() { playbackModel.song.observe(viewLifecycleOwner) { song -> if (song != null) { - binding.playbackCover.bindAlbumCover(song) - binding.playbackSong.text = song.resolvedName - binding.playbackInfo.bindSongInfo(song) + binding.playbackCover.applyAlbumCover(song) + binding.playbackSong.textSafe = song.resolvedName + binding.playbackInfo.textSafe = + getString(R.string.fmt_two, song.resolvedArtistName, song.resolvedAlbumName) binding.playbackProgressBar.max = song.seconds.toInt() } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt index 3d801db14..1d0b831ce 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -29,7 +29,7 @@ import com.google.android.material.slider.Slider import kotlin.math.max import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.R -import org.oxycblt.auxio.coil.bindAlbumCover +import org.oxycblt.auxio.coil.applyAlbumCover import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.music.MusicParent @@ -42,6 +42,7 @@ import org.oxycblt.auxio.util.getAttrColorSafe import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.stateList import org.oxycblt.auxio.util.systemBarInsetsCompat +import org.oxycblt.auxio.util.textSafe /** * A [Fragment] that displays more information about the song, along with more media controls. @@ -166,7 +167,7 @@ class PlaybackPanelFragment : override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) { if (fromUser) { - requireBinding().playbackPosition.text = value.toLong().toDuration(true) + requireBinding().playbackPosition.textSafe = value.toLong().toDuration(true) } } @@ -174,14 +175,14 @@ class PlaybackPanelFragment : if (song == null) return val binding = requireBinding() - binding.playbackCover.bindAlbumCover(song) - binding.playbackSong.text = song.resolvedName - binding.playbackArtist.text = song.resolvedArtistName - binding.playbackAlbum.text = song.resolvedAlbumName + binding.playbackCover.applyAlbumCover(song) + binding.playbackSong.textSafe = song.resolvedName + binding.playbackArtist.textSafe = song.resolvedArtistName + binding.playbackAlbum.textSafe = song.resolvedAlbumName // Normally if a song had a duration val seconds = song.seconds - binding.playbackDuration.text = seconds.toDuration(false) + binding.playbackDuration.textSafe = seconds.toDuration(false) binding.playbackSeekBar.apply { valueTo = max(seconds, 1L).toFloat() isEnabled = seconds > 0L @@ -199,7 +200,7 @@ class PlaybackPanelFragment : val binding = requireBinding() if (!binding.playbackPosition.isActivated) { binding.playbackSeekBar.value = position.toFloat() - binding.playbackPosition.text = position.toDuration(true) + binding.playbackPosition.textSafe = position.toDuration(true) } } 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 0bd228e70..3781621fc 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -65,6 +65,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { private val mMode = MutableLiveData(PlaybackMode.ALL_SONGS) // Other + // TODO: Move URI management to PlaybackService (more capable of taking commands) private var mIntentUri: Uri? = null /** The current song. */ 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 19703d5b9..6edbe040d 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 @@ -28,6 +28,7 @@ import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import com.google.android.material.shape.MaterialShapeDrawable import org.oxycblt.auxio.IntegerTable +import org.oxycblt.auxio.coil.applyAlbumCover import org.oxycblt.auxio.databinding.ItemQueueSongBinding import org.oxycblt.auxio.music.ActionHeader import org.oxycblt.auxio.music.Header @@ -41,6 +42,7 @@ import org.oxycblt.auxio.util.disableDropShadowCompat import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.stateList +import org.oxycblt.auxio.util.textSafe /** * The single adapter for both the Next Queue and the User Queue. @@ -125,7 +127,9 @@ class QueueAdapter(private val touchHelper: ItemTouchHelper) : @SuppressLint("ClickableViewAccessibility") override fun onBind(data: Song) { - binding.song = data + binding.songAlbumCover.applyAlbumCover(data) + binding.songName.textSafe = data.resolvedName + binding.songInfo.textSafe = data.resolvedArtistName binding.background.isInvisible = true 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 f4d43a158..b6a961823 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt @@ -36,6 +36,7 @@ import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.systemBarInsetsCompat +import org.oxycblt.auxio.util.textSafe /** * A [BottomSheetDialogFragment] that shows Auxio's about screen. @@ -54,13 +55,13 @@ class AboutFragment : ViewBindingFragment() { binding.aboutToolbar.setNavigationOnClickListener { findNavController().navigateUp() } - binding.aboutVersion.text = BuildConfig.VERSION_NAME + binding.aboutVersion.textSafe = BuildConfig.VERSION_NAME binding.aboutCode.setOnClickListener { openLinkInBrowser(LINK_CODEBASE) } binding.aboutFaq.setOnClickListener { openLinkInBrowser(LINK_FAQ) } binding.aboutLicenses.setOnClickListener { openLinkInBrowser(LINK_LICENSES) } homeModel.songs.observe(viewLifecycleOwner) { songs -> - binding.aboutSongCount.text = getString(R.string.fmt_songs_loaded, songs.size) + binding.aboutSongCount.textSafe = getString(R.string.fmt_songs_loaded, songs.size) } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt index c47fcd89b..43adea87a 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt @@ -249,11 +249,11 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : barView = getChildAt(1) // Child 2 is assumed to be the bar used when collapsed panelView = getChildAt(2) // Child 3 is assumed to be the panel used when expanded + // We actually move the bar and panel views into a container so that they have consistent + // behavior when be manipulate layouts later. removeView(barView) removeView(panelView) - // We actually move the bar and panel views into a container so that they have consistent - // behavior when be manipulate layouts later. containerView.apply { addView( barView, diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ViewHolders.kt b/app/src/main/java/org/oxycblt/auxio/ui/ViewHolders.kt index 02caff360..9c2ade04c 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/ViewHolders.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/ViewHolders.kt @@ -20,13 +20,15 @@ package org.oxycblt.auxio.ui import android.content.Context import android.view.View import androidx.appcompat.widget.TooltipCompat -import androidx.databinding.ViewDataBinding import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding +import org.oxycblt.auxio.R +import org.oxycblt.auxio.coil.applyAlbumCover +import org.oxycblt.auxio.coil.applyArtistImage +import org.oxycblt.auxio.coil.applyGenreImage import org.oxycblt.auxio.databinding.ItemActionHeaderBinding -import org.oxycblt.auxio.databinding.ItemAlbumBinding -import org.oxycblt.auxio.databinding.ItemArtistBinding -import org.oxycblt.auxio.databinding.ItemGenreBinding import org.oxycblt.auxio.databinding.ItemHeaderBinding +import org.oxycblt.auxio.databinding.ItemParentBinding import org.oxycblt.auxio.databinding.ItemSongBinding import org.oxycblt.auxio.music.ActionHeader import org.oxycblt.auxio.music.Album @@ -35,7 +37,10 @@ import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.Item import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.util.context +import org.oxycblt.auxio.util.getPluralSafe import org.oxycblt.auxio.util.inflater +import org.oxycblt.auxio.util.textSafe /** * A [RecyclerView.ViewHolder] that streamlines a lot of the common things across all viewholders. @@ -46,7 +51,7 @@ import org.oxycblt.auxio.util.inflater * @author OxygenCobalt */ abstract class BaseViewHolder( - private val binding: ViewDataBinding, + private val binding: ViewBinding, private val doOnClick: ((data: T) -> Unit)? = null, private val doOnLongClick: ((view: View, data: T) -> Unit)? = null ) : RecyclerView.ViewHolder(binding.root) { @@ -73,8 +78,6 @@ abstract class BaseViewHolder( } onBind(data) - - binding.executePendingBindings() } /** @@ -93,10 +96,9 @@ private constructor( ) : BaseViewHolder(binding, doOnClick, doOnLongClick) { override fun onBind(data: Song) { - binding.song = data - - binding.songName.requestLayout() - binding.songInfo.requestLayout() + binding.songAlbumCover.applyAlbumCover(data) + binding.songName.textSafe = data.resolvedName + binding.songInfo.textSafe = data.resolvedArtistName } companion object { @@ -115,14 +117,19 @@ private constructor( /** The Shared ViewHolder for a [Album]. Instantiation should be done with [from]. */ class AlbumViewHolder private constructor( - private val binding: ItemAlbumBinding, + private val binding: ItemParentBinding, doOnClick: (data: Album) -> Unit, doOnLongClick: (view: View, data: Album) -> Unit ) : BaseViewHolder(binding, doOnClick, doOnLongClick) { override fun onBind(data: Album) { - binding.album = data - binding.albumName.requestLayout() + binding.parentImage.applyAlbumCover(data) + binding.parentName.textSafe = data.resolvedName + binding.parentInfo.textSafe = + binding.context.getString( + R.string.fmt_two, + data.resolvedArtistName, + binding.context.getPluralSafe(R.plurals.fmt_song_count, data.songs.size)) } companion object { @@ -133,7 +140,7 @@ private constructor( doOnLongClick: (view: View, data: Album) -> Unit ): AlbumViewHolder { return AlbumViewHolder( - ItemAlbumBinding.inflate(context.inflater), doOnClick, doOnLongClick) + ItemParentBinding.inflate(context.inflater), doOnClick, doOnLongClick) } } } @@ -141,14 +148,19 @@ private constructor( /** The Shared ViewHolder for a [Artist]. Instantiation should be done with [from]. */ class ArtistViewHolder private constructor( - private val binding: ItemArtistBinding, + private val binding: ItemParentBinding, doOnClick: (Artist) -> Unit, doOnLongClick: (view: View, data: Artist) -> Unit ) : BaseViewHolder(binding, doOnClick, doOnLongClick) { override fun onBind(data: Artist) { - binding.artist = data - binding.artistName.requestLayout() + binding.parentImage.applyArtistImage(data) + binding.parentName.textSafe = data.resolvedName + binding.parentInfo.textSafe = + binding.context.getString( + R.string.fmt_two, + binding.context.getPluralSafe(R.plurals.fmt_album_count, data.albums.size), + binding.context.getPluralSafe(R.plurals.fmt_song_count, data.songs.size)) } companion object { @@ -159,7 +171,7 @@ private constructor( doOnLongClick: (view: View, data: Artist) -> Unit ): ArtistViewHolder { return ArtistViewHolder( - ItemArtistBinding.inflate(context.inflater), doOnClick, doOnLongClick) + ItemParentBinding.inflate(context.inflater), doOnClick, doOnLongClick) } } } @@ -167,14 +179,16 @@ private constructor( /** The Shared ViewHolder for a [Genre]. Instantiation should be done with [from]. */ class GenreViewHolder private constructor( - private val binding: ItemGenreBinding, + private val binding: ItemParentBinding, doOnClick: (Genre) -> Unit, doOnLongClick: (view: View, data: Genre) -> Unit ) : BaseViewHolder(binding, doOnClick, doOnLongClick) { override fun onBind(data: Genre) { - binding.genre = data - binding.genreName.requestLayout() + binding.parentImage.applyGenreImage(data) + binding.parentName.textSafe = data.resolvedName + binding.parentInfo.textSafe = + binding.context.getPluralSafe(R.plurals.fmt_song_count, data.songs.size) } companion object { @@ -185,7 +199,7 @@ private constructor( doOnLongClick: (view: View, data: Genre) -> Unit ): GenreViewHolder { return GenreViewHolder( - ItemGenreBinding.inflate(context.inflater), doOnClick, doOnLongClick) + ItemParentBinding.inflate(context.inflater), doOnClick, doOnLongClick) } } } @@ -195,7 +209,7 @@ class HeaderViewHolder private constructor(private val binding: ItemHeaderBindin BaseViewHolder
(binding) { override fun onBind(data: Header) { - binding.header = data + binding.title.textSafe = binding.context.getString(data.string) } companion object { @@ -211,13 +225,11 @@ class ActionHeaderViewHolder private constructor(private val binding: ItemAction BaseViewHolder(binding) { override fun onBind(data: ActionHeader) { - binding.header = data - - binding.executePendingBindings() - + binding.headerTitle.textSafe = binding.context.getString(data.string) binding.headerButton.apply { + setImageResource(data.icon) + contentDescription = context.getString(data.desc) TooltipCompat.setTooltipText(this, contentDescription) - setOnClickListener(data.onClick) } } 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 c8a9966fc..5c8ab6826 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt @@ -17,6 +17,7 @@ package org.oxycblt.auxio.util +import android.content.Context import android.content.res.ColorStateList import android.graphics.Insets import android.graphics.Rect @@ -24,44 +25,14 @@ import android.graphics.drawable.Drawable import android.os.Build import android.view.View import android.view.WindowInsets +import android.widget.TextView import androidx.annotation.ColorRes import androidx.core.graphics.drawable.DrawableCompat import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding import org.oxycblt.auxio.R -/** Converts this color to a single-color [ColorStateList]. */ -val @receiver:ColorRes Int.stateList - get() = ColorStateList.valueOf(this) - -/** - * Apply the recommended spans for a [RecyclerView]. - * - * @param shouldBeFullWidth Optional callback for determining whether an item should be full-width, - * regardless of spans - */ -fun RecyclerView.applySpans(shouldBeFullWidth: ((Int) -> Boolean)? = null) { - val spans = resources.getInteger(R.integer.recycler_spans) - - if (spans > 1) { - val mgr = GridLayoutManager(context, spans) - - if (shouldBeFullWidth != null) { - mgr.spanSizeLookup = - object : GridLayoutManager.SpanSizeLookup() { - override fun getSpanSize(position: Int): Int { - return if (shouldBeFullWidth(position)) spans else 1 - } - } - } - - layoutManager = mgr - } -} - -/** Returns whether a recyclerview can scroll. */ -fun RecyclerView.canScroll(): Boolean = computeVerticalScrollRange() > height - /** * Disables drop shadows on a view programmatically in a version-compatible manner. This only works * on Android 9 and above. Below that version, shadows will remain visible. @@ -113,9 +84,52 @@ private fun isUnderImpl( val View.isRtl: Boolean get() = layoutDirection == View.LAYOUT_DIRECTION_RTL + val Drawable.isRtl: Boolean get() = DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL +val ViewBinding.context: Context + get() = root.context + +var TextView.textSafe + get() = text + set(value) { + text = value + requestLayout() + } + +/** + * Apply the recommended spans for a [RecyclerView]. + * + * @param shouldBeFullWidth Optional callback for determining whether an item should be full-width, + * regardless of spans + */ +fun RecyclerView.applySpans(shouldBeFullWidth: ((Int) -> Boolean)? = null) { + val spans = resources.getInteger(R.integer.recycler_spans) + + if (spans > 1) { + val mgr = GridLayoutManager(context, spans) + + if (shouldBeFullWidth != null) { + mgr.spanSizeLookup = + object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (shouldBeFullWidth(position)) spans else 1 + } + } + } + + layoutManager = mgr + } +} + +/** Returns whether a recyclerview can scroll. */ +fun RecyclerView.canScroll(): Boolean = computeVerticalScrollRange() > height + +/** Converts this color to a single-color [ColorStateList]. */ +val @receiver:ColorRes Int.stateList + get() = ColorStateList.valueOf(this) + /** * Resolve system bar insets in a version-aware manner. This can be used to apply padding to a view * that properly follows all the frustrating changes that were made between 8-11. diff --git a/app/src/main/res/layout-h600dp/item_detail.xml b/app/src/main/res/layout-h600dp/item_detail.xml index 02a1eff41..ceb2bf629 100644 --- a/app/src/main/res/layout-h600dp/item_detail.xml +++ b/app/src/main/res/layout-h600dp/item_detail.xml @@ -1,11 +1,7 @@ - - - - - @@ -75,4 +71,3 @@ app:layout_constraintStart_toEndOf="@+id/detail_play_button" app:layout_constraintTop_toTopOf="@+id/detail_play_button" /> - \ No newline at end of file diff --git a/app/src/main/res/layout-land/fragment_playback_panel.xml b/app/src/main/res/layout-land/fragment_playback_panel.xml index cdb6b6573..4c85cadb2 100644 --- a/app/src/main/res/layout-land/fragment_playback_panel.xml +++ b/app/src/main/res/layout-land/fragment_playback_panel.xml @@ -1,185 +1,181 @@ - + android:id="@+id/playback_layout" + android:layout_width="match_parent" + android:layout_height="match_parent"> - + - + - + - - - - - - - + - - - - + android:layout_height="match_parent" + tools:text="Song Name" /> - + - + - + - + - + - + - + - - + + + + + + + + + diff --git a/app/src/main/res/layout-land/item_detail.xml b/app/src/main/res/layout-land/item_detail.xml index ef01119c9..52b5b7eee 100644 --- a/app/src/main/res/layout-land/item_detail.xml +++ b/app/src/main/res/layout-land/item_detail.xml @@ -1,11 +1,7 @@ - - - - - @@ -88,4 +84,3 @@ app:layout_constraintTop_toTopOf="@+id/detail_play_button" /> - \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp-land/fragment_playback_panel.xml b/app/src/main/res/layout-sw600dp-land/fragment_playback_panel.xml index e595a90cf..c43bb7896 100644 --- a/app/src/main/res/layout-sw600dp-land/fragment_playback_panel.xml +++ b/app/src/main/res/layout-sw600dp-land/fragment_playback_panel.xml @@ -1,18 +1,15 @@ - + android:id="@+id/playback_layout" + android:layout_width="match_parent" + android:layout_height="match_parent"> - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/fragment_playback_panel.xml b/app/src/main/res/layout-sw600dp/fragment_playback_panel.xml index 1fc82f7e9..aea4a61b2 100644 --- a/app/src/main/res/layout-sw600dp/fragment_playback_panel.xml +++ b/app/src/main/res/layout-sw600dp/fragment_playback_panel.xml @@ -1,18 +1,16 @@ - + android:id="@+id/playback_layout" + android:layout_width="match_parent" + android:layout_height="match_parent"> - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/item_detail.xml b/app/src/main/res/layout-sw600dp/item_detail.xml index 5aa3ccd6f..5a9165172 100644 --- a/app/src/main/res/layout-sw600dp/item_detail.xml +++ b/app/src/main/res/layout-sw600dp/item_detail.xml @@ -1,13 +1,10 @@ - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout-sw640dp/fragment_playback_bar.xml b/app/src/main/res/layout-sw640dp/fragment_playback_bar.xml index df6debb18..0889ad078 100644 --- a/app/src/main/res/layout-sw640dp/fragment_playback_bar.xml +++ b/app/src/main/res/layout-sw640dp/fragment_playback_bar.xml @@ -1,10 +1,7 @@ - - - @@ -95,5 +92,4 @@ app:trackColor="?attr/colorPrimary" tools:progress="70" /> - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout-sw840dp/item_detail.xml b/app/src/main/res/layout-sw840dp/item_detail.xml index f72177254..c315db0c0 100644 --- a/app/src/main/res/layout-sw840dp/item_detail.xml +++ b/app/src/main/res/layout-sw840dp/item_detail.xml @@ -1,11 +1,9 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-w600dp-land/fragment_playback_panel.xml b/app/src/main/res/layout-w600dp-land/fragment_playback_panel.xml index 1c85b7fc6..d55cb7867 100644 --- a/app/src/main/res/layout-w600dp-land/fragment_playback_panel.xml +++ b/app/src/main/res/layout-w600dp-land/fragment_playback_panel.xml @@ -1,19 +1,16 @@ - + android:id="@+id/playback_layout" + android:layout_width="match_parent" + android:layout_height="match_parent"> - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-w600dp/fragment_playback_bar.xml b/app/src/main/res/layout-w600dp/fragment_playback_bar.xml index 161e7d665..a2525403a 100644 --- a/app/src/main/res/layout-w600dp/fragment_playback_bar.xml +++ b/app/src/main/res/layout-w600dp/fragment_playback_bar.xml @@ -1,18 +1,15 @@ - + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index f53ad602b..db6d8919a 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,15 +1,11 @@ - - - - \ No newline at end of file + android:id="@+id/nav_host" + android:name="androidx.navigation.fragment.NavHostFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:defaultNavHost="true" + app:navGraph="@navigation/nav_main" + tools:layout="@layout/fragment_main" /> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_accent.xml b/app/src/main/res/layout/dialog_accent.xml index 68c6f1609..5b2f330c3 100644 --- a/app/src/main/res/layout/dialog_accent.xml +++ b/app/src/main/res/layout/dialog_accent.xml @@ -1,21 +1,17 @@ - - - - - \ No newline at end of file + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/accent_recycler" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:overScrollMode="never" + android:paddingStart="@dimen/spacing_medium" + android:paddingTop="@dimen/spacing_medium" + android:paddingEnd="@dimen/spacing_medium" + android:paddingBottom="@dimen/spacing_small" + app:layoutManager="org.oxycblt.auxio.accent.AccentGridLayoutManager" + app:layout_constraintBottom_toTopOf="@+id/accent_cancel" + app:layout_constraintTop_toBottomOf="@+id/accent_header" + tools:itemCount="18" + tools:listitem="@layout/item_accent" /> diff --git a/app/src/main/res/layout/dialog_excluded.xml b/app/src/main/res/layout/dialog_excluded.xml index 63dd05d5c..b43cae121 100644 --- a/app/src/main/res/layout/dialog_excluded.xml +++ b/app/src/main/res/layout/dialog_excluded.xml @@ -1,34 +1,32 @@ - + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingTop="@dimen/spacing_small"> - + + + android:padding="@dimen/spacing_medium" + android:text="@string/lbl_no_dirs" + android:textAlignment="center" + android:textAppearance="@style/TextAppearance.Auxio.TitleMidLarge" + android:textColor="?android:attr/textColorSecondary" /> - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_tabs.xml b/app/src/main/res/layout/dialog_tabs.xml index 181a4b739..fc292412b 100644 --- a/app/src/main/res/layout/dialog_tabs.xml +++ b/app/src/main/res/layout/dialog_tabs.xml @@ -1,18 +1,14 @@ - - - - - \ No newline at end of file + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/tab_recycler" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:overScrollMode="never" + android:paddingTop="@dimen/spacing_small" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + app:layout_constraintBottom_toTopOf="@+id/accent_cancel" + app:layout_constraintTop_toBottomOf="@+id/accent_header" + tools:itemCount="5" + tools:listitem="@layout/item_tab" /> diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index 7935f977a..5dbc9d7c5 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -1,183 +1,180 @@ - - + + + + + + + android:layout_height="match_parent" + android:clipToPadding="false" + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> - - - - - - - + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_medium"> - + android:layout_height="match_parent"> + + + + + + + android:layout_height="match_parent" + android:padding="@dimen/spacing_medium" + app:layout_constraintTop_toBottomOf="@+id/about_desc" + app:layout_constraintVertical_chainStyle="packed"> - - - - - - - - - - - - + app:layout_constraintBottom_toTopOf="@+id/about_version" + app:layout_constraintStart_toEndOf="@+id/about_version_icon" + app:layout_constraintTop_toTopOf="@+id/about_version_icon" /> - - - - - - - - + android:layout_marginStart="@dimen/spacing_medium" + android:textAppearance="@style/TextAppearance.Auxio.BodySmall" + app:layout_constraintBottom_toBottomOf="@+id/about_version_icon" + app:layout_constraintStart_toEndOf="@+id/about_version_icon" + app:layout_constraintTop_toBottomOf="@+id/about_version_title" + tools:text="16.16.16" /> - - - - \ No newline at end of file + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_detail.xml b/app/src/main/res/layout/fragment_detail.xml index 3d7620c64..326c83606 100644 --- a/app/src/main/res/layout/fragment_detail.xml +++ b/app/src/main/res/layout/fragment_detail.xml @@ -1,38 +1,34 @@ - + android:id="@+id/bar_layout" + android:layout_width="match_parent" + android:layout_height="match_parent"> - - + + + + + + + android:layout_height="match_parent" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" + tools:listitem="@layout/item_detail" /> - - - - - - - - - - - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 2bab4386f..fae3e6cb9 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -1,10 +1,7 @@ - - - @@ -59,4 +56,3 @@ - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home_list.xml b/app/src/main/res/layout/fragment_home_list.xml index caf5faa29..6d9b52e6f 100644 --- a/app/src/main/res/layout/fragment_home_list.xml +++ b/app/src/main/res/layout/fragment_home_list.xml @@ -1,9 +1,8 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index 25ff81e59..f05dfbc86 100644 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -1,19 +1,16 @@ - + android:layout_width="match_parent" + android:layout_height="match_parent"> - - - - - - diff --git a/app/src/main/res/layout/fragment_playback_bar.xml b/app/src/main/res/layout/fragment_playback_bar.xml index e0b79d3e5..c7bdf7165 100644 --- a/app/src/main/res/layout/fragment_playback_bar.xml +++ b/app/src/main/res/layout/fragment_playback_bar.xml @@ -1,18 +1,15 @@ - + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_playback_panel.xml b/app/src/main/res/layout/fragment_playback_panel.xml index 36340c5c7..2b904dbc3 100644 --- a/app/src/main/res/layout/fragment_playback_panel.xml +++ b/app/src/main/res/layout/fragment_playback_panel.xml @@ -1,18 +1,15 @@ - + android:id="@+id/playback_layout" + android:layout_width="match_parent" + android:layout_height="match_parent"> - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_queue.xml b/app/src/main/res/layout/fragment_queue.xml index 55e5be340..1eb3fed1d 100644 --- a/app/src/main/res/layout/fragment_queue.xml +++ b/app/src/main/res/layout/fragment_queue.xml @@ -1,18 +1,15 @@ - + android:id="@+id/queue_coordinator" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/colorSurface" + android:orientation="vertical"> - - - @@ -35,5 +32,4 @@ app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" tools:listitem="@layout/item_queue_song" /> - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 6c0c67b81..2938b0fe6 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -1,9 +1,7 @@ - - - @@ -55,4 +53,3 @@ tools:listitem="@layout/item_song" /> - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 826773711..29431c19c 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -1,10 +1,7 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_accent.xml b/app/src/main/res/layout/item_accent.xml index 84413120b..212ca04d5 100644 --- a/app/src/main/res/layout/item_accent.xml +++ b/app/src/main/res/layout/item_accent.xml @@ -1,26 +1,22 @@ - + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="@dimen/spacing_small" + android:paddingBottom="@dimen/spacing_small" + android:theme="@style/ThemeOverlay.Accent"> - + android:layout_gravity="center" + android:background="@drawable/ui_accent_circle" + android:padding="@dimen/spacing_medium" + android:scaleType="fitCenter" + android:src="@drawable/ic_check" + tools:backgroundTint="?attr/colorPrimary" + tools:ignore="ContentDescription, SpeakableTextPresentCheck" /> - - - - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_action_header.xml b/app/src/main/res/layout/item_action_header.xml index 09d0d188e..33c7b5767 100644 --- a/app/src/main/res/layout/item_action_header.xml +++ b/app/src/main/res/layout/item_action_header.xml @@ -1,26 +1,16 @@ - + android:layout_width="match_parent" + android:layout_height="wrap_content"> - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_album.xml b/app/src/main/res/layout/item_album.xml deleted file mode 100644 index 2e01aad92..000000000 --- a/app/src/main/res/layout/item_album.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_album_song.xml b/app/src/main/res/layout/item_album_song.xml index 8bdb5c468..d3c3b69d8 100644 --- a/app/src/main/res/layout/item_album_song.xml +++ b/app/src/main/res/layout/item_album_song.xml @@ -1,26 +1,19 @@ - + style="@style/Widget.Auxio.ItemLayout"> - - - - - - - - + tools:text="16" /> @@ -66,13 +56,11 @@ style="@style/Widget.Auxio.TextView.Item.Secondary" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="@{song.formattedDuration}" android:textColor="?android:attr/textColorSecondary" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toBottomOf="@+id/song_track" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/song_track" app:layout_constraintTop_toBottomOf="@+id/song_name" tools:text="16:16" /> - \ No newline at end of file diff --git a/app/src/main/res/layout/item_artist.xml b/app/src/main/res/layout/item_artist.xml index e58ea51a9..0f7818c68 100644 --- a/app/src/main/res/layout/item_artist.xml +++ b/app/src/main/res/layout/item_artist.xml @@ -1,47 +1,35 @@ - + xmlns:app="http://schemas.android.com/apk/res-auto" + style="@style/Widget.Auxio.ItemLayout"> - + - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_artist_album.xml b/app/src/main/res/layout/item_artist_album.xml deleted file mode 100644 index 8c51cd126..000000000 --- a/app/src/main/res/layout/item_artist_album.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_artist_song.xml b/app/src/main/res/layout/item_artist_song.xml deleted file mode 100644 index 705b3597f..000000000 --- a/app/src/main/res/layout/item_artist_song.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_detail.xml b/app/src/main/res/layout/item_detail.xml index fba1e2f59..0201c1338 100644 --- a/app/src/main/res/layout/item_detail.xml +++ b/app/src/main/res/layout/item_detail.xml @@ -1,11 +1,8 @@ - - - - @@ -75,4 +72,3 @@ app:layout_constraintStart_toEndOf="@+id/detail_play_button" app:layout_constraintTop_toTopOf="@+id/detail_play_button" /> - \ No newline at end of file diff --git a/app/src/main/res/layout/item_excluded_dir.xml b/app/src/main/res/layout/item_excluded_dir.xml index cf8f0d679..5382b51c0 100644 --- a/app/src/main/res/layout/item_excluded_dir.xml +++ b/app/src/main/res/layout/item_excluded_dir.xml @@ -1,14 +1,13 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_genre.xml b/app/src/main/res/layout/item_genre.xml deleted file mode 100644 index 67d6c9f35..000000000 --- a/app/src/main/res/layout/item_genre.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_genre_song.xml b/app/src/main/res/layout/item_genre_song.xml deleted file mode 100644 index 6d63080b6..000000000 --- a/app/src/main/res/layout/item_genre_song.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_header.xml b/app/src/main/res/layout/item_header.xml index d33f346e1..04f267978 100644 --- a/app/src/main/res/layout/item_header.xml +++ b/app/src/main/res/layout/item_header.xml @@ -1,17 +1,7 @@ - - - - - - - - @@ -20,7 +10,6 @@ style="@style/Widget.Auxio.TextView.Header" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@{context.getString(header.string)}" app:layout_constraintBottom_toTopOf="@id/header_divider" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -32,5 +21,4 @@ android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" /> - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_parent.xml b/app/src/main/res/layout/item_parent.xml new file mode 100644 index 000000000..a41d9f864 --- /dev/null +++ b/app/src/main/res/layout/item_parent.xml @@ -0,0 +1,38 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/item_queue_song.xml b/app/src/main/res/layout/item_queue_song.xml index 1adae6ad6..5e88772aa 100644 --- a/app/src/main/res/layout/item_queue_song.xml +++ b/app/src/main/res/layout/item_queue_song.xml @@ -1,17 +1,8 @@ - + - - - - - @@ -39,11 +30,9 @@ android:background="?attr/colorSurface"> @@ -69,10 +57,9 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/spacing_medium" - app:songInfo="@{song}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/song_drag_handle" - app:layout_constraintStart_toEndOf="@+id/album_cover" + app:layout_constraintStart_toEndOf="@+id/song_album_cover" app:layout_constraintTop_toBottomOf="@+id/song_name" tools:text="Artist / Album" /> @@ -89,10 +76,9 @@ android:paddingEnd="@dimen/spacing_medium" android:scaleType="center" android:src="@drawable/ic_handle" - app:layout_constraintBottom_toBottomOf="@+id/album_cover" + app:layout_constraintBottom_toBottomOf="@+id/song_album_cover" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="@+id/album_cover" /> + app:layout_constraintTop_toTopOf="@+id/song_album_cover" /> - \ No newline at end of file diff --git a/app/src/main/res/layout/item_song.xml b/app/src/main/res/layout/item_song.xml index c0083b60a..80e313bff 100644 --- a/app/src/main/res/layout/item_song.xml +++ b/app/src/main/res/layout/item_song.xml @@ -1,52 +1,39 @@ - + xmlns:app="http://schemas.android.com/apk/res-auto" + style="@style/Widget.Auxio.ItemLayout"> - + - - + - - - - - - - + - \ No newline at end of file diff --git a/app/src/main/res/layout/item_tab.xml b/app/src/main/res/layout/item_tab.xml index 845f2bf44..c953eca83 100644 --- a/app/src/main/res/layout/item_tab.xml +++ b/app/src/main/res/layout/item_tab.xml @@ -1,12 +1,11 @@ - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/view_seek_bar.xml b/app/src/main/res/layout/view_seek_bar.xml index 67d599dce..f8713ab62 100644 --- a/app/src/main/res/layout/view_seek_bar.xml +++ b/app/src/main/res/layout/view_seek_bar.xml @@ -1,9 +1,7 @@ - - - @@ -49,5 +47,4 @@ app:layout_constraintEnd_toEndOf="parent" tools:text="16:16" /> - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 2458f97c1..394535b90 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -26,7 +26,6 @@ 16sp 18sp - 20sp 2dp diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 4470ddbd5..b7f87867e 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -6,5 +6,5 @@ %1$s • %2$s %1$s • %2$s • %3$s - %d + %d \ No newline at end of file diff --git a/info/ARCHITECTURE.md b/info/ARCHITECTURE.md index 40afdeee0..575803617 100644 --- a/info/ARCHITECTURE.md +++ b/info/ARCHITECTURE.md @@ -44,16 +44,16 @@ should be added as a new `Fragment` implementation and added to one of the two n - `nav_main`: Navigation *from* `MainFragment` - `nav_explore`: Navigation *in* `MainFragment` -Fragments themselves are organized in order of lifecycle. So the first override would be `onCreate`, followed by -`onCreateView`, and so on. `onCreateView` is where all view instantiation and configuration takes place, and -is separated into three phases: +Fragments themselves are based off a super class called `ViewBindingFragment` that takes a view-binding and then +leverages it within the fragment lifecycle. - Create variables [Bindings, Adapters, etc] - Set up the UI - Set up ViewModel instances and LiveData observers `findViewById` is to **only** be used when interfacing with non-Auxio views. Otherwise, view-binding should be -used in all cases. Avoid usages of databinding outside of the `onCreateView` step unless absolutely necessary. +used in all cases. Code that involves retrieving the binding should be isolated into its own function, with +the binding being obtained by calling `requireBinding`. At times it may be more appropriate to use a `View` instead of a full blown fragment. This is okay as long as view-binding is still used. @@ -62,11 +62,6 @@ When creating a ViewHolder for a `RecyclerView`, one should use `BaseViewHolder` and automate some code shared across all ViewHolders. The only exceptions to this case are for ViewHolders that correspond to non-`BaseModel` data, in which a normal ViewHolder can be used instead. -Data is often bound using Binding Adapters, which are XML attributes assigned in layout files that can automatically -display data, usually written as `app:bindingAdapterName="@{data}"`. Its recommended to use these instead of applying -the attributes directly unless absolutely necessary. Usually it's okay to apply data programmatically if you begin -to duplicate layouts simply because they apply to different data objects, such as the detail UIs. - #### Object communication Auxio's codebase is mostly centered around 4 different types of code that communicates with each-other.