diff --git a/application/vlc-android/res/layout-land/audio_player.xml b/application/vlc-android/res/layout-land/audio_player.xml index 4f8640a0f96d0f783c22e3d20fcd29a032a3c839..aa3855c54e69c624e57bd8d21a61d6313300098a 100644 --- a/application/vlc-android/res/layout-land/audio_player.xml +++ b/application/vlc-android/res/layout-land/audio_player.xml @@ -296,7 +296,7 @@ android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@+id/header_play_pause" - app:layout_constraintStart_toEndOf="@+id/playlist_playasaudio_off" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <EditText diff --git a/application/vlc-android/res/layout/playlist_item.xml b/application/vlc-android/res/layout/playlist_item.xml index 4ce37114e20ec80e45c1783b79681f291b17f5e1..5f7afb290aba4657642e9226e50d2bd525c4ea71 100644 --- a/application/vlc-android/res/layout/playlist_item.xml +++ b/application/vlc-android/res/layout/playlist_item.xml @@ -39,6 +39,10 @@ name="showTrackNumbers" type="java.lang.Boolean" /> + <variable + name="showReorderButtons" + type="java.lang.Boolean" /> + <variable name="masked" type="java.lang.Boolean" /> @@ -214,6 +218,7 @@ android:layout_gravity="center" android:layout_marginEnd="8dp" android:background="?attr/selectableItemBackgroundBorderless" + android:visibility="@{showReorderButtons ? View.VISIBLE : View.GONE}" android:clickable="true" android:focusable="true" android:onClick="@{holder::onMoveDownClick}" @@ -236,6 +241,7 @@ android:clickable="true" android:focusable="true" android:onClick="@{holder::onMoveUpClick}" + android:visibility="@{showReorderButtons ? View.VISIBLE : View.GONE}" android:padding="8dp" android:scaleType="center" app:layout_constraintBottom_toBottomOf="parent" diff --git a/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.kt b/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.kt index c56dcc147e39fc59ceb53714f02d1f88fe7f6397..1c70ccb3d14a0a5ce015fb52d393f3576e4ee447 100644 --- a/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.kt +++ b/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.kt @@ -68,6 +68,7 @@ import org.videolan.medialibrary.Tools import org.videolan.medialibrary.interfaces.media.Bookmark import org.videolan.medialibrary.interfaces.media.MediaWrapper import org.videolan.medialibrary.media.MediaLibraryItem +import org.videolan.resources.AndroidDevices import org.videolan.resources.AppContextProvider import org.videolan.resources.TAG_ITEM import org.videolan.resources.util.parcelable @@ -164,6 +165,7 @@ class AudioPlayer : Fragment(), PlaylistAdapter.IPlayer, TextWatcher, IAudioPlay private var lastEndsAt = -1L private var isDragging = false private var currentChapters: Pair<MediaWrapper, List<MediaPlayer.Chapter>?>? = null + private lateinit var callback: SwipeDragItemTouchHelperCallback override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -177,6 +179,13 @@ class AudioPlayer : Fragment(), PlaylistAdapter.IPlayer, TextWatcher, IAudioPlay playlistModel = PlaylistModel.get(this) playlistModel.progress.observe(this@AudioPlayer) { it?.let { updateProgress(it) } } playlistModel.speed.observe(this@AudioPlayer) { showChips() } + playlistModel.filteringState.observe(this@AudioPlayer) { + callback.longPressDragEnable = !it + if (isTablet() || AndroidDevices.isTv) { + playlistAdapter.showReorderButtons = !it + playlistAdapter.notifyDataSetChanged() + } + } playlistAdapter.setModel(playlistModel) playlistModel.dataset.asFlow().conflate().onEach { doUpdate() @@ -232,7 +241,7 @@ class AudioPlayer : Fragment(), PlaylistAdapter.IPlayer, TextWatcher, IAudioPlay coverMediaSwitcherListener.onChapterSwitching(false) } - val callback = SwipeDragItemTouchHelperCallback(playlistAdapter, true) + callback = SwipeDragItemTouchHelperCallback(playlistAdapter, true) val touchHelper = ItemTouchHelper(callback) touchHelper.attachToRecyclerView(binding.songsList) diff --git a/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayerAnimator.kt b/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayerAnimator.kt index 1e8745c63235ab7541b493b7852d79ee82a82946..d98f5d122eb17e1bd6e836c36004124ee4d4c0b7 100644 --- a/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayerAnimator.kt +++ b/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayerAnimator.kt @@ -219,7 +219,10 @@ internal class AudioPlayerAnimator : IAudioPlayerAnimator, LifecycleObserver { override suspend fun updateBackground() { if (Settings.getInstance(audioPlayer.requireActivity()).getBoolean("blurred_cover_background", true)) { val mw = audioPlayer.playlistModel.currentMediaWrapper ?: return - if (currentCoverArt == mw.artworkMrl) return + if (currentCoverArt == mw.artworkMrl) { + if (currentCoverArt == null) setDefaultBackground() + return + } currentCoverArt = mw.artworkMrl if (mw.artworkMrl.isNullOrEmpty()) setDefaultBackground() else { diff --git a/application/vlc-android/src/org/videolan/vlc/gui/audio/PlaylistAdapter.kt b/application/vlc-android/src/org/videolan/vlc/gui/audio/PlaylistAdapter.kt index 7d341f3a20b7a48c67fb07356248fc7badd2489e..7046e54b80ae12aeb55cfc00e145e3897e3baaa2 100644 --- a/application/vlc-android/src/org/videolan/vlc/gui/audio/PlaylistAdapter.kt +++ b/application/vlc-android/src/org/videolan/vlc/gui/audio/PlaylistAdapter.kt @@ -72,6 +72,7 @@ private const val ACTION_MOVED = "action_moved" class PlaylistAdapter(private val player: IPlayer) : DiffUtilAdapter<MediaWrapper, PlaylistAdapter.ViewHolder>(), SwipeDragHelperAdapter, SchedulerCallback { var showTrackNumbers: Boolean = false + var showReorderButtons: Boolean = true private var defaultCoverVideo: BitmapDrawable private var defaultCoverAudio: BitmapDrawable private var model: PlaylistModel? = null @@ -156,8 +157,7 @@ class PlaylistAdapter(private val player: IPlayer) : DiffUtilAdapter<MediaWrappe val tablet = holder.binding.itemDelete.context.isTablet() || AndroidDevices.isTv if (tablet) holder.binding.itemDelete.setVisible() else holder.binding.itemDelete.setGone() - if (tablet) holder.binding.itemMoveDown.setVisible() else holder.binding.itemMoveDown.setGone() - if (tablet) holder.binding.itemMoveUp.setVisible() else holder.binding.itemMoveUp.setGone() + holder.binding.showReorderButtons = showReorderButtons && tablet holder.binding.executePendingBindings() } @@ -203,18 +203,21 @@ class PlaylistAdapter(private val player: IPlayer) : DiffUtilAdapter<MediaWrappe } override fun onItemDismiss(position: Int) { - val media = getItem(position) - val message = String.format(AppContextProvider.appResources.getString(R.string.remove_playlist_item), media.title) - if (player is Fragment) { - UiTools.snackerWithCancel(player.requireActivity(), message, overAudioPlayer = true, action = {}) { - model?.run { insertMedia(position, media) } - } - } else if (player is Activity) { - UiTools.snackerWithCancel(player, message, action = {}) { - model?.run { insertMedia(position, media) } + model?.let { + val media = getItem(position) + val message = String.format(AppContextProvider.appResources.getString(R.string.remove_playlist_item), media.title) + val originalPosition = it.getOriginalPosition(position) + if (player is Fragment) { + UiTools.snackerWithCancel(player.requireActivity(), message, overAudioPlayer = true, action = {}) { + model?.run { insertMedia(originalPosition, media) } + } + } else if (player is Activity) { + UiTools.snackerWithCancel(player, message, action = {}) { + model?.run { insertMedia(originalPosition, media) } + } } + remove(position) } - remove(position) } fun setModel(model: PlaylistModel) { diff --git a/application/vlc-android/src/org/videolan/vlc/gui/helpers/SwipeDragItemTouchHelperCallback.kt b/application/vlc-android/src/org/videolan/vlc/gui/helpers/SwipeDragItemTouchHelperCallback.kt index 6fc4601cff09a630141da21c4dc88a6213e4b602..5c1f2f7fe5da1deb4f2bca836ea09fc87666201c 100644 --- a/application/vlc-android/src/org/videolan/vlc/gui/helpers/SwipeDragItemTouchHelperCallback.kt +++ b/application/vlc-android/src/org/videolan/vlc/gui/helpers/SwipeDragItemTouchHelperCallback.kt @@ -28,7 +28,7 @@ import androidx.recyclerview.widget.RecyclerView import org.videolan.vlc.gui.helpers.hf.PinCodeDelegate import org.videolan.vlc.interfaces.SwipeDragHelperAdapter -class SwipeDragItemTouchHelperCallback(private val mAdapter: SwipeDragHelperAdapter, private val longPressDragEnable: Boolean = false, private val lockedInSafeMode: Boolean = false) : ItemTouchHelper.Callback() { +class SwipeDragItemTouchHelperCallback(private val mAdapter: SwipeDragHelperAdapter, var longPressDragEnable: Boolean = false, private val lockedInSafeMode: Boolean = false) : ItemTouchHelper.Callback() { private var dragFrom = -1 private var dragTo = -1 var swipeEnabled = true diff --git a/application/vlc-android/src/org/videolan/vlc/util/FilterDelegate.kt b/application/vlc-android/src/org/videolan/vlc/util/FilterDelegate.kt index 8d6774ec8e8ef78beb04caab02ba72782d817010..8b173c9735f8055e1a8be582cca1d9b1ed12d517 100644 --- a/application/vlc-android/src/org/videolan/vlc/util/FilterDelegate.kt +++ b/application/vlc-android/src/org/videolan/vlc/util/FilterDelegate.kt @@ -7,10 +7,10 @@ import org.videolan.medialibrary.interfaces.media.MediaWrapper import org.videolan.medialibrary.media.MediaLibraryItem import org.videolan.resources.AppContextProvider import org.videolan.vlc.media.MediaUtils -import java.util.* +import java.util.Locale -open class FilterDelegate<T : MediaLibraryItem>(protected val dataset: MutableLiveData<out List<T>>) { - private var sourceSet: List<T>? = null +open class FilterDelegate<T : MediaLibraryItem>(val dataset: MutableLiveData<out List<T>>) { + var sourceSet: List<T>? = null protected fun initSource() : List<T>? { if (sourceSet === null) sourceSet = (dataset.value) diff --git a/application/vlc-android/src/org/videolan/vlc/viewmodels/PlaylistModel.kt b/application/vlc-android/src/org/videolan/vlc/viewmodels/PlaylistModel.kt index ca046bb7fa16de6810d7fa4711191df93a0696f9..799615bdd17534f7200e52bcf690f8f0b472b467 100644 --- a/application/vlc-android/src/org/videolan/vlc/viewmodels/PlaylistModel.kt +++ b/application/vlc-android/src/org/videolan/vlc/viewmodels/PlaylistModel.kt @@ -24,7 +24,11 @@ import android.support.v4.media.session.PlaybackStateCompat import androidx.annotation.MainThread import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.* +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ObsoleteCoroutinesApi import kotlinx.coroutines.channels.actor @@ -47,12 +51,18 @@ class PlaylistModel : ViewModel(), PlaybackService.Callback by EmptyPBSCallback private var originalDataset : MutableList<MediaWrapper>? = null val selection : Int get() = if (filtering) -1 else service?.playlistManager?.currentIndex ?: -1 - private var filtering = false + var filtering = false + set(value) { + field = value + filteringState.value = value + } + val filteringState = MutableLiveData<Boolean>() val progress = MediatorLiveData<PlaybackProgress>() val speed = MediatorLiveData<Float>() val playerState = MutableLiveData<PlayerState>() val connected : Boolean get() = service !== null + var lastQuery: CharSequence? = null private val filter by lazy(LazyThreadSafetyMode.NONE) { PlaylistFilterDelegate(dataset) } @@ -76,7 +86,13 @@ class PlaylistModel : ViewModel(), PlaybackService.Callback by EmptyPBSCallback override fun update() { service?.run { - dataset.value = media.toMutableList() + if (filtering) { + originalDataset = media.toMutableList() + filter.sourceSet = originalDataset + dataset.value = filter.dataset.value?.toMutableList() ?: media.toMutableList() + filterActor.trySend(lastQuery) + } else + dataset.value = media.toMutableList() playerState.value = PlayerState(isPlaying, title, artist) } } @@ -84,14 +100,33 @@ class PlaylistModel : ViewModel(), PlaybackService.Callback by EmptyPBSCallback val hasMedia get() = service?.hasMedia() ?: false - fun insertMedia(position: Int, media: MediaWrapper) = service?.insertItem(position, media) + fun insertMedia(position: Int, media: MediaWrapper) { + service?.insertItem(position, media) + if (filtering) { + service?.let { + originalDataset = it.media.toMutableList() + filter.sourceSet = originalDataset + } + } + } + + /** + * Get original position even if current list is filtered + * + * @param position the current position in the filtered list or not + * @return the original position (in the unfiltered list) + */ + fun getOriginalPosition(position: Int) = if (filtering && originalDataset != null) { + originalDataset!!.indexOf(dataset.get(position)) + } else position - fun remove(position: Int) = service?.remove(position) + fun remove(position: Int) = service?.remove(getOriginalPosition(position)) fun move(from: Int, to: Int) = service?.moveItem(from, to) @MainThread fun filter(query: CharSequence?) { + lastQuery = query val filtering = query != null if (this.filtering != filtering) { this.filtering = filtering