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