From c9875a03a9c65d9ca5e01aa37a710b4948db5fab Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Wed, 26 Aug 2020 17:54:45 -0600 Subject: [PATCH] Add All Songs fragment Add an All Songs fragment, also modify the ViewPager structure. --- .../java/org/oxycblt/auxio/MainFragment.kt | 49 ++++++++++++--- .../oxycblt/auxio/library/LibraryViewModel.kt | 2 +- .../org/oxycblt/auxio/music/MusicUtils.kt | 27 +++++++++ .../auxio/music/processing/MusicSorter.kt | 27 +++++---- .../oxycblt/auxio/recycler/RecyclerUtils.kt | 12 ---- .../oxycblt/auxio/recycler/SongViewHolder.kt | 26 ++++++++ .../oxycblt/auxio/songs/SongDataAdapter.kt | 40 +++++++++++++ .../org/oxycblt/auxio/songs/SongsFragment.kt | 45 ++++++++++++++ .../org/oxycblt/auxio/songs/SongsViewModel.kt | 22 +++++++ app/src/main/res/layout/album_item.xml | 2 +- app/src/main/res/layout/fragment_library.xml | 14 ++--- app/src/main/res/layout/fragment_loading.xml | 6 +- app/src/main/res/layout/fragment_songs.xml | 30 ++++++++++ app/src/main/res/layout/song_item.xml | 59 +++++++++++++++++++ app/src/main/res/values/strings.xml | 6 +- 15 files changed, 321 insertions(+), 46 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/recycler/SongViewHolder.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/songs/SongDataAdapter.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/songs/SongsViewModel.kt create mode 100644 app/src/main/res/layout/fragment_songs.xml create mode 100644 app/src/main/res/layout/song_item.xml diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index e31859181..5505a2d63 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -1,6 +1,7 @@ package org.oxycblt.auxio import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -10,12 +11,22 @@ import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter import org.oxycblt.auxio.databinding.FragmentMainBinding import org.oxycblt.auxio.library.LibraryFragment - -// TODO: Placeholder, page count will be dynamic -private const val PAGES = 1 +import org.oxycblt.auxio.songs.SongsFragment class MainFragment : Fragment() { + private val shownFragments = listOf( + 0, 1 + ) + + private val libraryFragment: LibraryFragment by lazy { + LibraryFragment() + } + + private val songsFragment: SongsFragment by lazy { + SongsFragment() + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -28,15 +39,35 @@ class MainFragment : Fragment() { val adapter = FragmentAdapter(requireActivity()) binding.viewPager.adapter = adapter + Log.d(this::class.simpleName, "Fragment Created.") + return binding.root } -} -class FragmentAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) { - override fun getItemCount(): Int = PAGES + private fun getFragment(pos: Int): Fragment { + if (shownFragments.contains(pos)) { + return when (pos) { + 0 -> libraryFragment + 1 -> songsFragment - override fun createFragment(position: Int): Fragment { - // TODO: Also placeholder, remove when there are other fragments than just library - return LibraryFragment() + else -> libraryFragment + } + } + + // Not sure how this would happen but it might + Log.e( + this::class.simpleName, + "Something went terribly wrong while swapping fragments, Substituting with libraryFragment." + ) + + return libraryFragment + } + + inner class FragmentAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) { + override fun getItemCount(): Int = shownFragments.size + + override fun createFragment(position: Int): Fragment { + return getFragment(position) + } } } diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt index 1baf96e2b..26566bafe 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt @@ -8,7 +8,7 @@ import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.models.Album import org.oxycblt.auxio.music.models.Artist -class LibraryViewModel() : ViewModel() { +class LibraryViewModel : ViewModel() { private val mArtists = MutableLiveData>() private var mAlbums = MutableLiveData>() 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 4460345ae..8ac69f5ef 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt @@ -3,6 +3,11 @@ package org.oxycblt.auxio.music import android.content.ContentUris import android.net.Uri import android.provider.MediaStore +import android.widget.TextView +import androidx.databinding.BindingAdapter +import org.oxycblt.auxio.R +import org.oxycblt.auxio.music.models.Album +import org.oxycblt.auxio.music.models.Song private val ID3_GENRES = arrayOf( "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", @@ -41,6 +46,7 @@ fun String.toNamedGenre(): String { return ID3_GENRES.getOrNull(intGenre) ?: "" } +// Convert a song to its URI fun Long.toURI(): Uri { return ContentUris.withAppendedId( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, @@ -48,9 +54,30 @@ fun Long.toURI(): Uri { ) } +// Convert an albums ID into its album art URI fun Long.toAlbumArtURI(): Uri { return ContentUris.withAppendedId( Uri.parse("content://media/external/audio/albumart"), this ) } + +// Format the amount of songs in an album +@BindingAdapter("songCount") +fun TextView.getAlbumSongs(album: Album) { + text = if (album.numSongs < 2) { + context.getString(R.string.label_single_song) + } else { + context.getString(R.string.format_multi_song_count, album.numSongs.toString()) + } +} + +// Format the artist/album data for a song +@BindingAdapter("songData") +fun TextView.getSongData(song: Song) { + text = context.getString( + R.string.format_song_data, + song.album.artist.name, + song.album.title + ) +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/processing/MusicSorter.kt b/app/src/main/java/org/oxycblt/auxio/music/processing/MusicSorter.kt index f36695fc6..2e61f03f2 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/processing/MusicSorter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/processing/MusicSorter.kt @@ -24,8 +24,11 @@ class MusicSorter( // Find all songs that match the current album title val albumSongs = songs.filter { it.albumName == album.title } - // Then add them to the album, along with refreshing the cover - album.songs.addAll(albumSongs) + // Then add them to the album + for (song in albumSongs) { + song.album = album + album.songs.add(song) + } unknownSongs.removeAll(albumSongs) } @@ -36,13 +39,13 @@ class MusicSorter( // Reuse an existing unknown album if one is found val unknownAlbum = albums.find { it.title == "" } ?: Album() - unknownAlbum.songs.addAll(unknownSongs) - unknownAlbum.numSongs = unknownAlbum.songs.size - for (song in unknownSongs) { song.album = unknownAlbum + unknownAlbum.songs.add(song) } + unknownAlbum.numSongs = unknownAlbum.songs.size + albums.add(unknownAlbum) Log.d( @@ -61,8 +64,12 @@ class MusicSorter( // Find all albums that match the current artist name val artistAlbums = albums.filter { it.artistName == artist.name } - // And then add them to the album, along with refreshing the amount of albums - artist.albums.addAll(artistAlbums) + // Then add them to the artist, along with refreshing the amount of albums + for (album in artistAlbums) { + album.artist = artist + artist.albums.add(album) + } + artist.numAlbums = artist.albums.size unknownAlbums.removeAll(artistAlbums) @@ -74,14 +81,12 @@ class MusicSorter( // Reuse an existing unknown artist if one is found val unknownArtist = artists.find { it.name == "" } ?: Artist() - unknownArtist.albums.addAll(unknownAlbums) - unknownArtist.numAlbums = albums.size - for (album in unknownAlbums) { album.artist = unknownArtist + unknownArtist.albums.add(album) } - artists.add(unknownArtist) + unknownArtist.numAlbums = albums.size Log.d( this::class.simpleName, diff --git a/app/src/main/java/org/oxycblt/auxio/recycler/RecyclerUtils.kt b/app/src/main/java/org/oxycblt/auxio/recycler/RecyclerUtils.kt index b4c0fa1c9..6a9b5dd5a 100644 --- a/app/src/main/java/org/oxycblt/auxio/recycler/RecyclerUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/recycler/RecyclerUtils.kt @@ -1,24 +1,12 @@ package org.oxycblt.auxio.recycler import android.graphics.drawable.ColorDrawable -import android.widget.TextView import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils -import androidx.databinding.BindingAdapter import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R -import org.oxycblt.auxio.music.models.Album - -@BindingAdapter("songCount") -fun TextView.numSongsToText(album: Album) { - text = if (album.numSongs < 2) { - context.getString(R.string.label_single_song) - } else { - context.getString(R.string.format_multi_song_count, album.numSongs.toString()) - } -} // Apply a custom vertical divider fun RecyclerView.applyDivider() { diff --git a/app/src/main/java/org/oxycblt/auxio/recycler/SongViewHolder.kt b/app/src/main/java/org/oxycblt/auxio/recycler/SongViewHolder.kt new file mode 100644 index 000000000..f9014223d --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/recycler/SongViewHolder.kt @@ -0,0 +1,26 @@ +package org.oxycblt.auxio.recycler + +import androidx.recyclerview.widget.RecyclerView +import coil.load +import org.oxycblt.auxio.databinding.SongItemBinding +import org.oxycblt.auxio.music.models.Song + +// Generic ViewHolder for a song +class SongViewHolder( + private var binding: SongItemBinding +) : RecyclerView.ViewHolder(binding.root) { + + // Bind the view w/new data + fun bind(song: Song) { + binding.song = song + + // Load the album cover + binding.cover.load(song.album.coverUri) { + crossfade(true) + placeholder(android.R.color.transparent) + error(android.R.color.transparent) + } + + binding.executePendingBindings() + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/songs/SongDataAdapter.kt b/app/src/main/java/org/oxycblt/auxio/songs/SongDataAdapter.kt new file mode 100644 index 000000000..9fabcd681 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/songs/SongDataAdapter.kt @@ -0,0 +1,40 @@ +package org.oxycblt.auxio.songs + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import org.oxycblt.auxio.databinding.SongItemBinding +import org.oxycblt.auxio.music.models.Song +import org.oxycblt.auxio.recycler.SongViewHolder + +class SongDataAdapter : ListAdapter(DiffCallback) { + + var data = listOf() + set(newData) { + field = newData + submitList(data) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongViewHolder { + return SongViewHolder( + SongItemBinding.inflate(LayoutInflater.from(parent.context)) + ) + } + + override fun onBindViewHolder(holder: SongViewHolder, position: Int) { + val Song = getItem(position) + + holder.bind(Song) + } + + companion object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Song, newItem: Song): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame(oldItem: Song, newItem: Song): Boolean { + return oldItem.id == newItem.id + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt new file mode 100644 index 000000000..0b8be1794 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt @@ -0,0 +1,45 @@ +package org.oxycblt.auxio.songs + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import org.oxycblt.auxio.R +import org.oxycblt.auxio.databinding.FragmentSongsBinding +import org.oxycblt.auxio.recycler.applyDivider + +class SongsFragment : Fragment() { + private val songsModel: SongsViewModel by lazy { + ViewModelProvider(this).get(SongsViewModel::class.java) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val binding = DataBindingUtil.inflate( + inflater, R.layout.fragment_songs, container, false + ) + + val adapter = SongDataAdapter() + binding.songRecycler.adapter = adapter + binding.songRecycler.applyDivider() + + songsModel.songs.observe( + viewLifecycleOwner, + Observer { + adapter.data = it + } + ) + + Log.d(this::class.simpleName, "Fragment created.") + + return binding.root + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/songs/SongsViewModel.kt b/app/src/main/java/org/oxycblt/auxio/songs/SongsViewModel.kt new file mode 100644 index 000000000..fbefddafb --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/songs/SongsViewModel.kt @@ -0,0 +1,22 @@ +package org.oxycblt.auxio.songs + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.oxycblt.auxio.music.MusicRepository +import org.oxycblt.auxio.music.models.Song + +class SongsViewModel : ViewModel() { + + private val mSongs = MutableLiveData>() + val songs: LiveData> get() = mSongs + + init { + val repo = MusicRepository.getInstance() + + mSongs.value = repo.songs + + Log.d(this::class.simpleName, "ViewModel created.") + } +} diff --git a/app/src/main/res/layout/album_item.xml b/app/src/main/res/layout/album_item.xml index 54a2ebab5..e7db34730 100644 --- a/app/src/main/res/layout/album_item.xml +++ b/app/src/main/res/layout/album_item.xml @@ -53,7 +53,7 @@ app:layout_constraintStart_toEndOf="@+id/cover" app:layout_constraintTop_toBottomOf="@+id/album_name" app:songCount="@{album}" - tools:text="@string/tools_song_count" /> + tools:text="10 Songs" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index fca3cf9bc..4df3c1caa 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -3,9 +3,10 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> - + android:layout_height="match_parent" + android:orientation="vertical"> - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_loading.xml b/app/src/main/res/layout/fragment_loading.xml index d7c18d768..aade8579c 100644 --- a/app/src/main/res/layout/fragment_loading.xml +++ b/app/src/main/res/layout/fragment_loading.xml @@ -3,6 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"> + + + tools:text="Some kind of error." />