Add All Songs fragment

Add an All Songs fragment, also modify the ViewPager structure.
This commit is contained in:
OxygenCobalt 2020-08-26 17:54:45 -06:00
parent c109c3f359
commit c9875a03a9
15 changed files with 321 additions and 46 deletions

View file

@ -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
}
private fun getFragment(pos: Int): Fragment {
if (shownFragments.contains(pos)) {
return when (pos) {
0 -> libraryFragment
1 -> songsFragment
else -> libraryFragment
}
}
class FragmentAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
override fun getItemCount(): Int = PAGES
// 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 {
// TODO: Also placeholder, remove when there are other fragments than just library
return LibraryFragment()
return getFragment(position)
}
}
}

View file

@ -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<List<Artist>>()
private var mAlbums = MutableLiveData<List<Album>>()

View file

@ -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<String>(
"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
)
}

View file

@ -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,

View file

@ -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() {

View file

@ -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()
}
}

View file

@ -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<Song, SongViewHolder>(DiffCallback) {
var data = listOf<Song>()
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<Song>() {
override fun areItemsTheSame(oldItem: Song, newItem: Song): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Song, newItem: Song): Boolean {
return oldItem.id == newItem.id
}
}
}

View file

@ -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<FragmentSongsBinding>(
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
}
}

View file

@ -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<List<Song>>()
val songs: LiveData<List<Song>> get() = mSongs
init {
val repo = MusicRepository.getInstance()
mSongs.value = repo.songs
Log.d(this::class.simpleName, "ViewModel created.")
}
}

View file

@ -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" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -3,9 +3,10 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
@ -19,14 +20,11 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/library_recycler"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
android:layout_weight="1"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/album_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</layout>

View file

@ -3,6 +3,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- TODO: Add Animation -->
<data>
<variable
@ -33,12 +35,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary"
app:layout_constraintBottom_toTopOf="@+id/reset_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loading_bar"
tools:text="@string/error_music_load_failed" />
tools:text="Some kind of error." />
<Button
android:id="@+id/reset_button"

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:titleTextAppearance="@style/ToolbarStyle"
app:layout_constraintTop_toTopOf="parent"
app:title="@string/title_all_songs" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/song_recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/song_item" />
</LinearLayout>
</layout>

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="song"
type="org.oxycblt.auxio.music.models.Song" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/padding_medium">
<ImageView
android:id="@+id/cover"
android:layout_width="@dimen/cover_size_compact"
android:layout_height="@dimen/cover_size_compact"
android:contentDescription="@{@string/description_cover_art + song.album.title}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/backgrounds/scenic"
tools:srcCompat="@tools:sample/backgrounds/scenic" />
<TextView
android:id="@+id/album_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:text="@{song.title}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorPrimary"
app:layout_constraintBottom_toTopOf="@+id/song_count"
app:layout_constraintStart_toEndOf="@+id/cover"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Song Name" />
<TextView
android:id="@+id/song_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/cover"
app:layout_constraintTop_toBottomOf="@+id/album_name"
app:songData="@{song}"
tools:text="Artist / Album" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -9,10 +9,10 @@
<string name="label_single_song">1 Song</string>
<string name="format_multi_song_count">%s Songs</string>
<string name="format_song_data">%s / %s</string>
<string name="title_library_fragment"><b>Library</b></string>
<string name="title_library_fragment">Library</string>
<string name="title_all_songs">All Songs</string>
<string name="description_cover_art">Cover art for </string>
<string name="tools_song_count">10 Songs</string>
</resources>