diff options
9 files changed, 276 insertions, 171 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SimpleChipAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SimpleChipAdapter.java new file mode 100644 index 000000000..c7a04c3c7 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SimpleChipAdapter.java @@ -0,0 +1,57 @@ +package de.danoeh.antennapod.adapter; + +import android.content.Context; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import com.google.android.material.chip.Chip; +import de.danoeh.antennapod.R; + +import java.util.List; + +public abstract class SimpleChipAdapter extends RecyclerView.Adapter<SimpleChipAdapter.ViewHolder> { + private final Context context; + + public SimpleChipAdapter(Context context) { + this.context = context; + setHasStableIds(true); + } + + protected abstract List<String> getChips(); + + protected abstract void onRemoveClicked(int position); + + @Override + @NonNull + public SimpleChipAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + Chip chip = new Chip(context); + chip.setCloseIconVisible(true); + chip.setCloseIconResource(R.drawable.ic_delete); + return new SimpleChipAdapter.ViewHolder(chip); + } + + @Override + public void onBindViewHolder(@NonNull SimpleChipAdapter.ViewHolder holder, int position) { + holder.chip.setText(getChips().get(position)); + holder.chip.setOnCloseIconClickListener(v -> onRemoveClicked(position)); + } + + @Override + public int getItemCount() { + return getChips().size(); + } + + @Override + public long getItemId(int position) { + return getChips().get(position).hashCode(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + Chip chip; + + ViewHolder(Chip itemView) { + super(itemView); + chip = itemView; + } + } +}
\ No newline at end of file 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 ff58fb1a2..2aa3ab31c 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java @@ -1,75 +1,108 @@ 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; - +import android.content.DialogInterface; +import android.text.TextUtils; +import android.view.LayoutInflater; +import androidx.recyclerview.widget.GridLayoutManager; import com.google.android.material.dialog.MaterialAlertDialogBuilder; - import de.danoeh.antennapod.R; +import de.danoeh.antennapod.adapter.SimpleChipAdapter; +import de.danoeh.antennapod.databinding.EpisodeFilterDialogBinding; import de.danoeh.antennapod.model.feed.FeedFilter; +import de.danoeh.antennapod.view.ItemOffsetDecoration; + +import java.util.List; /** * Displays a dialog with a text box for filtering episodes and two radio buttons for exclusion/inclusion */ public abstract class EpisodeFilterDialog extends MaterialAlertDialogBuilder { - private final FeedFilter initialFilter; + private final EpisodeFilterDialogBinding viewBinding; + private final List<String> termList; public EpisodeFilterDialog(Context context, FeedFilter filter) { - super(context); - initialFilter = filter; + viewBinding = EpisodeFilterDialogBinding.inflate(LayoutInflater.from(context)); + setTitle(R.string.episode_filters_label); - View rootView = View.inflate(context, R.layout.episode_filter_dialog, null); - setView(rootView); + setView(viewBinding.getRoot()); - 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 (filter.hasMinimalDurationFilter()) { + viewBinding.durationCheckBox.setChecked(true); + // Store minimal duration in seconds, show in minutes + viewBinding.episodeFilterDurationText + .setText(String.valueOf(filter.getMinimalDurationFilter() / 60)); + } - if (initialFilter.includeOnly()) { - radioInclude.setChecked(true); - etxtEpisodeFilterText.setText(initialFilter.getIncludeFilter()); - } else if(initialFilter.excludeOnly()) { - radioExclude.setChecked(true); - etxtEpisodeFilterText.setText(initialFilter.getExcludeFilter()); + if (filter.excludeOnly()) { + termList = filter.getExcludeFilter(); + viewBinding.excludeRadio.setChecked(true); } else { - radioExclude.setChecked(false); - 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)); + termList = filter.getIncludeFilter(); + viewBinding.includeRadio.setChecked(true); } + setupWordsList(); 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(); - } - 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)); - } - ); + setPositiveButton(R.string.confirm_label, this::onConfirmClick); + } + + private void setupWordsList() { + viewBinding.termsRecycler.setLayoutManager(new GridLayoutManager(getContext(), 2)); + viewBinding.termsRecycler.addItemDecoration(new ItemOffsetDecoration(getContext(), 4)); + SimpleChipAdapter adapter = new SimpleChipAdapter(getContext()) { + @Override + protected List<String> getChips() { + return termList; + } + + @Override + protected void onRemoveClicked(int position) { + termList.remove(position); + notifyDataSetChanged(); + } + }; + viewBinding.termsRecycler.setAdapter(adapter); + viewBinding.termsTextInput.setEndIconOnClickListener(v -> { + String newWord = viewBinding.termsTextInput.getEditText().getText().toString().replace("\"", "").trim(); + if (TextUtils.isEmpty(newWord) || termList.contains(newWord)) { + return; + } + termList.add(newWord); + viewBinding.termsTextInput.getEditText().setText(""); + adapter.notifyDataSetChanged(); + }); } protected abstract void onConfirmed(FeedFilter filter); + + private void onConfirmClick(DialogInterface dialog, int which) { + int minimalDuration = -1; + if (viewBinding.durationCheckBox.isChecked()) { + try { + // Store minimal duration in seconds + minimalDuration = Integer.parseInt( + viewBinding.episodeFilterDurationText.getText().toString()) * 60; + } catch (NumberFormatException e) { + // Do not change anything on error + } + } + String excludeFilter = ""; + String includeFilter = ""; + if (viewBinding.includeRadio.isChecked()) { + includeFilter = toFilterString(termList); + } else { + excludeFilter = toFilterString(termList); + } + onConfirmed(new FeedFilter(includeFilter, excludeFilter, minimalDuration)); + } + + private String toFilterString(List<String> words) { + StringBuilder result = new StringBuilder(); + for (String word : words) { + result.append("\"").append(word).append("\" "); + } + return result.toString(); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java index 47a706c86..d7ca80b17 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java @@ -6,21 +6,19 @@ import android.text.TextUtils; import android.util.Log; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import android.widget.ArrayAdapter; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.fragment.app.DialogFragment; import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.chip.Chip; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.adapter.SimpleChipAdapter; import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.NavDrawerData; -import de.danoeh.antennapod.model.feed.FeedPreferences; import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.storage.NavDrawerData; import de.danoeh.antennapod.databinding.EditTagsDialogBinding; +import de.danoeh.antennapod.model.feed.FeedPreferences; import de.danoeh.antennapod.view.ItemOffsetDecoration; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -36,7 +34,7 @@ public class TagSettingsDialog extends DialogFragment { private static final String ARG_FEED_PREFERENCES = "feed_preferences"; private List<String> displayedTags; private EditTagsDialogBinding viewBinding; - private TagSelectionAdapter adapter; + private SimpleChipAdapter adapter; public static TagSettingsDialog newInstance(List<FeedPreferences> preferencesList) { TagSettingsDialog fragment = new TagSettingsDialog(); @@ -62,12 +60,22 @@ public class TagSettingsDialog extends DialogFragment { viewBinding = EditTagsDialogBinding.inflate(getLayoutInflater()); viewBinding.tagsRecycler.setLayoutManager(new GridLayoutManager(getContext(), 2)); viewBinding.tagsRecycler.addItemDecoration(new ItemOffsetDecoration(getContext(), 4)); - adapter = new TagSelectionAdapter(); - adapter.setHasStableIds(true); + adapter = new SimpleChipAdapter(getContext()) { + @Override + protected List<String> getChips() { + return displayedTags; + } + + @Override + protected void onRemoveClicked(int position) { + displayedTags.remove(position); + notifyDataSetChanged(); + } + }; viewBinding.tagsRecycler.setAdapter(adapter); viewBinding.rootFolderCheckbox.setChecked(commonTags.contains(FeedPreferences.TAG_ROOT)); - viewBinding.newTagButton.setOnClickListener(v -> + viewBinding.newTagTextInput.setEndIconOnClickListener(v -> addTag(viewBinding.newTagEditText.getText().toString().trim())); loadTags(); @@ -140,44 +148,4 @@ public class TagSettingsDialog extends DialogFragment { DBWriter.setFeedPreferences(preferences); } } - - public class TagSelectionAdapter extends RecyclerView.Adapter<TagSelectionAdapter.ViewHolder> { - - @Override - @NonNull - public TagSelectionAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - Chip chip = new Chip(getContext()); - chip.setCloseIconVisible(true); - chip.setCloseIconResource(R.drawable.ic_delete); - return new TagSelectionAdapter.ViewHolder(chip); - } - - @Override - public void onBindViewHolder(@NonNull TagSelectionAdapter.ViewHolder holder, int position) { - holder.chip.setText(displayedTags.get(position)); - holder.chip.setOnCloseIconClickListener(v -> { - displayedTags.remove(position); - notifyDataSetChanged(); - }); - } - - @Override - public int getItemCount() { - return displayedTags.size(); - } - - @Override - public long getItemId(int position) { - return displayedTags.get(position).hashCode(); - } - - public class ViewHolder extends RecyclerView.ViewHolder { - Chip chip; - - ViewHolder(Chip itemView) { - super(itemView); - chip = itemView; - } - } - } } diff --git a/app/src/main/res/layout/edit_tags_dialog.xml b/app/src/main/res/layout/edit_tags_dialog.xml index a3974525d..7ad938777 100644 --- a/app/src/main/res/layout/edit_tags_dialog.xml +++ b/app/src/main/res/layout/edit_tags_dialog.xml @@ -28,25 +28,24 @@ android:layout_height="wrap_content" android:text="@string/feed_folders_include_root" /> - <LinearLayout + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/newTagTextInput" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal"> + app:endIconMode="custom" + app:endIconDrawable="@drawable/ic_add" + app:endIconContentDescription="@string/add_tag" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"> <AutoCompleteTextView android:id="@+id/newTagEditText" - android:layout_width="0dp" + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" + android:padding="16dp" android:inputType="text" android:ems="10" /> - <ImageButton - android:id="@+id/newTagButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:srcCompat="@drawable/ic_add" /> - - </LinearLayout> + </com.google.android.material.textfield.TextInputLayout> </LinearLayout> diff --git a/app/src/main/res/layout/episode_filter_dialog.xml b/app/src/main/res/layout/episode_filter_dialog.xml index e8672c2f3..938589c5a 100644 --- a/app/src/main/res/layout/episode_filter_dialog.xml +++ b/app/src/main/res/layout/episode_filter_dialog.xml @@ -1,60 +1,99 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" android:padding="16dp"> - <RadioGroup - android:id="@+id/radio_filter_group" + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> - <RadioButton - android:id="@+id/radio_filter_include" - android:layout_width="wrap_content" + <RadioGroup + android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/episode_filters_include" /> + android:layout_marginBottom="8dp" + android:orientation="horizontal"> - <RadioButton - android:id="@+id/radio_filter_exclude" - android:layout_width="wrap_content" + <RadioButton + android:id="@+id/includeRadio" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/include_terms" /> + + <RadioButton + android:id="@+id/excludeRadio" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/exclude_terms" /> + + </RadioGroup> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/termsRecycler" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/episode_filters_exclude" /> + tools:itemCount="2" /> - </RadioGroup> + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/termsTextInput" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:endIconMode="custom" + app:endIconDrawable="@drawable/ic_add" + app:endIconContentDescription="@string/add_term" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"> - <EditText - android:id="@+id/etxtEpisodeFilterText" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:cursorVisible="true" - android:focusable="true" - android:focusableInTouchMode="true" - android:hint="@string/episode_filters_hint" - android:lines="8" - android:maxLines="20" - 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" /> + <com.google.android.material.textfield.TextInputEditText + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="text" + android:singleLine="true" /> - <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> + </com.google.android.material.textfield.TextInputLayout> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_marginTop="16dp" + android:layout_marginBottom="8dp" + android:background="?android:attr/listDivider" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center_vertical"> + + <CheckBox + android:id="@+id/durationCheckBox" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/exclude_episodes_shorter_than" /> + + <EditText + android:id="@+id/episodeFilterDurationText" + android:layout_width="50dp" + android:layout_height="wrap_content" + android:cursorVisible="true" + android:focusable="true" + android:focusableInTouchMode="true" + android:inputType="numberSigned" + android:lines="1" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/time_minutes" /> + + </LinearLayout> + + </LinearLayout> + +</ScrollView> 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 3840f6387..c7c47f4fd 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 @@ -21,8 +21,8 @@ public class FeedFilterTest { assertFalse(filter.excludeOnly()); assertFalse(filter.includeOnly()); - assertEquals("", filter.getExcludeFilter()); - assertEquals("", filter.getIncludeFilter()); + assertEquals("", filter.getExcludeFilterRaw()); + assertEquals("", filter.getIncludeFilterRaw()); assertTrue(filter.shouldAutoDownload(item)); } @@ -38,8 +38,8 @@ public class FeedFilterTest { assertFalse(filter.excludeOnly()); assertTrue(filter.includeOnly()); - assertEquals("", filter.getExcludeFilter()); - assertEquals(includeFilter, filter.getIncludeFilter()); + assertEquals("", filter.getExcludeFilterRaw()); + assertEquals(includeFilter, filter.getIncludeFilterRaw()); assertTrue(filter.shouldAutoDownload(item)); assertFalse(filter.shouldAutoDownload(item2)); } @@ -56,8 +56,8 @@ public class FeedFilterTest { assertTrue(filter.excludeOnly()); assertFalse(filter.includeOnly()); - assertEquals(excludeFilter, filter.getExcludeFilter()); - assertEquals("", filter.getIncludeFilter()); + assertEquals(excludeFilter, filter.getExcludeFilterRaw()); + assertEquals("", filter.getIncludeFilterRaw()); assertFalse(filter.shouldAutoDownload(item)); assertTrue(filter.shouldAutoDownload(item2)); } @@ -77,8 +77,8 @@ public class FeedFilterTest { assertFalse(filter.excludeOnly()); assertTrue(filter.includeOnly()); - assertEquals("", filter.getExcludeFilter()); - assertEquals(includeFilter, filter.getIncludeFilter()); + assertEquals("", filter.getExcludeFilterRaw()); + assertEquals(includeFilter, filter.getIncludeFilterRaw()); assertTrue(filter.shouldAutoDownload(item)); assertFalse(filter.shouldAutoDownload(item2)); assertTrue(filter.shouldAutoDownload(item3)); @@ -99,8 +99,8 @@ public class FeedFilterTest { assertTrue(filter.excludeOnly()); assertFalse(filter.includeOnly()); - assertEquals(excludeFilter, filter.getExcludeFilter()); - assertEquals("", filter.getIncludeFilter()); + assertEquals(excludeFilter, filter.getExcludeFilterRaw()); + assertEquals("", filter.getIncludeFilterRaw()); assertFalse(filter.shouldAutoDownload(item)); assertTrue(filter.shouldAutoDownload(item2)); assertFalse(filter.shouldAutoDownload(item3)); 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 3b35fe5bd..f537f33e2 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 @@ -102,14 +102,22 @@ public class FeedFilter implements Serializable { return false; } - public String getIncludeFilter() { + public String getIncludeFilterRaw() { return includeFilter; } - public String getExcludeFilter() { + public String getExcludeFilterRaw() { return excludeFilter; } + public List<String> getIncludeFilter() { + return includeFilter == null ? new ArrayList<>() : parseTerms(includeFilter); + } + + public List<String> getExcludeFilter() { + return excludeFilter == null ? new ArrayList<>() : parseTerms(excludeFilter); + } + public int getMinimalDurationFilter() { return minimalDuration; } diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java index 9798a9fb3..329c1e7b9 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java @@ -448,8 +448,8 @@ public class PodDBAdapter { values.put(KEY_FEED_VOLUME_ADAPTION, prefs.getVolumeAdaptionSetting().toInteger()); values.put(KEY_USERNAME, prefs.getUsername()); 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_INCLUDE_FILTER, prefs.getFilter().getIncludeFilterRaw()); + values.put(KEY_EXCLUDE_FILTER, prefs.getFilter().getExcludeFilterRaw()); values.put(KEY_MINIMAL_DURATION_FILTER, prefs.getFilter().getMinimalDurationFilter()); values.put(KEY_FEED_PLAYBACK_SPEED, prefs.getFeedPlaybackSpeed()); values.put(KEY_FEED_TAGS, prefs.getTagsAsString()); diff --git a/ui/i18n/src/main/res/values/strings.xml b/ui/i18n/src/main/res/values/strings.xml index a2aa6ecef..a6f5d8053 100644 --- a/ui/i18n/src/main/res/values/strings.xml +++ b/ui/i18n/src/main/res/values/strings.xml @@ -190,6 +190,7 @@ <item quantity="other">%d subscriptions updated.</item> </plurals> <string name="edit_tags">Edit tags</string> + <string name="add_tag">Add tag</string> <string name="rename_tag_label">Rename tag</string> <string name="confirm_mobile_feed_refresh_dialog_message">Refreshing podcasts over mobile data connection is disabled in the settings.\n\nDo you want to refresh anyway?</string> @@ -690,10 +691,10 @@ <string name="auto_download_settings_label">Auto Download Settings</string> <string name="episode_filters_label">Episode Filter</string> <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="add_term">Add Term</string> + <string name="exclude_terms">Exclude episodes containing any of the terms below</string> + <string name="include_terms">Include only episodes containing any of the terms below</string> + <string name="exclude_episodes_shorter_than">Exclude episodes shorter than</string> <string name="keep_updated">Keep Updated</string> <string name="keep_updated_summary">Include this podcast when (auto-)refreshing all podcasts</string> <string name="auto_download_disabled_globally">Auto download is disabled in the main AntennaPod settings</string> |