Implemented search field for PVR views

* Implemented search field for PVR views. (#695)
* Implemented search field for PVRChannelEPGListFragment. (#695)
This commit is contained in:
Selaron 2019-12-22 16:52:36 +01:00 committed by Synced Synapse
parent a3ed983fee
commit dd53f621e5
4 changed files with 361 additions and 15 deletions

View File

@ -0,0 +1,212 @@
package org.xbmc.kore.ui;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
import android.text.TextUtils;
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.EditText;
import org.xbmc.kore.R;
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
public abstract class AbstractSearchableFragment extends Fragment implements SearchView.OnQueryTextListener {
private String searchFilter = null;
private String savedSearchFilter;
private boolean supportsSearch;
private boolean isPaused;
private SearchView searchView;
private final String BUNDLE_KEY_SEARCH_QUERY = "search_query";
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = super.onCreateView(inflater, container, savedInstanceState);
if (savedInstanceState != null) {
savedSearchFilter = savedInstanceState.getString(BUNDLE_KEY_SEARCH_QUERY);
}
searchFilter = savedSearchFilter;
return root;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.abstractcursorlistfragment, menu);
if (supportsSearch) {
setupSearchMenuItem(menu, inflater);
}
super.onCreateOptionsMenu(menu, inflater);
}
/**
* Use this to indicate your fragment supports search queries.
* Get the entered search query using {@link #getSearchFilter()}
* <br/>
* Note: make sure this is set before {@link #onCreateOptionsMenu(Menu, MenuInflater)} is called.
* For instance in {@link #onAttach(Activity)}
*
* @param supportsSearch true if you support search queries, false otherwise
*/
public void setSupportsSearch(boolean supportsSearch) {
this.supportsSearch = supportsSearch;
}
/**
* Save the search state of the list fragment
*/
public void saveSearchState() {
savedSearchFilter = searchFilter;
}
/**
* @return text entered in searchview
*/
public String getSearchFilter() {
return searchFilter;
}
private void setupSearchMenuItem(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.media_search, menu);
MenuItem searchMenuItem = menu.findItem(R.id.action_search);
if (searchMenuItem != null) {
searchView = (SearchView) MenuItemCompat.getActionView(searchMenuItem);
searchView.setOnQueryTextListener(this);
searchView.setQueryHint(getString(R.string.action_search));
if (!TextUtils.isEmpty(savedSearchFilter)) {
searchMenuItem.expandActionView();
searchView.setQuery(savedSearchFilter, false);
//noinspection RestrictedApi
searchView.clearFocus();
}
MenuItemCompat.setOnActionExpandListener(searchMenuItem, new MenuItemCompat.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
searchFilter = savedSearchFilter = null;
refreshList();
return true;
}
});
}
//Handle clearing search query using the close button (X button).
View view = searchView.findViewById(R.id.search_close_btn);
if (view != null) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EditText editText = (EditText) searchView.findViewById(R.id.search_src_text);
editText.setText("");
searchView.setQuery("", false);
searchFilter = savedSearchFilter = "";
refreshList();
}
});
}
}
/**
* Search view callbacks
*/
/** {@inheritDoc} */
@Override
public boolean onQueryTextChange(String newText) {
if ((!searchView.hasFocus()) && TextUtils.isEmpty(newText)) {
//onQueryTextChange called as a result of manually expanding the searchView in setupSearchMenuItem(...)
return true;
}
/**
* When this fragment is paused, onQueryTextChange is called with an empty string.
* This causes problems restoring the list fragment when returning.
*/
if (isPaused)
return true;
searchFilter = newText;
refreshList();
return true;
}
/** {@inheritDoc} */
@Override
public boolean onQueryTextSubmit(String newText) {
// All is handled in onQueryTextChange
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.action_refresh:
refreshList();
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onResume() {
super.onResume();
isPaused = false;
}
@Override
public void onPause() {
super.onPause();
isPaused = true;
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (!TextUtils.isEmpty(searchFilter)) {
savedSearchFilter = searchFilter;
}
outState.putString(BUNDLE_KEY_SEARCH_QUERY, savedSearchFilter);
super.onSaveInstanceState(outState);
}
/**
* Event bus post. Called when the syncing process ended
*
* @param event Refreshes data
*/
public void onEventMainThread(MediaSyncEvent event) {
onSyncProcessEnded(event);
}
protected void onSyncProcessEnded(MediaSyncEvent event) {
}
/**
* Use this to reload the items in the list
*/
protected abstract void refreshList();
}

View File

