diff options
8 files changed, 111 insertions, 8 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java index ee19a0339..595f37e40 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.dialog; import android.content.Context; import android.view.View; +import android.widget.CheckBox; import android.widget.EditText; import android.widget.RadioButton; @@ -14,7 +15,6 @@ import de.danoeh.antennapod.model.feed.FeedFilter; * Displays a dialog with a text box for filtering episodes and two radio buttons for exclusion/inclusion */ public abstract class EpisodeFilterDialog extends AlertDialog.Builder { - private final FeedFilter initialFilter; public EpisodeFilterDialog(Context context, FeedFilter filter) { @@ -26,8 +26,10 @@ public abstract class EpisodeFilterDialog extends AlertDialog.Builder { setView(rootView); final EditText etxtEpisodeFilterText = rootView.findViewById(R.id.etxtEpisodeFilterText); + final EditText etxtEpisodeFilterDurationText = rootView.findViewById(R.id.etxtEpisodeFilterDurationText); final RadioButton radioInclude = rootView.findViewById(R.id.radio_filter_include); final RadioButton radioExclude = rootView.findViewById(R.id.radio_filter_exclude); + final CheckBox checkboxDuration = rootView.findViewById(R.id.checkbox_filter_duration); if (initialFilter.includeOnly()) { radioInclude.setChecked(true); @@ -40,18 +42,31 @@ public abstract class EpisodeFilterDialog extends AlertDialog.Builder { radioInclude.setChecked(false); etxtEpisodeFilterText.setText(""); } + if (initialFilter.hasMinimalDurationFilter()) { + checkboxDuration.setChecked(true); + // Store minimal duration in seconds, show in minutes + etxtEpisodeFilterDurationText.setText(String.valueOf(initialFilter.getMinimalDurationFilter() / 60)); + } setNegativeButton(R.string.cancel_label, null); setPositiveButton(R.string.confirm_label, (dialog, which) -> { String includeString = ""; String excludeString = ""; + int minimalDuration = -1; if (radioInclude.isChecked()) { includeString = etxtEpisodeFilterText.getText().toString(); } else { excludeString = etxtEpisodeFilterText.getText().toString(); } - - onConfirmed(new FeedFilter(includeString, excludeString)); + if (checkboxDuration.isChecked()) { + try { + // Store minimal duration in seconds + minimalDuration = Integer.parseInt(etxtEpisodeFilterDurationText.getText().toString()) * 60; + } catch (NumberFormatException e) { + // Do not change anything on error + } + } + onConfirmed(new FeedFilter(includeString, excludeString, minimalDuration)); } ); } diff --git a/app/src/main/res/layout/episode_filter_dialog.xml b/app/src/main/res/layout/episode_filter_dialog.xml index 9661a8e72..e8672c2f3 100644 --- a/app/src/main/res/layout/episode_filter_dialog.xml +++ b/app/src/main/res/layout/episode_filter_dialog.xml @@ -40,4 +40,21 @@ android:minLines="1" android:scrollbars="vertical" /> + <CheckBox + android:id="@+id/checkbox_filter_duration" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/episode_filters_duration" /> + + <EditText + android:id="@+id/etxtEpisodeFilterDurationText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:cursorVisible="true" + android:focusable="true" + android:focusableInTouchMode="true" + android:inputType="numberSigned" + android:lines="1" /> + </LinearLayout> diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java index 46ab7502b..b3eec8c1d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java @@ -322,6 +322,10 @@ class DBUpgrader { db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN " + PodDBAdapter.KEY_FEED_TAGS + " TEXT;"); } + if (oldVersion < 2050000) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + + " ADD COLUMN " + PodDBAdapter.KEY_MINIMAL_DURATION_FILTER + " INTEGER DEFAULT -1"); + } } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java index 55cfafbbb..f1364255d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java @@ -53,7 +53,7 @@ public class PodDBAdapter { private static final String TAG = "PodDBAdapter"; public static final String DATABASE_NAME = "Antennapod.db"; - public static final int VERSION = 2030000; + public static final int VERSION = 2050000; /** * Maximum number of arguments for IN-operator. @@ -113,6 +113,7 @@ public class PodDBAdapter { public static final String KEY_LAST_PLAYED_TIME = "last_played_time"; public static final String KEY_INCLUDE_FILTER = "include_filter"; public static final String KEY_EXCLUDE_FILTER = "exclude_filter"; + public static final String KEY_MINIMAL_DURATION_FILTER = "minimal_duration_filter"; public static final String KEY_FEED_PLAYBACK_SPEED = "feed_playback_speed"; public static final String KEY_FEED_SKIP_INTRO = "feed_skip_intro"; public static final String KEY_FEED_SKIP_ENDING = "feed_skip_ending"; @@ -145,6 +146,7 @@ public class PodDBAdapter { + KEY_PASSWORD + " TEXT," + KEY_INCLUDE_FILTER + " TEXT DEFAULT ''," + KEY_EXCLUDE_FILTER + " TEXT DEFAULT ''," + + KEY_MINIMAL_DURATION_FILTER + " INTEGER DEFAULT -1," + KEY_KEEP_UPDATED + " INTEGER DEFAULT 1," + KEY_IS_PAGED + " INTEGER DEFAULT 0," + KEY_NEXT_PAGE_LINK + " TEXT," @@ -257,6 +259,7 @@ public class PodDBAdapter { TABLE_NAME_FEEDS + "." + KEY_FEED_VOLUME_ADAPTION, TABLE_NAME_FEEDS + "." + KEY_INCLUDE_FILTER, TABLE_NAME_FEEDS + "." + KEY_EXCLUDE_FILTER, + TABLE_NAME_FEEDS + "." + KEY_MINIMAL_DURATION_FILTER, TABLE_NAME_FEEDS + "." + KEY_FEED_PLAYBACK_SPEED, TABLE_NAME_FEEDS + "." + KEY_FEED_TAGS, TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_INTRO, @@ -450,6 +453,7 @@ public class PodDBAdapter { values.put(KEY_PASSWORD, prefs.getPassword()); values.put(KEY_INCLUDE_FILTER, prefs.getFilter().getIncludeFilter()); values.put(KEY_EXCLUDE_FILTER, prefs.getFilter().getExcludeFilter()); + values.put(KEY_MINIMAL_DURATION_FILTER, prefs.getFilter().getMinimalDurationFilter()); values.put(KEY_FEED_PLAYBACK_SPEED, prefs.getFeedPlaybackSpeed()); values.put(KEY_FEED_TAGS, prefs.getTagsAsString()); values.put(KEY_FEED_SKIP_INTRO, prefs.getFeedSkipIntro()); diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedPreferencesCursorMapper.java b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedPreferencesCursorMapper.java index cab6ea618..cd46bcf94 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedPreferencesCursorMapper.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedPreferencesCursorMapper.java @@ -29,6 +29,7 @@ public abstract class FeedPreferencesCursorMapper { int indexPassword = cursor.getColumnIndex(PodDBAdapter.KEY_PASSWORD); int indexIncludeFilter = cursor.getColumnIndex(PodDBAdapter.KEY_INCLUDE_FILTER); int indexExcludeFilter = cursor.getColumnIndex(PodDBAdapter.KEY_EXCLUDE_FILTER); + int indexMinimalDurationFilter = cursor.getColumnIndex(PodDBAdapter.KEY_MINIMAL_DURATION_FILTER); int indexFeedPlaybackSpeed = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_PLAYBACK_SPEED); int indexAutoSkipIntro = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_INTRO); int indexAutoSkipEnding = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_ENDING); @@ -47,6 +48,7 @@ public abstract class FeedPreferencesCursorMapper { String password = cursor.getString(indexPassword); String includeFilter = cursor.getString(indexIncludeFilter); String excludeFilter = cursor.getString(indexExcludeFilter); + int minimalDurationFilter = cursor.getInt(indexMinimalDurationFilter); float feedPlaybackSpeed = cursor.getFloat(indexFeedPlaybackSpeed); int feedAutoSkipIntro = cursor.getInt(indexAutoSkipIntro); int feedAutoSkipEnding = cursor.getInt(indexAutoSkipEnding); @@ -62,7 +64,7 @@ public abstract class FeedPreferencesCursorMapper { volumeAdaptionSetting, username, password, - new FeedFilter(includeFilter, excludeFilter), + new FeedFilter(includeFilter, excludeFilter, minimalDurationFilter), feedPlaybackSpeed, feedAutoSkipIntro, feedAutoSkipEnding, diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 390fe7d95..780de55ac 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -702,6 +702,7 @@ <string name="episode_filters_description">List of terms used to decide if an episode should be included or excluded when auto downloading</string> <string name="episode_filters_include">Include</string> <string name="episode_filters_exclude">Exclude</string> + <string name="episode_filters_duration">Minimal Duration (in minutes)</string> <string name="episode_filters_hint">Single words \n\"Multiple Words\"</string> <string name="keep_updated">Keep Updated</string> <string name="keep_updated_summary">Include this podcast when (auto-)refreshing all podcasts</string> diff --git a/core/src/test/java/de/danoeh/antennapod/core/feed/FeedFilterTest.java b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedFilterTest.java index 4ad578727..3840f6387 100644 --- a/core/src/test/java/de/danoeh/antennapod/core/feed/FeedFilterTest.java +++ b/core/src/test/java/de/danoeh/antennapod/core/feed/FeedFilterTest.java @@ -1,7 +1,10 @@ package de.danoeh.antennapod.core.feed; +import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.model.feed.FeedFilter; import de.danoeh.antennapod.model.feed.FeedItem; +import de.danoeh.antennapod.model.feed.FeedMedia; + import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -125,4 +128,32 @@ public class FeedFilterTest { assertFalse(filter.shouldAutoDownload(doNotDownload2)); } + @Test + public void testMinimalDurationFilter() { + FeedItem download = new FeedItem(); + download.setTitle("Hello friend!"); + FeedMedia downloadMedia = FeedMediaMother.anyFeedMedia(); + downloadMedia.setDuration(Converter.durationStringShortToMs("05:00", false)); + download.setMedia(downloadMedia); + // because duration of the media in unknown + FeedItem download2 = new FeedItem(); + download2.setTitle("Hello friend!"); + FeedMedia unknownDurationMedia = FeedMediaMother.anyFeedMedia(); + download2.setMedia(unknownDurationMedia); + // because it is not long enough + FeedItem doNotDownload = new FeedItem(); + doNotDownload.setTitle("Hello friend!"); + FeedMedia doNotDownloadMedia = FeedMediaMother.anyFeedMedia(); + doNotDownloadMedia.setDuration(Converter.durationStringShortToMs("02:00", false)); + doNotDownload.setMedia(doNotDownloadMedia); + + int minimalDurationFilter = 3 * 60; + FeedFilter filter = new FeedFilter("", "", minimalDurationFilter); + + assertTrue(filter.hasMinimalDurationFilter()); + assertTrue(filter.shouldAutoDownload(download)); + assertFalse(filter.shouldAutoDownload(doNotDownload)); + assertTrue(filter.shouldAutoDownload(download2)); + } + } diff --git a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedFilter.java b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedFilter.java index 31d263b24..3b35fe5bd 100644 --- a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedFilter.java +++ b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedFilter.java @@ -10,18 +10,24 @@ import java.util.regex.Pattern; public class FeedFilter implements Serializable { private final String includeFilter; private final String excludeFilter; + private final int minimalDuration; public FeedFilter() { - this("", ""); + this("", "", -1); } - public FeedFilter(String includeFilter, String excludeFilter) { + public FeedFilter(String includeFilter, String excludeFilter, int minimalDuration) { // We're storing the strings and not the parsed terms because // 1. It's easier to show the user exactly what they typed in this way // (we don't have to recreate it) // 2. We don't know if we'll actually be asked to parse anything anyways. this.includeFilter = includeFilter; this.excludeFilter = excludeFilter; + this.minimalDuration = minimalDuration; + } + + public FeedFilter(String includeFilter, String excludeFilter) { + this(includeFilter, excludeFilter, -1); } /** @@ -49,11 +55,20 @@ public class FeedFilter implements Serializable { List<String> includeTerms = parseTerms(includeFilter); List<String> excludeTerms = parseTerms(excludeFilter); - if (includeTerms.size() == 0 && excludeTerms.size() == 0) { + if (includeTerms.size() == 0 && excludeTerms.size() == 0 && minimalDuration <= -1) { // nothing has been specified, so include everything return true; } + // Check if the episode is long enough if minimal duration filter is on + if (hasMinimalDurationFilter() && item.getMedia() != null) { + int durationInMs = item.getMedia().getDuration(); + // Minimal Duration is stored in seconds + if (durationInMs > 0 && durationInMs / 1000 < minimalDuration) { + return false; + } + } + // check using lowercase so the users don't have to worry about case. String title = item.getTitle().toLowerCase(Locale.getDefault()); @@ -78,6 +93,12 @@ public class FeedFilter implements Serializable { return true; } + // if they only set minimal duration filter and arrived here, autodownload + // should happen + if (hasMinimalDurationFilter()) { + return true; + } + return false; } @@ -89,6 +110,10 @@ public class FeedFilter implements Serializable { return excludeFilter; } + public int getMinimalDurationFilter() { + return minimalDuration; + } + /** * @return true if only include is set */ @@ -110,4 +135,8 @@ public class FeedFilter implements Serializable { public boolean hasExcludeFilter() { return excludeFilter.length() > 0; } + + public boolean hasMinimalDurationFilter() { + return minimalDuration > -1; + } } |