Allow sorting of PVR Recordings and optionally hide watched items. (#694)

* Added menu options to PVR recordings view fragment: Sorting and hide_watched option.
Default to unsorted (showing the order delivered by Kodi). Default to not hide watched recordings. (#385)

* Implemented sorting and filtering of PVR recordings as per user's selection. (#385)
This commit is contained in:
Selaron 2019-12-17 20:34:42 +01:00 committed by Synced Synapse
parent 37ef130a7c
commit a3ed983fee
5 changed files with 238 additions and 3 deletions

View File

@ -49,7 +49,8 @@ public class Settings {
SORT_BY_ALBUM = 5, SORT_BY_ALBUM = 5,
SORT_BY_ARTIST = 6, SORT_BY_ARTIST = 6,
SORT_BY_ARTIST_YEAR = 7, SORT_BY_ARTIST_YEAR = 7,
SORT_BY_LAST_PLAYED = 8; SORT_BY_LAST_PLAYED = 8,
UNSORTED = 9;
/** /**
* Preferences keys. * 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 String KEY_PREF_TVSHOWS_SHOW_WATCHED_STATUS = "tvshows_show_watched_status";
public static final boolean DEFAULT_PREF_TVSHOWS_SHOW_WATCHED_STATUS = true; 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 // Filter disabled addons on addons list
public static final String KEY_PREF_ADDONS_FILTER_HIDE_DISABLED = "addons_filter_hide_disabled"; 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; public static final boolean DEFAULT_PREF_ADDONS_FILTER_HIDE_DISABLED = false;

View File

@ -16,12 +16,17 @@
package org.xbmc.kore.ui.sections.video; package org.xbmc.kore.ui.sections.video;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout; import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
@ -32,6 +37,7 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.xbmc.kore.R; import org.xbmc.kore.R;
import org.xbmc.kore.Settings;
import org.xbmc.kore.host.HostManager; import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.ApiCallback; import org.xbmc.kore.jsonrpc.ApiCallback;
import org.xbmc.kore.jsonrpc.method.PVR; 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.LogUtils;
import org.xbmc.kore.utils.UIUtils; import org.xbmc.kore.utils.UIUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import butterknife.BindView; import butterknife.BindView;
@ -96,7 +105,7 @@ public class PVRRecordingsListFragment extends Fragment
@Override @Override
public void onActivityCreated (Bundle savedInstanceState) { public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(false); setHasOptionsMenu(true);
browseRecordings(); browseRecordings();
} }
@ -106,6 +115,91 @@ public class PVRRecordingsListFragment extends Fragment
unbinder.unbind(); 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 * 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 // To prevent the empty text from appearing on the first load, set it now
emptyView.setText(getString(R.string.no_recordings_found_refresh)); 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<PVRType.DetailsRecording> finalResult = filter(result);
sort(finalResult);
setupRecordingsGridview(finalResult);
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.setRefreshing(false);
} }
private List<PVRType.DetailsRecording> filter(List<PVRType.DetailsRecording> 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<PVRType.DetailsRecording> 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<PVRType.DetailsRecording> 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<PVRType.DetailsRecording> 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<PVRType.DetailsRecording>() {
@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<PVRType.DetailsRecording>() {
@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 @Override
public void onError(int errorCode, String description) { public void onError(int errorCode, String description) {
if (!isAdded()) return; if (!isAdded()) return;

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2015 Synced Synapse. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:title="@string/sort_order"
app:showAsAction="never">
<menu>
<group
android:checkableBehavior="single">
<item
android:id="@+id/action_sort_by_name_and_date_added"
android:title="@string/sort_by_name"
app:showAsAction="never"/>
<item
android:id="@+id/action_sort_by_date_added"
android:title="@string/sort_by_date_added"
app:showAsAction="never"/>
<item
android:id="@+id/action_unsorted"
android:title="@string/unsorted"
app:showAsAction="never"/>
</group>
</menu>
</item>
<group
android:checkableBehavior="all">
<item
android:id="@+id/action_hide_watched"
android:title="@string/hide_watched"
app:showAsAction="never"/>
</group>
</menu>

View File

@ -325,6 +325,7 @@
<string name="sort_by_length">Nach Länge</string> <string name="sort_by_length">Nach Länge</string>
<string name="sort_by_date_added">Nach Hinzufügungsdatum</string> <string name="sort_by_date_added">Nach Hinzufügungsdatum</string>
<string name="sort_by_last_played">Zuletzt gespielt</string> <string name="sort_by_last_played">Zuletzt gespielt</string>
<string name="unsorted">Nicht sortiert</string>
<string name="ignore_prefixes">Präfixe ignorieren</string> <string name="ignore_prefixes">Präfixe ignorieren</string>
<!-- Preferences strings --> <!-- Preferences strings -->

View File

@ -340,6 +340,7 @@
<string name="sort_by_length">By length</string> <string name="sort_by_length">By length</string>
<string name="sort_by_date_added">By date added</string> <string name="sort_by_date_added">By date added</string>
<string name="sort_by_last_played">By last played</string> <string name="sort_by_last_played">By last played</string>
<string name="unsorted">Unsorted</string>
<string name="ignore_prefixes">Ignore prefixes</string> <string name="ignore_prefixes">Ignore prefixes</string>
<string name="show_watched_status">Show watched status</string> <string name="show_watched_status">Show watched status</string>
<string name="show_rating">Show rating</string> <string name="show_rating">Show rating</string>