Add a HeaderView binder

This commit is contained in:
tzugen 2021-10-18 12:57:21 +02:00
parent 5f716f5008
commit e81b1ef8c2
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
9 changed files with 595 additions and 625 deletions

View File

@ -1,114 +0,0 @@
package org.moire.ultrasonic.util;
import android.content.Context;
import org.moire.ultrasonic.domain.MusicDirectory;
import java.util.HashSet;
import java.util.Set;
public class AlbumHeader
{
private boolean isAllVideo;
private long totalDuration;
private Set<String> artists;
private Set<String> grandParents;
private Set<String> genres;
private Set<Integer> years;
public boolean getIsAllVideo()
{
return isAllVideo;
}
public long getTotalDuration()
{
return totalDuration;
}
public Set<String> getArtists()
{
return artists;
}
public Set<String> getGrandParents()
{
return this.grandParents;
}
public Set<String> getGenres()
{
return this.genres;
}
public Set<Integer> getYears()
{
return this.years;
}
public AlbumHeader()
{
this.artists = new HashSet<String>();
this.grandParents = new HashSet<String>();
this.genres = new HashSet<String>();
this.years = new HashSet<Integer>();
this.isAllVideo = true;
this.totalDuration = 0;
}
public static AlbumHeader processEntries(Context context, Iterable<MusicDirectory.Entry> entries)
{
AlbumHeader albumHeader = new AlbumHeader();
for (MusicDirectory.Entry entry : entries)
{
if (!entry.isVideo())
{
albumHeader.isAllVideo = false;
}
if (!entry.isDirectory())
{
if (Settings.getShouldUseFolderForArtistName())
{
albumHeader.processGrandParents(entry);
}
if (entry.getArtist() != null)
{
Integer duration = entry.getDuration();
if (duration != null)
{
albumHeader.totalDuration += duration;
}
albumHeader.artists.add(entry.getArtist());
}
if (entry.getGenre() != null)
{
albumHeader.genres.add(entry.getGenre());
}
if (entry.getYear() != null)
{
albumHeader.years.add(entry.getYear());
}
}
}
return albumHeader;
}
private void processGrandParents(MusicDirectory.Entry entry)
{
String grandParent = Util.getGrandparent(entry.getPath());
if (grandParent != null)
{
this.grandParents.add(grandParent);
}
}
}

View File

@ -0,0 +1,97 @@
package org.moire.ultrasonic.util
import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.util.Settings.shouldUseFolderForArtistName
import org.moire.ultrasonic.util.Util.getGrandparent
import java.util.HashSet
class AlbumHeader(
var entries: List<MusicDirectory.Entry>,
var name: String,
songCount: Int
): Identifiable {
var isAllVideo: Boolean
private set
var totalDuration: Long
private set
var childCount = 0
private val _artists: MutableSet<String>
private val _grandParents: MutableSet<String>
private val _genres: MutableSet<String>
private val _years: MutableSet<Int>
val artists: Set<String>
get() = _artists
val grandParents: Set<String>
get() = _grandParents
val genres: Set<String>
get() = _genres
val years: Set<Int>
get() = _years
private fun processGrandParents(entry: MusicDirectory.Entry) {
val grandParent = getGrandparent(entry.path)
if (grandParent != null) {
_grandParents.add(grandParent)
}
}
@Suppress("NestedBlockDepth")
private fun processEntries(list: List<MusicDirectory.Entry>) {
entries = list
childCount = entries.size
for (entry in entries) {
if (!entry.isVideo) {
isAllVideo = false
}
if (!entry.isDirectory) {
if (shouldUseFolderForArtistName) {
processGrandParents(entry)
}
if (entry.artist != null) {
val duration = entry.duration
if (duration != null) {
totalDuration += duration.toLong()
}
_artists.add(entry.artist!!)
}
if (entry.genre != null) {
_genres.add(entry.genre!!)
}
if (entry.year != null) {
_years.add(entry.year!!)
}
}
}
}
init {
_artists = HashSet()
_grandParents = HashSet()
_genres = HashSet()
_years = HashSet()
isAllVideo = true
totalDuration = 0
processEntries(entries)
}
override val id: String
get() = "HEADER"
override val longId: Long
get() = id.hashCode().toLong()
override fun compareTo(other: Identifiable): Int {
return this.longId.compareTo(other.longId)
}
}

View File