@ -20,6 +20,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
@ -34,6 +35,7 @@ import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.ApiCallback;
import org.xbmc.kore.jsonrpc.method.PVR;
import org.xbmc.kore.jsonrpc.type.PVRType;
import org.xbmc.kore.ui.AbstractSearchableFragment;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
@ -49,7 +51,7 @@ import butterknife.Unbinder;
/**
* Fragment that presents the Guide for a channel
*/
public class PVRChannelEPGListFragment extends Fragment
public class PVRChannelEPGListFragment extends AbstractSearchableFragment
implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = LogUtils.makeLogTag(PVRChannelEPGListFragment.class);
@ -113,13 +115,16 @@ public class PVRChannelEPGListFragment extends Fragment
});
listView.setEmptyView(emptyView);
super.onCreateView(inflater, container, savedInstanceState);
return root;
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(false);
setHasOptionsMenu(true);
setSupportsSearch(true);
browseEPG();
}
@ -144,6 +149,11 @@ public class PVRChannelEPGListFragment extends Fragment
}
}
@Override
protected void refreshList() {
onRefresh();
}
/**
* Get the EPF for the channel and setup the listview
*/
@ -155,7 +165,10 @@ public class PVRChannelEPGListFragment extends Fragment
if (!isAdded()) return;
// To prevent the empty text from appearing on the first load, set it now
emptyView.setText(getString(R.string.no_broadcasts_found_refresh));
setupEPGListview(result);
List<PVRType.DetailsBroadcast> finalResult = filter(result);
setupEPGListview(finalResult);
swipeRefreshLayout.setRefreshing(false);
}
@ -173,6 +186,47 @@ public class PVRChannelEPGListFragment extends Fragment
}, callbackHandler);
}
private List<PVRType.DetailsBroadcast> filter(List<PVRType.DetailsBroadcast> itemList) {
String searchFilter = getSearchFilter();
if (TextUtils.isEmpty(searchFilter)) {
return itemList;
}
// Split searchFilter to multiple lowercase words
String[] lcWords = searchFilter.toLowerCase().split(" ");;
List<PVRType.DetailsBroadcast> result = new ArrayList<>(itemList.size());
for (PVRType.DetailsBroadcast item:itemList) {
// Require all words to match the item:
boolean allWordsMatch = true;
for (String lcWord:lcWords) {
if (!searchFilterWordMatches(lcWord, item)) {
allWordsMatch = false;
break;
}
}
if (!allWordsMatch) {
continue; // skip this item
}
result.add(item);
}
return result;
}
public boolean searchFilterWordMatches(String lcWord, PVRType.DetailsBroadcast item) {
if (item.title.toLowerCase().contains(lcWord)) {
return true;
}
if (item.plot.toLowerCase().contains(lcWord)){
return true;
}
return false;
}
/**
* Called when we get the Guide
*

View File

@ -20,8 +20,8 @@ import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
@ -41,10 +41,12 @@ import org.xbmc.kore.jsonrpc.ApiException;
import org.xbmc.kore.jsonrpc.method.PVR;
import org.xbmc.kore.jsonrpc.method.Player;
import org.xbmc.kore.jsonrpc.type.PVRType;
import org.xbmc.kore.ui.AbstractSearchableFragment;
import org.xbmc.kore.ui.OnBackPressedListener;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
@ -54,7 +56,7 @@ import butterknife.Unbinder;
/**
* Fragment that presents the movie list
*/
public class PVRChannelsListFragment extends Fragment
public class PVRChannelsListFragment extends AbstractSearchableFragment
implements SwipeRefreshLayout.OnRefreshListener, OnBackPressedListener {
private static final String TAG = LogUtils.makeLogTag(PVRChannelsListFragment.class);
@ -118,13 +120,16 @@ public class PVRChannelsListFragment extends Fragment
});
gridView.setEmptyView(emptyView);
super.onCreateView(inflater, container, savedInstanceState);
return root;
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(false);
setHasOptionsMenu(true);
setSupportsSearch(true);
if (selectedChannelGroupId == -1) {
if ((channelGroupAdapter == null) ||
@ -174,6 +179,11 @@ public class PVRChannelsListFragment extends Fragment
unbinder.unbind();
}
@Override
protected void refreshList() {
onRefresh();
}
/**
* Swipe refresh layout callback
*/
@ -252,6 +262,46 @@ public class PVRChannelsListFragment extends Fragment
}, callbackHandler);
}
private List<PVRType.DetailsChannel> filter(List<PVRType.DetailsChannel> itemList) {
String searchFilter = getSearchFilter();
if (TextUtils.isEmpty(searchFilter)) {
return itemList;
}
// Split searchFilter to multiple lowercase words
String[] lcWords = searchFilter.toLowerCase().split(" ");;
List<PVRType.DetailsChannel> result = new ArrayList<>(itemList.size());
for (PVRType.DetailsChannel item:itemList) {
// Require all words to match the item:
boolean allWordsMatch = true;
for (String lcWord:lcWords) {
if (!searchFilterWordMatches(lcWord, item)) {
allWordsMatch = false;
break;
}
}
if (!allWordsMatch) {
continue; // skip this item
}
result.add(item);
}
return result;
}
public boolean searchFilterWordMatches(String lcWord, PVRType.DetailsChannel item) {
if (item.label.toLowerCase().contains(lcWord)) {
return true;
}
if (item.broadcastnow != null && item.broadcastnow.title.toLowerCase().contains(lcWord)){
return true;
}
return false;
}
/**
* Called when we get the channel groups
*
@ -296,7 +346,10 @@ public class PVRChannelsListFragment extends Fragment
// To prevent the empty text from appearing on the first load, set it now
emptyView.setText(getString(R.string.no_channels_found_refresh));
setupChannelsGridview(result);
List<PVRType.DetailsChannel> finalResult = filter(result);
setupChannelsGridview(finalResult);
swipeRefreshLayout.setRefreshing(false);
}

View File

@ -21,8 +21,8 @@ import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -43,6 +43,7 @@ import org.xbmc.kore.jsonrpc.ApiCallback;
import org.xbmc.kore.jsonrpc.method.PVR;
import org.xbmc.kore.jsonrpc.method.Player;
import org.xbmc.kore.jsonrpc.type.PVRType;
import org.xbmc.kore.ui.AbstractSearchableFragment;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
@ -58,7 +59,7 @@ import butterknife.Unbinder;
/**
* Fragment that presents the PVR recordings list
*/
public class PVRRecordingsListFragment extends Fragment
public class PVRRecordingsListFragment extends AbstractSearchableFragment
implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = LogUtils.makeLogTag(PVRRecordingsListFragment.class);
@ -85,6 +86,7 @@ public class PVRRecordingsListFragment extends Fragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_pvr_list, container, false);
unbinder = ButterKnife.bind(this, root);
hostManager = HostManager.getInstance(getActivity());
@ -99,6 +101,8 @@ public class PVRRecordingsListFragment extends Fragment
});
gridView.setEmptyView(emptyView);
super.onCreateView(inflater, container, savedInstanceState);
return root;
}
@ -106,6 +110,7 @@ public class PVRRecordingsListFragment extends Fragment
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
setSupportsSearch(true);
browseRecordings();
}
@ -153,9 +158,7 @@ public class PVRRecordingsListFragment extends Fragment
}
/**
* Use this to reload the items in the list
*/
@Override
public void refreshList() {
onRefresh();
}
@ -199,7 +202,6 @@ public class PVRRecordingsListFragment extends Fragment
return super.onOptionsItemSelected(item);
}
/**
* Swipe refresh layout callback
*/
@ -243,7 +245,12 @@ public class PVRRecordingsListFragment extends Fragment
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
boolean hideWatched = preferences.getBoolean(Settings.KEY_PREF_PVR_RECORDINGS_FILTER_HIDE_WATCHED, Settings.DEFAULT_PREF_PVR_RECORDINGS_FILTER_HIDE_WATCHED);
if (!hideWatched) {
String searchFilter = getSearchFilter();
boolean hasSearchFilter = !TextUtils.isEmpty(searchFilter);
// Split searchFilter to multiple lowercase words
String[] lcWords = hasSearchFilter ? searchFilter.toLowerCase().split(" ") : null;
if (!(hideWatched || hasSearchFilter)) {
return itemList;
}
@ -268,7 +275,19 @@ public class PVRRecordingsListFragment extends Fragment
}
}
// more conditions may be added here
if (hasSearchFilter) {
// Require all lowercase words to match the item:
boolean allWordsMatch = true;
for (String lcWord:lcWords) {
if (!searchFilterWordMatches(lcWord, item)) {
allWordsMatch = false;
break;
}
}
if (!allWordsMatch) {
continue; // skip this item
}
}
result.add(item);
}
@ -276,6 +295,14 @@ public class PVRRecordingsListFragment extends Fragment
return result;
}
private boolean searchFilterWordMatches(String lcWord, PVRType.DetailsRecording item) {
if (item.title.toLowerCase().contains(lcWord)
|| item.channel.toLowerCase().contains(lcWord)) {
return true;
}
return false;
}
private void sort(List<PVRType.DetailsRecording> itemList) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());