Merge pull request #1689 from tomaThomas/playlist-search

Add search in playlist feature
This commit is contained in:
Hemanth Savarala 2024-12-07 01:01:53 +05:30 committed by GitHub
commit 27d4074852
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 137 additions and 7 deletions

View file

@ -161,4 +161,5 @@ const val CIRCLE_PLAY_BUTTON = "circle_play_button"
const val SWIPE_ANYWHERE_NOW_PLAYING = "swipe_anywhere_now_playing"
const val PAUSE_HISTORY = "pause_history"
const val MANAGE_AUDIO_FOCUS = "manage_audio_focus"
const val SWIPE_DOWN_DISMISS = "swipe_to_dismiss"
const val SWIPE_DOWN_DISMISS = "swipe_to_dismiss"
const val ENABLE_SEARCH_PLAYLIST= "enable_search_playlist"

View file

@ -25,6 +25,7 @@ import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.db.toSongsEntity
import code.name.monkey.retromusic.dialogs.RemoveSongFromPlaylistDialog
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.ViewUtil
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
@ -43,9 +44,20 @@ class OrderablePlaylistSongAdapter(
val libraryViewModel: LibraryViewModel by activity.viewModel()
private var filtered = false
private var filter: CharSequence? = null
private var fullDataSet: MutableList<Song>
init {
this.setHasStableIds(true)
this.setMultiSelectMenuRes(R.menu.menu_playlists_songs_selection)
fullDataSet = dataSet.toMutableList()
}
override fun swapDataSet(dataSet: List<Song>) {
super.swapDataSet(dataSet)
fullDataSet = dataSet.toMutableList()
onFilter(filter)
}
override fun getItemId(position: Int): Long {
@ -90,13 +102,22 @@ class OrderablePlaylistSongAdapter(
return super.onSongMenuItemClick(item)
}
override fun onClick(v: View?) {
if (isInQuickSelectMode || !filtered) {
super.onClick(v)
} else {
val position = fullDataSet.indexOf(dataSet.get(layoutPosition))
MusicPlayerRemote.openQueueKeepShuffleMode(fullDataSet, position, true)
}
}
init {
dragView?.isVisible = true
}
}
override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean {
if (isInQuickSelectMode) {
if (isInQuickSelectMode || filtered) {
return false
}
return ViewUtil.hitTest(holder.imageText!!, x, y) || ViewUtil.hitTest(
@ -127,8 +148,27 @@ class OrderablePlaylistSongAdapter(
}
fun saveSongs(playlistEntity: PlaylistEntity) {
onFilter(null)
activity.lifecycleScope.launch(Dispatchers.IO) {
libraryViewModel.insertSongs(dataSet.toSongsEntity(playlistEntity))
}
}
fun onFilter(text: CharSequence?) {
filter = text
if (text.isNullOrEmpty()) {
filtered = false
dataSet = fullDataSet
} else {
filtered = true
dataSet = fullDataSet.filter { song -> song.title.contains(text, ignoreCase = true) }
.toMutableList()
}
notifyDataSetChanged()
}
fun hasSongs(): Boolean {
return itemCount > 0 || (filtered && fullDataSet.size > 0)
}
}

View file

@ -8,6 +8,8 @@ import android.view.MenuItem
import android.view.View
import androidx.core.view.doOnPreDraw
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
@ -21,12 +23,14 @@ import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.elevatedAccentColor
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.fragments.search.clearText
import code.name.monkey.retromusic.glide.RetroGlideExtension.playlistOptions
import code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreview
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ThemedFastScroller
import com.bumptech.glide.Glide
import com.google.android.material.shape.MaterialShapeDrawable
@ -35,6 +39,9 @@ import com.google.android.material.transition.MaterialContainerTransform
import com.google.android.material.transition.MaterialSharedAxis
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
@ -51,6 +58,8 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
private lateinit var playlist: PlaylistWithSongs
private lateinit var playlistSongAdapter: OrderablePlaylistSongAdapter
private val _searchFlow = MutableSharedFlow<CharSequence?>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform(requireContext(), true).apply {
@ -71,6 +80,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
// binding.container.transitionName = playlist.playlistEntity.playlistName
setUpRecyclerView()
setUpSearch()
setupButtons()
viewModel.getPlaylist().observe(viewLifecycleOwner) { playlistWithSongs ->
playlist = playlistWithSongs
@ -112,6 +122,33 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
}
}
private fun setUpSearch() {
if (!PreferenceUtil.enableSearchPlaylist) {
binding.playlistSearchView.visibility = View.GONE
} else {
binding.playlistSearchView.visibility = View.VISIBLE
}
binding.playlistSearchView.addTextChangedListener { text ->
lifecycleScope.launch {
_searchFlow.emit(text)
binding.clearSearch.visibility =
if (text.isNullOrBlank()) View.GONE else View.VISIBLE
}
}
binding.clearSearch.setOnClickListener {
lifecycleScope.launch {
_searchFlow.emit(null)
binding.playlistSearchView.clearText()
binding.clearSearch.visibility = View.GONE
}
}
lifecycleScope.launch {
_searchFlow.debounce(300).collect { text ->
playlistSongAdapter.onFilter(text)
}
}
}
private fun setUpRecyclerView() {
playlistSongAdapter = OrderablePlaylistSongAdapter(
arguments.extraPlaylistId,
@ -150,8 +187,18 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
}
private fun checkIsEmpty() {
binding.empty.isVisible = playlistSongAdapter.itemCount == 0
binding.emptyText.isVisible = playlistSongAdapter.itemCount == 0
if (_binding != null) {
if (playlistSongAdapter.itemCount != 0) {
binding.empty.isVisible = false
} else {
binding.empty.isVisible = true
if (playlistSongAdapter.hasSongs()) {
binding.emptyText.text = getString(R.string.no_search_results)
} else {
binding.emptyText.text = getString(R.string.no_songs)
}
}
}
}
override fun onDestroy() {
@ -160,6 +207,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
}
override fun onPause() {
binding.playlistSearchView.clearText()
playlistSongAdapter.saveSongs(playlist.playlistEntity)
super.onPause()
}

View file

@ -680,6 +680,9 @@ object PreferenceUtil {
val rememberLastTab: Boolean
get() = sharedPreferences.getBoolean(REMEMBER_LAST_TAB, true)
val enableSearchPlaylist: Boolean
get() = sharedPreferences.getBoolean(ENABLE_SEARCH_PLAYLIST, true)
var lastTab: Int
get() = sharedPreferences
.getInt(LAST_USED_TAB, 0)

View file

@ -30,6 +30,33 @@
android:paddingBottom="16dp"
android:paddingHorizontal="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/playlistSearchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/play_button"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/clear_search"
android:background="@null"
android:hint="@string/action_search"
android:inputType="text|textAutoComplete"
android:padding="12dp"
android:textAppearance="@style/TextViewSubtitle1">
</com.google.android.material.textfield.TextInputEditText>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/clear_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?roundSelector"
android:padding="10dp"
android:visibility="gone"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/playlistSearchView"
app:srcCompat="@drawable/ic_close"
app:tint="?attr/colorControlNormal"
tools:visibility="visible" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image"
android:layout_width="0dp"
@ -116,12 +143,13 @@
<LinearLayout
android:id="@android:id/empty"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="30dp"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:visibility="visible">
<com.google.android.material.textview.MaterialTextView

View file

@ -277,6 +277,7 @@
<string name="no_purchase_found">No purchase found.</string>
<string name="no_results">No results</string>
<string name="no_songs">You have no songs</string>
<string name="no_search_results">No search results</string>
<string name="normal">Normal</string>
<string name="normal_lyrics">Normal lyrics</string>
<string name="not_listed_in_media_store"><![CDATA[<b>%s</b> is not listed in the media store.]]></string>
@ -365,6 +366,7 @@
<string name="pref_summary_open_source_licences">License details for open source software</string>
<string name="pref_summary_pause_history">When enabled, newly played songs won\'t show in history</string>
<string name="pref_summary_remember_tab">Navigate to the last used tab on start</string>
<string name="pref_summary_enable_search_playlist">Show a search field in a playlist</string>
<string name="pref_summary_show_lyrics">Display synced lyrics over album cover</string>
<string name="pref_summary_suggestions">Show New Music Mix on homescreen</string>
<string name="pref_summary_swipe_anywhere_now_playing">Enables changing song by swiping anywhere on the now playing screen</string>
@ -412,6 +414,7 @@
<string name="pref_title_open_source_licences">Open source licences</string>
<string name="pref_title_pause_history">Pause history</string>
<string name="pref_title_remember_tab">Remember last tab</string>
<string name="pref_title_enable_search_playlist">Enable search in playlist</string>
<string name="pref_title_show_lyrics">Show lyrics</string>
<string name="pref_title_suggestions">Show suggestions</string>
<string name="pref_title_swipe_anywhere_now_playing">Swipe anywhere to change song</string>

View file

@ -67,6 +67,13 @@
android:summary="@string/pref_summary_remember_tab"
android:title="@string/pref_title_remember_tab" />
<code.name.monkey.appthemehelper.common.prefs.supportv7.ATESwitchPreference
android:defaultValue="true"
android:key="enable_search_playlist"
android:layout="@layout/list_item_view_switch"
android:summary="@string/pref_summary_enable_search_playlist"
android:title="@string/pref_title_enable_search_playlist" />
<code.name.monkey.appthemehelper.common.prefs.supportv7.ATEListPreference
android:defaultValue="0"
android:entries="@array/pref_tab_text_mode_titles"

View file

@ -17,7 +17,7 @@ yslibrary_keyboardvisibilityevent_version = "3.0.0-RC3"
koinAndroid = "3.4.0"
kotlinGradlePlugin = "1.9.22"
kotlinxCoroutinesAndroid = "1.7.3"
kotlinxCoroutinesAndroid = "1.8.1"
android_tab_library_version = "2.2.0"
fast_scroll_libraryVersion = "1.2.0"
lifecycle_version = "2.7.0"