Use RecycleView inside PlayerFragment
This commit is contained in:
parent
6277ee73c0
commit
d243ae1b44
|
@ -30,7 +30,6 @@ ext.versions = [
|
||||||
okhttp : "3.12.13",
|
okhttp : "3.12.13",
|
||||||
koin : "3.0.2",
|
koin : "3.0.2",
|
||||||
picasso : "2.71828",
|
picasso : "2.71828",
|
||||||
sortListView : "1.0.1",
|
|
||||||
|
|
||||||
junit4 : "4.13.2",
|
junit4 : "4.13.2",
|
||||||
junit5 : "5.8.1",
|
junit5 : "5.8.1",
|
||||||
|
@ -92,7 +91,6 @@ ext.other = [
|
||||||
dexter : "com.karumi:dexter:$versions.dexter",
|
dexter : "com.karumi:dexter:$versions.dexter",
|
||||||
timber : "com.jakewharton.timber:timber:$versions.timber",
|
timber : "com.jakewharton.timber:timber:$versions.timber",
|
||||||
fastScroll : "com.simplecityapps:recyclerview-fastscroll:$versions.fastScroll",
|
fastScroll : "com.simplecityapps:recyclerview-fastscroll:$versions.fastScroll",
|
||||||
sortListView : "com.github.tzugen:drag-sort-listview:$versions.sortListView",
|
|
||||||
colorPickerView : "com.github.skydoves:colorpickerview:$versions.colorPicker",
|
colorPickerView : "com.github.skydoves:colorpickerview:$versions.colorPicker",
|
||||||
rxJava : "io.reactivex.rxjava3:rxjava:$versions.rxJava",
|
rxJava : "io.reactivex.rxjava3:rxjava:$versions.rxJava",
|
||||||
rxAndroid : "io.reactivex.rxjava3:rxandroid:$versions.rxAndroid",
|
rxAndroid : "io.reactivex.rxjava3:rxandroid:$versions.rxAndroid",
|
||||||
|
|
|
@ -104,7 +104,6 @@ dependencies {
|
||||||
implementation other.koinAndroid
|
implementation other.koinAndroid
|
||||||
implementation other.okhttpLogging
|
implementation other.okhttpLogging
|
||||||
implementation other.fastScroll
|
implementation other.fastScroll
|
||||||
implementation other.sortListView
|
|
||||||
implementation other.colorPickerView
|
implementation other.colorPickerView
|
||||||
implementation other.rxJava
|
implementation other.rxJava
|
||||||
implementation other.rxAndroid
|
implementation other.rxAndroid
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
package org.moire.ultrasonic.util
|
package org.moire.ultrasonic.util
|
||||||
|
|
||||||
|
import java.util.HashSet
|
||||||
import org.moire.ultrasonic.domain.Identifiable
|
import org.moire.ultrasonic.domain.Identifiable
|
||||||
import org.moire.ultrasonic.domain.MusicDirectory
|
import org.moire.ultrasonic.domain.MusicDirectory
|
||||||
import org.moire.ultrasonic.util.Settings.shouldUseFolderForArtistName
|
import org.moire.ultrasonic.util.Settings.shouldUseFolderForArtistName
|
||||||
import org.moire.ultrasonic.util.Util.getGrandparent
|
import org.moire.ultrasonic.util.Util.getGrandparent
|
||||||
import java.util.HashSet
|
|
||||||
|
|
||||||
class AlbumHeader(
|
class AlbumHeader(
|
||||||
var entries: List<MusicDirectory.Entry>,
|
var entries: List<MusicDirectory.Entry>,
|
||||||
var name: String,
|
var name: String,
|
||||||
songCount: Int
|
songCount: Int
|
||||||
): Identifiable {
|
) : Identifiable {
|
||||||
var isAllVideo: Boolean
|
var isAllVideo: Boolean
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
@ -72,7 +72,6 @@ class AlbumHeader(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
_artists = HashSet()
|
_artists = HashSet()
|
||||||
_grandParents = HashSet()
|
_grandParents = HashSet()
|
||||||
|
|
|
@ -37,6 +37,8 @@ import org.moire.ultrasonic.util.Util;
|
||||||
*
|
*
|
||||||
* @author Sindre Mehus
|
* @author Sindre Mehus
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
public class AlbumView extends UpdateView
|
public class AlbumView extends UpdateView
|
||||||
{
|
{
|
||||||
private static Drawable starDrawable;
|
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 android.widget.TextView
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.drakeet.multitype.ItemViewBinder
|
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.KoinComponent
|
||||||
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.subsonic.ImageLoaderProvider
|
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||||
import org.moire.ultrasonic.util.AlbumHeader
|
import org.moire.ultrasonic.util.AlbumHeader
|
||||||
import org.moire.ultrasonic.util.Util
|
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
|
* This Binder can bind a list of entries into a Header
|
||||||
|
@ -51,7 +50,6 @@ class HeaderViewBinder(
|
||||||
val context = weakContext.get() ?: return
|
val context = weakContext.get() ?: return
|
||||||
val resources = context.resources
|
val resources = context.resources
|
||||||
|
|
||||||
|
|
||||||
val artworkSelection = random.nextInt(item.childCount)
|
val artworkSelection = random.nextInt(item.childCount)
|
||||||
|
|
||||||
imageLoaderProvider.getImageLoader().loadImage(
|
imageLoaderProvider.getImageLoader().loadImage(
|
||||||
|
@ -61,7 +59,6 @@ class HeaderViewBinder(
|
||||||
|
|
||||||
holder.titleView.text = item.name
|
holder.titleView.text = item.name
|
||||||
|
|
||||||
|
|
||||||
// Don't show a header if all entries are videos
|
// Don't show a header if all entries are videos
|
||||||
if (item.isAllVideo) {
|
if (item.isAllVideo) {
|
||||||
return
|
return
|
||||||
|
@ -74,7 +71,6 @@ class HeaderViewBinder(
|
||||||
}
|
}
|
||||||
holder.artistView.text = artist
|
holder.artistView.text = artist
|
||||||
|
|
||||||
|
|
||||||
val genre: String = if (item.genres.size == 1) {
|
val genre: String = if (item.genres.size == 1) {
|
||||||
item.genres.iterator().next()
|
item.genres.iterator().next()
|
||||||
} else {
|
} else {
|
||||||
|
@ -83,7 +79,6 @@ class HeaderViewBinder(
|
||||||
|
|
||||||
holder.genreView.text = genre
|
holder.genreView.text = genre
|
||||||
|
|
||||||
|
|
||||||
val year: String = if (item.years.size == 1) {
|
val year: String = if (item.years.size == 1) {
|
||||||
item.years.iterator().next().toString()
|
item.years.iterator().next().toString()
|
||||||
} else {
|
} else {
|
||||||
|
@ -92,7 +87,6 @@ class HeaderViewBinder(
|
||||||
|
|
||||||
holder.yearView.text = year
|
holder.yearView.text = year
|
||||||
|
|
||||||
|
|
||||||
val songs = resources.getQuantityString(
|
val songs = resources.getQuantityString(
|
||||||
R.plurals.select_album_n_songs, item.childCount,
|
R.plurals.select_album_n_songs, item.childCount,
|
||||||
item.childCount
|
item.childCount
|
||||||
|
|
|
@ -25,7 +25,7 @@ class ImageHelper(context: Context) {
|
||||||
val themesMatch = theme == currentTheme
|
val themesMatch = theme == currentTheme
|
||||||
if (!themesMatch) theme = currentTheme
|
if (!themesMatch) theme = currentTheme
|
||||||
|
|
||||||
if (!themesMatch || force ) {
|
if (!themesMatch || force) {
|
||||||
getDrawables(context)
|
getDrawables(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ 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 com.drakeet.multitype.MultiTypeAdapter
|
import com.drakeet.multitype.MultiTypeAdapter
|
||||||
import org.moire.ultrasonic.domain.Identifiable
|
|
||||||
import java.util.TreeSet
|
import java.util.TreeSet
|
||||||
|
import org.moire.ultrasonic.domain.Identifiable
|
||||||
|
|
||||||
class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
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")
|
throw IllegalAccessException("You must use submitList() to add data to the MultiTypeDiffAdapter")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var mDiffer: AsyncListDiffer<T> = AsyncListDiffer(
|
var mDiffer: AsyncListDiffer<T> = AsyncListDiffer(
|
||||||
AdapterListUpdateCallback(this),
|
AdapterListUpdateCallback(this),
|
||||||
AsyncDifferConfig.Builder(diffCallback).build()
|
AsyncDifferConfig.Builder(diffCallback).build()
|
||||||
|
@ -54,7 +53,6 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
||||||
mDiffer.addListListener(mListener)
|
mDiffer.addListListener(mListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submits a new list to be diffed, and displayed.
|
* Submits a new list to be diffed, and displayed.
|
||||||
*
|
*
|
||||||
|
@ -88,8 +86,6 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
||||||
mDiffer.submitList(list, commitCallback)
|
mDiffer.submitList(list, commitCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return mDiffer.currentList.size
|
return mDiffer.currentList.size
|
||||||
}
|
}
|
||||||
|
@ -151,7 +147,6 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
||||||
selectionRevision.postValue(selectionRevision.value!! + 1)
|
selectionRevision.postValue(selectionRevision.value!! + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun setSelectionStatusOfAll(select: Boolean): Int {
|
fun setSelectionStatusOfAll(select: Boolean): Int {
|
||||||
// Clear current selection
|
// Clear current selection
|
||||||
selectedSet.clear()
|
selectedSet.clear()
|
||||||
|
@ -163,10 +158,13 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
||||||
if (!select) return 0
|
if (!select) return 0
|
||||||
|
|
||||||
// Select them all
|
// Select them all
|
||||||
getCurrentList().mapNotNullTo(selectedSet, { entry ->
|
getCurrentList().mapNotNullTo(
|
||||||
// Exclude any -1 ids, eg. headers and other UI elements
|
selectedSet,
|
||||||
entry.longId.takeIf { it != -1L }
|
{ entry ->
|
||||||
})
|
// Exclude any -1 ids, eg. headers and other UI elements
|
||||||
|
entry.longId.takeIf { it != -1L }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return selectedSet.count()
|
return selectedSet.count()
|
||||||
}
|
}
|
||||||
|
@ -175,6 +173,18 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
||||||
return selectedSet.contains(longId)
|
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 {
|
companion object {
|
||||||
/**
|
/**
|
||||||
|
@ -190,8 +200,5 @@ class MultiTypeDiffAdapter<T : Identifiable> : MultiTypeAdapter() {
|
||||||
return oldItem.id == newItem.id
|
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.ActiveServerProvider
|
||||||
import org.moire.ultrasonic.data.ServerSetting
|
import org.moire.ultrasonic.data.ServerSetting
|
||||||
import org.moire.ultrasonic.util.ServerColor
|
import org.moire.ultrasonic.util.ServerColor
|
||||||
import org.moire.ultrasonic.fragment.ServerSettingsModel
|
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,6 +2,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.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import com.drakeet.multitype.ItemViewBinder
|
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.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 checkable: Boolean,
|
val checkable: Boolean,
|
||||||
val draggable: Boolean,
|
val draggable: Boolean,
|
||||||
context: Context,
|
context: Context,
|
||||||
val lifecycleOwner: LifecycleOwner
|
val lifecycleOwner: LifecycleOwner,
|
||||||
|
private val onClickCallback: ((View, DownloadFile?) -> Unit)? = null
|
||||||
) : ItemViewBinder<Identifiable, TrackViewHolder>(), KoinComponent {
|
) : ItemViewBinder<Identifiable, TrackViewHolder>(), KoinComponent {
|
||||||
|
|
||||||
|
|
||||||
// //
|
// //
|
||||||
// onItemClick: (MusicDirectory.Entry) -> Unit,
|
// onItemClick: (MusicDirectory.Entry) -> Unit,
|
||||||
// onContextMenuClick: (MenuItem, MusicDirectory.Entry) -> Boolean,
|
// onContextMenuClick: (MenuItem, MusicDirectory.Entry) -> Boolean,
|
||||||
|
@ -40,12 +40,13 @@ class TrackViewBinder(
|
||||||
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), adapter as MultiTypeDiffAdapter<Identifiable>)
|
return TrackViewHolder(inflater.inflate(layout, parent, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: TrackViewHolder, item: Identifiable) {
|
override fun onBindViewHolder(holder: TrackViewHolder, item: Identifiable) {
|
||||||
|
|
||||||
val downloadFile: DownloadFile?
|
val downloadFile: DownloadFile?
|
||||||
|
val _adapter = adapter as MultiTypeDiffAdapter<*>
|
||||||
|
|
||||||
when (item) {
|
when (item) {
|
||||||
is MusicDirectory.Entry -> {
|
is MusicDirectory.Entry -> {
|
||||||
|
@ -65,33 +66,47 @@ class TrackViewBinder(
|
||||||
file = downloadFile,
|
file = downloadFile,
|
||||||
checkable = checkable,
|
checkable = checkable,
|
||||||
draggable = draggable,
|
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
|
// Listen to changes in selection status and update ourselves
|
||||||
holder.adapter.selectionRevision.observe(lifecycleOwner, {
|
_adapter.selectionRevision.observe(
|
||||||
val newStatus = holder.adapter.isSelected(item.longId)
|
lifecycleOwner,
|
||||||
|
{
|
||||||
|
val newStatus = _adapter.isSelected(item.longId)
|
||||||
|
|
||||||
if (newStatus != holder.check.isChecked) holder.check.isChecked = newStatus
|
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()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
downloadFile.progress.observe(lifecycleOwner, {
|
// Observe download status
|
||||||
Timber.w("CAUGHT PROGRESS CHANGE")
|
downloadFile.status.observe(
|
||||||
|
lifecycleOwner,
|
||||||
|
{
|
||||||
|
holder.updateStatus(it)
|
||||||
|
_adapter.notifyChanged()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
downloadFile.progress.observe(
|
||||||
|
lifecycleOwner,
|
||||||
|
{
|
||||||
holder.updateProgress(it)
|
holder.updateProgress(it)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
holder.itemClickListener = onClickCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,13 @@ 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.MutableLiveData
|
||||||
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
|
||||||
|
@ -31,8 +31,7 @@ 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, var adapter: MultiTypeDiffAdapter<Identifiable>) :
|
class TrackViewHolder(val view: View) : 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)
|
||||||
|
@ -49,6 +48,8 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
||||||
var duration: TextView = view.findViewById(R.id.song_duration)
|
var duration: TextView = view.findViewById(R.id.song_duration)
|
||||||
var progress: TextView = view.findViewById(R.id.song_status)
|
var progress: TextView = view.findViewById(R.id.song_status)
|
||||||
|
|
||||||
|
var itemClickListener: ((View, DownloadFile?) -> Unit)? = null
|
||||||
|
|
||||||
var entry: MusicDirectory.Entry? = null
|
var entry: MusicDirectory.Entry? = null
|
||||||
private set
|
private set
|
||||||
var downloadFile: DownloadFile? = null
|
var downloadFile: DownloadFile? = null
|
||||||
|
@ -59,6 +60,8 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
||||||
private var statusImage: Drawable? = null
|
private var statusImage: Drawable? = null
|
||||||
private var playing = false
|
private var playing = false
|
||||||
|
|
||||||
|
var observableChecked = MutableLiveData(false)
|
||||||
|
|
||||||
private val useFiveStarRating: Boolean by lazy {
|
private val useFiveStarRating: Boolean by lazy {
|
||||||
val features: FeatureStorage = get()
|
val features: FeatureStorage = get()
|
||||||
features.isFeatureEnabled(Feature.FIVE_STAR_RATING)
|
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()
|
private val mediaPlayerController: MediaPlayerController by inject()
|
||||||
|
|
||||||
lateinit var imageHelper: ImageHelper
|
lateinit var imageHelper: ImageHelper
|
||||||
|
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
val nowChecked = !check.isChecked
|
if (itemClickListener != null) {
|
||||||
isChecked = nowChecked
|
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
|
title.text = entryDescription.title
|
||||||
duration.text = entryDescription.duration
|
duration.text = entryDescription.duration
|
||||||
|
|
||||||
|
|
||||||
if (Settings.shouldShowTrackNumber && song.track != null && song.track!! > 0) {
|
if (Settings.shouldShowTrackNumber && song.track != null && song.track!! > 0) {
|
||||||
track.text = entryDescription.trackNumber
|
track.text = entryDescription.trackNumber
|
||||||
} else {
|
} else {
|
||||||
|
@ -100,7 +106,7 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
||||||
}
|
}
|
||||||
|
|
||||||
check.isVisible = (checkable && !song.isVideo)
|
check.isVisible = (checkable && !song.isVideo)
|
||||||
check.isChecked = isSelected
|
setCheckedSilent(isSelected)
|
||||||
drag.isVisible = draggable
|
drag.isVisible = draggable
|
||||||
|
|
||||||
if (ActiveServerProvider.isOffline()) {
|
if (ActiveServerProvider.isOffline()) {
|
||||||
|
@ -109,7 +115,7 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
||||||
} else {
|
} else {
|
||||||
setupStarButtons(song)
|
setupStarButtons(song)
|
||||||
}
|
}
|
||||||
|
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,9 +157,6 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
// TODO: Should be removed
|
// TODO: Should be removed
|
||||||
fun update() {
|
fun update() {
|
||||||
|
@ -218,12 +221,10 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun updateStatus(status: DownloadStatus) {
|
fun updateStatus(status: DownloadStatus) {
|
||||||
if (status == cachedStatus) return
|
if (status == cachedStatus) return
|
||||||
cachedStatus = status
|
cachedStatus = status
|
||||||
|
|
||||||
|
|
||||||
Timber.w("STATUS: %s", status)
|
Timber.w("STATUS: %s", status)
|
||||||
|
|
||||||
when (status) {
|
when (status) {
|
||||||
|
@ -254,7 +255,7 @@ class TrackViewHolder(val view: View, var adapter: MultiTypeDiffAdapter<Identifi
|
||||||
fun updateProgress(p: Int) {
|
fun updateProgress(p: Int) {
|
||||||
if (cachedStatus == DownloadStatus.DOWNLOADING) {
|
if (cachedStatus == DownloadStatus.DOWNLOADING) {
|
||||||
progress.text = Util.formatPercentage(p)
|
progress.text = Util.formatPercentage(p)
|
||||||
} else {
|
} else {
|
||||||
progress.text = null
|
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) {
|
override fun setChecked(newStatus: Boolean) {
|
||||||
if (newStatus) {
|
observableChecked.postValue(newStatus)
|
||||||
adapter.notifySelected(downloadFile!!.longId)
|
|
||||||
} else {
|
|
||||||
adapter.notifyUnselected(downloadFile!!.longId)
|
|
||||||
}
|
|
||||||
check.isChecked = 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 androidx.lifecycle.LiveData
|
||||||
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.adapters.MultiTypeDiffAdapter
|
|
||||||
import org.moire.ultrasonic.adapters.TrackViewBinder
|
import org.moire.ultrasonic.adapters.TrackViewBinder
|
||||||
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
|
||||||
|
@ -54,7 +52,7 @@ class DownloadsFragment : MultiListFragment<DownloadFile>() {
|
||||||
|
|
||||||
viewAdapter.register(
|
viewAdapter.register(
|
||||||
TrackViewBinder(
|
TrackViewBinder(
|
||||||
checkable = true,
|
checkable = false,
|
||||||
draggable = false,
|
draggable = false,
|
||||||
context = requireContext(),
|
context = requireContext(),
|
||||||
lifecycleOwner = viewLifecycleOwner
|
lifecycleOwner = viewLifecycleOwner
|
||||||
|
@ -65,7 +63,6 @@ class DownloadsFragment : MultiListFragment<DownloadFile>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DownloadListModel(application: Application) : GenericListModel(application) {
|
class DownloadListModel(application: Application) : GenericListModel(application) {
|
||||||
private val downloader by inject<Downloader>()
|
private val downloader by inject<Downloader>()
|
||||||
|
|
||||||
|
@ -73,6 +70,3 @@ class DownloadListModel(application: Application) : GenericListModel(application
|
||||||
return downloader.observableDownloads
|
return downloader.observableDownloads
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,18 +8,14 @@ import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
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.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.GenericEntry
|
|
||||||
import org.moire.ultrasonic.domain.Identifiable
|
import org.moire.ultrasonic.domain.Identifiable
|
||||||
import org.moire.ultrasonic.domain.MusicFolder
|
import org.moire.ultrasonic.domain.MusicFolder
|
||||||
import org.moire.ultrasonic.subsonic.DownloadHandler
|
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
|
* 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 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() {
|
abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
||||||
internal val activeServerProvider: ActiveServerProvider by inject()
|
internal val activeServerProvider: ActiveServerProvider by inject()
|
||||||
|
@ -94,7 +89,7 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
||||||
*/
|
*/
|
||||||
@Suppress("CommentOverPrivateProperty")
|
@Suppress("CommentOverPrivateProperty")
|
||||||
private val musicFolderObserver = { folders: List<MusicFolder> ->
|
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 {
|
fun showFolderHeader(): Boolean {
|
||||||
return listModel.showSelectFolderHeader(arguments) &&
|
return listModel.showSelectFolderHeader(arguments) &&
|
||||||
!listModel.isOffline() && !Settings.shouldUseId3Tags
|
!listModel.isOffline() && !Settings.shouldUseId3Tags
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun setTitle(title: String?) {
|
open fun setTitle(title: String?) {
|
||||||
|
@ -147,9 +142,13 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
||||||
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(
|
||||||
newItems -> viewAdapter.submitList(newItems)
|
viewLifecycleOwner,
|
||||||
})
|
{
|
||||||
|
newItems ->
|
||||||
|
viewAdapter.submitList(newItems)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Setup the Music folder handling
|
// Setup the Music folder handling
|
||||||
listModel.getMusicFolders().observe(viewLifecycleOwner, musicFolderObserver)
|
listModel.getMusicFolders().observe(viewLifecycleOwner, musicFolderObserver)
|
||||||
|
@ -165,7 +164,7 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure whether to show the folder header
|
// Configure whether to show the folder header
|
||||||
//viewAdapter.folderHeaderEnabled = showFolderHeader()
|
// viewAdapter.folderHeaderEnabled = showFolderHeader()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -187,7 +186,7 @@ abstract class MultiListFragment<T : Identifiable> : Fragment() {
|
||||||
abstract fun onItemClick(item: T)
|
abstract fun onItemClick(item: T)
|
||||||
}
|
}
|
||||||
|
|
||||||
//abstract class EntryListFragment<T : GenericEntry, TA : GenericRowAdapter<T>> :
|
// abstract class EntryListFragment<T : GenericEntry, TA : GenericRowAdapter<T>> :
|
||||||
// GenericListFragment<T, TA>() {
|
// GenericListFragment<T, TA>() {
|
||||||
// @Suppress("LongMethod")
|
// @Suppress("LongMethod")
|
||||||
// override fun onContextMenuItemSelected(menuItem: MenuItem, item: T): Boolean {
|
// 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))
|
// bundle.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, (item is Artist))
|
||||||
// findNavController().navigate(itemClickTarget, bundle)
|
// findNavController().navigate(itemClickTarget, bundle)
|
||||||
// }
|
// }
|
||||||
//}
|
// }
|
||||||
|
|
|
@ -36,8 +36,10 @@ import android.widget.ViewFlipper
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.Navigation
|
import androidx.navigation.Navigation
|
||||||
import com.mobeta.android.dslv.DragSortListView
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import com.mobeta.android.dslv.DragSortListView.DragSortListener
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.SimpleDateFormat
|
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.KoinComponent
|
||||||
import org.koin.core.component.get
|
import org.koin.core.component.get
|
||||||
import org.moire.ultrasonic.R
|
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.EqualizerController
|
||||||
import org.moire.ultrasonic.audiofx.VisualizerController
|
import org.moire.ultrasonic.audiofx.VisualizerController
|
||||||
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.MusicDirectory
|
import org.moire.ultrasonic.domain.MusicDirectory
|
||||||
import org.moire.ultrasonic.domain.PlayerState
|
import org.moire.ultrasonic.domain.PlayerState
|
||||||
import org.moire.ultrasonic.domain.RepeatMode
|
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.Settings
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
import org.moire.ultrasonic.view.AutoRepeatButton
|
import org.moire.ultrasonic.view.AutoRepeatButton
|
||||||
import org.moire.ultrasonic.view.SongListAdapter
|
|
||||||
import org.moire.ultrasonic.view.VisualizerView
|
import org.moire.ultrasonic.view.VisualizerView
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@ -94,6 +98,8 @@ class PlayerFragment :
|
||||||
GestureDetector.OnGestureListener,
|
GestureDetector.OnGestureListener,
|
||||||
KoinComponent,
|
KoinComponent,
|
||||||
CoroutineScope by CoroutineScope(Dispatchers.Main) {
|
CoroutineScope by CoroutineScope(Dispatchers.Main) {
|
||||||
|
|
||||||
|
// Settings
|
||||||
private var swipeDistance = 0
|
private var swipeDistance = 0
|
||||||
private var swipeVelocity = 0
|
private var swipeVelocity = 0
|
||||||
private var jukeboxAvailable = false
|
private var jukeboxAvailable = false
|
||||||
|
@ -104,6 +110,7 @@ class PlayerFragment :
|
||||||
// Detectors & Callbacks
|
// Detectors & Callbacks
|
||||||
private lateinit var gestureScanner: GestureDetector
|
private lateinit var gestureScanner: GestureDetector
|
||||||
private lateinit var cancellationToken: CancellationToken
|
private lateinit var cancellationToken: CancellationToken
|
||||||
|
private lateinit var dragTouchHelper: ItemTouchHelper
|
||||||
|
|
||||||
// Data & Services
|
// Data & Services
|
||||||
private val networkAndStorageChecker: NetworkAndStorageChecker by inject()
|
private val networkAndStorageChecker: NetworkAndStorageChecker by inject()
|
||||||
|
@ -114,6 +121,7 @@ class PlayerFragment :
|
||||||
private lateinit var executorService: ScheduledExecutorService
|
private lateinit var executorService: ScheduledExecutorService
|
||||||
private var currentPlaying: DownloadFile? = null
|
private var currentPlaying: DownloadFile? = null
|
||||||
private var currentSong: MusicDirectory.Entry? = null
|
private var currentSong: MusicDirectory.Entry? = null
|
||||||
|
private lateinit var viewManager: LinearLayoutManager
|
||||||
private var rxBusSubscription: Disposable? = null
|
private var rxBusSubscription: Disposable? = null
|
||||||
private var ioScope = CoroutineScope(Dispatchers.IO)
|
private var ioScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
|
@ -133,7 +141,7 @@ class PlayerFragment :
|
||||||
private lateinit var albumTextView: TextView
|
private lateinit var albumTextView: TextView
|
||||||
private lateinit var artistTextView: TextView
|
private lateinit var artistTextView: TextView
|
||||||
private lateinit var albumArtImageView: ImageView
|
private lateinit var albumArtImageView: ImageView
|
||||||
private lateinit var playlistView: DragSortListView
|
private lateinit var playlistView: RecyclerView
|
||||||
private lateinit var positionTextView: TextView
|
private lateinit var positionTextView: TextView
|
||||||
private lateinit var downloadTrackTextView: TextView
|
private lateinit var downloadTrackTextView: TextView
|
||||||
private lateinit var downloadTotalDurationTextView: TextView
|
private lateinit var downloadTotalDurationTextView: TextView
|
||||||
|
@ -146,6 +154,10 @@ class PlayerFragment :
|
||||||
private lateinit var fullStar: Drawable
|
private lateinit var fullStar: Drawable
|
||||||
private lateinit var progressBar: SeekBar
|
private lateinit var progressBar: SeekBar
|
||||||
|
|
||||||
|
internal val viewAdapter: MultiTypeDiffAdapter<Identifiable> by lazy {
|
||||||
|
MultiTypeDiffAdapter()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
Util.applyTheme(this.context)
|
Util.applyTheme(this.context)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -322,14 +334,7 @@ class PlayerFragment :
|
||||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {}
|
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
playlistView.setOnItemClickListener { _, _, position, _ ->
|
initPlaylistDisplay()
|
||||||
networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
|
|
||||||
launch(CommunicationError.getHandler(context)) {
|
|
||||||
mediaPlayerController.play(position)
|
|
||||||
onCurrentChanged()
|
|
||||||
onSliderProgressChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerForContextMenu(playlistView)
|
registerForContextMenu(playlistView)
|
||||||
|
|
||||||
|
@ -432,15 +437,12 @@ class PlayerFragment :
|
||||||
|
|
||||||
// Scroll to current playing.
|
// Scroll to current playing.
|
||||||
private fun scrollToCurrent() {
|
private fun scrollToCurrent() {
|
||||||
val adapter = playlistView.adapter
|
val index = mediaPlayerController.playList.indexOf(currentPlaying)
|
||||||
if (adapter != null) {
|
|
||||||
val count = adapter.count
|
if (index != -1) {
|
||||||
for (i in 0 until count) {
|
val smoothScroller = LinearSmoothScroller(context)
|
||||||
if (currentPlaying == playlistView.getItemAtPosition(i)) {
|
smoothScroller.targetPosition = index
|
||||||
playlistView.smoothScrollToPositionFromTop(i, 40)
|
viewManager.startSmoothScroll(smoothScroller)
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -535,7 +537,7 @@ class PlayerFragment :
|
||||||
super.onCreateContextMenu(menu, view, menuInfo)
|
super.onCreateContextMenu(menu, view, menuInfo)
|
||||||
if (view === playlistView) {
|
if (view === playlistView) {
|
||||||
val info = menuInfo as AdapterContextMenuInfo?
|
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
|
val menuInflater = requireActivity().menuInflater
|
||||||
menuInflater.inflate(R.menu.nowplaying_context, menu)
|
menuInflater.inflate(R.menu.nowplaying_context, menu)
|
||||||
val song: MusicDirectory.Entry?
|
val song: MusicDirectory.Entry?
|
||||||
|
@ -561,7 +563,7 @@ class PlayerFragment :
|
||||||
|
|
||||||
override fun onContextItemSelected(menuItem: MenuItem): Boolean {
|
override fun onContextItemSelected(menuItem: MenuItem): Boolean {
|
||||||
val info = menuItem.menuInfo as AdapterContextMenuInfo
|
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(
|
return menuItemSelected(menuItem.itemId, downloadFile) || super.onContextItemSelected(
|
||||||
menuItem
|
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() {
|
private fun onPlaylistChanged() {
|
||||||
val mediaPlayerController = mediaPlayerController
|
val mediaPlayerController = mediaPlayerController
|
||||||
val list = mediaPlayerController.playList
|
val list = mediaPlayerController.playList
|
||||||
emptyTextView.setText(R.string.download_empty)
|
emptyTextView.setText(R.string.playlist_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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun drag(from: Int, to: Int) {}
|
viewAdapter.submitList(list)
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
emptyTextView.isVisible = list.isEmpty()
|
emptyTextView.isVisible = list.isEmpty()
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,10 @@ package org.moire.ultrasonic.fragment
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.AdapterView.AdapterContextMenuInfo
|
import android.widget.AdapterView.AdapterContextMenuInfo
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
@ -27,12 +25,12 @@ import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.Navigation
|
import androidx.navigation.Navigation
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import java.util.Collections
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.moire.ultrasonic.R
|
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.TrackViewBinder
|
import org.moire.ultrasonic.adapters.TrackViewBinder
|
||||||
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
|
||||||
|
@ -49,7 +47,6 @@ import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator
|
||||||
import org.moire.ultrasonic.util.Settings
|
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
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
@ -106,7 +103,6 @@ class TrackCollectionFragment :
|
||||||
// FIXME
|
// FIXME
|
||||||
override val itemClickTarget: Int = R.id.trackCollectionFragment
|
override val itemClickTarget: Int = R.id.trackCollectionFragment
|
||||||
|
|
||||||
|
|
||||||
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()
|
||||||
|
@ -232,9 +228,12 @@ class TrackCollectionFragment :
|
||||||
enableButtons()
|
enableButtons()
|
||||||
|
|
||||||
// Update the buttons when the selection has changed
|
// Update the buttons when the selection has changed
|
||||||
viewAdapter.selectionRevision.observe(viewLifecycleOwner, {
|
viewAdapter.selectionRevision.observe(
|
||||||
enableButtons()
|
viewLifecycleOwner,
|
||||||
})
|
{
|
||||||
|
enableButtons()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Loads the data
|
// Loads the data
|
||||||
updateDisplay(false)
|
updateDisplay(false)
|
||||||
|
@ -454,7 +453,6 @@ class TrackCollectionFragment :
|
||||||
val toastResId = R.string.select_album_n_selected
|
val toastResId = R.string.select_album_n_selected
|
||||||
Util.toast(activity, getString(toastResId, selectedCount.coerceAtLeast(0)))
|
Util.toast(activity, getString(toastResId, selectedCount.coerceAtLeast(0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableButtons(selection: List<MusicDirectory.Entry> = getSelectedSongs()) {
|
private fun enableButtons(selection: List<MusicDirectory.Entry> = getSelectedSongs()) {
|
||||||
|
@ -519,12 +517,14 @@ class TrackCollectionFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delete() {
|
private fun delete() {
|
||||||
var songs = getSelectedSongs()
|
val songs = getSelectedSongs()
|
||||||
|
|
||||||
if (songs.isEmpty()) {
|
Util.toast(
|
||||||
selectAll(selected = true, toast = false)
|
context,
|
||||||
songs = getSelectedSongs()
|
resources.getQuantityString(
|
||||||
}
|
R.plurals.select_album_n_songs_deleted, songs.size, songs.size
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
mediaPlayerController.delete(songs)
|
mediaPlayerController.delete(songs)
|
||||||
}
|
}
|
||||||
|
@ -544,8 +544,8 @@ class TrackCollectionFragment :
|
||||||
|
|
||||||
// Hide more button when results are less than album list size
|
// Hide more button when results are less than album list size
|
||||||
if (musicDirectory.getChildren().size < requireArguments().getInt(
|
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
|
moreButton!!.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
|
@ -568,7 +568,6 @@ class TrackCollectionFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val updateInterfaceWithEntries = Observer<List<MusicDirectory.Entry>> {
|
private val updateInterfaceWithEntries = Observer<List<MusicDirectory.Entry>> {
|
||||||
|
|
||||||
val entryList: MutableList<MusicDirectory.Entry> = it.toMutableList()
|
val entryList: MutableList<MusicDirectory.Entry> = it.toMutableList()
|
||||||
|
@ -577,7 +576,6 @@ class TrackCollectionFragment :
|
||||||
Collections.sort(entryList, EntryByDiscAndTrackComparator())
|
Collections.sort(entryList, EntryByDiscAndTrackComparator())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var allVideos = true
|
var allVideos = true
|
||||||
var songCount = 0
|
var songCount = 0
|
||||||
|
|
||||||
|
@ -650,14 +648,6 @@ class TrackCollectionFragment :
|
||||||
playAllButtonVisible = !(isAlbumList || entryList.isEmpty()) && !allVideos
|
playAllButtonVisible = !(isAlbumList || entryList.isEmpty()) && !allVideos
|
||||||
shareButtonVisible = !isOffline() && songCount > 0
|
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) {
|
if (playAllButton != null) {
|
||||||
playAllButton!!.isVisible = playAllButtonVisible
|
playAllButton!!.isVisible = playAllButtonVisible
|
||||||
}
|
}
|
||||||
|
@ -666,11 +656,10 @@ class TrackCollectionFragment :
|
||||||
shareButton!!.isVisible = shareButtonVisible
|
shareButton!!.isVisible = shareButtonVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (songCount > 0 && listModel.showHeader) {
|
if (songCount > 0 && listModel.showHeader) {
|
||||||
val 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(it, 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)
|
||||||
|
@ -678,7 +667,6 @@ class TrackCollectionFragment :
|
||||||
viewAdapter.submitList(entryList)
|
viewAdapter.submitList(entryList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val playAll = requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false)
|
val playAll = requireArguments().getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false)
|
||||||
if (playAll && songCount > 0) {
|
if (playAll && songCount > 0) {
|
||||||
playAll(
|
playAll(
|
||||||
|
@ -688,8 +676,6 @@ class TrackCollectionFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
listModel.currentListIsSortable = true
|
listModel.currentListIsSortable = true
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSelectedSongs(): List<MusicDirectory.Entry> {
|
private fun getSelectedSongs(): List<MusicDirectory.Entry> {
|
||||||
|
@ -702,8 +688,6 @@ class TrackCollectionFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override fun setTitle(title: String?) {
|
override fun setTitle(title: String?) {
|
||||||
setTitle(this@TrackCollectionFragment, title)
|
setTitle(this@TrackCollectionFragment, title)
|
||||||
}
|
}
|
||||||
|
@ -787,16 +771,11 @@ class TrackCollectionFragment :
|
||||||
menuItem: MenuItem,
|
menuItem: MenuItem,
|
||||||
item: MusicDirectory.Entry
|
item: MusicDirectory.Entry
|
||||||
): Boolean {
|
): Boolean {
|
||||||
//TODO
|
// TODO
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemClick(item: MusicDirectory.Entry) {
|
override fun onItemClick(item: MusicDirectory.Entry) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,8 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.koin.core.component.inject
|
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
import org.moire.ultrasonic.domain.MusicDirectory
|
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.MusicService
|
||||||
import org.moire.ultrasonic.service.MusicServiceFactory
|
import org.moire.ultrasonic.service.MusicServiceFactory
|
||||||
import org.moire.ultrasonic.util.Settings
|
import org.moire.ultrasonic.util.Settings
|
||||||
|
|
|
@ -89,7 +89,6 @@ class DownloadFile(
|
||||||
}
|
}
|
||||||
|
|
||||||
status = MutableLiveData(state)
|
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?,
|
var fileFormat: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
fun getMediaDescriptionForEntry(
|
fun getMediaDescriptionForEntry(
|
||||||
song: MusicDirectory.Entry,
|
song: MusicDirectory.Entry,
|
||||||
mediaId: String? = null,
|
mediaId: String? = null,
|
||||||
|
@ -921,8 +920,8 @@ object Util {
|
||||||
|
|
||||||
if (artistName != null) {
|
if (artistName != null) {
|
||||||
if (Settings.shouldDisplayBitrateWithArtist && (
|
if (Settings.shouldDisplayBitrateWithArtist && (
|
||||||
!bitRate.isNullOrBlank() || !fileFormat.isNullOrBlank()
|
!bitRate.isNullOrBlank() || !fileFormat.isNullOrBlank()
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
artist.append(artistName).append(" (").append(
|
artist.append(artistName).append(" (").append(
|
||||||
String.format(
|
String.format(
|
||||||
|
@ -939,7 +938,6 @@ object Util {
|
||||||
|
|
||||||
val trackNumber = song.track ?: 0
|
val trackNumber = song.track ?: 0
|
||||||
|
|
||||||
|
|
||||||
val title = StringBuilder(LINE_LENGTH)
|
val title = StringBuilder(LINE_LENGTH)
|
||||||
if (Settings.shouldShowTrackNumber && trackNumber > 0) {
|
if (Settings.shouldShowTrackNumber && trackNumber > 0) {
|
||||||
trackText = String.format(Locale.ROOT, "%02d.", trackNumber)
|
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.content.Context
|
||||||
//import android.graphics.drawable.AnimationDrawable
|
// import android.graphics.drawable.AnimationDrawable
|
||||||
//import android.graphics.drawable.Drawable
|
// import android.graphics.drawable.Drawable
|
||||||
//import android.view.View
|
// import android.view.View
|
||||||
//import android.widget.Checkable
|
// import android.widget.Checkable
|
||||||
//import android.widget.CheckedTextView
|
// import android.widget.CheckedTextView
|
||||||
//import android.widget.ImageView
|
// 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.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.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
|
||||||
//import org.moire.ultrasonic.fragment.DownloadRowAdapter
|
// import org.moire.ultrasonic.fragment.DownloadRowAdapter
|
||||||
//import org.moire.ultrasonic.service.DownloadFile
|
// import org.moire.ultrasonic.service.DownloadFile
|
||||||
//import org.moire.ultrasonic.service.MediaPlayerController
|
// import org.moire.ultrasonic.service.MediaPlayerController
|
||||||
//import org.moire.ultrasonic.service.MusicServiceFactory
|
// import org.moire.ultrasonic.service.MusicServiceFactory
|
||||||
//import org.moire.ultrasonic.util.Settings
|
// 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
|
||||||
//
|
//
|
||||||
///**
|
// /**
|
||||||
// * 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 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 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)
|
||||||
// var fiveStar1: ImageView = view.findViewById(R.id.song_five_star_1)
|
// var fiveStar1: ImageView = view.findViewById(R.id.song_five_star_1)
|
||||||
|
@ -252,42 +252,42 @@
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
//// fun updateDownloadStatus2(
|
// // fun updateDownloadStatus2(
|
||||||
//// downloadFile: DownloadFile,
|
// // downloadFile: DownloadFile,
|
||||||
//// ) {
|
// // ) {
|
||||||
////
|
// //
|
||||||
//// var image: Drawable? = null
|
// // var image: Drawable? = null
|
||||||
////
|
// //
|
||||||
//// when (downloadFile.status.value) {
|
// // when (downloadFile.status.value) {
|
||||||
//// DownloadStatus.DONE -> {
|
// // DownloadStatus.DONE -> {
|
||||||
//// image = if (downloadFile.isSaved) DownloadRowAdapter.pinImage else DownloadRowAdapter.downloadedImage
|
// // image = if (downloadFile.isSaved) DownloadRowAdapter.pinImage else DownloadRowAdapter.downloadedImage
|
||||||
//// status.text = null
|
// // status.text = null
|
||||||
//// }
|
// // }
|
||||||
//// DownloadStatus.DOWNLOADING -> {
|
// // DownloadStatus.DOWNLOADING -> {
|
||||||
//// status.text = Util.formatPercentage(downloadFile.progress.value!!)
|
// // status.text = Util.formatPercentage(downloadFile.progress.value!!)
|
||||||
//// image = DownloadRowAdapter.downloadingImage
|
// // image = DownloadRowAdapter.downloadingImage
|
||||||
//// }
|
// // }
|
||||||
//// else -> {
|
// // else -> {
|
||||||
//// status.text = null
|
// // status.text = null
|
||||||
//// }
|
// // }
|
||||||
//// }
|
// // }
|
||||||
////
|
// //
|
||||||
//// // TODO: Migrate the image animation stuff from SongView into this class
|
// // // TODO: Migrate the image animation stuff from SongView into this class
|
||||||
////
|
// //
|
||||||
//// if (image != null) {
|
// // if (image != null) {
|
||||||
//// status.setCompoundDrawablesWithIntrinsicBounds(
|
// // status.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
//// image, null, null, null
|
// // image, null, null, null
|
||||||
//// )
|
// // )
|
||||||
//// }
|
// // }
|
||||||
////
|
// //
|
||||||
//// if (image === DownloadRowAdapter.downloadingImage) {
|
// // if (image === DownloadRowAdapter.downloadingImage) {
|
||||||
//// // FIXME
|
// // // FIXME
|
||||||
////// val frameAnimation = image as AnimationDrawable
|
// //// val frameAnimation = image as AnimationDrawable
|
||||||
//////
|
// ////
|
||||||
////// frameAnimation.setVisible(true, true)
|
// //// frameAnimation.setVisible(true, true)
|
||||||
////// frameAnimation.start()
|
// //// frameAnimation.start()
|
||||||
//// }
|
// // }
|
||||||
//// }
|
// // }
|
||||||
//
|
//
|
||||||
// override fun setChecked(newStatus: Boolean) {
|
// override fun setChecked(newStatus: Boolean) {
|
||||||
// check.isChecked = newStatus
|
// check.isChecked = newStatus
|
||||||
|
@ -313,4 +313,4 @@
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
//}
|
// }
|
||||||
|
|
|
@ -2,31 +2,22 @@
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
xmlns:a="http://schemas.android.com/apk/res/android"
|
xmlns:a="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
a:orientation="vertical"
|
a:orientation="vertical"
|
||||||
a:layout_width="fill_parent"
|
a:layout_width="fill_parent"
|
||||||
a:layout_height="fill_parent">
|
a:layout_height="fill_parent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
a:id="@+id/playlist_empty"
|
a:id="@+id/playlist_empty"
|
||||||
a:text="@string/download.empty"
|
a:text="@string/playlist.empty"
|
||||||
a:layout_width="fill_parent"
|
a:layout_width="fill_parent"
|
||||||
a:layout_height="wrap_content"
|
a:layout_height="wrap_content"
|
||||||
a:padding="10dip"/>
|
a:padding="10dip"/>
|
||||||
|
|
||||||
<com.mobeta.android.dslv.DragSortListView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
a:id="@+id/playlist_view"
|
a:id="@+id/playlist_view"
|
||||||
a:layout_width="fill_parent"
|
a:layout_width="fill_parent"
|
||||||
a:layout_height="0dip"
|
a:layout_height="0dip"
|
||||||
a:layout_weight="1"
|
a:layout_weight="1"
|
||||||
a:fastScrollEnabled="true"
|
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" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
|
@ -15,7 +15,8 @@
|
||||||
a:background="@android:color/transparent"
|
a:background="@android:color/transparent"
|
||||||
a:focusable="false"
|
a:focusable="false"
|
||||||
a:gravity="center_vertical"
|
a:gravity="center_vertical"
|
||||||
a:src="?attr/drag_vertical" />
|
a:src="?attr/drag_vertical"
|
||||||
|
a:importantForAccessibility="no" />
|
||||||
|
|
||||||
<CheckedTextView
|
<CheckedTextView
|
||||||
a:id="@+id/song_check"
|
a:id="@+id/song_check"
|
||||||
|
@ -96,6 +97,7 @@
|
||||||
a:focusable="false"
|
a:focusable="false"
|
||||||
a:gravity="center_vertical"
|
a:gravity="center_vertical"
|
||||||
a:paddingEnd="8dip"
|
a:paddingEnd="8dip"
|
||||||
a:src="?attr/star_hollow" />
|
a:src="?attr/star_hollow"
|
||||||
|
a:contentDescription="@string/download.menu_star"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
|
@ -43,7 +43,7 @@
|
||||||
<string name="delete_playlist">Opravdu smazat %1$s</string>
|
<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_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.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_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_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>
|
<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="delete_playlist">Möchtest du %1$s löschen</string>
|
||||||
<string name="download.bookmark_removed" formatted="false">Lesezeichen entfernt</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.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_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_off">Fernbedienung ausgeschaltet. Musik wird auf dem Telefon wiedergegeben.</string>
|
||||||
<string name="download.jukebox_offline">Fernbedienungs-Modus is Offline nicht verfügbar.</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="delete_playlist">Quieres eliminar %1$s</string>
|
||||||
<string name="download.bookmark_removed" formatted="false">Marcador eliminado.</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.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_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_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>
|
<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="delete_playlist">Voulez-vous supprimer %1$s</string>
|
||||||
<string name="download.bookmark_removed" formatted="false">Signet supprimé.</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.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_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_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>
|
<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="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_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.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_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_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>
|
<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="delete_playlist">Vuoi eliminare %1$s</string>
|
||||||
<string name="download.bookmark_removed" formatted="false">Segnalibro rimosso.</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.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_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_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>
|
<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="delete_playlist">Wil je %1$s verwijderen?</string>
|
||||||
<string name="download.bookmark_removed" formatted="false">Bladwijzer verwijderd.</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.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_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_off">Afstandsbediening uitgeschakeld; muziek wordt afgespeeld op de telefoon.</string>
|
||||||
<string name="download.jukebox_offline">Afstandsbediening is niet beschikbaar in offline-modus.</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="delete_playlist">Czy chcesz usunąć %1$s?</string>
|
||||||
<string name="download.bookmark_removed" formatted="false">Zakładka usunięta.</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.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_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_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>
|
<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="delete_playlist">Você quer excluir %1$s</string>
|
||||||
<string name="download.bookmark_removed" formatted="false">Favorito removido.</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.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_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_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>
|
<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="delete_playlist">Você quer apagar %1$s</string>
|
||||||
<string name="download.bookmark_removed" formatted="false">Favorito removido.</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.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_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_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>
|
<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="delete_playlist">Вы хотите удалить %1$s</string>
|
||||||
<string name="download.bookmark_removed" formatted="false">Закладка удалена</string>
|
<string name="download.bookmark_removed" formatted="false">Закладка удалена</string>
|
||||||
<string name="download.bookmark_set_at_position" formatted="false">Закладка установлена на %s</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_not_authorized">Пульт дистанционного управления не допускается. Пожалуйста, включите режим музыкального автомата в <b> Пользователи > Настройки </b>на вашем Subsonic сервере.</string>
|
||||||
<string name="download.jukebox_off">Пульт управления выключен. Музыка играет на телефоне</string>
|
<string name="download.jukebox_off">Пульт управления выключен. Музыка играет на телефоне</string>
|
||||||
<string name="download.jukebox_offline">Пульт дистанционного управления недоступен в автономном режиме.</string>
|
<string name="download.jukebox_offline">Пульт дистанционного управления недоступен в автономном режиме.</string>
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
<string name="delete_playlist">确定要删除 %1$s吗</string>
|
<string name="delete_playlist">确定要删除 %1$s吗</string>
|
||||||
<string name="download.bookmark_removed" formatted="false">书签已删除。</string>
|
<string name="download.bookmark_removed" formatted="false">书签已删除。</string>
|
||||||
<string name="download.bookmark_set_at_position" formatted="false">书签设置为 %s。</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_not_authorized">不允许远程控制. 请在您的服务器上的 <b>Users > Settings</b> 打开点唱机模式。</string>
|
||||||
<string name="download.jukebox_off">关闭远程控制,音乐将在手机上播放</string>
|
<string name="download.jukebox_off">关闭远程控制,音乐将在手机上播放</string>
|
||||||
<string name="download.jukebox_offline">离线模式不支持远程控制</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="delete_playlist">Do you want to delete %1$s</string>
|
||||||
<string name="download.bookmark_removed" formatted="false">Bookmark removed.</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.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_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_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>
|
<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="one">%d song unpinned</item>
|
||||||
<item quantity="other">%d songs unpinned</item>
|
<item quantity="other">%d songs unpinned</item>
|
||||||
</plurals>
|
</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">
|
<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>
|
||||||
|
|
Loading…
Reference in New Issue