Add search functionality to playlist selection dialog

- Add search EditText with icon to filter playlists by name
- Implement real-time filtering with TextWatcher
- Add clear button that appears when text is entered
- Search is case-insensitive and matches partial playlist names
- Store complete playlist list for filtering operations

Closes #13154
This commit is contained in:
Muril 2026-02-06 16:53:17 -03:00
parent 56a043669a
commit 7e4ecf0ceb
3 changed files with 126 additions and 0 deletions

View File

@ -3,9 +3,12 @@ package org.schabi.newpipe.local.dialog;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.DEFAULT_THUMBNAIL_ID;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
@ -21,7 +24,10 @@ import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.local.LocalItemListAdapter;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
@ -32,7 +38,10 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
private RecyclerView playlistRecyclerView;
private LocalItemListAdapter playlistAdapter;
private TextView playlistDuplicateIndicator;
private EditText playlistSearchEditText;
private View playlistSearchClear;
private List<PlaylistDuplicatesEntry> allPlaylists = new ArrayList<>();
private final CompositeDisposable playlistDisposables = new CompositeDisposable();
/**
@ -82,6 +91,11 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
final View newPlaylistButton = view.findViewById(R.id.newPlaylist);
newPlaylistButton.setOnClickListener(ignored -> openCreatePlaylistDialog());
// Setup search functionality
playlistSearchEditText = view.findViewById(R.id.playlist_search_edit_text);
playlistSearchClear = view.findViewById(R.id.playlist_search_clear);
setupSearch();
playlistDisposables.add(playlistManager
.getPlaylistDuplicates(getStreamEntities().get(0).getUrl())
.observeOn(AndroidSchedulers.mainThread())
@ -103,12 +117,66 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
playlistDisposables.clear();
playlistRecyclerView = null;
playlistAdapter = null;
playlistSearchEditText = null;
playlistSearchClear = null;
allPlaylists.clear();
}
/*//////////////////////////////////////////////////////////////////////////
// Helper
//////////////////////////////////////////////////////////////////////////*/
private void setupSearch() {
if (playlistSearchEditText == null || playlistSearchClear == null) {
return;
}
playlistSearchEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(final CharSequence s, final int start,
final int count, final int after) {
}
@Override
public void onTextChanged(final CharSequence s, final int start,
final int before, final int count) {
}
@Override
public void afterTextChanged(final Editable s) {
final String query = s.toString();
playlistSearchClear.setVisibility(
query.isEmpty() ? View.GONE : View.VISIBLE);
filterPlaylists(query);
}
});
playlistSearchClear.setOnClickListener(v -> {
playlistSearchEditText.setText("");
playlistSearchClear.setVisibility(View.GONE);
});
}
private void filterPlaylists(final String query) {
if (playlistAdapter == null || allPlaylists.isEmpty()) {
return;
}
if (query.isEmpty()) {
playlistAdapter.clearStreamItemList();
playlistAdapter.addItems(allPlaylists);
} else {
final String lowerCaseQuery = query.toLowerCase(Locale.getDefault());
final List<PlaylistDuplicatesEntry> filteredPlaylists = allPlaylists.stream()
.filter(playlist -> playlist.name.toLowerCase(Locale.getDefault())
.contains(lowerCaseQuery))
.collect(Collectors.toList());
playlistAdapter.clearStreamItemList();
playlistAdapter.addItems(filteredPlaylists);
}
}
/** Display create playlist dialog. */
public void openCreatePlaylistDialog() {
if (getStreamEntities() == null || !isAdded()) {
@ -129,6 +197,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
if (playlistAdapter != null
&& playlistRecyclerView != null
&& playlistDuplicateIndicator != null) {
allPlaylists = playlists;
playlistAdapter.clearStreamItemList();
playlistAdapter.addItems(playlists);
playlistRecyclerView.setVisibility(View.VISIBLE);

View File

@ -3,10 +3,66 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/playlist_search_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="8dp"
android:background="?attr/rounded_rectangle_background">
<org.schabi.newpipe.views.NewPipeEditText
android:id="@+id/playlist_search_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="@null"
android:drawableStart="@drawable/ic_search"
android:drawablePadding="8dp"
android:focusable="true"
android:focusableInTouchMode="true"
android:hint="@string/search_playlists"
android:imeOptions="actionSearch|flagNoFullscreen"
android:inputType="textFilter|textNoSuggestions"
android:maxLines="1"
android:paddingStart="12dp"
android:paddingTop="12dp"
android:paddingEnd="48dp"
android:paddingBottom="12dp"
android:textSize="15sp" />
<FrameLayout
android:id="@+id/playlist_search_clear"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="end|center_vertical"
android:contentDescription="@string/clear"
android:focusable="true"
android:visibility="gone">
<View
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="center"
android:background="?attr/selectableItemBackgroundBorderless" />
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:contentDescription="@string/clear"
android:scaleType="fitCenter"
android:src="@drawable/ic_close" />
</FrameLayout>
</FrameLayout>
<RelativeLayout
android:id="@+id/newPlaylist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/playlist_search_container"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">

View File

@ -456,6 +456,7 @@
<string name="preferred_player_fetcher_notification_title">Getting info…</string>
<string name="preferred_player_fetcher_notification_message">"Loading requested content"</string>
<!-- Local Playlist -->
<string name="search_playlists">Search playlists</string>
<string name="create_playlist">New Playlist</string>
<string name="duplicate_in_playlist">The playlists that are grayed out already contain this item.</string>
<string name="rename_playlist">Rename</string>