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