diff --git a/app/src/main/java/org/xbmc/kore/Settings.java b/app/src/main/java/org/xbmc/kore/Settings.java index 83bb7e2..bdaab72 100644 --- a/app/src/main/java/org/xbmc/kore/Settings.java +++ b/app/src/main/java/org/xbmc/kore/Settings.java @@ -49,7 +49,8 @@ public class Settings { SORT_BY_ALBUM = 5, SORT_BY_ARTIST = 6, SORT_BY_ARTIST_YEAR = 7, - SORT_BY_LAST_PLAYED = 8; + SORT_BY_LAST_PLAYED = 8, + UNSORTED = 9; /** * Preferences keys. @@ -136,6 +137,14 @@ public class Settings { public static final String KEY_PREF_TVSHOWS_SHOW_WATCHED_STATUS = "tvshows_show_watched_status"; public static final boolean DEFAULT_PREF_TVSHOWS_SHOW_WATCHED_STATUS = true; + // Filter watched pvr recordings on movie list + public static final String KEY_PREF_PVR_RECORDINGS_FILTER_HIDE_WATCHED = "pvr_recordings_filter_hide_watched"; + public static final boolean DEFAULT_PREF_PVR_RECORDINGS_FILTER_HIDE_WATCHED = false; + + // Sort order on pvr recordings + public static final String KEY_PREF_PVR_RECORDINGS_SORT_ORDER = "pvr_recordings_sort_order"; + public static final int DEFAULT_PREF_PVR_RECORDINGS_SORT_ORDER = UNSORTED; + // Filter disabled addons on addons list public static final String KEY_PREF_ADDONS_FILTER_HIDE_DISABLED = "addons_filter_hide_disabled"; public static final boolean DEFAULT_PREF_ADDONS_FILTER_HIDE_DISABLED = false; diff --git a/app/src/main/java/org/xbmc/kore/ui/sections/video/PVRRecordingsListFragment.java b/app/src/main/java/org/xbmc/kore/ui/sections/video/PVRRecordingsListFragment.java index cfbcd4d..c203077 100644 --- a/app/src/main/java/org/xbmc/kore/ui/sections/video/PVRRecordingsListFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/sections/video/PVRRecordingsListFragment.java @@ -16,12 +16,17 @@ package org.xbmc.kore.ui.sections.video; import android.content.Context; +import android.content.SharedPreferences; 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.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; @@ -32,6 +37,7 @@ import android.widget.TextView; import android.widget.Toast; import org.xbmc.kore.R; +import org.xbmc.kore.Settings; import org.xbmc.kore.host.HostManager; import org.xbmc.kore.jsonrpc.ApiCallback; import org.xbmc.kore.jsonrpc.method.PVR; @@ -40,6 +46,9 @@ import org.xbmc.kore.jsonrpc.type.PVRType; import org.xbmc.kore.utils.LogUtils; import org.xbmc.kore.utils.UIUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import butterknife.BindView; @@ -96,7 +105,7 @@ public class PVRRecordingsListFragment extends Fragment @Override public void onActivityCreated (Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - setHasOptionsMenu(false); + setHasOptionsMenu(true); browseRecordings(); } @@ -106,6 +115,91 @@ public class PVRRecordingsListFragment extends Fragment unbinder.unbind(); } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if (!isAdded()) { + // HACK: Fix crash reported on Play Store. Why does this is necessary is beyond me + // copied from MovieListFragment#onCreateOptionsMenu + super.onCreateOptionsMenu(menu, inflater); + return; + } + + inflater.inflate(R.menu.pvr_recording_list, menu); + + // Setup filters + MenuItem hideWatched = menu.findItem(R.id.action_hide_watched), + sortByNameAndDate = menu.findItem(R.id.action_sort_by_name_and_date_added), + sortByDateAdded = menu.findItem(R.id.action_sort_by_date_added), + unsorted = menu.findItem(R.id.action_unsorted); + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); + hideWatched.setChecked(preferences.getBoolean(Settings.KEY_PREF_PVR_RECORDINGS_FILTER_HIDE_WATCHED, Settings.DEFAULT_PREF_PVR_RECORDINGS_FILTER_HIDE_WATCHED)); + + int sortOrder = preferences.getInt(Settings.KEY_PREF_PVR_RECORDINGS_SORT_ORDER, Settings.DEFAULT_PREF_PVR_RECORDINGS_SORT_ORDER); + switch (sortOrder) { + case Settings.SORT_BY_DATE_ADDED: + sortByDateAdded.setChecked(true); + break; + case Settings.SORT_BY_NAME: + sortByNameAndDate.setChecked(true); + break; + default: + unsorted.setChecked(true); + break; + } + + super.onCreateOptionsMenu(menu, inflater); + } + + + /** + * Use this to reload the items in the list + */ + public void refreshList() { + onRefresh(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); + switch (item.getItemId()) { + case R.id.action_hide_watched: + item.setChecked(!item.isChecked()); + preferences.edit() + .putBoolean(Settings.KEY_PREF_PVR_RECORDINGS_FILTER_HIDE_WATCHED, item.isChecked()) + .apply(); + refreshList(); + break; + case R.id.action_sort_by_name_and_date_added: + item.setChecked(true); + preferences.edit() + .putInt(Settings.KEY_PREF_PVR_RECORDINGS_SORT_ORDER, Settings.SORT_BY_NAME) + .apply(); + refreshList(); + break; + case R.id.action_sort_by_date_added: + item.setChecked(true); + preferences.edit() + .putInt(Settings.KEY_PREF_PVR_RECORDINGS_SORT_ORDER, Settings.SORT_BY_DATE_ADDED) + .apply(); + refreshList(); + break; + case R.id.action_unsorted: + item.setChecked(true); + preferences.edit() + .putInt(Settings.KEY_PREF_PVR_RECORDINGS_SORT_ORDER, Settings.UNSORTED) + .apply(); + refreshList(); + break; + default: + break; + } + + return super.onOptionsItemSelected(item); + } + + /** * Swipe refresh layout callback */ @@ -134,10 +228,90 @@ public class PVRRecordingsListFragment extends Fragment // To prevent the empty text from appearing on the first load, set it now emptyView.setText(getString(R.string.no_recordings_found_refresh)); - setupRecordingsGridview(result); + + // As the JSON RPC API does not support sorting or filter parameters for PVR.GetRecordings + // we apply the sorting and filtering right here. + // See https://kodi.wiki/view/JSON-RPC_API/v9#PVR.GetRecordings + List finalResult = filter(result); + sort(finalResult); + + setupRecordingsGridview(finalResult); swipeRefreshLayout.setRefreshing(false); } + private List filter(List itemList) { + 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) { + return itemList; + } + + List result = new ArrayList<>(itemList.size()); + for (PVRType.DetailsRecording item:itemList) { + if (hideWatched) { + if (item.playcount > 0) { + continue; // Skip this item as it is played. + } else { + // Heuristic: Try to guess if it's play from resume timestamp. + double resumePosition = item.resume.position; + int runtime = item.runtime; + if (runtime < resumePosition) { + // Tv show duration is smaller than resume position. + // The tv show likely has been watched. + // It's still possible some minutes have not yet been watched + // at the end of the show as some minutes at the + // recording start do not belong to the show. + // Never the less skip this item. + continue; + } + } + } + + // more conditions may be added here + + result.add(item); + } + + return result; + } + + private void sort(List itemList) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); + + int sortOrder = preferences.getInt(Settings.KEY_PREF_PVR_RECORDINGS_SORT_ORDER, Settings.DEFAULT_PREF_PVR_RECORDINGS_SORT_ORDER); + + Comparator comparator; + switch (sortOrder) { + case Settings.SORT_BY_DATE_ADDED: + // sort by recording start time descending (most current first) + // luckily the starttime is in sortable format yyyy-MM-dd hh:mm:ss + comparator = new Comparator() { + @Override + public int compare(PVRType.DetailsRecording a, PVRType.DetailsRecording b) { + return b.starttime.compareTo(a.starttime); + } + }; + Collections.sort(itemList, comparator); + break; + case Settings.SORT_BY_NAME: + // sort by recording title and start time + comparator = new Comparator() { + @Override + public int compare(PVRType.DetailsRecording a, PVRType.DetailsRecording b) { + int result = a.title.compareToIgnoreCase(b.title); + if (0 == result) { // note the yoda condition ;) + // sort by starttime descending (most current first) + result = b.starttime.compareTo(a.starttime); + } + return result; + } + }; + Collections.sort(itemList, comparator); + break; + } + } + @Override public void onError(int errorCode, String description) { if (!isAdded()) return; diff --git a/app/src/main/res/menu/pvr_recording_list.xml b/app/src/main/res/menu/pvr_recording_list.xml new file mode 100644 index 0000000..c52edea --- /dev/null +++ b/app/src/main/res/menu/pvr_recording_list.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c0cc8f6..9b7f3ec 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -325,6 +325,7 @@ Nach Länge Nach Hinzufügungsdatum Zuletzt gespielt + Nicht sortiert Präfixe ignorieren diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index af71674..5d731b2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -340,6 +340,7 @@ By length By date added By last played + Unsorted Ignore prefixes Show watched status Show rating