From e81b1ef8c22cf447423e4ce29a53041324875152 Mon Sep 17 00:00:00 2001 From: tzugen Date: Mon, 18 Oct 2021 12:57:21 +0200 Subject: [PATCH] Add a HeaderView binder --- .../moire/ultrasonic/util/AlbumHeader.java | 114 ---- .../org/moire/ultrasonic/util/AlbumHeader.kt | 97 +++ .../ultrasonic/adapters/HeaderViewBinder.kt | 105 +++ .../ultrasonic/adapters/TrackViewBinder.kt | 6 +- .../ultrasonic/fragment/DownloadsFragment.kt | 102 +-- .../ultrasonic/fragment/MultiListFragment.kt | 14 +- .../fragment/TrackCollectionFragment.kt | 166 ++--- .../kotlin/org/moire/ultrasonic/util/Util.kt | 2 +- .../moire/ultrasonic/view/SongViewHolder.kt | 614 +++++++++--------- 9 files changed, 595 insertions(+), 625 deletions(-) delete mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/util/AlbumHeader.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/util/AlbumHeader.kt create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/HeaderViewBinder.kt diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/AlbumHeader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/AlbumHeader.java deleted file mode 100644 index 947b86f1..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/AlbumHeader.java +++ /dev/null @@ -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 artists; - private Set grandParents; - private Set genres; - private Set years; - - public boolean getIsAllVideo() - { - return isAllVideo; - } - - public long getTotalDuration() - { - return totalDuration; - } - - public Set getArtists() - { - return artists; - } - - public Set getGrandParents() - { - return this.grandParents; - } - - public Set getGenres() - { - return this.genres; - } - - public Set getYears() - { - return this.years; - } - - public AlbumHeader() - { - this.artists = new HashSet(); - this.grandParents = new HashSet(); - this.genres = new HashSet(); - this.years = new HashSet(); - - this.isAllVideo = true; - this.totalDuration = 0; - } - - public static AlbumHeader processEntries(Context context, Iterable 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); - } - } -} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/AlbumHeader.kt b/ultrasonic/src/main/java/org/moire/ultrasonic/util/AlbumHeader.kt new file mode 100644 index 00000000..522e94ef --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/AlbumHeader.kt @@ -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, + var name: String, + songCount: Int +): Identifiable { + var isAllVideo: Boolean + private set + + var totalDuration: Long + private set + + var childCount = 0 + + private val _artists: MutableSet + private val _grandParents: MutableSet + private val _genres: MutableSet + private val _years: MutableSet + + val artists: Set + get() = _artists + + val grandParents: Set + get() = _grandParents + + val genres: Set + get() = _genres + + val years: Set + 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) { + 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) + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/HeaderViewBinder.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/HeaderViewBinder.kt new file mode 100644 index 00000000..4b3f6fd0 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/HeaderViewBinder.kt @@ -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(), KoinComponent { + + private val weakContext: WeakReference = 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 + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/TrackViewBinder.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/TrackViewBinder.kt index e5e74d2d..1533f67c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/TrackViewBinder.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/adapters/TrackViewBinder.kt @@ -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, @@ -70,7 +66,7 @@ class TrackViewBinder( checkable = checkable, draggable = draggable ) - + // Observe download status // item.status.observe( // lifecycleOwner, diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/DownloadsFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/DownloadsFragment.kt index a3a69fe2..0af78788 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/DownloadsFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/DownloadsFragment.kt @@ -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() { +class DownloadsFragment : MultiListFragment>() { /** * 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( /** * 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 by lazy { + val adapter = MultiTypeDiffAdapter() + 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( } } -class DownloadRowAdapter( - itemList: List, - onItemClick: (DownloadFile) -> Unit, - onContextMenuClick: (MenuItem, DownloadFile) -> Boolean, - onMusicFolderUpdate: (String?) -> Unit, - val context: Context, - val lifecycleOwner: LifecycleOwner -) : GenericRowAdapter( - 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() diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MultiListFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MultiListFragment.kt index e3604620..18009be2 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MultiListFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MultiListFragment.kt @@ -71,20 +71,20 @@ abstract class MultiListFragment : 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 diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt index a2c379a7..1cf42767 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -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>() { - 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 = 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 + get() { + val list: MutableList = 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) { + private fun updateInterfaceWithEntries(newList: List) { + + val entryList: MutableList = 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 = 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, - name: CharSequence?, - songCount: Int - ): View? { - val coverArtView = header!!.findViewById(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(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(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(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(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(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(R.id.select_album_duration) - durationView.text = duration - - return header - } - private fun getSelectedSongs(): MutableList { val songs: MutableList = 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!!) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt index d84fb080..c35f4503 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt @@ -871,7 +871,7 @@ object Util { val descriptionBuilder = MediaDescriptionCompat.Builder() val desc = readableEntryDescription(song) - var title = "" + val title: String if (groupNameId != null) descriptionBuilder.setExtras( diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SongViewHolder.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SongViewHolder.kt index 020b9ac9..6a9c0db5 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SongViewHolder.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/view/SongViewHolder.kt @@ -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 -// } -// DownloadStatus.DOWNLOADING -> { -// status.text = Util.formatPercentage(downloadFile.progress.value!!) -// image = DownloadRowAdapter.downloadingImage -// } -// else -> { -// 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 // } // -// // TODO: Migrate the image animation stuff from SongView into this class +// check.isVisible = (checkable && !song.isVideo) +// drag.isVisible = draggable // -// if (image != null) { -// status.setCompoundDrawablesWithIntrinsicBounds( -// image, null, null, null +// 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 // ) -// } // -// if (image === DownloadRowAdapter.downloadingImage) { -// // FIXME -//// val frameAnimation = image as AnimationDrawable -//// -//// frameAnimation.setVisible(true, true) -//// frameAnimation.start() +// 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() +// } // } // } - - 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 - } - - -} \ No newline at end of file +// +// +// @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, +//// ) { +//// +//// 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 toggle() { +// check.toggle() +// } +// +// fun maximizeOrMinimize() { +// isMaximized = !isMaximized +// +// title.isSingleLine = !isMaximized +// artist.isSingleLine = !isMaximized +// } +// +// enum class ImageType { +// None, Pin, Downloaded, Downloading +// } +// +// +//} \ No newline at end of file