@ -0,0 +1,105 @@
package org.moire.ultrasonic.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.drakeet.multitype.ItemViewBinder
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.R
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.util.AlbumHeader
import org.moire.ultrasonic.util.Util
import java.lang.ref.WeakReference
import java.util.Random
/**
* This Binder can bind a list of entries into a Header
*/
class HeaderViewBinder(
context: Context
) : ItemViewBinder<AlbumHeader, HeaderViewBinder.ViewHolder>(), KoinComponent {
private val weakContext: WeakReference<Context> = WeakReference(context)
private val random: Random = Random()
private val imageLoaderProvider: ImageLoaderProvider by inject()
// Set our layout files
val layout = R.layout.select_album_header
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
return ViewHolder(inflater.inflate(layout, parent, false))
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val coverArtView: ImageView = itemView.findViewById(R.id.select_album_art)
val titleView: TextView = itemView.findViewById(R.id.select_album_title)
val artistView: TextView = itemView.findViewById(R.id.select_album_artist)
val durationView: TextView = itemView.findViewById(R.id.select_album_duration)
val songCountView: TextView = itemView.findViewById(R.id.select_album_song_count)
val yearView: TextView = itemView.findViewById(R.id.select_album_year)
val genreView: TextView = itemView.findViewById(R.id.select_album_genre)
}
override fun onBindViewHolder(holder: ViewHolder, item: AlbumHeader) {
val context = weakContext.get() ?: return
val resources = context.resources
val artworkSelection = random.nextInt(item.childCount)
imageLoaderProvider.getImageLoader().loadImage(
holder.coverArtView, item.entries[artworkSelection], false,
Util.getAlbumImageSize(context)
)
holder.titleView.text = item.name
// Don't show a header if all entries are videos
if (item.isAllVideo) {
return
}
val artist: String = when {
item.artists.size == 1 -> item.artists.iterator().next()
item.grandParents.size == 1 -> item.grandParents.iterator().next()
else -> context.resources.getString(R.string.common_various_artists)
}
holder.artistView.text = artist
val genre: String = if (item.genres.size == 1) {
item.genres.iterator().next()
} else {
context.resources.getString(R.string.common_multiple_genres)
}
holder.genreView.text = genre
val year: String = if (item.years.size == 1) {
item.years.iterator().next().toString()
} else {
resources.getString(R.string.common_multiple_years)
}
holder.yearView.text = year
val songs = resources.getQuantityString(
R.plurals.select_album_n_songs, item.childCount,
item.childCount
)
holder.songCountView.text = songs
val duration = Util.formatTotalDuration(item.totalDuration)
holder.durationView.text = duration
}
}

View File

