mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-10 16:50:57 +01:00
Migrate DownloadsFragment to new system
This commit is contained in:
parent
e81b1ef8c2
commit
d0e39efc50
@ -226,10 +226,10 @@ public class BookmarksFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
// Display toast: N tracks selected / N tracks unselected
|
||||
// Display toast: N tracks selected
|
||||
if (toast)
|
||||
{
|
||||
int toastResId = selected ? R.string.select_album_n_selected : R.string.select_album_n_unselected;
|
||||
int toastResId = R.string.select_album_n_selected;
|
||||
Util.toast(getContext(), getString(toastResId, selectedCount));
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ class AlbumHeader(
|
||||
get() = "HEADER"
|
||||
|
||||
override val longId: Long
|
||||
get() = id.hashCode().toLong()
|
||||
get() = -1L
|
||||
|
||||
override fun compareTo(other: Identifiable): Int {
|
||||
return this.longId.compareTo(other.longId)
|
||||
|
@ -1,24 +1,23 @@
|
||||
package org.moire.ultrasonic.adapters
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.MotionEvent
|
||||
import androidx.recyclerview.selection.ItemDetailsLookup
|
||||
import androidx.recyclerview.selection.ItemKeyProvider
|
||||
import androidx.recyclerview.selection.SelectionTracker
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.recyclerview.widget.AdapterListUpdateCallback
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.AsyncListDiffer
|
||||
import androidx.recyclerview.widget.AsyncListDiffer.ListListener
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.drakeet.multitype.MultiTypeAdapter
|
||||
import org.moire.ultrasonic.domain.Identifiable
|
||||
import timber.log.Timber
|
||||
import java.util.TreeSet
|
||||
|
||||
class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
||||
|
||||
val diffCallback = GenericDiffCallback<T>()
|
||||
var tracker: SelectionTracker<Long>? = null
|
||||
internal var selectedSet: TreeSet<Long> = TreeSet()
|
||||
internal var selectionRevision: MutableLiveData<Int> = MutableLiveData(0)
|
||||
|
||||
private val diffCallback = GenericDiffCallback<T>()
|
||||
|
||||
init {
|
||||
setHasStableIds(true)
|
||||
@ -28,10 +27,14 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
||||
return getItem(position).longId
|
||||
}
|
||||
|
||||
private fun getItem(position: Int): T {
|
||||
return mDiffer.currentList[position]
|
||||
}
|
||||
|
||||
override var items: List<Any>
|
||||
get() = getCurrentList()
|
||||
set(value) {
|
||||
throw Exception("You must use submitList() to add data to the MultiTypeDiffAdapter")
|
||||
throw IllegalAccessException("You must use submitList() to add data to the MultiTypeDiffAdapter")
|
||||
}
|
||||
|
||||
|
||||
@ -86,9 +89,7 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
||||
mDiffer.submitList(list, commitCallback)
|
||||
}
|
||||
|
||||
protected fun getItem(position: Int): T {
|
||||
return mDiffer.currentList[position]
|
||||
}
|
||||
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return mDiffer.currentList.size
|
||||
@ -130,8 +131,42 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
||||
// Void
|
||||
}
|
||||
|
||||
fun notifySelected(id: Long) {
|
||||
selectedSet.add(id)
|
||||
|
||||
// Update revision counter
|
||||
selectionRevision.postValue(selectionRevision.value!! + 1)
|
||||
}
|
||||
|
||||
fun notifyUnselected(id: Long) {
|
||||
selectedSet.remove(id)
|
||||
|
||||
// Update revision counter
|
||||
selectionRevision.postValue(selectionRevision.value!! + 1)
|
||||
}
|
||||
|
||||
fun setSelectionStatusOfAll(select: Boolean): Int {
|
||||
// Clear current selection
|
||||
selectedSet.clear()
|
||||
|
||||
// Update revision counter
|
||||
selectionRevision.postValue(selectionRevision.value!! + 1)
|
||||
|
||||
// Nothing to reselect
|
||||
if (!select) return 0
|
||||
|
||||
// Select them all
|
||||
getCurrentList().mapNotNullTo(selectedSet, { entry ->
|
||||
// Exclude any -1 ids, eg. headers and other UI elements
|
||||
entry.longId.takeIf { it != -1L }
|
||||
})
|
||||
|
||||
return selectedSet.count()
|
||||
}
|
||||
|
||||
fun isSelected(longId: Long): Boolean {
|
||||
return selectedSet.contains(longId)
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
@ -150,8 +185,6 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package org.moire.ultrasonic.adapters
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.drakeet.multitype.ItemViewBinder
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
@ -11,12 +12,13 @@ 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 timber.log.Timber
|
||||
|
||||
class TrackViewBinder(
|
||||
val selectedSet: MutableSet<Long>,
|
||||
val checkable: Boolean,
|
||||
val draggable: Boolean,
|
||||
context: Context
|
||||
context: Context,
|
||||
val lifecycleOwner: LifecycleOwner
|
||||
) : ItemViewBinder<Identifiable, TrackViewHolder>(), KoinComponent {
|
||||
|
||||
|
||||
@ -35,12 +37,10 @@ class TrackViewBinder(
|
||||
val contextMenuLayout = R.menu.artist_context_menu
|
||||
|
||||
private val downloader: Downloader by inject()
|
||||
|
||||
private val imageHelper: ImageHelper = ImageHelper(context)
|
||||
|
||||
|
||||
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): TrackViewHolder {
|
||||
return TrackViewHolder(inflater.inflate(layout, parent, false), selectedSet)
|
||||
return TrackViewHolder(inflater.inflate(layout, parent, false), adapter as MultiTypeDiffAdapter<Identifiable>)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: TrackViewHolder, item: Identifiable) {
|
||||
@ -64,23 +64,29 @@ class TrackViewBinder(
|
||||
holder.setSong(
|
||||
file = downloadFile,
|
||||
checkable = checkable,
|
||||
draggable = draggable
|
||||
draggable = draggable,
|
||||
holder.adapter.isSelected(item.longId)
|
||||
)
|
||||
|
||||
// Listen to changes in selection status and update ourselves
|
||||
holder.adapter.selectionRevision.observe(lifecycleOwner, {
|
||||
val newStatus = holder.adapter.isSelected(item.longId)
|
||||
|
||||
if (newStatus != holder.check.isChecked) holder.check.isChecked = newStatus
|
||||
})
|
||||
|
||||
// Observe download status
|
||||
// item.status.observe(
|
||||
// lifecycleOwner,
|
||||
// {
|
||||
// holder.updateDownloadStatus(item)
|
||||
// }
|
||||
// )
|
||||
//
|
||||
// item.progress.observe(
|
||||
// lifecycleOwner,
|
||||
// {
|
||||
// holder.updateDownloadStatus(item)
|
||||
// }
|
||||
// )
|
||||
downloadFile.status.observe(lifecycleOwner, {
|
||||
Timber.w("CAUGHT STATUS CHANGE")
|
||||
holder.updateDownloadStatus(downloadFile)
|
||||
}
|
||||
)
|
||||
|
||||
downloadFile.progress.observe(lifecycleOwner, {
|
||||
Timber.w("CAUGHT PROGRESS CHANGE")
|
||||
holder.updateDownloadStatus(downloadFile)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
@ -9,12 +9,14 @@ import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||
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.Identifiable
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.featureflags.Feature
|
||||
import org.moire.ultrasonic.featureflags.FeatureStorage
|
||||
@ -29,8 +31,9 @@ import timber.log.Timber
|
||||
* Used to display songs and videos in a `ListView`.
|
||||
* TODO: Video List item
|
||||
*/
|
||||
class TrackViewHolder(val view: View, val selectedSet: MutableSet<Long>) :
|
||||
class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifiable>) :
|
||||
RecyclerView.ViewHolder(view), Checkable, KoinComponent {
|
||||
|
||||
var check: CheckedTextView = view.findViewById(R.id.song_check)
|
||||
var rating: LinearLayout = view.findViewById(R.id.song_rating)
|
||||
private var fiveStar1: ImageView = view.findViewById(R.id.song_five_star_1)
|
||||
@ -99,6 +102,7 @@ class TrackViewHolder(val view: View, val selectedSet: MutableSet<Long>) :
|
||||
}
|
||||
|
||||
check.isVisible = (checkable && !song.isVideo)
|
||||
check.isChecked = isSelected
|
||||
drag.isVisible = draggable
|
||||
|
||||
if (ActiveServerProvider.isOffline()) {
|
||||
@ -109,9 +113,6 @@ class TrackViewHolder(val view: View, val selectedSet: MutableSet<Long>) :
|
||||
}
|
||||
|
||||
update()
|
||||
|
||||
isChecked = isSelected
|
||||
|
||||
}
|
||||
|
||||
private fun setupStarButtons(song: MusicDirectory.Entry) {
|
||||
@ -219,7 +220,6 @@ class TrackViewHolder(val view: View, val selectedSet: MutableSet<Long>) :
|
||||
}
|
||||
|
||||
fun updateDownloadStatus(downloadFile: DownloadFile) {
|
||||
|
||||
if (downloadFile.isWorkDone) {
|
||||
val newLeftImageType =
|
||||
if (downloadFile.isSaved) ImageType.Pin else ImageType.Downloaded
|
||||
@ -274,10 +274,9 @@ class TrackViewHolder(val view: View, val selectedSet: MutableSet<Long>) :
|
||||
|
||||
override fun setChecked(newStatus: Boolean) {
|
||||
if (newStatus) {
|
||||
selectedSet.add(downloadFile!!.longId)
|
||||
Timber.d("Selectedset %s", selectedSet.toString())
|
||||
adapter.notifySelected(downloadFile!!.longId)
|
||||
} else {
|
||||
selectedSet.remove(downloadFile!!.longId)
|
||||
adapter.notifyUnselected(downloadFile!!.longId)
|
||||
}
|
||||
check.isChecked = newStatus
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package org.moire.ultrasonic.fragment
|
||||
import android.app.Application
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.koin.core.component.inject
|
||||
@ -13,9 +14,8 @@ 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 java.util.TreeSet
|
||||
|
||||
class DownloadsFragment : MultiListFragment<DownloadFile, MultiTypeDiffAdapter<Identifiable>>() {
|
||||
class DownloadsFragment : MultiListFragment<DownloadFile>() {
|
||||
|
||||
/**
|
||||
* The ViewModel to use to get the data
|
||||
@ -36,22 +36,6 @@ class DownloadsFragment : MultiListFragment<DownloadFile, MultiTypeDiffAdapter<I
|
||||
return listModel.getList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the Adapter for the RecyclerView with a lazy delegate
|
||||
*/
|
||||
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 {
|
||||
// Do nothing
|
||||
return true
|
||||
@ -64,6 +48,21 @@ class DownloadsFragment : MultiListFragment<DownloadFile, MultiTypeDiffAdapter<I
|
||||
override fun setTitle(title: String?) {
|
||||
FragmentTitle.setTitle(this, Util.appContext().getString(R.string.menu_downloads))
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewAdapter.register(
|
||||
TrackViewBinder(
|
||||
checkable = true,
|
||||
draggable = false,
|
||||
context = requireContext(),
|
||||
lifecycleOwner = viewLifecycleOwner
|
||||
)
|
||||
)
|
||||
|
||||
viewAdapter.submitList(listModel.getList().value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -16,6 +16,7 @@ import com.drakeet.multitype.MultiTypeAdapter
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.adapters.MultiTypeDiffAdapter
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.GenericEntry
|
||||
@ -33,7 +34,7 @@ import org.moire.ultrasonic.view.SelectMusicFolderView
|
||||
* @param T: The type of data which will be used (must extend GenericEntry)
|
||||
* @param TA: The Adapter to use (must extend GenericRowAdapter)
|
||||
*/
|
||||
abstract class MultiListFragment<T : Identifiable, TA : MultiTypeAdapter> : Fragment() {
|
||||
abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
||||
internal val activeServerProvider: ActiveServerProvider by inject()
|
||||
internal val serverSettingsModel: ServerSettingsModel by viewModel()
|
||||
internal val imageLoaderProvider: ImageLoaderProvider by inject()
|
||||
@ -47,7 +48,9 @@ abstract class MultiListFragment<T : Identifiable, TA : MultiTypeAdapter> : Frag
|
||||
* The Adapter for the RecyclerView
|
||||
* Recommendation: Implement this as a lazy delegate
|
||||
*/
|
||||
internal abstract val viewAdapter: TA
|
||||
internal val viewAdapter: MultiTypeDiffAdapter<Identifiable> by lazy {
|
||||
MultiTypeDiffAdapter()
|
||||
}
|
||||
|
||||
/**
|
||||
* The ViewModel to use to get the data
|
||||
@ -144,9 +147,9 @@ abstract class MultiListFragment<T : Identifiable, TA : MultiTypeAdapter> : Frag
|
||||
liveDataItems = getLiveData(arguments)
|
||||
|
||||
// Register an observer to update our UI when the data changes
|
||||
// liveDataItems.observe(viewLifecycleOwner, {
|
||||
// newItems -> viewAdapter.submitList(newItems)
|
||||
// })
|
||||
liveDataItems.observe(viewLifecycleOwner, {
|
||||
newItems -> viewAdapter.submitList(newItems)
|
||||
})
|
||||
|
||||
// Setup the Music folder handling
|
||||
listModel.getMusicFolders().observe(viewLifecycleOwner, musicFolderObserver)
|
||||
|
@ -16,7 +16,6 @@ import kotlinx.coroutines.withContext
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.adapters.ServerRowAdapter
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.fragment.EditServerFragment.Companion.EDIT_SERVER_INTENT_INDEX
|
||||
import org.moire.ultrasonic.service.MediaPlayerController
|
||||
|
@ -34,7 +34,6 @@ 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
|
||||
@ -51,7 +50,6 @@ import org.moire.ultrasonic.util.Settings
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import timber.log.Timber
|
||||
import java.util.Collections
|
||||
import java.util.TreeSet
|
||||
|
||||
/**
|
||||
* Displays a group of tracks, eg. the songs of an album, of a playlist etc.
|
||||
@ -61,7 +59,7 @@ import java.util.TreeSet
|
||||
* TODO: Handle updates (playstatus, download status)
|
||||
*/
|
||||
class TrackCollectionFragment :
|
||||
MultiListFragment<MusicDirectory.Entry, MultiTypeDiffAdapter<Identifiable>>() {
|
||||
MultiListFragment<MusicDirectory.Entry>() {
|
||||
|
||||
private var albumButtons: View? = null
|
||||
private var emptyView: TextView? = null
|
||||
@ -86,8 +84,6 @@ class TrackCollectionFragment :
|
||||
|
||||
override val listModel: TrackCollectionModel by viewModels()
|
||||
|
||||
private var selectedSet: TreeSet<Long> = TreeSet()
|
||||
|
||||
/**
|
||||
* The id of the main layout
|
||||
*/
|
||||
@ -111,19 +107,6 @@ class TrackCollectionFragment :
|
||||
override val itemClickTarget: Int = R.id.trackCollectionFragment
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
Util.applyTheme(this.context)
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.track_list, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
cancellationToken = CancellationToken()
|
||||
@ -136,7 +119,7 @@ class TrackCollectionFragment :
|
||||
updateDisplay(true)
|
||||
}
|
||||
|
||||
listModel.currentList.observe(viewLifecycleOwner, defaultObserver)
|
||||
listModel.currentList.observe(viewLifecycleOwner, updateInterfaceWithEntries)
|
||||
listModel.songsForGenre.observe(viewLifecycleOwner, songsForGenreObserver)
|
||||
|
||||
// listView!!.setOnItemClickListener { parent, theView, position, _ ->
|
||||
@ -185,9 +168,11 @@ class TrackCollectionFragment :
|
||||
selectButton!!.setOnClickListener {
|
||||
selectAllOrNone()
|
||||
}
|
||||
|
||||
playNowButton!!.setOnClickListener {
|
||||
playNow(false)
|
||||
}
|
||||
|
||||
playNextButton!!.setOnClickListener {
|
||||
downloadHandler.download(
|
||||
this@TrackCollectionFragment, append = true,
|
||||
@ -195,18 +180,23 @@ class TrackCollectionFragment :
|
||||
songs = getSelectedSongs()
|
||||
)
|
||||
}
|
||||
|
||||
playLastButton!!.setOnClickListener {
|
||||
playNow(true)
|
||||
}
|
||||
|
||||
pinButton!!.setOnClickListener {
|
||||
downloadBackground(true)
|
||||
}
|
||||
|
||||
unpinButton!!.setOnClickListener {
|
||||
unpin()
|
||||
}
|
||||
|
||||
downloadButton!!.setOnClickListener {
|
||||
downloadBackground(false)
|
||||
}
|
||||
|
||||
deleteButton!!.setOnClickListener {
|
||||
delete()
|
||||
}
|
||||
@ -214,7 +204,6 @@ class TrackCollectionFragment :
|
||||
registerForContextMenu(listView!!)
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
|
||||
// Create a View Manager
|
||||
viewManager = LinearLayoutManager(this.context)
|
||||
|
||||
@ -225,7 +214,6 @@ class TrackCollectionFragment :
|
||||
adapter = viewAdapter
|
||||
}
|
||||
|
||||
|
||||
viewAdapter.register(
|
||||
HeaderViewBinder(
|
||||
context = requireContext()
|
||||
@ -234,16 +222,20 @@ class TrackCollectionFragment :
|
||||
|
||||
viewAdapter.register(
|
||||
TrackViewBinder(
|
||||
selectedSet = selectedSet,
|
||||
checkable = true,
|
||||
draggable = false,
|
||||
context = requireContext()
|
||||
context = requireContext(),
|
||||
lifecycleOwner = viewLifecycleOwner
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
enableButtons()
|
||||
|
||||
// Update the buttons when the selection has changed
|
||||
viewAdapter.selectionRevision.observe(viewLifecycleOwner, {
|
||||
enableButtons()
|
||||
})
|
||||
|
||||
// Loads the data
|
||||
updateDisplay(false)
|
||||
}
|
||||
@ -387,33 +379,24 @@ 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
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the underlying list
|
||||
*/
|
||||
private val childCount: Int
|
||||
get() {
|
||||
val count = viewAdapter.getCurrentList().count()
|
||||
if (listModel.showHeader) {
|
||||
return listView!!.childCount - 1
|
||||
return count - 1
|
||||
} else {
|
||||
return listView!!.childCount
|
||||
return count
|
||||
}
|
||||
}
|
||||
|
||||
private fun playAll(shuffle: Boolean = false, append: Boolean = false) {
|
||||
var hasSubFolders = false
|
||||
|
||||
for (vh in viewHolders) {
|
||||
val entry = vh.entry
|
||||
if (entry != null && entry.isDirectory) {
|
||||
for (item in viewAdapter.getCurrentList()) {
|
||||
if (item is MusicDirectory.Entry && item.isDirectory) {
|
||||
hasSubFolders = true
|
||||
break
|
||||
}
|
||||
@ -436,7 +419,6 @@ class TrackCollectionFragment :
|
||||
isArtist = isArtist
|
||||
)
|
||||
} else {
|
||||
selectAll(selected = true, toast = false)
|
||||
downloadHandler.download(
|
||||
fragment = this,
|
||||
append = append,
|
||||
@ -444,49 +426,38 @@ class TrackCollectionFragment :
|
||||
autoPlay = !append,
|
||||
playNext = false,
|
||||
shuffle = shuffle,
|
||||
songs = getSelectedSongs()
|
||||
songs = getAllSongs()
|
||||
)
|
||||
selectAll(selected = false, toast = false)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun getAllSongs(): List<MusicDirectory.Entry> {
|
||||
return viewAdapter.getCurrentList().filter {
|
||||
it is MusicDirectory.Entry && !it.isDirectory
|
||||
} as List<MusicDirectory.Entry>
|
||||
}
|
||||
|
||||
private fun selectAllOrNone() {
|
||||
val someUnselected = selectedSet.size < childCount
|
||||
val someUnselected = viewAdapter.selectedSet.size < childCount
|
||||
|
||||
selectAll(someUnselected, true)
|
||||
|
||||
}
|
||||
|
||||
private fun selectAll(selected: Boolean, toast: Boolean) {
|
||||
var selectedCount = viewAdapter.selectedSet.size * -1
|
||||
|
||||
var selectedCount = 0
|
||||
selectedCount += viewAdapter.setSelectionStatusOfAll(selected)
|
||||
|
||||
listView!!
|
||||
|
||||
for (vh in viewHolders) {
|
||||
val entry = vh.entry
|
||||
|
||||
if (entry != null && !entry.isDirectory && !entry.isVideo) {
|
||||
vh.isChecked = selected
|
||||
selectedCount++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Display toast: N tracks selected / N tracks unselected
|
||||
// Display toast: N tracks selected
|
||||
if (toast) {
|
||||
val toastResId = if (selected)
|
||||
R.string.select_album_n_selected
|
||||
else
|
||||
R.string.select_album_n_unselected
|
||||
Util.toast(activity, getString(toastResId, selectedCount))
|
||||
val toastResId = R.string.select_album_n_selected
|
||||
Util.toast(activity, getString(toastResId, selectedCount.coerceAtLeast(0)))
|
||||
}
|
||||
|
||||
enableButtons()
|
||||
}
|
||||
|
||||
private fun enableButtons() {
|
||||
val selection = getSelectedSongs()
|
||||
private fun enableButtons(selection: List<MusicDirectory.Entry> = getSelectedSongs()) {
|
||||
val enabled = selection.isNotEmpty()
|
||||
var unpinEnabled = false
|
||||
var deleteEnabled = false
|
||||
@ -517,8 +488,7 @@ class TrackCollectionFragment :
|
||||
var songs = getSelectedSongs()
|
||||
|
||||
if (songs.isEmpty()) {
|
||||
selectAll(selected = true, toast = false)
|
||||
songs = getSelectedSongs()
|
||||
songs = getAllSongs()
|
||||
}
|
||||
|
||||
downloadBackground(save, songs)
|
||||
@ -596,15 +566,12 @@ class TrackCollectionFragment :
|
||||
Navigation.findNavController(requireView())
|
||||
.navigate(R.id.trackCollectionFragment, bundle)
|
||||
}
|
||||
|
||||
//updateInterfaceWithEntries(musicDirectory)
|
||||
}
|
||||
|
||||
private val defaultObserver = Observer(this::updateInterfaceWithEntries)
|
||||
|
||||
private fun updateInterfaceWithEntries(newList: List<MusicDirectory.Entry>) {
|
||||
private val updateInterfaceWithEntries = Observer<List<MusicDirectory.Entry>> {
|
||||
|
||||
val entryList: MutableList<MusicDirectory.Entry> = newList.toMutableList()
|
||||
val entryList: MutableList<MusicDirectory.Entry> = it.toMutableList()
|
||||
|
||||
if (listModel.currentListIsSortable && Settings.shouldSortByDisc) {
|
||||
Collections.sort(entryList, EntryByDiscAndTrackComparator())
|
||||
@ -683,6 +650,7 @@ class TrackCollectionFragment :
|
||||
playAllButtonVisible = !(isAlbumList || entryList.isEmpty()) && !allVideos
|
||||
shareButtonVisible = !isOffline() && songCount > 0
|
||||
|
||||
// TODO!!
|
||||
// listView!!.removeHeaderView(emptyView!!)
|
||||
// if (entries.isEmpty()) {
|
||||
// emptyView!!.text = getString(R.string.select_album_empty)
|
||||
@ -700,9 +668,9 @@ class TrackCollectionFragment :
|
||||
|
||||
|
||||
if (songCount > 0 && listModel.showHeader) {
|
||||
var name = listModel.currentDirectory.value?.name
|
||||
val name = listModel.currentDirectory.value?.name
|
||||
val intentAlbumName = requireArguments().getString(Constants.INTENT_EXTRA_NAME_NAME, "Name")!!
|
||||
val albumHeader = AlbumHeader(newList, name?: intentAlbumName, songCount)
|
||||
val albumHeader = AlbumHeader(it, name?: intentAlbumName, songCount)
|
||||
val mixedList: MutableList<Identifiable> = mutableListOf(albumHeader)
|
||||
mixedList.addAll(entryList)
|
||||
viewAdapter.submitList(mixedList)
|
||||
@ -724,26 +692,17 @@ class TrackCollectionFragment :
|
||||
|
||||
}
|
||||
|
||||
private fun getSelectedSongs(): MutableList<MusicDirectory.Entry> {
|
||||
val songs: MutableList<MusicDirectory.Entry> = mutableListOf()
|
||||
|
||||
for (vh in viewHolders) {
|
||||
if (vh.isChecked) {
|
||||
songs.add(vh.entry!!)
|
||||
}
|
||||
private fun getSelectedSongs(): List<MusicDirectory.Entry> {
|
||||
// Walk through selected set and get the Entries based on the saved ids.
|
||||
return viewAdapter.getCurrentList().mapNotNull {
|
||||
if (it is MusicDirectory.Entry && viewAdapter.isSelected(it.longId))
|
||||
it
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
for (key in selectedSet) {
|
||||
songs.add(viewAdapter.getCurrentList().findLast {
|
||||
it.longId == key
|
||||
} as MusicDirectory.Entry)
|
||||
}
|
||||
return songs
|
||||
}
|
||||
|
||||
override val viewAdapter: MultiTypeDiffAdapter<Identifiable> by lazy {
|
||||
MultiTypeDiffAdapter()
|
||||
}
|
||||
|
||||
|
||||
override fun setTitle(title: String?) {
|
||||
setTitle(this@TrackCollectionFragment, title)
|
||||
|
@ -34,7 +34,6 @@ class TrackCollectionModel(application: Application) : GenericListModel(applicat
|
||||
val currentDirectory: MutableLiveData<MusicDirectory> = MutableLiveData()
|
||||
val currentList: MutableLiveData<List<MusicDirectory.Entry>> = MutableLiveData()
|
||||
val songsForGenre: MutableLiveData<MusicDirectory> = MutableLiveData()
|
||||
private val downloader: Downloader by inject()
|
||||
|
||||
suspend fun getMusicFolders(refresh: Boolean) {
|
||||
withContext(Dispatchers.IO) {
|
||||
|
@ -139,6 +139,8 @@ class DownloadFile(
|
||||
Util.delete(completeFile)
|
||||
Util.delete(saveFile)
|
||||
|
||||
status.postValue(DownloadStatus.IDLE)
|
||||
|
||||
Util.scanMedia(saveFile)
|
||||
}
|
||||
|
||||
@ -150,6 +152,7 @@ class DownloadFile(
|
||||
saveFile.name, completeFile.name
|
||||
)
|
||||
}
|
||||
status.postValue(DownloadStatus.DONE)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,7 +130,6 @@
|
||||
<string name="search.title">Hledat</string>
|
||||
<string name="select_album.empty">Média nenalezena</string>
|
||||
<string name="select_album.n_selected">%d skladeb označeno.</string>
|
||||
<string name="select_album.n_unselected">%d skladeb odznačeno.</string>
|
||||
<string name="select_album.no_network">Varování: Připojení nedostupné.</string>
|
||||
<string name="select_album.no_sdcard">Chyba: SD karta nedostupná.</string>
|
||||
<string name="select_album.play_all">Přehrát vše</string>
|
||||
|
@ -128,8 +128,7 @@
|
||||
<string name="search.songs">Titel</string>
|
||||
<string name="search.title">Suche</string>
|
||||
<string name="select_album.empty">Keine Medien gefunden</string>
|
||||
<string name="select_album.n_selected">%d Titel ausgewählt.</string>
|
||||
<string name="select_album.n_unselected">%d Titel abgewählt.</string>
|
||||
<string name="select_album.n_selected">%d Titel ausgewählt</string>
|
||||
<string name="select_album.no_network">Warnung: kein Netz.</string>
|
||||
<string name="select_album.no_sdcard">Fehler: Keine SD Karte verfügbar.</string>
|
||||
<string name="select_album.play_all">Alles wiedergeben</string>
|
||||
|
@ -145,7 +145,6 @@
|
||||
<string name="search.title">Buscar</string>
|
||||
<string name="select_album.empty">No se han encontrado medios</string>
|
||||
<string name="select_album.n_selected">%d pista(s) seleccionada(s).</string>
|
||||
<string name="select_album.n_unselected">%d pista(s) deseleccionada(s).</string>
|
||||
<string name="select_album.no_network">Atención: No hay red disponible.</string>
|
||||
<string name="select_album.no_sdcard">Error: No hay tarjeta SD disponible.</string>
|
||||
<string name="select_album.play_all">Reproducir todo</string>
|
||||
|
@ -142,7 +142,6 @@
|
||||
<string name="search.title">Recherche</string>
|
||||
<string name="select_album.empty">Aucun titre trouvé</string>
|
||||
<string name="select_album.n_selected">%d pistes sélectionnées.</string>
|
||||
<string name="select_album.n_unselected">%d pistes non sélectionnés.</string>
|
||||
<string name="select_album.no_network">Avertissement : Aucun réseau disponible.</string>
|
||||
<string name="select_album.no_sdcard">Erreur : Aucune carte SD disponible.</string>
|
||||
<string name="select_album.play_all">Tout jouer</string>
|
||||
|
@ -140,7 +140,6 @@
|
||||
<string name="search.title">Keresés</string>
|
||||
<string name="select_album.empty">Nem található média!</string>
|
||||
<string name="select_album.n_selected">%d dal kijelölve.</string>
|
||||
<string name="select_album.n_unselected">%d dal visszavonva.</string>
|
||||
<string name="select_album.no_network">Figyelem: Hálózat nem áll rendelkezésre!</string>
|
||||
<string name="select_album.no_sdcard">Hiba: SD kártya nem áll rendelkezésre!</string>
|
||||
<string name="select_album.play_all">Összes lejátszása</string>
|
||||
|
@ -126,7 +126,6 @@
|
||||
<string name="search.title">Cerca</string>
|
||||
<string name="select_album.empty">Nessun media trovato</string>
|
||||
<string name="select_album.n_selected">%dtracce selezionate.</string>
|
||||
<string name="select_album.n_unselected">%d tracce non selezionate.</string>
|
||||
<string name="select_album.no_network">Attenzione: nessuna rete disponibile.</string>
|
||||
<string name="select_album.no_sdcard">Errore: Nessuna memoria SD disponibile.</string>
|
||||
<string name="select_album.play_all">Riproduci tutto</string>
|
||||
|
@ -145,7 +145,6 @@
|
||||
<string name="search.title">Zoeken</string>
|
||||
<string name="select_album.empty">Geen media gevonden</string>
|
||||
<string name="select_album.n_selected">%d nummers geselecteerd.</string>
|
||||
<string name="select_album.n_unselected">%d nummers gedeselecteerd.</string>
|
||||
<string name="select_album.no_network">Waarschuwing: geen internetverbinding.</string>
|
||||
<string name="select_album.no_sdcard">Fout: geen SD-kaart beschikbaar.</string>
|
||||
<string name="select_album.play_all">Alles afspelen</string>
|
||||
|
@ -128,7 +128,6 @@
|
||||
<string name="search.title">Wyszukiwanie</string>
|
||||
<string name="select_album.empty">Brak mediów</string>
|
||||
<string name="select_album.n_selected">Zaznaczono %d utworów.</string>
|
||||
<string name="select_album.n_unselected">Odznaczono %d utworów.</string>
|
||||
<string name="select_album.no_network">Uwaga: sieć niedostępna.</string>
|
||||
<string name="select_album.no_sdcard">Błąd: Niedostępna karta SD.</string>
|
||||
<string name="select_album.play_all">Odtwórz wszystkie</string>
|
||||
|
@ -142,7 +142,6 @@
|
||||
<string name="search.title">Pesquisar</string>
|
||||
<string name="select_album.empty">Nenhuma mídia encontrada</string>
|
||||
<string name="select_album.n_selected">%d faixas selecionadas.</string>
|
||||
<string name="select_album.n_unselected">%d faixas desselecionadas.</string>
|
||||
<string name="select_album.no_network">Aviso: Nenhuma rede disponível.</string>
|
||||
<string name="select_album.no_sdcard">Erro: Nenhum cartão SD disponível.</string>
|
||||
<string name="select_album.play_all">Tocar Tudo</string>
|
||||
|
@ -128,7 +128,6 @@
|
||||
<string name="search.title">Pesquisar</string>
|
||||
<string name="select_album.empty">Nenhuma mídia encontrada</string>
|
||||
<string name="select_album.n_selected">%d faixas selecionadas.</string>
|
||||
<string name="select_album.n_unselected">%d faixas desselecionadas.</string>
|
||||
<string name="select_album.no_network">Aviso: Nenhuma rede disponível.</string>
|
||||
<string name="select_album.no_sdcard">Erro: Nenhum cartão SD disponível.</string>
|
||||
<string name="select_album.play_all">Tocar Tudo</string>
|
||||
|
@ -142,7 +142,6 @@
|
||||
<string name="search.title">Поиск</string>
|
||||
<string name="select_album.empty">Медиа не найдена</string>
|
||||
<string name="select_album.n_selected">%d треки выбраны.</string>
|
||||
<string name="select_album.n_unselected">%d треки не выбраны.</string>
|
||||
<string name="select_album.no_network">Предупреждение: сеть недоступна.</string>
|
||||
<string name="select_album.no_sdcard">Ошибка: нет SD-карты</string>
|
||||
<string name="select_album.play_all">Воспроизвести все</string>
|
||||
|
@ -141,7 +141,6 @@
|
||||
<string name="search.title">搜索</string>
|
||||
<string name="select_album.empty">找不到歌曲</string>
|
||||
<string name="select_album.n_selected">已选择 %d 首曲目。</string>
|
||||
<string name="select_album.n_unselected">未选择 %d 首曲目。</string>
|
||||
<string name="select_album.no_network">警告:网络不可用</string>
|
||||
<string name="select_album.no_sdcard">错误:没有SD卡</string>
|
||||
<string name="select_album.play_all">播放所有</string>
|
||||
|
@ -146,8 +146,7 @@
|
||||
<string name="search.songs">Songs</string>
|
||||
<string name="search.title">Search</string>
|
||||
<string name="select_album.empty">No media found</string>
|
||||
<string name="select_album.n_selected">%d tracks selected.</string>
|
||||
<string name="select_album.n_unselected">%d tracks unselected.</string>
|
||||
<string name="select_album.n_selected">%d tracks selected</string>
|
||||
<string name="select_album.no_network">Warning: No network available.</string>
|
||||
<string name="select_album.no_sdcard">Error: No SD card available.</string>
|
||||
<string name="select_album.play_all">Play All</string>
|
||||
@ -454,24 +453,24 @@
|
||||
<item quantity="other">%d songs</item>
|
||||
</plurals>
|
||||
<plurals name="select_album_n_songs_pinned">
|
||||
<item quantity="one">%d song selected to be pinned.</item>
|
||||
<item quantity="other">%d songs selected to be pinned.</item>
|
||||
<item quantity="one">%d song selected to be pinned</item>
|
||||
<item quantity="other">%d songs selected to be pinned</item>
|
||||
</plurals>
|
||||
<plurals name="select_album_n_songs_downloaded">
|
||||
<item quantity="one">%d song selected to be downloaded.</item>
|
||||
<item quantity="other">%d songs selected to be downloaded.</item>
|
||||
<item quantity="one">%d song selected to be downloaded</item>
|
||||
<item quantity="other">%d songs selected to be downloaded</item>
|
||||
</plurals>
|
||||
<plurals name="select_album_n_songs_unpinned">
|
||||
<item quantity="one">%d song selected to be unpinned.</item>
|
||||
<item quantity="other">%d songs selected to be unpinned.</item>
|
||||
<item quantity="one">%d song unpinned</item>
|
||||
<item quantity="other">%d songs unpinned</item>
|
||||
</plurals>
|
||||
<plurals name="select_album_n_songs_added">
|
||||
<item quantity="one">%d song added to the end of play queue.</item>
|
||||
<item quantity="other">%d songs added to the end of play queue.</item>
|
||||
<item quantity="one">%d song added to the end of play queue</item>
|
||||
<item quantity="other">%d songs added to the end of play queue</item>
|
||||
</plurals>
|
||||
<plurals name="select_album_n_songs_play_next">
|
||||
<item quantity="one">%d song inserted after current song.</item>
|
||||
<item quantity="other">%d songs inserted after current song.</item>
|
||||
<item quantity="one">%d song inserted after current song</item>
|
||||
<item quantity="other">%d songs inserted after current song</item>
|
||||
</plurals>
|
||||
<plurals name="select_album_donate_dialog_n_trial_days_left">
|
||||
<item quantity="one">%d day left of trial period</item>
|
||||
|
Loading…
x
Reference in New Issue
Block a user