Use RecycleView inside PlayerFragment
This commit is contained in:
parent
6277ee73c0
commit
d243ae1b44
|
@ -30,7 +30,6 @@ ext.versions = [
|
|||
okhttp : "3.12.13",
|
||||
koin : "3.0.2",
|
||||
picasso : "2.71828",
|
||||
sortListView : "1.0.1",
|
||||
|
||||
junit4 : "4.13.2",
|
||||
junit5 : "5.8.1",
|
||||
|
@ -92,7 +91,6 @@ ext.other = [
|
|||
dexter : "com.karumi:dexter:$versions.dexter",
|
||||
timber : "com.jakewharton.timber:timber:$versions.timber",
|
||||
fastScroll : "com.simplecityapps:recyclerview-fastscroll:$versions.fastScroll",
|
||||
sortListView : "com.github.tzugen:drag-sort-listview:$versions.sortListView",
|
||||
colorPickerView : "com.github.skydoves:colorpickerview:$versions.colorPicker",
|
||||
rxJava : "io.reactivex.rxjava3:rxjava:$versions.rxJava",
|
||||
rxAndroid : "io.reactivex.rxjava3:rxandroid:$versions.rxAndroid",
|
||||
|
|
|
@ -104,7 +104,6 @@ dependencies {
|
|||
implementation other.koinAndroid
|
||||
implementation other.okhttpLogging
|
||||
implementation other.fastScroll
|
||||
implementation other.sortListView
|
||||
implementation other.colorPickerView
|
||||
implementation other.rxJava
|
||||
implementation other.rxAndroid
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
package org.moire.ultrasonic.util
|
||||
|
||||
import java.util.HashSet
|
||||
import org.moire.ultrasonic.domain.Identifiable
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.util.Settings.shouldUseFolderForArtistName
|
||||
import org.moire.ultrasonic.util.Util.getGrandparent
|
||||
import java.util.HashSet
|
||||
|
||||
class AlbumHeader(
|
||||
var entries: List<MusicDirectory.Entry>,
|
||||
var name: String,
|
||||
songCount: Int
|
||||
): Identifiable {
|
||||
) : Identifiable {
|
||||
var isAllVideo: Boolean
|
||||
private set
|
||||
|
||||
|
@ -72,7 +72,6 @@ class AlbumHeader(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
_artists = HashSet()
|
||||
_grandParents = HashSet()
|
||||
|
|
|
@ -37,6 +37,8 @@ import org.moire.ultrasonic.util.Util;
|
|||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
|
||||
|
||||
public class AlbumView extends UpdateView
|
||||
{
|
||||
private static Drawable starDrawable;
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
package org.moire.ultrasonic.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.service.DownloadFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SongListAdapter extends ArrayAdapter<DownloadFile>
|
||||
{
|
||||
Context context;
|
||||
|
||||
public SongListAdapter(Context context, final List<DownloadFile> entries)
|
||||
{
|
||||
super(context, android.R.layout.simple_list_item_1, entries);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final int position, final View convertView, final ViewGroup parent)
|
||||
{
|
||||
DownloadFile downloadFile = getItem(position);
|
||||
MusicDirectory.Entry entry = downloadFile.getSong();
|
||||
|
||||
SongView view;
|
||||
|
||||
if (convertView instanceof SongView)
|
||||
{
|
||||
SongView currentView = (SongView) convertView;
|
||||
if (currentView.getEntry().equals(entry))
|
||||
{
|
||||
currentView.update();
|
||||
return currentView;
|
||||
}
|
||||
else
|
||||
{
|
||||
EntryAdapter.SongViewHolder viewHolder = (EntryAdapter.SongViewHolder) convertView.getTag();
|
||||
view = currentView;
|
||||
view.setViewHolder(viewHolder);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
view = new SongView(this.context);
|
||||
view.setLayout(entry);
|
||||
}
|
||||
|
||||
view.setSong(entry, false, true);
|
||||
return view;
|
||||
}
|
||||
}
|
|
@ -8,15 +8,14 @@ import android.widget.ImageView
|
|||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.drakeet.multitype.ItemViewBinder
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Random
|
||||
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
|
||||
|
@ -51,7 +50,6 @@ class HeaderViewBinder(
|
|||
val context = weakContext.get() ?: return
|
||||
val resources = context.resources
|
||||
|
||||
|
||||
val artworkSelection = random.nextInt(item.childCount)
|
||||
|
||||
imageLoaderProvider.getImageLoader().loadImage(
|
||||
|
@ -61,7 +59,6 @@ class HeaderViewBinder(
|
|||
|
||||
holder.titleView.text = item.name
|
||||
|
||||
|
||||
// Don't show a header if all entries are videos
|
||||
if (item.isAllVideo) {
|
||||
return
|
||||
|
@ -74,7 +71,6 @@ class HeaderViewBinder(
|
|||
}
|
||||
holder.artistView.text = artist
|
||||
|
||||
|
||||
val genre: String = if (item.genres.size == 1) {
|
||||
item.genres.iterator().next()
|
||||
} else {
|
||||
|
@ -83,7 +79,6 @@ class HeaderViewBinder(
|
|||
|
||||
holder.genreView.text = genre
|
||||
|
||||
|
||||
val year: String = if (item.years.size == 1) {
|
||||
item.years.iterator().next().toString()
|
||||
} else {
|
||||
|
@ -92,7 +87,6 @@ class HeaderViewBinder(
|
|||
|
||||
holder.yearView.text = year
|
||||
|
||||
|
||||
val songs = resources.getQuantityString(
|
||||
R.plurals.select_album_n_songs, item.childCount,
|
||||
item.childCount
|
||||
|
|
|
@ -25,7 +25,7 @@ class ImageHelper(context: Context) {
|
|||
val themesMatch = theme == currentTheme
|
||||
if (!themesMatch) theme = currentTheme
|
||||
|
||||
if (!themesMatch || force ) {
|
||||
if (!themesMatch || force) {
|
||||
getDrawables(context)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ import androidx.recyclerview.widget.AsyncListDiffer
|
|||
import androidx.recyclerview.widget.AsyncListDiffer.ListListener
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import com.drakeet.multitype.MultiTypeAdapter
|
||||
import org.moire.ultrasonic.domain.Identifiable
|
||||
import java.util.TreeSet
|
||||
import org.moire.ultrasonic.domain.Identifiable
|
||||
|
||||
class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
||||
|
||||
|
@ -36,7 +36,6 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
|||
throw IllegalAccessException("You must use submitList() to add data to the MultiTypeDiffAdapter")
|
||||
}
|
||||
|
||||
|
||||
var mDiffer: AsyncListDiffer<T> = AsyncListDiffer(
|
||||
AdapterListUpdateCallback(this),
|
||||
AsyncDifferConfig.Builder(diffCallback).build()
|
||||
|
@ -54,7 +53,6 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
|||
mDiffer.addListListener(mListener)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Submits a new list to be diffed, and displayed.
|
||||
*
|
||||
|
@ -88,8 +86,6 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
|||
mDiffer.submitList(list, commitCallback)
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return mDiffer.currentList.size
|
||||
}
|
||||
|
@ -151,7 +147,6 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
|||
selectionRevision.postValue(selectionRevision.value!! + 1)
|
||||
}
|
||||
|
||||
|
||||
fun setSelectionStatusOfAll(select: Boolean): Int {
|
||||
// Clear current selection
|
||||
selectedSet.clear()
|
||||
|
@ -163,10 +158,13 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
|||
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 }
|
||||
})
|
||||
getCurrentList().mapNotNullTo(
|
||||
selectedSet,
|
||||
{ entry ->
|
||||
// Exclude any -1 ids, eg. headers and other UI elements
|
||||
entry.longId.takeIf { it != -1L }
|
||||
}
|
||||
)
|
||||
|
||||
return selectedSet.count()
|
||||
}
|
||||
|
@ -175,6 +173,18 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
|||
return selectedSet.contains(longId)
|
||||
}
|
||||
|
||||
fun moveItem(from: Int, to: Int): List<T> {
|
||||
val list = getCurrentList().toMutableList()
|
||||
val fromLocation = list[from]
|
||||
list.removeAt(from)
|
||||
if (to < from) {
|
||||
list.add(to + 1, fromLocation)
|
||||
} else {
|
||||
list.add(to - 1, fromLocation)
|
||||
}
|
||||
submitList(list)
|
||||
return list as List<T>
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
|
@ -190,8 +200,5 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
|||
return oldItem.id == newItem.id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import org.moire.ultrasonic.R
|
|||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.data.ServerSetting
|
||||
import org.moire.ultrasonic.util.ServerColor
|
||||
import org.moire.ultrasonic.fragment.ServerSettingsModel
|
||||
import org.moire.ultrasonic.util.Util
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.moire.ultrasonic.adapters
|
|||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.drakeet.multitype.ItemViewBinder
|
||||
|
@ -12,16 +13,15 @@ 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 checkable: Boolean,
|
||||
val draggable: Boolean,
|
||||
context: Context,
|
||||
val lifecycleOwner: LifecycleOwner
|
||||
val lifecycleOwner: LifecycleOwner,
|
||||
private val onClickCallback: ((View, DownloadFile?) -> Unit)? = null
|
||||
) : ItemViewBinder<Identifiable, TrackViewHolder>(), KoinComponent {
|
||||
|
||||
|
||||
// //
|
||||
// onItemClick: (MusicDirectory.Entry) -> Unit,
|
||||
// onContextMenuClick: (MenuItem, MusicDirectory.Entry) -> Boolean,
|
||||
|
@ -40,12 +40,13 @@ class TrackViewBinder(
|
|||
private val imageHelper: ImageHelper = ImageHelper(context)
|
||||
|
||||
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): TrackViewHolder {
|
||||
return TrackViewHolder(inflater.inflate(layout, parent, false), adapter as MultiTypeDiffAdapter<Identifiable>)
|
||||
return TrackViewHolder(inflater.inflate(layout, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: TrackViewHolder, item: Identifiable) {
|
||||
|
||||
val downloadFile: DownloadFile?
|
||||
val _adapter = adapter as MultiTypeDiffAdapter<*>
|
||||
|
||||
when (item) {
|
||||
is MusicDirectory.Entry -> {
|
||||
|
@ -65,33 +66,47 @@ class TrackViewBinder(
|
|||
file = downloadFile,
|
||||
checkable = checkable,
|
||||
draggable = draggable,
|
||||
holder.adapter.isSelected(item.longId)
|
||||
_adapter.isSelected(item.longId)
|
||||
)
|
||||
|
||||
// Notify the adapter of selection changes
|
||||
holder.observableChecked.observe(
|
||||
lifecycleOwner,
|
||||
{ newValue ->
|
||||
if (newValue) {
|
||||
_adapter.notifySelected(item.longId)
|
||||
} else {
|
||||
_adapter.notifyUnselected(item.longId)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Listen to changes in selection status and update ourselves
|
||||
holder.adapter.selectionRevision.observe(lifecycleOwner, {
|
||||
val newStatus = holder.adapter.isSelected(item.longId)
|
||||
_adapter.selectionRevision.observe(
|
||||
lifecycleOwner,
|
||||
{
|
||||
val newStatus = _adapter.isSelected(item.longId)
|
||||
|
||||
if (newStatus != holder.check.isChecked) holder.check.isChecked = newStatus
|
||||
})
|
||||
|
||||
// Observe download status
|
||||
downloadFile.status.observe(lifecycleOwner, {
|
||||
Timber.w("CAUGHT STATUS CHANGE")
|
||||
holder.updateStatus(it)
|
||||
holder.adapter.notifyChanged()
|
||||
if (newStatus != holder.check.isChecked) holder.check.isChecked = newStatus
|
||||
}
|
||||
)
|
||||
|
||||
downloadFile.progress.observe(lifecycleOwner, {
|
||||
Timber.w("CAUGHT PROGRESS CHANGE")
|
||||
// Observe download status
|
||||
downloadFile.status.observe(
|
||||
lifecycleOwner,
|
||||
{
|
||||
holder.updateStatus(it)
|
||||
_adapter.notifyChanged()
|
||||
}
|
||||
)
|
||||
|
||||
downloadFile.progress.observe(
|
||||
lifecycleOwner,
|
||||
{
|
||||
holder.updateProgress(it)
|
||||
}
|
||||
)
|
||||
|
||||
holder.itemClickListener = onClickCallback
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -9,13 +9,13 @@ import android.widget.ImageView
|
|||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
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
|
||||
|
@ -31,8 +31,7 @@ import timber.log.Timber
|
|||
* Used to display songs and videos in a `ListView`.
|
||||
* TODO: Video List item
|
||||
*/
|
||||
class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifiable>) :
|
||||
RecyclerView.ViewHolder(view), Checkable, KoinComponent {
|
||||
class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable, KoinComponent {
|
||||
|
||||
var check: CheckedTextView = view.findViewById(R.id.song_check)
|
||||
var rating: LinearLayout = view.findViewById(R.id.song_rating)
|
||||
|
@ -49,6 +48,8 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
|||
var duration: TextView = view.findViewById(R.id.song_duration)
|
||||
var progress: TextView = view.findViewById(R.id.song_status)
|
||||
|
||||
var itemClickListener: ((View, DownloadFile?) -> Unit)? = null
|
||||
|
||||
var entry: MusicDirectory.Entry? = null
|
||||
private set
|
||||
var downloadFile: DownloadFile? = null
|
||||
|
@ -59,6 +60,8 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
|||
private var statusImage: Drawable? = null
|
||||
private var playing = false
|
||||
|
||||
var observableChecked = MutableLiveData(false)
|
||||
|
||||
private val useFiveStarRating: Boolean by lazy {
|
||||
val features: FeatureStorage = get()
|
||||
features.isFeatureEnabled(Feature.FIVE_STAR_RATING)
|
||||
|
@ -67,11 +70,15 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
|||
private val mediaPlayerController: MediaPlayerController by inject()
|
||||
|
||||
lateinit var imageHelper: ImageHelper
|
||||
|
||||
|
||||
init {
|
||||
itemView.setOnClickListener {
|
||||
val nowChecked = !check.isChecked
|
||||
isChecked = nowChecked
|
||||
if (itemClickListener != null) {
|
||||
itemClickListener?.invoke(it, downloadFile)
|
||||
} else {
|
||||
val nowChecked = !check.isChecked
|
||||
isChecked = nowChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,7 +99,6 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
|||
title.text = entryDescription.title
|
||||
duration.text = entryDescription.duration
|
||||
|
||||
|
||||
if (Settings.shouldShowTrackNumber && song.track != null && song.track!! > 0) {
|
||||
track.text = entryDescription.trackNumber
|
||||
} else {
|
||||
|
@ -100,7 +106,7 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
|||
}
|
||||
|
||||
check.isVisible = (checkable && !song.isVideo)
|
||||
check.isChecked = isSelected
|
||||
setCheckedSilent(isSelected)
|
||||
drag.isVisible = draggable
|
||||
|
||||
if (ActiveServerProvider.isOffline()) {
|
||||
|
@ -109,7 +115,7 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
|||
} else {
|
||||
setupStarButtons(song)
|
||||
}
|
||||
|
||||
|
||||
update()
|
||||
}
|
||||
|
||||
|
@ -151,9 +157,6 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Synchronized
|
||||
// TODO: Should be removed
|
||||
fun update() {
|
||||
|
@ -218,12 +221,10 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
fun updateStatus(status: DownloadStatus) {
|
||||
if (status == cachedStatus) return
|
||||
cachedStatus = status
|
||||
|
||||
|
||||
Timber.w("STATUS: %s", status)
|
||||
|
||||
when (status) {
|
||||
|
@ -254,7 +255,7 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
|||
fun updateProgress(p: Int) {
|
||||
if (cachedStatus == DownloadStatus.DOWNLOADING) {
|
||||
progress.text = Util.formatPercentage(p)
|
||||
} else {
|
||||
} else {
|
||||
progress.text = null
|
||||
}
|
||||
}
|
||||
|
@ -271,13 +272,12 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
|||
}
|
||||
}
|
||||
|
||||
private fun setCheckedSilent(newStatus: Boolean) {
|
||||
check.isChecked = newStatus
|
||||
}
|
||||
|
||||
override fun setChecked(newStatus: Boolean) {
|
||||
if (newStatus) {
|
||||
adapter.notifySelected(downloadFile!!.longId)
|
||||
} else {
|
||||
adapter.notifyUnselected(downloadFile!!.longId)
|
||||
}
|
||||
observableChecked.postValue(newStatus)
|
||||
check.isChecked = newStatus
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package org.moire.ultrasonic.adapters.legacy
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.adapters.ImageHelper
|
||||
import org.moire.ultrasonic.adapters.TrackViewHolder
|
||||
import org.moire.ultrasonic.service.DownloadFile
|
||||
|
||||
/**
|
||||
* Legacy bridge to provide Views to a ListView using RecyclerView.ViewHolders
|
||||
*/
|
||||
class SongListAdapter(
|
||||
ctx: Context,
|
||||
entries: List<DownloadFile?>?,
|
||||
val lifecycleOwner: LifecycleOwner
|
||||
) :
|
||||
ArrayAdapter<DownloadFile?>(ctx, android.R.layout.simple_list_item_1, entries!!) {
|
||||
|
||||
val layout = R.layout.song_list_item
|
||||
private val imageHelper: ImageHelper = ImageHelper(context)
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val downloadFile = getItem(position)!!
|
||||
var view = convertView
|
||||
val holder: TrackViewHolder
|
||||
|
||||
if (view == null) {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
view = inflater.inflate(layout, parent, false)
|
||||
}
|
||||
|
||||
if (view?.tag is TrackViewHolder) {
|
||||
holder = view.tag as TrackViewHolder
|
||||
} else {
|
||||
holder = TrackViewHolder(view!!)
|
||||
view.tag = holder
|
||||
}
|
||||
|
||||
holder.imageHelper = imageHelper
|
||||
|
||||
holder.setSong(
|
||||
file = downloadFile,
|
||||
checkable = false,
|
||||
draggable = true
|
||||
)
|
||||
|
||||
// Observe download status
|
||||
downloadFile.status.observe(
|
||||
lifecycleOwner,
|
||||
{
|
||||
holder.updateStatus(it)
|
||||
}
|
||||
)
|
||||
|
||||
downloadFile.progress.observe(
|
||||
lifecycleOwner,
|
||||
{
|
||||
holder.updateProgress(it)
|
||||
}
|
||||
)
|
||||
|
||||
return view
|
||||
}
|
||||
}
|
|
@ -8,9 +8,7 @@ import androidx.fragment.app.viewModels
|
|||
import androidx.lifecycle.LiveData
|
||||
import org.koin.core.component.inject
|
||||
import org.moire.ultrasonic.R
|
||||
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
|
||||
|
@ -54,7 +52,7 @@ class DownloadsFragment : MultiListFragment<DownloadFile>() {
|
|||
|
||||
viewAdapter.register(
|
||||
TrackViewBinder(
|
||||
checkable = true,
|
||||
checkable = false,
|
||||
draggable = false,
|
||||
context = requireContext(),
|
||||
lifecycleOwner = viewLifecycleOwner
|
||||
|
@ -65,7 +63,6 @@ class DownloadsFragment : MultiListFragment<DownloadFile>() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class DownloadListModel(application: Application) : GenericListModel(application) {
|
||||
private val downloader by inject<Downloader>()
|
||||
|
||||
|
@ -73,6 +70,3 @@ class DownloadListModel(application: Application) : GenericListModel(application
|
|||
return downloader.observableDownloads
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -8,18 +8,14 @@ import android.view.ViewGroup
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
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
|
||||
import org.moire.ultrasonic.domain.Identifiable
|
||||
import org.moire.ultrasonic.domain.MusicFolder
|
||||
import org.moire.ultrasonic.subsonic.DownloadHandler
|
||||
|
@ -32,7 +28,6 @@ import org.moire.ultrasonic.view.SelectMusicFolderView
|
|||
/**
|
||||
* An abstract Model, which can be extended to display a list of items of type T from the API
|
||||
* @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> : Fragment() {
|
||||
internal val activeServerProvider: ActiveServerProvider by inject()
|
||||
|
@ -94,7 +89,7 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
|||
*/
|
||||
@Suppress("CommentOverPrivateProperty")
|
||||
private val musicFolderObserver = { folders: List<MusicFolder> ->
|
||||
//viewAdapter.setFolderList(folders, listModel.activeServer.musicFolderId)
|
||||
// viewAdapter.setFolderList(folders, listModel.activeServer.musicFolderId)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,7 +110,7 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
|||
*/
|
||||
fun showFolderHeader(): Boolean {
|
||||
return listModel.showSelectFolderHeader(arguments) &&
|
||||
!listModel.isOffline() && !Settings.shouldUseId3Tags
|
||||
!listModel.isOffline() && !Settings.shouldUseId3Tags
|
||||
}
|
||||
|
||||
open fun setTitle(title: String?) {
|
||||
|
@ -147,9 +142,13 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
|||
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)
|
||||
|
@ -165,7 +164,7 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
|||
}
|
||||
|
||||
// Configure whether to show the folder header
|
||||
//viewAdapter.folderHeaderEnabled = showFolderHeader()
|
||||
// viewAdapter.folderHeaderEnabled = showFolderHeader()
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -187,7 +186,7 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
|||
abstract fun onItemClick(item: T)
|
||||
}
|
||||
|
||||
//abstract class EntryListFragment<T : GenericEntry, TA : GenericRowAdapter<T>> :
|
||||
// abstract class EntryListFragment<T : GenericEntry, TA : GenericRowAdapter<T>> :
|
||||
// GenericListFragment<T, TA>() {
|
||||
// @Suppress("LongMethod")
|
||||
// override fun onContextMenuItemSelected(menuItem: MenuItem, item: T): Boolean {
|
||||
|
@ -284,4 +283,4 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
|||
// bundle.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, (item is Artist))
|
||||
// findNavController().navigate(itemClickTarget, bundle)
|
||||
// }
|
||||
//}
|
||||
// }
|
||||
|
|
|
@ -36,8 +36,10 @@ import android.widget.ViewFlipper
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.Navigation
|
||||
import com.mobeta.android.dslv.DragSortListView
|
||||
import com.mobeta.android.dslv.DragSortListView.DragSortListener
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
|
@ -58,9 +60,12 @@ import org.koin.android.ext.android.inject
|
|||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.adapters.MultiTypeDiffAdapter
|
||||
import org.moire.ultrasonic.adapters.TrackViewBinder
|
||||
import org.moire.ultrasonic.audiofx.EqualizerController
|
||||
import org.moire.ultrasonic.audiofx.VisualizerController
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
|
||||
import org.moire.ultrasonic.domain.Identifiable
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.domain.PlayerState
|
||||
import org.moire.ultrasonic.domain.RepeatMode
|
||||
|
@ -81,7 +86,6 @@ import org.moire.ultrasonic.util.Constants
|
|||
import org.moire.ultrasonic.util.Settings
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import org.moire.ultrasonic.view.AutoRepeatButton
|
||||
import org.moire.ultrasonic.view.SongListAdapter
|
||||
import org.moire.ultrasonic.view.VisualizerView
|
||||
import timber.log.Timber
|
||||
|
||||
|
@ -94,6 +98,8 @@ class PlayerFragment :
|
|||
GestureDetector.OnGestureListener,
|
||||
KoinComponent,
|
||||
CoroutineScope by CoroutineScope(Dispatchers.Main) {
|
||||
|
||||
// Settings
|
||||
private var swipeDistance = 0
|
||||
private var swipeVelocity = 0
|
||||
private var jukeboxAvailable = false
|
||||
|
@ -104,6 +110,7 @@ class PlayerFragment :
|
|||
// Detectors & Callbacks
|
||||
private lateinit var gestureScanner: GestureDetector
|
||||
private lateinit var cancellationToken: CancellationToken
|
||||
private lateinit var dragTouchHelper: ItemTouchHelper
|
||||
|
||||
// Data & Services
|
||||
private val networkAndStorageChecker: NetworkAndStorageChecker by inject()
|
||||
|
@ -114,6 +121,7 @@ class PlayerFragment :
|
|||
private lateinit var executorService: ScheduledExecutorService
|
||||
private var currentPlaying: DownloadFile? = null
|
||||
private var currentSong: MusicDirectory.Entry? = null
|
||||
private lateinit var viewManager: LinearLayoutManager
|
||||
private var rxBusSubscription: Disposable? = null
|
||||
private var ioScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
|
@ -133,7 +141,7 @@ class PlayerFragment :
|
|||
private lateinit var albumTextView: TextView
|
||||
private lateinit var artistTextView: TextView
|
||||
private lateinit var albumArtImageView: ImageView
|
||||
private lateinit var playlistView: DragSortListView
|
||||
private lateinit var playlistView: RecyclerView
|
||||
private lateinit var positionTextView: TextView
|
||||
private lateinit var downloadTrackTextView: TextView
|
||||
private lateinit var downloadTotalDurationTextView: TextView
|
||||
|
@ -146,6 +154,10 @@ class PlayerFragment :
|
|||
private lateinit var fullStar: Drawable
|
||||
private lateinit var progressBar: SeekBar
|
||||
|
||||
internal val viewAdapter: MultiTypeDiffAdapter<Identifiable> by lazy {
|
||||
MultiTypeDiffAdapter()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
Util.applyTheme(this.context)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -322,14 +334,7 @@ class PlayerFragment :
|
|||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {}
|
||||
})
|
||||
|
||||
playlistView.setOnItemClickListener { _, _, position, _ ->
|
||||
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
||||
launch(CommunicationError.getHandler(context)) {
|
||||
mediaPlayerController.play(position)
|
||||
onCurrentChanged()
|
||||
onSliderProgressChanged()
|
||||
}
|
||||
}
|
||||
initPlaylistDisplay()
|
||||
|
||||
registerForContextMenu(playlistView)
|
||||
|
||||
|
@ -432,15 +437,12 @@ class PlayerFragment :
|
|||
|
||||
// Scroll to current playing.
|
||||
private fun scrollToCurrent() {
|
||||
val adapter = playlistView.adapter
|
||||
if (adapter != null) {
|
||||
val count = adapter.count
|
||||
for (i in 0 until count) {
|
||||
if (currentPlaying == playlistView.getItemAtPosition(i)) {
|
||||
playlistView.smoothScrollToPositionFromTop(i, 40)
|
||||
return
|
||||
}
|
||||
}
|
||||
val index = mediaPlayerController.playList.indexOf(currentPlaying)
|
||||
|
||||
if (index != -1) {
|
||||
val smoothScroller = LinearSmoothScroller(context)
|
||||
smoothScroller.targetPosition = index
|
||||
viewManager.startSmoothScroll(smoothScroller)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -535,7 +537,7 @@ class PlayerFragment :
|
|||
super.onCreateContextMenu(menu, view, menuInfo)
|
||||
if (view === playlistView) {
|
||||
val info = menuInfo as AdapterContextMenuInfo?
|
||||
val downloadFile = playlistView.getItemAtPosition(info!!.position) as DownloadFile
|
||||
val downloadFile = viewAdapter.getCurrentList()[info!!.position] as DownloadFile
|
||||
val menuInflater = requireActivity().menuInflater
|
||||
menuInflater.inflate(R.menu.nowplaying_context, menu)
|
||||
val song: MusicDirectory.Entry?
|
||||
|
@ -561,7 +563,7 @@ class PlayerFragment :
|
|||
|
||||
override fun onContextItemSelected(menuItem: MenuItem): Boolean {
|
||||
val info = menuItem.menuInfo as AdapterContextMenuInfo
|
||||
val downloadFile = playlistView.getItemAtPosition(info.position) as DownloadFile
|
||||
val downloadFile = viewAdapter.getCurrentList()[info.position] as DownloadFile
|
||||
return menuItemSelected(menuItem.itemId, downloadFile) || super.onContextItemSelected(
|
||||
menuItem
|
||||
)
|
||||
|
@ -842,43 +844,71 @@ class PlayerFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun initPlaylistDisplay() {
|
||||
// Create a View Manager
|
||||
viewManager = LinearLayoutManager(this.context)
|
||||
|
||||
// Hook up the view with the manager and the adapter
|
||||
playlistView.apply {
|
||||
setHasFixedSize(true)
|
||||
layoutManager = viewManager
|
||||
adapter = viewAdapter
|
||||
}
|
||||
|
||||
// Create listener
|
||||
val listener: ((View, DownloadFile?) -> Unit) = { _, file ->
|
||||
val list = mediaPlayerController.playList
|
||||
val index = list.indexOf(file)
|
||||
mediaPlayerController.play(index)
|
||||
onCurrentChanged()
|
||||
onSliderProgressChanged()
|
||||
}
|
||||
|
||||
viewAdapter.register(
|
||||
TrackViewBinder(
|
||||
checkable = false,
|
||||
draggable = true,
|
||||
context = requireContext(),
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
listener
|
||||
)
|
||||
)
|
||||
|
||||
dragTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
|
||||
ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0
|
||||
) {
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
|
||||
val from = viewHolder.bindingAdapterPosition
|
||||
val to = target.bindingAdapterPosition
|
||||
|
||||
// FIXME:
|
||||
// Needs to be changed in the playlist as well...
|
||||
// Move it in the data set
|
||||
(recyclerView.adapter as MultiTypeDiffAdapter<*>).moveItem(from, to)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
dragTouchHelper.attachToRecyclerView(playlistView)
|
||||
}
|
||||
|
||||
private fun onPlaylistChanged() {
|
||||
val mediaPlayerController = mediaPlayerController
|
||||
val list = mediaPlayerController.playList
|
||||
emptyTextView.setText(R.string.download_empty)
|
||||
val adapter = SongListAdapter(context, list)
|
||||
playlistView.adapter = adapter
|
||||
playlistView.setDragSortListener(object : DragSortListener {
|
||||
override fun drop(from: Int, to: Int) {
|
||||
if (from != to) {
|
||||
val item = adapter.getItem(from)
|
||||
adapter.remove(item)
|
||||
adapter.notifyDataSetChanged()
|
||||
adapter.insert(item, to)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
emptyTextView.setText(R.string.playlist_empty)
|
||||
|
||||
override fun drag(from: Int, to: Int) {}
|
||||
override fun remove(which: Int) {
|
||||
|
||||
val item = adapter.getItem(which) ?: return
|
||||
|
||||
val currentPlaying = mediaPlayerController.currentPlaying
|
||||
if (currentPlaying == item) {
|
||||
mediaPlayerController.next()
|
||||
}
|
||||
adapter.remove(item)
|
||||
adapter.notifyDataSetChanged()
|
||||
val songRemoved = String.format(
|
||||
resources.getString(R.string.download_song_removed),
|
||||
item.song.title
|
||||
)
|
||||
Util.toast(context, songRemoved)
|
||||
onPlaylistChanged()
|
||||
onCurrentChanged()
|
||||
}
|
||||
})
|
||||
viewAdapter.submitList(list)
|
||||
|
||||
emptyTextView.isVisible = list.isEmpty()
|
||||
|
||||
|
|
|
@ -10,12 +10,10 @@ package org.moire.ultrasonic.fragment
|
|||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
|
@ -27,12 +25,12 @@ import androidx.lifecycle.viewModelScope
|
|||
import androidx.navigation.Navigation
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import java.util.Collections
|
||||
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.data.ActiveServerProvider.Companion.isOffline
|
||||
import org.moire.ultrasonic.domain.Identifiable
|
||||
|
@ -49,7 +47,6 @@ import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator
|
|||
import org.moire.ultrasonic.util.Settings
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import timber.log.Timber
|
||||
import java.util.Collections
|
||||
|
||||
/**
|
||||
* Displays a group of tracks, eg. the songs of an album, of a playlist etc.
|
||||
|
@ -106,7 +103,6 @@ class TrackCollectionFragment :
|
|||
// FIXME
|
||||
override val itemClickTarget: Int = R.id.trackCollectionFragment
|
||||
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
cancellationToken = CancellationToken()
|
||||
|
@ -232,9 +228,12 @@ class TrackCollectionFragment :
|
|||
enableButtons()
|
||||
|
||||
// Update the buttons when the selection has changed
|
||||
viewAdapter.selectionRevision.observe(viewLifecycleOwner, {
|
||||
enableButtons()
|
||||
})
|
||||
viewAdapter.selectionRevision.observe(
|
||||
viewLifecycleOwner,
|
||||
{
|
||||
enableButtons()
|
||||
}
|
||||
)
|
||||
|
||||
// Loads the data
|
||||
updateDisplay(false)
|
||||
|
@ -454,7 +453,6 @@ class TrackCollectionFragment :
|
|||
val toastResId = R.string.select_album_n_selected
|
||||
Util.toast(activity, getString(toastResId, selectedCount.coerceAtLeast(0)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun enableButtons(selection: List<MusicDirectory.Entry> = getSelectedSongs()) {
|
||||
|
@ -519,12 +517,14 @@ class TrackCollectionFragment :
|
|||
}
|
||||
|
||||
private fun delete() {
|
||||
var songs = getSelectedSongs()
|
||||
val songs = getSelectedSongs()
|
||||
|
||||
if (songs.isEmpty()) {
|
||||
selectAll(selected = true, toast = false)
|
||||
songs = getSelectedSongs()
|
||||
}
|
||||
Util.toast(
|
||||
context,
|
||||
resources.getQuantityString(
|
||||
R.plurals.select_album_n_songs_deleted, songs.size, songs.size
|
||||
)
|
||||
)
|
||||
|
||||
mediaPlayerController.delete(songs)
|
||||
}
|
||||
|
@ -544,8 +544,8 @@ class TrackCollectionFragment :
|
|||
|
||||
// Hide more button when results are less than album list size
|
||||
if (musicDirectory.getChildren().size < requireArguments().getInt(
|
||||
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0
|
||||
)
|
||||
Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0
|
||||
)
|
||||
) {
|
||||
moreButton!!.visibility = View.GONE
|
||||
} else {
|
||||
|
@ -568,7 +568,6 @@ class TrackCollectionFragment :
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private val updateInterfaceWithEntries = Observer<List<MusicDirectory.Entry>> {
|
||||
|
||||
val entryList: MutableList<MusicDirectory.Entry> = it.toMutableList()
|
||||
|
@ -577,7 +576,6 @@ class TrackCollectionFragment :
|
|||
Collections.sort(entryList, EntryByDiscAndTrackComparator())
|
||||
}
|
||||
|
||||
|
||||
var allVideos = true
|
||||
var songCount = 0
|
||||
|
||||
|
@ -650,14 +648,6 @@ 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)
|
||||
// emptyView!!.setPadding(10, 10, 10, 10)
|
||||
// listView!!.addHeaderView(emptyView, null, false)
|
||||
// }
|
||||
|
||||
if (playAllButton != null) {
|
||||
playAllButton!!.isVisible = playAllButtonVisible
|
||||
}
|
||||
|
@ -666,11 +656,10 @@ class TrackCollectionFragment :
|
|||
shareButton!!.isVisible = shareButtonVisible
|
||||
}
|
||||
|
||||
|
||||
if (songCount > 0 && listModel.showHeader) {
|
||||
val name = listModel.currentDirectory.value?.name
|
||||
val intentAlbumName = requireArguments().getString(Constants.INTENT_EXTRA_NAME_NAME, "Name")!!
|
||||
val albumHeader = AlbumHeader(it, name?: intentAlbumName, songCount)
|
||||
val albumHeader = AlbumHeader(it, name ?: intentAlbumName, songCount)
|
||||
val mixedList: MutableList<Identifiable> = mutableListOf(albumHeader)
|
||||
mixedList.addAll(entryList)
|
||||
viewAdapter.submitList(mixedList)
|
||||
|
@ -678,7 +667,6 @@ class TrackCollectionFragment :
|
|||
viewAdapter.submitList(entryList)
|
||||
}
|
||||
|
||||
|
||||
val playAll = requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false)
|
||||
if (playAll && songCount > 0) {
|
||||
playAll(
|
||||
|
@ -688,8 +676,6 @@ class TrackCollectionFragment :
|
|||
}
|
||||
|
||||
listModel.currentListIsSortable = true
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun getSelectedSongs(): List<MusicDirectory.Entry> {
|
||||
|
@ -702,8 +688,6 @@ class TrackCollectionFragment :
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun setTitle(title: String?) {
|
||||
setTitle(this@TrackCollectionFragment, title)
|
||||
}
|
||||
|
@ -787,16 +771,11 @@ class TrackCollectionFragment :
|
|||
menuItem: MenuItem,
|
||||
item: MusicDirectory.Entry
|
||||
): Boolean {
|
||||
//TODO
|
||||
// TODO
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onItemClick(item: MusicDirectory.Entry) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -13,11 +13,8 @@ import androidx.lifecycle.MutableLiveData
|
|||
import java.util.LinkedList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.component.inject
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.domain.MusicDirectory
|
||||
import org.moire.ultrasonic.service.DownloadFile
|
||||
import org.moire.ultrasonic.service.Downloader
|
||||
import org.moire.ultrasonic.service.MusicService
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory
|
||||
import org.moire.ultrasonic.util.Settings
|
||||
|
|
|
@ -89,7 +89,6 @@ class DownloadFile(
|
|||
}
|
||||
|
||||
status = MutableLiveData(state)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -491,4 +491,3 @@ class Downloader(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package org.moire.ultrasonic.util
|
||||
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.ItemTouchHelper.DOWN
|
||||
import androidx.recyclerview.widget.ItemTouchHelper.UP
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.moire.ultrasonic.adapters.MultiTypeDiffAdapter
|
||||
import timber.log.Timber
|
||||
|
||||
class DragSortCallback : ItemTouchHelper.SimpleCallback(UP or DOWN, 0) {
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
|
||||
val from = viewHolder.bindingAdapterPosition
|
||||
val to = target.bindingAdapterPosition
|
||||
|
||||
Timber.w("MOVED %s %s", to, from)
|
||||
|
||||
// Move it in the data set
|
||||
(recyclerView.adapter as MultiTypeDiffAdapter<*>).moveItem(from, to)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
}
|
||||
}
|
|
@ -862,7 +862,6 @@ object Util {
|
|||
var fileFormat: String?,
|
||||
)
|
||||
|
||||
|
||||
fun getMediaDescriptionForEntry(
|
||||
song: MusicDirectory.Entry,
|
||||
mediaId: String? = null,
|
||||
|
@ -921,8 +920,8 @@ object Util {
|
|||
|
||||
if (artistName != null) {
|
||||
if (Settings.shouldDisplayBitrateWithArtist && (
|
||||
!bitRate.isNullOrBlank() || !fileFormat.isNullOrBlank()
|
||||
)
|
||||
!bitRate.isNullOrBlank() || !fileFormat.isNullOrBlank()
|
||||
)
|
||||
) {
|
||||
artist.append(artistName).append(" (").append(
|
||||
String.format(
|
||||
|
@ -939,7 +938,6 @@ object Util {
|
|||
|
||||
val trackNumber = song.track ?: 0
|
||||
|
||||
|
||||
val title = StringBuilder(LINE_LENGTH)
|
||||
if (Settings.shouldShowTrackNumber && trackNumber > 0) {
|
||||
trackText = String.format(Locale.ROOT, "%02d.", trackNumber)
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
//package org.moire.ultrasonic.view
|
||||
// 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
|
||||
// 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 {
|
||||
// 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)
|
||||
|
@ -252,42 +252,42 @@
|
|||
// }
|
||||
// }
|
||||
//
|
||||
//// 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()
|
||||
//// }
|
||||
//// }
|
||||
// // 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
|
||||
|
@ -313,4 +313,4 @@
|
|||
// }
|
||||
//
|
||||
//
|
||||
//}
|
||||
// }
|
||||
|
|
|
@ -2,31 +2,22 @@
|
|||
|
||||
<LinearLayout
|
||||
xmlns:a="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
a:orientation="vertical"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="fill_parent">
|
||||
|
||||
<TextView
|
||||
a:id="@+id/playlist_empty"
|
||||
a:text="@string/download.empty"
|
||||
a:text="@string/playlist.empty"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="wrap_content"
|
||||
a:padding="10dip"/>
|
||||
|
||||
<com.mobeta.android.dslv.DragSortListView
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
a:id="@+id/playlist_view"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="0dip"
|
||||
a:layout_weight="1"
|
||||
a:fastScrollEnabled="true"
|
||||
a:textFilterEnabled="true"
|
||||
app:drag_handle_id="@+id/song_drag"
|
||||
app:remove_enabled="true"
|
||||
app:remove_mode="flingRemove"
|
||||
app:fling_handle_id="@+id/song_drag"
|
||||
app:drag_start_mode="onMove"
|
||||
app:float_background_color="?attr/color_background"
|
||||
app:float_alpha="0.7" />
|
||||
a:fastScrollEnabled="true" />
|
||||
|
||||
</LinearLayout>
|
|
@ -15,7 +15,8 @@
|
|||
a:background="@android:color/transparent"
|
||||
a:focusable="false"
|
||||
a:gravity="center_vertical"
|
||||
a:src="?attr/drag_vertical" />
|
||||
a:src="?attr/drag_vertical"
|
||||
a:importantForAccessibility="no" />
|
||||
|
||||
<CheckedTextView
|
||||
a:id="@+id/song_check"
|
||||
|
@ -96,6 +97,7 @@
|
|||
a:focusable="false"
|
||||
a:gravity="center_vertical"
|
||||
a:paddingEnd="8dip"
|
||||
a:src="?attr/star_hollow" />
|
||||
a:src="?attr/star_hollow"
|
||||
a:contentDescription="@string/download.menu_star"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -43,7 +43,7 @@
|
|||
<string name="delete_playlist">Opravdu smazat %1$s</string>
|
||||
<string name="download.bookmark_removed" formatted="false">Záložka odstraněna.</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">Záložka vytvořena na %s.</string>
|
||||
<string name="download.empty">Playlist je prázdný</string>
|
||||
<string name="playlist.empty">Playlist je prázdný</string>
|
||||
<string name="download.jukebox_not_authorized">Vzdálené ovládání není povoleno. Povolte jukebox mód v <b>Uživatelském > Nastavení</b> na Subsonic serveru.</string>
|
||||
<string name="download.jukebox_off">Vzdálené ovládání vypnuto. Hudba je přehrávána na telefonu.</string>
|
||||
<string name="download.jukebox_offline">Vzdálené ovládání není dostupné v offline módu.</string>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<string name="delete_playlist">Möchtest du %1$s löschen</string>
|
||||
<string name="download.bookmark_removed" formatted="false">Lesezeichen entfernt</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">Lesezeichen gesetzt als %s.</string>
|
||||
<string name="download.empty">Wiedergabeliste ist leer</string>
|
||||
<string name="playlist.empty">Wiedergabeliste ist leer</string>
|
||||
<string name="download.jukebox_not_authorized">Fernbedienung ist nicht erlaubt. Bitte Jukebox Modus auf dem Subsonic Server in <b>Benutzer > Einstellungen</b> aktivieren.</string>
|
||||
<string name="download.jukebox_off">Fernbedienung ausgeschaltet. Musik wird auf dem Telefon wiedergegeben.</string>
|
||||
<string name="download.jukebox_offline">Fernbedienungs-Modus is Offline nicht verfügbar.</string>
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
<string name="delete_playlist">Quieres eliminar %1$s</string>
|
||||
<string name="download.bookmark_removed" formatted="false">Marcador eliminado.</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">Marcador añadido a %s.</string>
|
||||
<string name="download.empty">La lista de reproducción esta vacía</string>
|
||||
<string name="playlist.empty">La lista de reproducción esta vacía</string>
|
||||
<string name="download.jukebox_not_authorized">El control remoto no esta habilitado. Por favor habilita el modo jukebox en <b>Configuración > Usuarios</b> en tu servidor de Subsonic.</string>
|
||||
<string name="download.jukebox_off">Control remoto apagado. La música se reproduce en tu dispositivo.</string>
|
||||
<string name="download.jukebox_offline">Control remoto no disponible en modo fuera de línea.</string>
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<string name="delete_playlist">Voulez-vous supprimer %1$s</string>
|
||||
<string name="download.bookmark_removed" formatted="false">Signet supprimé.</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">Signet ajouté à %s.</string>
|
||||
<string name="download.empty">La playlist est vide</string>
|
||||
<string name="playlist.empty">La playlist est vide</string>
|
||||
<string name="download.jukebox_not_authorized">La télécommande n\'est pas autorisée. Veuillez activer le mode jukebox dans <b>Utilisateurs > Paramètres</b> à partir de votre serveur Subsonic.</string>
|
||||
<string name="download.jukebox_off">Mode jukebox désactivé. La musique est jouée sur l\'appareil.</string>
|
||||
<string name="download.jukebox_offline">Le mode jukebox n\'est pas disponible en mode déconnecté.</string>
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<string name="delete_playlist">Biztos, hogy törölni akarja? %1$s</string>
|
||||
<string name="download.bookmark_removed" formatted="false">Könyvjelző eltávolítva.</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">Könyvjelző beállítva %s.</string>
|
||||
<string name="download.empty">A várólista üres!</string>
|
||||
<string name="playlist.empty">A várólista üres!</string>
|
||||
<string name="download.jukebox_not_authorized">A távvezérlés nem áll rendelkezésre. Kérjük, engedélyezze a Jukebox módot a <b>Felhasználók > Beállítások</b> menüpontban, az Ön Subsonic kiszolgálóján!</string>
|
||||
<string name="download.jukebox_off">Távvezérlés kikapcsolása. A zenelejátszás a telefonon történik.</string>
|
||||
<string name="download.jukebox_offline">A távvezérlés nem lehetséges kapcsolat nélküli módban!</string>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<string name="delete_playlist">Vuoi eliminare %1$s</string>
|
||||
<string name="download.bookmark_removed" formatted="false">Segnalibro rimosso.</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">Segnalibro impostato su %s.</string>
|
||||
<string name="download.empty">Playlist vuota</string>
|
||||
<string name="playlist.empty">Playlist vuota</string>
|
||||
<string name="download.jukebox_not_authorized">Il controllo remoto non è consentito. Per favore abilita la modalità jukebox nelle <b> Impostazioni > Utente </b> nel server Airsonic.</string>
|
||||
<string name="download.jukebox_off">Controllo remoto disattivato. La musica verrà riprodotta sullo smartphone.</string>
|
||||
<string name="download.jukebox_offline">Il controllo remoto non è disponibile nella modalità offline. </string>
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
<string name="delete_playlist">Wil je %1$s verwijderen?</string>
|
||||
<string name="download.bookmark_removed" formatted="false">Bladwijzer verwijderd.</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">Bladwijzer ingesteld op %s.</string>
|
||||
<string name="download.empty">Lege afspeellijst</string>
|
||||
<string name="playlist.empty">Lege afspeellijst</string>
|
||||
<string name="download.jukebox_not_authorized">Afstandsbediening wordt niet ondersteund. Schakel jukebox-modus in op je Subsonic-server via <b>Gebruikers > Instellingen</b>.</string>
|
||||
<string name="download.jukebox_off">Afstandsbediening uitgeschakeld; muziek wordt afgespeeld op de telefoon.</string>
|
||||
<string name="download.jukebox_offline">Afstandsbediening is niet beschikbaar in offline-modus.</string>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<string name="delete_playlist">Czy chcesz usunąć %1$s?</string>
|
||||
<string name="download.bookmark_removed" formatted="false">Zakładka usunięta.</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">Zakładka ustawiona na %s.</string>
|
||||
<string name="download.empty">Playlista jest pusta</string>
|
||||
<string name="playlist.empty">Playlista jest pusta</string>
|
||||
<string name="download.jukebox_not_authorized">Kontrola pilotem jest niedostępna. Proszę uruchomić tryb jukebox w <b>Użytkownicy > Ustawienia</b> na serwerze Subsonic.</string>
|
||||
<string name="download.jukebox_off">Tryb pilota jest wyłączony. Muzyka jest odtwarzana w telefonie.</string>
|
||||
<string name="download.jukebox_offline">Pilot jest niedostępny w trybie offline.</string>
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<string name="delete_playlist">Você quer excluir %1$s</string>
|
||||
<string name="download.bookmark_removed" formatted="false">Favorito removido.</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">Favorito marcado em %s.</string>
|
||||
<string name="download.empty">Playlist está vazia</string>
|
||||
<string name="playlist.empty">Playlist está vazia</string>
|
||||
<string name="download.jukebox_not_authorized">Controle remoto não está permitido. Habilite o modo jukebox em <b>Usuário > Configurações</b> no seu servidor Subsonic.</string>
|
||||
<string name="download.jukebox_off">Controle remoto desligado. Música tocada no celular.</string>
|
||||
<string name="download.jukebox_offline">Controle remoto não está disponível no modo offline.</string>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<string name="delete_playlist">Você quer apagar %1$s</string>
|
||||
<string name="download.bookmark_removed" formatted="false">Favorito removido.</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">Favorito marcado em %s.</string>
|
||||
<string name="download.empty">Playlist está vazia</string>
|
||||
<string name="playlist.empty">Playlist está vazia</string>
|
||||
<string name="download.jukebox_not_authorized">Controle remoto não está permitido. Habilite o modo jukebox em <b>Usuário > Configurações</b> no seu servidor Subsonic.</string>
|
||||
<string name="download.jukebox_off">Controle remoto desligado. Música tocada no celular.</string>
|
||||
<string name="download.jukebox_offline">Controle remoto não está disponível no modo offline.</string>
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<string name="delete_playlist">Вы хотите удалить %1$s</string>
|
||||
<string name="download.bookmark_removed" formatted="false">Закладка удалена</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">Закладка установлена на %s</string>
|
||||
<string name="download.empty">Плейлист пустой</string>
|
||||
<string name="playlist.empty">Плейлист пустой</string>
|
||||
<string name="download.jukebox_not_authorized">Пульт дистанционного управления не допускается. Пожалуйста, включите режим музыкального автомата в <b> Пользователи > Настройки </b>на вашем Subsonic сервере.</string>
|
||||
<string name="download.jukebox_off">Пульт управления выключен. Музыка играет на телефоне</string>
|
||||
<string name="download.jukebox_offline">Пульт дистанционного управления недоступен в автономном режиме.</string>
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<string name="delete_playlist">确定要删除 %1$s吗</string>
|
||||
<string name="download.bookmark_removed" formatted="false">书签已删除。</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">书签设置为 %s。</string>
|
||||
<string name="download.empty">空的播放列表</string>
|
||||
<string name="playlist.empty">空的播放列表</string>
|
||||
<string name="download.jukebox_not_authorized">不允许远程控制. 请在您的服务器上的 <b>Users > Settings</b> 打开点唱机模式。</string>
|
||||
<string name="download.jukebox_off">关闭远程控制,音乐将在手机上播放</string>
|
||||
<string name="download.jukebox_offline">离线模式不支持远程控制</string>
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
<string name="delete_playlist">Do you want to delete %1$s</string>
|
||||
<string name="download.bookmark_removed" formatted="false">Bookmark removed.</string>
|
||||
<string name="download.bookmark_set_at_position" formatted="false">Bookmark set at %s.</string>
|
||||
<string name="download.empty">Playlist is empty</string>
|
||||
<string name="playlist.empty">Playlist is empty</string>
|
||||
<string name="download.jukebox_not_authorized">Remote control is not allowed. Please enable jukebox mode in <b>Users > Settings</b> on your Subsonic server.</string>
|
||||
<string name="download.jukebox_off">Turned off remote control. Music is played on phone.</string>
|
||||
<string name="download.jukebox_offline">Remote control is not available in offline mode.</string>
|
||||
|
@ -464,6 +464,10 @@
|
|||
<item quantity="one">%d song unpinned</item>
|
||||
<item quantity="other">%d songs unpinned</item>
|
||||
</plurals>
|
||||
<plurals name="select_album_n_songs_deleted">
|
||||
<item quantity="one">%d song deleted</item>
|
||||
<item quantity="other">%d songs deleted</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>
|
||||
|
|
Loading…
Reference in New Issue