@ -1,11 +1,8 @@
package org.moire.ultrasonic.adapters
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Checkable
import androidx.recyclerview.selection.SelectionTracker
import com.drakeet.multitype.ItemViewBinder
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@ -14,7 +11,6 @@ import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.service.DownloadFile
import org.moire.ultrasonic.service.Downloader
import org.moire.ultrasonic.util.Settings
class TrackViewBinder(
val selectedSet: MutableSet<Long>,

View File

@ -1,44 +1,27 @@
package org.moire.ultrasonic.fragment
import android.app.Application
import android.content.Context
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.fragment.app.viewModels
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.recyclerview.widget.RecyclerView
import org.koin.core.component.inject
import org.moire.ultrasonic.R
import org.moire.ultrasonic.adapters.GenericRowAdapter
import org.moire.ultrasonic.adapters.MultiTypeDiffAdapter
import org.moire.ultrasonic.adapters.TrackViewBinder
import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.service.DownloadFile
import org.moire.ultrasonic.service.Downloader
import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.view.SongViewHolder
import java.util.TreeSet
class DownloadsFragment : GenericListFragment<DownloadFile, DownloadRowAdapter>() {
class DownloadsFragment : MultiListFragment<DownloadFile, MultiTypeDiffAdapter<Identifiable>>() {
/**
* The ViewModel to use to get the data
*/
override val listModel: DownloadListModel by viewModels()
/**
* The id of the main layout
*/
override val mainLayout: Int = R.layout.generic_list
/**
* The id of the refresh view
*/
override val refreshListId: Int = R.id.generic_list_refresh
/**
* The id of the RecyclerView
*/
override val recyclerViewId = R.id.generic_list_recycler
/**
* The id of the target in the navigation graph where we should go,
* after the user has clicked on an item
@ -56,15 +39,17 @@ class DownloadsFragment : GenericListFragment<DownloadFile, DownloadRowAdapter>(
/**
* Provide the Adapter for the RecyclerView with a lazy delegate
*/
override val viewAdapter: DownloadRowAdapter by lazy {
DownloadRowAdapter(
liveDataItems.value ?: listOf(),
{ entry -> onItemClick(entry) },
{ menuItem, entry -> onContextMenuItemSelected(menuItem, entry) },
onMusicFolderUpdate,
requireContext(),
viewLifecycleOwner
override val viewAdapter: MultiTypeDiffAdapter<Identifiable> by lazy {
val adapter = MultiTypeDiffAdapter<Identifiable>()
adapter.register(
TrackViewBinder(
selectedSet = TreeSet(),
checkable = false,
draggable = false,
context = requireContext()
)
)
adapter
}
override fun onContextMenuItemSelected(menuItem: MenuItem, item: DownloadFile): Boolean {
@ -81,63 +66,6 @@ class DownloadsFragment : GenericListFragment<DownloadFile, DownloadRowAdapter>(
}
}
class DownloadRowAdapter(
itemList: List<DownloadFile>,
onItemClick: (DownloadFile) -> Unit,
onContextMenuClick: (MenuItem, DownloadFile) -> Boolean,
onMusicFolderUpdate: (String?) -> Unit,
val context: Context,
val lifecycleOwner: LifecycleOwner
) : GenericRowAdapter<DownloadFile>(
onItemClick,
onContextMenuClick,
onMusicFolderUpdate
) {
init {
super.submitList(itemList)
}
// Set our layout files
override val layout = R.layout.song_list_item
override val contextMenuLayout = R.menu.artist_context_menu
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is SongViewHolder) {
val downloadFile = currentList[position]
holder.setSong(downloadFile, checkable = false, draggable = false)
// Observe download status
downloadFile.status.observe(
lifecycleOwner,
{
holder.updateDownloadStatus(downloadFile)
}
)
downloadFile.progress.observe(
lifecycleOwner,
{
holder.updateDownloadStatus(downloadFile)
}
)
}
}
/**
* Creates an instance of our ViewHolder class
*/
override fun newViewHolder(view: View): RecyclerView.ViewHolder {
return SongViewHolder(view, context)
}
}
class DownloadListModel(application: Application) : GenericListModel(application) {
private val downloader by inject<Downloader>()

View File

@ -71,20 +71,20 @@ abstract class MultiListFragment<T : Identifiable, TA : MultiTypeAdapter> : Frag
*/
protected abstract val itemClickTarget: Int
/**
* The id of the RecyclerView
*/
protected abstract val recyclerViewId: Int
/**
* The id of the main layout
*/
abstract val mainLayout: Int
open val mainLayout: Int = R.layout.generic_list
/**
* The id of the refresh view
*/
abstract val refreshListId: Int
open val refreshListId: Int = R.id.generic_list_refresh
/**
* The id of the RecyclerView
*/
open val recyclerViewId = R.id.generic_list_recycler
/**
* The observer to be called if the available music folders have changed

View File

@ -31,13 +31,13 @@ import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.moire.ultrasonic.R
import org.moire.ultrasonic.adapters.HeaderViewBinder
import org.moire.ultrasonic.adapters.MultiTypeDiffAdapter
import org.moire.ultrasonic.adapters.TrackViewBinder
import org.moire.ultrasonic.adapters.TrackViewHolder
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.getTitle
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
@ -51,17 +51,18 @@ import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util
import timber.log.Timber
import java.util.Collections
import java.util.Random
import java.util.TreeSet
/**
* Displays a group of tracks, eg. the songs of an album, of a playlist etc.
* TODO: Refactor this fragment and model to extend the GenericListFragment
* TODO: Move Clickhandler into ViewBinders
* TODO: Migrate Album/artistsRow
* TODO: Wrong count (selectall)
* TODO: Handle updates (playstatus, download status)
*/
class TrackCollectionFragment :
MultiListFragment<MusicDirectory.Entry, MultiTypeDiffAdapter<Identifiable>>() {
private var header: View? = null
private var albumButtons: View? = null
private var emptyView: TextView? = null
private var selectButton: ImageView? = null
@ -84,7 +85,6 @@ class TrackCollectionFragment :
private var cancellationToken: CancellationToken? = null
override val listModel: TrackCollectionModel by viewModels()
private val random: Random = Random()
private var selectedSet: TreeSet<Long> = TreeSet()
@ -136,11 +136,6 @@ class TrackCollectionFragment :
updateDisplay(true)
}
header = LayoutInflater.from(context).inflate(
R.layout.select_album_header, listView,
false
)
listModel.currentList.observe(viewLifecycleOwner, defaultObserver)
listModel.songsForGenre.observe(viewLifecycleOwner, songsForGenreObserver)
@ -231,17 +226,25 @@ class TrackCollectionFragment :
}
viewAdapter.register(
HeaderViewBinder(
context = requireContext()
)
)
viewAdapter.register(
TrackViewBinder(
selectedSet = selectedSet,
checkable = true,
draggable = false,
context = context!!
context = requireContext()
)
)
enableButtons()
// Loads the data
updateDisplay(false)
}
@ -253,6 +256,7 @@ class TrackCollectionFragment :
}
private fun updateDisplay(refresh: Boolean) {
// FIXME: Use refresh
getLiveData(requireArguments())
}
@ -383,12 +387,32 @@ class TrackCollectionFragment :
}
}
private val viewHolders: List<TrackViewHolder>
get() {
val list: MutableList<TrackViewHolder> = mutableListOf()
for (i in 0 until listView!!.childCount) {
val vh = listView!!.findViewHolderForAdapterPosition(i)
if (vh is TrackViewHolder) {
list.add(vh)
}
}
return list
}
private val childCount: Int
get() {
if (listModel.showHeader) {
return listView!!.childCount - 1
} else {
return listView!!.childCount
}
}
private fun playAll(shuffle: Boolean = false, append: Boolean = false) {
var hasSubFolders = false
for (i in 0 until listView!!.childCount) {
val vh = listView!!.findViewHolderForAdapterPosition(i) as TrackViewHolder?
val entry = vh?.entry
for (vh in viewHolders) {
val entry = vh.entry
if (entry != null && entry.isDirectory) {
hasSubFolders = true
break
@ -427,20 +451,19 @@ class TrackCollectionFragment :
}
private fun selectAllOrNone() {
val someUnselected = selectedSet.size < listView!!.childCount
val someUnselected = selectedSet.size < childCount
selectAll(someUnselected, true)
}
private fun selectAll(selected: Boolean, toast: Boolean) {
val count = listView!!.childCount
var selectedCount = 0
listView!!
for (i in 0 until count) {
val vh = listView!!.findViewHolderForAdapterPosition(i) as TrackViewHolder
for (vh in viewHolders) {
val entry = vh.entry
if (entry != null && !entry.isDirectory && !entry.isVideo) {
@ -579,16 +602,19 @@ class TrackCollectionFragment :
private val defaultObserver = Observer(this::updateInterfaceWithEntries)
private fun updateInterfaceWithEntries(list: List<MusicDirectory.Entry>) {
private fun updateInterfaceWithEntries(newList: List<MusicDirectory.Entry>) {
val entryList: MutableList<MusicDirectory.Entry> = newList.toMutableList()
if (listModel.currentListIsSortable && Settings.shouldSortByDisc) {
Collections.sort(list, EntryByDiscAndTrackComparator())
Collections.sort(entryList, EntryByDiscAndTrackComparator())
}
var allVideos = true
var songCount = 0
for (entry in list) {
for (entry in entryList) {
if (!entry.isVideo) {
allVideos = false
}
@ -600,18 +626,6 @@ class TrackCollectionFragment :
val listSize = requireArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0)
if (songCount > 0) {
// if (listModel.showHeader) {
// val intentAlbumName = requireArguments().getString(Constants.INTENT_EXTRA_NAME_NAME)
// val directoryName = musicDirectory.name
// val header = createHeader(
// entries, intentAlbumName ?: directoryName,
// songCount
// )
//// if (header != null && listView!!.headerViewsCount == 0) {
//// listView!!.addHeaderView(header, null, false)
//// }
// }
pinButton!!.visibility = View.VISIBLE
unpinButton!!.visibility = View.VISIBLE
downloadButton!!.visibility = View.VISIBLE
@ -653,7 +667,7 @@ class TrackCollectionFragment :
playNextButton!!.visibility = View.GONE
playLastButton!!.visibility = View.GONE
if (listSize == 0 || list.size < listSize) {
if (listSize == 0 || entryList.size < listSize) {
albumButtons!!.visibility = View.GONE
} else {
moreButton!!.visibility = View.VISIBLE
@ -666,7 +680,7 @@ class TrackCollectionFragment :
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE
)
playAllButtonVisible = !(isAlbumList || list.isEmpty()) && !allVideos
playAllButtonVisible = !(isAlbumList || entryList.isEmpty()) && !allVideos
shareButtonVisible = !isOffline() && songCount > 0
// listView!!.removeHeaderView(emptyView!!)
@ -684,7 +698,18 @@ class TrackCollectionFragment :
shareButton!!.isVisible = shareButtonVisible
}
viewAdapter.submitList(list)
if (songCount > 0 && listModel.showHeader) {
var name = listModel.currentDirectory.value?.name
val intentAlbumName = requireArguments().getString(Constants.INTENT_EXTRA_NAME_NAME, "Name")!!
val albumHeader = AlbumHeader(newList, name?: intentAlbumName, songCount)
val mixedList: MutableList<Identifiable> = mutableListOf(albumHeader)
mixedList.addAll(entryList)
viewAdapter.submitList(mixedList)
} else {
viewAdapter.submitList(entryList)
}
val playAll = requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false)
if (playAll && songCount > 0) {
@ -699,77 +724,10 @@ class TrackCollectionFragment :
}
private fun createHeader(
entries: List<MusicDirectory.Entry>,
name: CharSequence?,
songCount: Int
): View? {
val coverArtView = header!!.findViewById<View>(R.id.select_album_art) as ImageView
val artworkSelection = random.nextInt(entries.size)
imageLoaderProvider.getImageLoader().loadImage(
coverArtView, entries[artworkSelection], false,
Util.getAlbumImageSize(context)
)
val albumHeader = AlbumHeader.processEntries(context, entries)
val titleView = header!!.findViewById<View>(R.id.select_album_title) as TextView
titleView.text = name ?: getTitle(this@TrackCollectionFragment) // getActionBarSubtitle());
// Don't show a header if all entries are videos
if (albumHeader.isAllVideo) {
return null
}
val artistView = header!!.findViewById<TextView>(R.id.select_album_artist)
val artist: String = when {
albumHeader.artists.size == 1 -> albumHeader.artists.iterator().next()
albumHeader.grandParents.size == 1 -> albumHeader.grandParents.iterator().next()
else -> resources.getString(R.string.common_various_artists)
}
artistView.text = artist
val genreView = header!!.findViewById<TextView>(R.id.select_album_genre)
val genre: String = if (albumHeader.genres.size == 1)
albumHeader.genres.iterator().next()
else
resources.getString(R.string.common_multiple_genres)
genreView.text = genre
val yearView = header!!.findViewById<TextView>(R.id.select_album_year)
val year: String = if (albumHeader.years.size == 1)
albumHeader.years.iterator().next().toString()
else
resources.getString(R.string.common_multiple_years)
yearView.text = year
val songCountView = header!!.findViewById<TextView>(R.id.select_album_song_count)
val songs = resources.getQuantityString(
R.plurals.select_album_n_songs, songCount,
songCount
)
songCountView.text = songs
val duration = Util.formatTotalDuration(albumHeader.totalDuration)
val durationView = header!!.findViewById<TextView>(R.id.select_album_duration)
durationView.text = duration
return header
}
private fun getSelectedSongs(): MutableList<MusicDirectory.Entry> {
val songs: MutableList<MusicDirectory.Entry> = mutableListOf()
for (i in 0 until listView!!.childCount) {
val vh = listView!!.findViewHolderForAdapterPosition(i) as TrackViewHolder? ?: continue
for (vh in viewHolders) {
if (vh.isChecked) {
songs.add(vh.entry!!)
}

View File

@ -871,7 +871,7 @@ object Util {
val descriptionBuilder = MediaDescriptionCompat.Builder()
val desc = readableEntryDescription(song)
var title = ""
val title: String
if (groupNameId != null)
descriptionBuilder.setExtras(

View File

@ -1,316 +1,316 @@
package org.moire.ultrasonic.view
import android.content.Context
import android.graphics.drawable.AnimationDrawable
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.Checkable
import android.widget.CheckedTextView
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.component.inject
import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.featureflags.Feature
import org.moire.ultrasonic.featureflags.FeatureStorage
import org.moire.ultrasonic.fragment.DownloadRowAdapter
import org.moire.ultrasonic.service.DownloadFile
import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.service.MusicServiceFactory
import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util
import timber.log.Timber
/**
* Used to display songs and videos in a `ListView`.
* TODO: Video List item
*/
class SongViewHolder(view: View, context: Context) : RecyclerView.ViewHolder(view), Checkable, KoinComponent {
var check: CheckedTextView = view.findViewById(R.id.song_check)
var rating: LinearLayout = view.findViewById(R.id.song_rating)
var fiveStar1: ImageView = view.findViewById(R.id.song_five_star_1)
var fiveStar2: ImageView = view.findViewById(R.id.song_five_star_2)
var fiveStar3: ImageView = view.findViewById(R.id.song_five_star_3)
var fiveStar4: ImageView = view.findViewById(R.id.song_five_star_4)
var fiveStar5: ImageView = view.findViewById(R.id.song_five_star_5)
var star: ImageView = view.findViewById(R.id.song_star)
var drag: ImageView = view.findViewById(R.id.song_drag)
var track: TextView = view.findViewById(R.id.song_track)
var title: TextView = view.findViewById(R.id.song_title)
var artist: TextView = view.findViewById(R.id.song_artist)
var duration: TextView = view.findViewById(R.id.song_duration)
var status: TextView = view.findViewById(R.id.song_status)
var entry: MusicDirectory.Entry? = null
private set
var downloadFile: DownloadFile? = null
private set
private var isMaximized = false
private var leftImage: Drawable? = null
private var previousLeftImageType: ImageType? = null
private var previousRightImageType: ImageType? = null
private var leftImageType: ImageType? = null
private var playing = false
private val features: FeatureStorage = get()
private val useFiveStarRating: Boolean = features.isFeatureEnabled(Feature.FIVE_STAR_RATING)
private val mediaPlayerController: MediaPlayerController by inject()
fun setSong(file: DownloadFile, checkable: Boolean, draggable: Boolean) {
val song = file.song
downloadFile = file
entry = song
val entryDescription = Util.readableEntryDescription(song)
artist.text = entryDescription.artist
title.text = entryDescription.title
duration.text = entryDescription.duration
if (Settings.shouldShowTrackNumber && song.track != null && song.track!! > 0) {
track.text = entryDescription.trackNumber
} else {
track.isVisible = false
}
check.isVisible = (checkable && !song.isVideo)
drag.isVisible = draggable
if (ActiveServerProvider.isOffline()) {
star.isVisible = false
rating.isVisible = false
} else {
setupStarButtons(song)
}
update()
}
private fun setupStarButtons(song: MusicDirectory.Entry) {
if (useFiveStarRating) {
star.isVisible = false
val rating = if (song.userRating == null) 0 else song.userRating!!
fiveStar1.setImageDrawable(
if (rating > 0) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
)
fiveStar2.setImageDrawable(
if (rating > 1) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
)
fiveStar3.setImageDrawable(
if (rating > 2) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
)
fiveStar4.setImageDrawable(
if (rating > 3) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
)
fiveStar5.setImageDrawable(
if (rating > 4) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
)
} else {
rating.isVisible = false
star.setImageDrawable(
if (song.starred) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
)
star.setOnClickListener {
val isStarred = song.starred
val id = song.id
if (!isStarred) {
star.setImageDrawable(DownloadRowAdapter.starDrawable)
song.starred = true
} else {
star.setImageDrawable(DownloadRowAdapter.starHollowDrawable)
song.starred = false
}
Thread {
val musicService = MusicServiceFactory.getMusicService()
try {
if (!isStarred) {
musicService.star(id, null, null)
} else {
musicService.unstar(id, null, null)
}
} catch (all: Exception) {
Timber.e(all)
}
}.start()
}
}
}
@Synchronized
// TDOD: Should be removed
fun update() {
val song = entry ?: return
updateDownloadStatus(downloadFile!!)
if (entry?.starred != true) {
if (star.drawable !== DownloadRowAdapter.starHollowDrawable) {
star.setImageDrawable(DownloadRowAdapter.starHollowDrawable)
}
} else {
if (star.drawable !== DownloadRowAdapter.starDrawable) {
star.setImageDrawable(DownloadRowAdapter.starDrawable)
}
}
val rating = entry?.userRating ?: 0
fiveStar1.setImageDrawable(
if (rating > 0) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
)
fiveStar2.setImageDrawable(
if (rating > 1) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
)
fiveStar3.setImageDrawable(
if (rating > 2) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
)
fiveStar4.setImageDrawable(
if (rating > 3) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
)
fiveStar5.setImageDrawable(
if (rating > 4) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
)
val playing = mediaPlayerController.currentPlaying === downloadFile
if (playing) {
if (!this.playing) {
this.playing = true
title.setCompoundDrawablesWithIntrinsicBounds(
DownloadRowAdapter.playingImage, null, null, null
)
}
} else {
if (this.playing) {
this.playing = false
title.setCompoundDrawablesWithIntrinsicBounds(
0, 0, 0, 0
)
}
}
}
fun updateDownloadStatus(downloadFile: DownloadFile) {
if (downloadFile.isWorkDone) {
val newLeftImageType =
if (downloadFile.isSaved) ImageType.Pin else ImageType.Downloaded
if (leftImageType != newLeftImageType) {
leftImage = if (downloadFile.isSaved) {
DownloadRowAdapter.pinImage
} else {
DownloadRowAdapter.downloadedImage
}
leftImageType = newLeftImageType
}
} else {
leftImageType = ImageType.None
leftImage = null
}
val rightImageType: ImageType
val rightImage: Drawable?
if (downloadFile.isDownloading && !downloadFile.isDownloadCancelled) {
status.text = Util.formatPercentage(downloadFile.progress.value!!)
rightImageType = ImageType.Downloading
rightImage = DownloadRowAdapter.downloadingImage
} else {
rightImageType = ImageType.None
rightImage = null
val statusText = status.text
if (!statusText.isNullOrEmpty()) status.text = null
}
if (previousLeftImageType != leftImageType || previousRightImageType != rightImageType) {
previousLeftImageType = leftImageType
previousRightImageType = rightImageType
status.setCompoundDrawablesWithIntrinsicBounds(
leftImage, null, rightImage, null
)
if (rightImage === DownloadRowAdapter.downloadingImage) {
// FIXME
val frameAnimation = rightImage as AnimationDrawable?
frameAnimation?.setVisible(true, true)
frameAnimation?.start()
}
}
}
// fun updateDownloadStatus2(
// downloadFile: DownloadFile,
// ) {
//package org.moire.ultrasonic.view
//
// var image: Drawable? = null
//import android.content.Context
//import android.graphics.drawable.AnimationDrawable
//import android.graphics.drawable.Drawable
//import android.view.View
//import android.widget.Checkable
//import android.widget.CheckedTextView
//import android.widget.ImageView
//import android.widget.LinearLayout
//import android.widget.TextView
//import androidx.core.view.isVisible
//import androidx.recyclerview.widget.RecyclerView
//import org.koin.core.component.KoinComponent
//import org.koin.core.component.get
//import org.koin.core.component.inject
//import org.moire.ultrasonic.R
//import org.moire.ultrasonic.data.ActiveServerProvider
//import org.moire.ultrasonic.domain.MusicDirectory
//import org.moire.ultrasonic.featureflags.Feature
//import org.moire.ultrasonic.featureflags.FeatureStorage
//import org.moire.ultrasonic.fragment.DownloadRowAdapter
//import org.moire.ultrasonic.service.DownloadFile
//import org.moire.ultrasonic.service.MediaPlayerController
//import org.moire.ultrasonic.service.MusicServiceFactory
//import org.moire.ultrasonic.util.Settings
//import org.moire.ultrasonic.util.Util
//import timber.log.Timber
//
// when (downloadFile.status.value) {
// DownloadStatus.DONE -> {
// image = if (downloadFile.isSaved) DownloadRowAdapter.pinImage else DownloadRowAdapter.downloadedImage
// status.text = null
///**
// * Used to display songs and videos in a `ListView`.
// * TODO: Video List item
// */
//class SongViewHolder(view: View, context: Context) : RecyclerView.ViewHolder(view), Checkable, KoinComponent {
// var check: CheckedTextView = view.findViewById(R.id.song_check)
// var rating: LinearLayout = view.findViewById(R.id.song_rating)
// var fiveStar1: ImageView = view.findViewById(R.id.song_five_star_1)
// var fiveStar2: ImageView = view.findViewById(R.id.song_five_star_2)
// var fiveStar3: ImageView = view.findViewById(R.id.song_five_star_3)
// var fiveStar4: ImageView = view.findViewById(R.id.song_five_star_4)
// var fiveStar5: ImageView = view.findViewById(R.id.song_five_star_5)
// var star: ImageView = view.findViewById(R.id.song_star)
// var drag: ImageView = view.findViewById(R.id.song_drag)
// var track: TextView = view.findViewById(R.id.song_track)
// var title: TextView = view.findViewById(R.id.song_title)
// var artist: TextView = view.findViewById(R.id.song_artist)
// var duration: TextView = view.findViewById(R.id.song_duration)
// var status: TextView = view.findViewById(R.id.song_status)
//
// var entry: MusicDirectory.Entry? = null
// private set
// var downloadFile: DownloadFile? = null
// private set
//
// private var isMaximized = false
// private var leftImage: Drawable? = null
// private var previousLeftImageType: ImageType? = null
// private var previousRightImageType: ImageType? = null
// private var leftImageType: ImageType? = null
// private var playing = false
//
// private val features: FeatureStorage = get()
// private val useFiveStarRating: Boolean = features.isFeatureEnabled(Feature.FIVE_STAR_RATING)
// private val mediaPlayerController: MediaPlayerController by inject()
//
// fun setSong(file: DownloadFile, checkable: Boolean, draggable: Boolean) {
// val song = file.song
// downloadFile = file
// entry = song
//
// val entryDescription = Util.readableEntryDescription(song)
//
// artist.text = entryDescription.artist
// title.text = entryDescription.title
// duration.text = entryDescription.duration
//
//
// if (Settings.shouldShowTrackNumber && song.track != null && song.track!! > 0) {
// track.text = entryDescription.trackNumber
// } else {
// track.isVisible = false
// }
// DownloadStatus.DOWNLOADING -> {
// status.text = Util.formatPercentage(downloadFile.progress.value!!)
// image = DownloadRowAdapter.downloadingImage
//
// check.isVisible = (checkable && !song.isVideo)
// drag.isVisible = draggable
//
// if (ActiveServerProvider.isOffline()) {
// star.isVisible = false
// rating.isVisible = false
// } else {
// setupStarButtons(song)
// }
// update()
// }
//
// private fun setupStarButtons(song: MusicDirectory.Entry) {
// if (useFiveStarRating) {
// star.isVisible = false
// val rating = if (song.userRating == null) 0 else song.userRating!!
// fiveStar1.setImageDrawable(
// if (rating > 0) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
// )
// fiveStar2.setImageDrawable(
// if (rating > 1) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
// )
// fiveStar3.setImageDrawable(
// if (rating > 2) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
// )
// fiveStar4.setImageDrawable(
// if (rating > 3) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
// )
// fiveStar5.setImageDrawable(
// if (rating > 4) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
// )
// } else {
// rating.isVisible = false
// star.setImageDrawable(
// if (song.starred) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
// )
//
// star.setOnClickListener {
// val isStarred = song.starred
// val id = song.id
//
// if (!isStarred) {
// star.setImageDrawable(DownloadRowAdapter.starDrawable)
// song.starred = true
// } else {
// star.setImageDrawable(DownloadRowAdapter.starHollowDrawable)
// song.starred = false
// }
// Thread {
// val musicService = MusicServiceFactory.getMusicService()
// try {
// if (!isStarred) {
// musicService.star(id, null, null)
// } else {
// musicService.unstar(id, null, null)
// }
// } catch (all: Exception) {
// Timber.e(all)
// }
// }.start()
// }
// else -> {
// status.text = null
// }
// }
//
// // TODO: Migrate the image animation stuff from SongView into this class
//
// if (image != null) {
// status.setCompoundDrawablesWithIntrinsicBounds(
// image, null, null, null
// @Synchronized
// // TDOD: Should be removed
// fun update() {
// val song = entry ?: return
//
// updateDownloadStatus(downloadFile!!)
//
// if (entry?.starred != true) {
// if (star.drawable !== DownloadRowAdapter.starHollowDrawable) {
// star.setImageDrawable(DownloadRowAdapter.starHollowDrawable)
// }
// } else {
// if (star.drawable !== DownloadRowAdapter.starDrawable) {
// star.setImageDrawable(DownloadRowAdapter.starDrawable)
// }
// }
//
// val rating = entry?.userRating ?: 0
// fiveStar1.setImageDrawable(
// if (rating > 0) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
// )
// fiveStar2.setImageDrawable(
// if (rating > 1) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
// )
// fiveStar3.setImageDrawable(
// if (rating > 2) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
// )
// fiveStar4.setImageDrawable(
// if (rating > 3) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
// )
// fiveStar5.setImageDrawable(
// if (rating > 4) DownloadRowAdapter.starDrawable else DownloadRowAdapter.starHollowDrawable
// )
//
// val playing = mediaPlayerController.currentPlaying === downloadFile
//
// if (playing) {
// if (!this.playing) {
// this.playing = true
// title.setCompoundDrawablesWithIntrinsicBounds(
// DownloadRowAdapter.playingImage, null, null, null
// )
// }
// } else {
// if (this.playing) {
// this.playing = false
// title.setCompoundDrawablesWithIntrinsicBounds(
// 0, 0, 0, 0
// )
// }
// }
// }
//
// if (image === DownloadRowAdapter.downloadingImage) {
// fun updateDownloadStatus(downloadFile: DownloadFile) {
//
// if (downloadFile.isWorkDone) {
// val newLeftImageType =
// if (downloadFile.isSaved) ImageType.Pin else ImageType.Downloaded
//
// if (leftImageType != newLeftImageType) {
// leftImage = if (downloadFile.isSaved) {
// DownloadRowAdapter.pinImage
// } else {
// DownloadRowAdapter.downloadedImage
// }
// leftImageType = newLeftImageType
// }
// } else {
// leftImageType = ImageType.None
// leftImage = null
// }
//
// val rightImageType: ImageType
// val rightImage: Drawable?
//
// if (downloadFile.isDownloading && !downloadFile.isDownloadCancelled) {
// status.text = Util.formatPercentage(downloadFile.progress.value!!)
//
// rightImageType = ImageType.Downloading
// rightImage = DownloadRowAdapter.downloadingImage
// } else {
// rightImageType = ImageType.None
// rightImage = null
//
// val statusText = status.text
// if (!statusText.isNullOrEmpty()) status.text = null
// }
//
// if (previousLeftImageType != leftImageType || previousRightImageType != rightImageType) {
// previousLeftImageType = leftImageType
// previousRightImageType = rightImageType
//
// status.setCompoundDrawablesWithIntrinsicBounds(
// leftImage, null, rightImage, null
// )
//
// if (rightImage === DownloadRowAdapter.downloadingImage) {
// // FIXME
//// val frameAnimation = image as AnimationDrawable
// val frameAnimation = rightImage as AnimationDrawable?
//
// frameAnimation?.setVisible(true, true)
// frameAnimation?.start()
// }
// }
// }
//
//// fun updateDownloadStatus2(
//// downloadFile: DownloadFile,
//// ) {
////
//// frameAnimation.setVisible(true, true)
//// frameAnimation.start()
//// var image: Drawable? = null
////
//// when (downloadFile.status.value) {
//// DownloadStatus.DONE -> {
//// image = if (downloadFile.isSaved) DownloadRowAdapter.pinImage else DownloadRowAdapter.downloadedImage
//// status.text = null
//// }
//// DownloadStatus.DOWNLOADING -> {
//// status.text = Util.formatPercentage(downloadFile.progress.value!!)
//// image = DownloadRowAdapter.downloadingImage
//// }
//// else -> {
//// status.text = null
//// }
//// }
////
//// // TODO: Migrate the image animation stuff from SongView into this class
////
//// if (image != null) {
//// status.setCompoundDrawablesWithIntrinsicBounds(
//// image, null, null, null
//// )
//// }
////
//// if (image === DownloadRowAdapter.downloadingImage) {
//// // FIXME
////// val frameAnimation = image as AnimationDrawable
//////
////// frameAnimation.setVisible(true, true)
////// frameAnimation.start()
//// }
//// }
//
// override fun setChecked(newStatus: Boolean) {
// check.isChecked = newStatus
// }
//
// override fun isChecked(): Boolean {
// return check.isChecked
// }
override fun setChecked(newStatus: Boolean) {
check.isChecked = newStatus
}
override fun isChecked(): Boolean {
return check.isChecked
}
override fun toggle() {
check.toggle()
}
fun maximizeOrMinimize() {
isMaximized = !isMaximized
title.isSingleLine = !isMaximized
artist.isSingleLine = !isMaximized
}
enum class ImageType {
None, Pin, Downloaded, Downloading
}
}
//
// override fun toggle() {
// check.toggle()
// }
//
// fun maximizeOrMinimize() {
// isMaximized = !isMaximized
//
// title.isSingleLine = !isMaximized
// artist.isSingleLine = !isMaximized
// }
//
// enum class ImageType {
// None, Pin, Downloaded, Downloading
// }
//
//
//}