diff options
21 files changed, 754 insertions, 357 deletions
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java index 53396372a..cd9810ba9 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java @@ -30,7 +30,6 @@ import java.util.List; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.longClick; -import static androidx.test.espresso.action.ViewActions.scrollTo; import static androidx.test.espresso.action.ViewActions.swipeUp; import static androidx.test.espresso.intent.Intents.intended; import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent; @@ -123,7 +122,7 @@ public class NavigationDrawerTest { for (int i = 0; i < uiTestUtils.hostedFeeds.size(); i++) { Feed f = uiTestUtils.hostedFeeds.get(i); openNavDrawer(); - onDrawerItem(withText(f.getTitle())).perform(scrollTo(), click()); + onDrawerItem(withText(f.getTitle())).perform(click()); onView(isRoot()).perform(waitForView(allOf(isDescendantOfA(withId(R.id.appBar)), withText(f.getTitle()), isDisplayed()), 1000)); } diff --git a/app/src/androidTest/java/de/test/antennapod/ui/TextOnlyFeedsTest.java b/app/src/androidTest/java/de/test/antennapod/ui/TextOnlyFeedsTest.java index 488c87052..abae07bac 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/TextOnlyFeedsTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/TextOnlyFeedsTest.java @@ -18,9 +18,10 @@ import java.io.IOException; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.action.ViewActions.scrollTo; +import static androidx.test.espresso.action.ViewActions.swipeUp; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.isRoot; +import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static de.test.antennapod.EspressoTestUtils.onDrawerItem; import static de.test.antennapod.EspressoTestUtils.openNavDrawer; @@ -61,7 +62,8 @@ public class TextOnlyFeedsTest { uiTestUtils.addLocalFeedData(false); final Feed feed = uiTestUtils.hostedFeeds.get(0); openNavDrawer(); - onDrawerItem(withText(feed.getTitle())).perform(scrollTo(), click()); + onView(withId(R.id.nav_list)).perform(swipeUp()); + onDrawerItem(withText(feed.getTitle())).perform(click()); onView(withText(feed.getItemAtIndex(0).getTitle())).perform(click()); onView(isRoot()).perform(waitForView(withText(R.string.mark_read_no_media_label), 3000)); onView(withText(R.string.mark_read_no_media_label)).perform(click()); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/CoverLoader.java b/app/src/main/java/de/danoeh/antennapod/adapter/CoverLoader.java index d782d4ed5..aeaf526be 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/CoverLoader.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/CoverLoader.java @@ -21,6 +21,7 @@ import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.glide.ApGlideSettings; public class CoverLoader { + private int resource = 0; private String uri; private String fallbackUri; private TextView txtvPlaceholder; @@ -37,6 +38,11 @@ public class CoverLoader { return this; } + public CoverLoader withResource(int resource) { + this.resource = resource; + return this; + } + public CoverLoader withFallbackUri(String uri) { fallbackUri = uri; return this; @@ -66,6 +72,12 @@ public class CoverLoader { } public void load() { + if (resource != 0) { + imgvCover.setImageResource(resource); + CoverTarget.setPlaceholderVisibility(txtvPlaceholder, textAndImageCombined); + return; + } + RequestOptions options = new RequestOptions() .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) .fitCenter() @@ -106,15 +118,7 @@ public class CoverLoader { @Override public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) { - TextView txtvPlaceholder = placeholder.get(); - if (txtvPlaceholder != null) { - if (textAndImageCombined) { - int bgColor = txtvPlaceholder.getContext().getResources().getColor(R.color.feed_text_bg); - txtvPlaceholder.setBackgroundColor(bgColor); - } else { - txtvPlaceholder.setVisibility(View.INVISIBLE); - } - } + setPlaceholderVisibility(placeholder.get(), textAndImageCombined); ImageView ivCover = cover.get(); ivCover.setImageDrawable(resource); } @@ -124,5 +128,16 @@ public class CoverLoader { ImageView ivCover = cover.get(); ivCover.setImageDrawable(placeholder); } + + static void setPlaceholderVisibility(TextView placeholder, boolean textAndImageCombined) { + if (placeholder != null) { + if (textAndImageCombined) { + int bgColor = placeholder.getContext().getResources().getColor(R.color.feed_text_bg); + placeholder.setBackgroundColor(bgColor); + } else { + placeholder.setVisibility(View.INVISIBLE); + } + } + } } }
\ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java index 7c8943f36..de3242b1a 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -1,20 +1,21 @@ package de.danoeh.antennapod.adapter; import android.app.Activity; -import android.content.Context; import android.content.SharedPreferences; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; import android.util.TypedValue; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.joanzapata.iconify.Iconify; @@ -23,6 +24,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.storage.NavDrawerData; import de.danoeh.antennapod.fragment.AddFeedFragment; import de.danoeh.antennapod.fragment.DownloadsFragment; import de.danoeh.antennapod.fragment.EpisodesFragment; @@ -30,6 +32,7 @@ import de.danoeh.antennapod.fragment.NavDrawerFragment; import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; import de.danoeh.antennapod.fragment.QueueFragment; import de.danoeh.antennapod.fragment.SubscriptionFragment; +import de.danoeh.antennapod.ui.common.ThemeUtils; import org.apache.commons.lang3.ArrayUtils; import java.lang.ref.WeakReference; @@ -42,10 +45,9 @@ import java.util.List; /** * BaseAdapter for the navigation drawer */ -public class NavListAdapter extends BaseAdapter +public class NavListAdapter extends RecyclerView.Adapter<NavListAdapter.Holder> implements SharedPreferences.OnSharedPreferenceChangeListener { - private static final int VIEW_TYPE_COUNT = 3; public static final int VIEW_TYPE_NAV = 0; public static final int VIEW_TYPE_SECTION_DIVIDER = 1; private static final int VIEW_TYPE_SUBSCRIPTION = 2; @@ -56,7 +58,7 @@ public class NavListAdapter extends BaseAdapter */ public static final String SUBSCRIPTION_LIST_TAG = "SubscriptionList"; - private static List<String> tags; + private static List<String> fragmentTags; private static String[] titles; private final ItemAccess itemAccess; @@ -96,7 +98,7 @@ public class NavListAdapter extends BaseAdapter showSubscriptionList = false; } - tags = newTags; + fragmentTags = newTags; notifyDataSetChanged(); } @@ -133,19 +135,18 @@ public class NavListAdapter extends BaseAdapter default: return null; } - TypedArray ta = context.obtainStyledAttributes(new int[] { icon } ); + TypedArray ta = context.obtainStyledAttributes(new int[] { icon }); Drawable result = ta.getDrawable(0); ta.recycle(); return result; } - public List<String> getTags() { - return Collections.unmodifiableList(tags); + public List<String> getFragmentTags() { + return Collections.unmodifiableList(fragmentTags); } - @Override - public int getCount() { + public int getItemCount() { int baseCount = getSubscriptionOffset(); if (showSubscriptionList) { baseCount += itemAccess.getCount(); @@ -154,25 +155,18 @@ public class NavListAdapter extends BaseAdapter } @Override - public Object getItem(int position) { + public long getItemId(int position) { int viewType = getItemViewType(position); - if (viewType == VIEW_TYPE_NAV) { - return getLabel(tags.get(position)); - } else if (viewType == VIEW_TYPE_SECTION_DIVIDER) { - return ""; + if (viewType == VIEW_TYPE_SUBSCRIPTION) { + return itemAccess.getItem(position - getSubscriptionOffset()).id; } else { - return itemAccess.getItem(position); + return -position - 1; // IDs are >0 } } @Override - public long getItemId(int position) { - return position; - } - - @Override public int getItemViewType(int position) { - if (0 <= position && position < tags.size()) { + if (0 <= position && position < fragmentTags.size()) { return VIEW_TYPE_NAV; } else if (position < getSubscriptionOffset()) { return VIEW_TYPE_SECTION_DIVIDER; @@ -181,69 +175,67 @@ public class NavListAdapter extends BaseAdapter } } - @Override - public int getViewTypeCount() { - return VIEW_TYPE_COUNT; - } - public int getSubscriptionOffset() { - return tags.size() > 0 ? tags.size() + 1 : 0; + return fragmentTags.size() > 0 ? fragmentTags.size() + 1 : 0; } + @NonNull + @Override + public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(activity.get()); + if (viewType == VIEW_TYPE_NAV) { + return new NavHolder(inflater.inflate(R.layout.nav_listitem, parent, false)); + } else if (viewType == VIEW_TYPE_SECTION_DIVIDER) { + return new DividerHolder(inflater.inflate(R.layout.nav_section_item, parent, false)); + } else { + return new FeedHolder(inflater.inflate(R.layout.nav_listitem, parent, false)); + } + } @Override - public View getView(int position, View convertView, ViewGroup parent) { + public void onBindViewHolder(@NonNull Holder holder, int position) { int viewType = getItemViewType(position); - View v; + + holder.itemView.setOnCreateContextMenuListener(null); if (viewType == VIEW_TYPE_NAV) { - v = getNavView((String) getItem(position), position, convertView, parent); + bindNavView(getLabel(fragmentTags.get(position)), position, (NavHolder) holder); } else if (viewType == VIEW_TYPE_SECTION_DIVIDER) { - v = getSectionDividerView(convertView, parent); + bindSectionDivider((DividerHolder) holder); } else { - v = getFeedView(position, convertView, parent); + int itemPos = position - getSubscriptionOffset(); + NavDrawerData.DrawerItem item = itemAccess.getItem(itemPos); + bindListItem(item, (FeedHolder) holder); + if (item.type == NavDrawerData.DrawerItem.Type.FEED) { + bindFeedView((NavDrawerData.FeedDrawerItem) item, (FeedHolder) holder); + holder.itemView.setOnCreateContextMenuListener(itemAccess); + } else { + bindFolderView((NavDrawerData.FolderDrawerItem) item, (FeedHolder) holder); + } } - if (v != null && viewType != VIEW_TYPE_SECTION_DIVIDER) { + if (viewType != VIEW_TYPE_SECTION_DIVIDER) { TypedValue typedValue = new TypedValue(); - if (position == itemAccess.getSelectedItemIndex()) { - v.getContext().getTheme().resolveAttribute(R.attr.drawer_activated_color, typedValue, true); - v.setBackgroundResource(typedValue.resourceId); - } else { - v.getContext().getTheme().resolveAttribute(android.R.attr.windowBackground, typedValue, true); - v.setBackgroundResource(typedValue.resourceId); - } + activity.get().getTheme().resolveAttribute(itemAccess.isSelected(position) + ? R.attr.drawer_activated_color : android.R.attr.windowBackground, typedValue, true); + holder.itemView.setBackgroundResource(typedValue.resourceId); + + holder.itemView.setOnClickListener(v -> itemAccess.onItemClick(position)); + holder.itemView.setOnLongClickListener(v -> itemAccess.onItemLongClick(position)); } - return v; } - private View getNavView(String title, int position, View convertView, ViewGroup parent) { + private void bindNavView(String title, int position, NavHolder holder) { Activity context = activity.get(); - if(context == null) { - return null; - } - NavHolder holder; - if (convertView == null) { - holder = new NavHolder(); - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - convertView = inflater.inflate(R.layout.nav_listitem, parent, false); - - holder.image = convertView.findViewById(R.id.imgvCover); - holder.title = convertView.findViewById(R.id.txtvTitle); - holder.count = convertView.findViewById(R.id.txtvCount); - convertView.setTag(holder); - } else { - holder = (NavHolder) convertView.getTag(); + if (context == null) { + return; } - holder.title.setText(title); // reset for re-use holder.count.setVisibility(View.GONE); holder.count.setOnClickListener(null); - String tag = tags.get(position); + String tag = fragmentTags.get(position); if (tag.equals(QueueFragment.TAG)) { int queueSize = itemAccess.getQueueSize(); if (queueSize > 0) { @@ -262,78 +254,64 @@ public class NavListAdapter extends BaseAdapter holder.count.setText(NumberFormat.getInstance().format(sum)); holder.count.setVisibility(View.VISIBLE); } - } else if(tag.equals(DownloadsFragment.TAG) && UserPreferences.isEnableAutodownload()) { + } else if (tag.equals(DownloadsFragment.TAG) && UserPreferences.isEnableAutodownload()) { int epCacheSize = UserPreferences.getEpisodeCacheSize(); // don't count episodes that can be reclaimed - int spaceUsed = itemAccess.getNumberOfDownloadedItems() - - itemAccess.getReclaimableItems(); + int spaceUsed = itemAccess.getNumberOfDownloadedItems() + - itemAccess.getReclaimableItems(); if (epCacheSize > 0 && spaceUsed >= epCacheSize) { holder.count.setText("{md-disc-full 150%}"); Iconify.addIcons(holder.count); holder.count.setVisibility(View.VISIBLE); holder.count.setOnClickListener(v -> - new AlertDialog.Builder(context) + new AlertDialog.Builder(context) .setTitle(R.string.episode_cache_full_title) .setMessage(R.string.episode_cache_full_message) - .setPositiveButton(android.R.string.ok, (dialog, which) -> {}) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { }) .show() ); } } - holder.image.setImageDrawable(getDrawable(tags.get(position))); - - return convertView; + holder.image.setImageDrawable(getDrawable(fragmentTags.get(position))); } - private View getSectionDividerView(View convertView, ViewGroup parent) { + private void bindSectionDivider(DividerHolder holder) { Activity context = activity.get(); - if(context == null) { - return null; + if (context == null) { + return; } - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - convertView = inflater.inflate(R.layout.nav_section_item, parent, false); - TextView feedsFilteredMsg = convertView.findViewById(R.id.nav_feeds_filtered_message); if (UserPreferences.getSubscriptionsFilter().isEnabled() && showSubscriptionList) { - convertView.setEnabled(true); - feedsFilteredMsg.setText("{md-info-outline} " + context.getString(R.string.subscriptions_are_filtered)); - Iconify.addIcons(feedsFilteredMsg); - feedsFilteredMsg.setVisibility(View.VISIBLE); + holder.itemView.setEnabled(true); + holder.feedsFilteredMsg.setText("{md-info-outline} " + + context.getString(R.string.subscriptions_are_filtered)); + Iconify.addIcons(holder.feedsFilteredMsg); + holder.feedsFilteredMsg.setVisibility(View.VISIBLE); } else { - convertView.setEnabled(false); - feedsFilteredMsg.setVisibility(View.GONE); + holder.itemView.setEnabled(false); + holder.feedsFilteredMsg.setVisibility(View.GONE); } + } - return convertView; + private void bindListItem(NavDrawerData.DrawerItem item, FeedHolder holder) { + if (item.getCounter() > 0) { + holder.count.setVisibility(View.VISIBLE); + holder.count.setText(NumberFormat.getInstance().format(item.getCounter())); + } else { + holder.count.setVisibility(View.GONE); + } + holder.title.setText(item.getTitle()); + int padding = (int) (activity.get().getResources().getDimension(R.dimen.thumbnail_length_navlist) / 2); + holder.itemView.setPadding(item.getLayer() * padding, 0, 0, 0); } - private View getFeedView(int position, View convertView, ViewGroup parent) { + private void bindFeedView(NavDrawerData.FeedDrawerItem drawerItem, FeedHolder holder) { + Feed feed = drawerItem.feed; Activity context = activity.get(); - if(context == null) { - return null; - } - int feedPos = position - getSubscriptionOffset(); - Feed feed = itemAccess.getItem(feedPos); - - FeedHolder holder; - if (convertView == null) { - holder = new FeedHolder(); - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - convertView = inflater.inflate(R.layout.nav_listitem, parent, false); - - holder.image = convertView.findViewById(R.id.imgvCover); - holder.title = convertView.findViewById(R.id.txtvTitle); - holder.failure = convertView.findViewById(R.id.itxtvFailure); - holder.count = convertView.findViewById(R.id.txtvCount); - convertView.setTag(holder); - } else { - holder = (FeedHolder) convertView.getTag(); + if (context == null) { + return; } Glide.with(context) @@ -346,9 +324,7 @@ public class NavListAdapter extends BaseAdapter .dontAnimate()) .into(holder.image); - holder.title.setText(feed.getTitle()); - - if(feed.hasLastUpdateFailed()) { + if (feed.hasLastUpdateFailed()) { RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) holder.title.getLayoutParams(); p.addRule(RelativeLayout.LEFT_OF, R.id.itxtvFailure); holder.failure.setVisibility(View.VISIBLE); @@ -357,39 +333,87 @@ public class NavListAdapter extends BaseAdapter p.addRule(RelativeLayout.LEFT_OF, R.id.txtvCount); holder.failure.setVisibility(View.GONE); } - int counter = itemAccess.getFeedCounter(feed.getId()); - if(counter > 0) { - holder.count.setVisibility(View.VISIBLE); - holder.count.setText(NumberFormat.getInstance().format(counter)); - } else { + } + + private void bindFolderView(NavDrawerData.FolderDrawerItem folder, FeedHolder holder) { + Activity context = activity.get(); + if (context == null) { + return; + } + if (folder.isOpen) { holder.count.setVisibility(View.GONE); } - return convertView; + Glide.with(context).clear(holder.image); + holder.image.setImageResource(ThemeUtils.getDrawableFromAttr(context, R.attr.ic_folder)); + holder.failure.setVisibility(View.GONE); } - static class NavHolder { - ImageView image; - TextView title; - TextView count; + static class Holder extends RecyclerView.ViewHolder { + public Holder(@NonNull View itemView) { + super(itemView); + } } - static class FeedHolder { - ImageView image; - TextView title; - IconTextView failure; - TextView count; + static class DividerHolder extends Holder { + final TextView feedsFilteredMsg; + + public DividerHolder(@NonNull View itemView) { + super(itemView); + feedsFilteredMsg = itemView.findViewById(R.id.nav_feeds_filtered_message); + } } - public interface ItemAccess { + static class NavHolder extends Holder { + final ImageView image; + final TextView title; + final TextView count; + + public NavHolder(@NonNull View itemView) { + super(itemView); + image = itemView.findViewById(R.id.imgvCover); + title = itemView.findViewById(R.id.txtvTitle); + count = itemView.findViewById(R.id.txtvCount); + } + } + + static class FeedHolder extends Holder { + final ImageView image; + final TextView title; + final IconTextView failure; + final TextView count; + + public FeedHolder(@NonNull View itemView) { + super(itemView); + image = itemView.findViewById(R.id.imgvCover); + title = itemView.findViewById(R.id.txtvTitle); + failure = itemView.findViewById(R.id.itxtvFailure); + count = itemView.findViewById(R.id.txtvCount); + } + } + + public interface ItemAccess extends View.OnCreateContextMenuListener { int getCount(); - Feed getItem(int position); - int getSelectedItemIndex(); + + NavDrawerData.DrawerItem getItem(int position); + + boolean isSelected(int position); + int getQueueSize(); + int getNumberOfNewItems(); + int getNumberOfDownloadedItems(); + int getReclaimableItems(); - int getFeedCounter(long feedId); + int getFeedCounterSum(); + + void onItemClick(int position); + + boolean onItemLongClick(int position); + + @Override + void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo); } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java index f7d6358de..fc97cace5 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java @@ -21,17 +21,16 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.LocalFeedUpdater; +import de.danoeh.antennapod.core.storage.NavDrawerData; import de.danoeh.antennapod.fragment.FeedItemlistFragment; +import de.danoeh.antennapod.fragment.SubscriptionFragment; +import de.danoeh.antennapod.ui.common.ThemeUtils; import jp.shts.android.library.TriangleLabelView; /** * Adapter for subscriptions */ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnItemClickListener { - - /** placeholder object that indicates item should be added */ - public static final Object ADD_ITEM_OBJ = new Object(); - /** the position in the view that holds the add item; 0 is the first, -1 is the last position */ private static final String TAG = "SubscriptionsAdapter"; @@ -60,7 +59,7 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI @Override public long getItemId(int position) { - return itemAccess.getItem(position).getId(); + return ((NavDrawerData.DrawerItem) getItem(position)).id; } @Override @@ -83,11 +82,13 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI holder = (Holder) convertView.getTag(); } - final Feed feed = (Feed) getItem(position); - if (feed == null) return null; + final NavDrawerData.DrawerItem drawerItem = (NavDrawerData.DrawerItem) getItem(position); + if (drawerItem == null) { + return null; + } - holder.feedTitle.setText(feed.getTitle()); - holder.imageView.setContentDescription(feed.getTitle()); + holder.feedTitle.setText(drawerItem.getTitle()); + holder.imageView.setContentDescription(drawerItem.getTitle()); holder.feedTitle.setVisibility(View.VISIBLE); // Fix TriangleLabelView corner for RTL @@ -96,30 +97,46 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI holder.count.setCorner(TriangleLabelView.Corner.TOP_LEFT); } - int count = itemAccess.getFeedCounter(feed.getId()); - if(count > 0) { - holder.count.setPrimaryText( - NumberFormat.getInstance().format(itemAccess.getFeedCounter(feed.getId()))); + if (drawerItem.getCounter() > 0) { + holder.count.setPrimaryText(NumberFormat.getInstance().format(drawerItem.getCounter())); holder.count.setVisibility(View.VISIBLE); } else { holder.count.setVisibility(View.GONE); } - boolean textAndImageCombined = feed.isLocalFeed() - && LocalFeedUpdater.getDefaultIconUrl(convertView.getContext()).equals(feed.getImageUrl()); - new CoverLoader(mainActivityRef.get()) - .withUri(feed.getImageUrl()) - .withPlaceholderView(holder.feedTitle, textAndImageCombined) - .withCoverView(holder.imageView) - .load(); - + if (drawerItem.type == NavDrawerData.DrawerItem.Type.FEED) { + Feed feed = ((NavDrawerData.FeedDrawerItem) drawerItem).feed; + boolean textAndImageCombined = feed.isLocalFeed() + && LocalFeedUpdater.getDefaultIconUrl(convertView.getContext()).equals(feed.getImageUrl()); + new CoverLoader(mainActivityRef.get()) + .withUri(feed.getImageUrl()) + .withPlaceholderView(holder.feedTitle, textAndImageCombined) + .withCoverView(holder.imageView) + .load(); + } else { + new CoverLoader(mainActivityRef.get()) + .withResource(ThemeUtils.getDrawableFromAttr(mainActivityRef.get(), R.attr.ic_folder)) + .withPlaceholderView(holder.feedTitle, true) + .withCoverView(holder.imageView) + .load(); + } return convertView; } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - Fragment fragment = FeedItemlistFragment.newInstance(getItemId(position)); - mainActivityRef.get().loadChildFragment(fragment); + final NavDrawerData.DrawerItem drawerItem = (NavDrawerData.DrawerItem) getItem(position); + if (drawerItem == null) { + return; + } + if (drawerItem.type == NavDrawerData.DrawerItem.Type.FEED) { + Feed feed = ((NavDrawerData.FeedDrawerItem) drawerItem).feed; + Fragment fragment = FeedItemlistFragment.newInstance(feed.getId()); + mainActivityRef.get().loadChildFragment(fragment); + } else if (drawerItem.type == NavDrawerData.DrawerItem.Type.FOLDER) { + Fragment fragment = SubscriptionFragment.newInstance(drawerItem.getTitle()); + mainActivityRef.get().loadChildFragment(fragment); + } } static class Holder { @@ -130,7 +147,7 @@ public class SubscriptionsAdapter extends BaseAdapter implements AdapterView.OnI public interface ItemAccess { int getCount(); - Feed getItem(int position); - int getFeedCounter(long feedId); + + NavDrawerData.DrawerItem getItem(int position); } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java new file mode 100644 index 000000000..24a0cc192 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java @@ -0,0 +1,116 @@ +package de.danoeh.antennapod.dialog; + +import android.app.Dialog; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +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.core.feed.FeedPreferences; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.databinding.EditTagsDialogBinding; +import de.danoeh.antennapod.view.ItemOffsetDecoration; + +import java.util.ArrayList; +import java.util.List; + +public class TagSettingsDialog extends DialogFragment { + public static final String TAG = "TagSettingsDialog"; + private static final String ARG_FEED_PREFERENCES = "feed_preferences"; + private List<String> displayedTags; + + public static TagSettingsDialog newInstance(FeedPreferences preferences) { + TagSettingsDialog fragment = new TagSettingsDialog(); + Bundle args = new Bundle(); + args.putSerializable(ARG_FEED_PREFERENCES, preferences); + fragment.setArguments(args); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + FeedPreferences preferences = (FeedPreferences) getArguments().getSerializable(ARG_FEED_PREFERENCES); + displayedTags = new ArrayList<>(preferences.getTags()); + displayedTags.remove(FeedPreferences.TAG_ROOT); + + EditTagsDialogBinding viewBinding = EditTagsDialogBinding.inflate(getLayoutInflater()); + viewBinding.tagsRecycler.setLayoutManager(new GridLayoutManager(getContext(), 2)); + viewBinding.tagsRecycler.addItemDecoration(new ItemOffsetDecoration(getContext(), 4)); + TagSelectionAdapter adapter = new TagSelectionAdapter(); + adapter.setHasStableIds(true); + viewBinding.tagsRecycler.setAdapter(adapter); + viewBinding.rootFolderCheckbox.setChecked(preferences.getTags().contains(FeedPreferences.TAG_ROOT)); + + + viewBinding.newTagButton.setOnClickListener(v -> { + String name = viewBinding.newTagEditText.getText().toString().trim(); + if (TextUtils.isEmpty(name) || displayedTags.contains(name)) { + return; + } + displayedTags.add(name); + viewBinding.newTagEditText.setText(""); + adapter.notifyDataSetChanged(); + }); + + AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); + dialog.setView(viewBinding.getRoot()); + dialog.setTitle(R.string.feed_folders_label); + dialog.setPositiveButton(android.R.string.ok, (d, input) -> { + preferences.getTags().clear(); + preferences.getTags().addAll(displayedTags); + if (viewBinding.rootFolderCheckbox.isChecked()) { + preferences.getTags().add(FeedPreferences.TAG_ROOT); + } + DBWriter.setFeedPreferences(preferences); + }); + dialog.setNegativeButton(R.string.cancel_label, null); + return dialog.create(); + } + + 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_black); + 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/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java index e24c89478..11fb68ec3 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java @@ -30,6 +30,7 @@ import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.dialog.AuthenticationDialog; import de.danoeh.antennapod.dialog.EpisodeFilterDialog; import de.danoeh.antennapod.dialog.FeedPreferenceSkipDialog; +import de.danoeh.antennapod.dialog.TagSettingsDialog; import io.reactivex.Maybe; import io.reactivex.MaybeOnSubscribe; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -105,6 +106,7 @@ public class FeedSettingsFragment extends Fragment { private static final CharSequence PREF_CATEGORY_AUTO_DOWNLOAD = "autoDownloadCategory"; private static final String PREF_FEED_PLAYBACK_SPEED = "feedPlaybackSpeed"; private static final String PREF_AUTO_SKIP = "feedAutoSkip"; + private static final String PREF_TAGS = "tags"; private static final DecimalFormat SPEED_FORMAT = new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.US)); @@ -160,6 +162,7 @@ public class FeedSettingsFragment extends Fragment { setupPlaybackSpeedPreference(); setupFeedAutoSkipPreference(); setupEpisodeNotificationPreference(); + setupTags(); updateAutoDeleteSummary(); updateVolumeReductionValue(); @@ -395,6 +398,13 @@ public class FeedSettingsFragment extends Fragment { } } + private void setupTags() { + findPreference(PREF_TAGS).setOnPreferenceClickListener(preference -> { + TagSettingsDialog.newInstance(feedPreferences).show(getChildFragmentManager(), TagSettingsDialog.TAG); + return true; + }); + } + private void setupEpisodeNotificationPreference() { SwitchPreferenceCompat pref = findPreference("episodeNotification"); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java index be74678d3..209bc62e5 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java @@ -12,15 +12,16 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ListView; import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; +import androidx.core.util.Pair; import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.bottomsheet.BottomSheetBehavior; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; @@ -34,6 +35,7 @@ import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.core.storage.NavDrawerData; import de.danoeh.antennapod.dialog.RemoveFeedDialog; import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog; import de.danoeh.antennapod.dialog.RenameFeedDialog; @@ -46,12 +48,15 @@ import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; -public class NavDrawerFragment extends Fragment implements AdapterView.OnItemClickListener, - AdapterView.OnItemLongClickListener, SharedPreferences.OnSharedPreferenceChangeListener { +public class NavDrawerFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener { @VisibleForTesting public static final String PREF_LAST_FRAGMENT_TAG = "prefLastFragmentTag"; + private static final String PREF_OPEN_FOLDERS = "prefOpenFolders"; @VisibleForTesting public static final String PREF_NAME = "NavDrawerPrefs"; public static final String TAG = "NavDrawerFragment"; @@ -66,12 +71,13 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli NavListAdapter.SUBSCRIPTION_LIST_TAG }; - private DBReader.NavDrawerData navDrawerData; - private int selectedNavListIndex = -1; - private int position = -1; + private NavDrawerData navDrawerData; + private List<NavDrawerData.DrawerItem> flatItemList; + private NavDrawerData.DrawerItem contextPressedItem = null; private NavListAdapter navAdapter; private Disposable disposable; private ProgressBar progressBar; + private Set<String> openFolders = new HashSet<>(); @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @@ -79,40 +85,21 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli super.onCreateView(inflater, container, savedInstanceState); View root = inflater.inflate(R.layout.nav_list, container, false); + SharedPreferences preferences = getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + openFolders = new HashSet<>(preferences.getStringSet(PREF_OPEN_FOLDERS, new HashSet<>())); // Must not modify + progressBar = root.findViewById(R.id.progressBar); - ListView navList = root.findViewById(R.id.nav_list); + RecyclerView navList = root.findViewById(R.id.nav_list); navAdapter = new NavListAdapter(itemAccess, getActivity()); + navAdapter.setHasStableIds(true); navList.setAdapter(navAdapter); - navList.setOnItemClickListener(this); - navList.setOnItemLongClickListener(this); - registerForContextMenu(navList); - updateSelection(); + navList.setLayoutManager(new LinearLayoutManager(getContext())); root.findViewById(R.id.nav_settings).setOnClickListener(v -> startActivity(new Intent(getActivity(), PreferenceActivity.class))); - getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) - .registerOnSharedPreferenceChangeListener(this); - return root; - } - private void updateSelection() { - String lastNavFragment = getLastNavFragment(getContext()); - int tagIndex = navAdapter.getTags().indexOf(lastNavFragment); - if (tagIndex >= 0) { - selectedNavListIndex = tagIndex; - } else if (StringUtils.isNumeric(lastNavFragment)) { // last fragment was not a list, but a feed - long feedId = Long.parseLong(lastNavFragment); - if (navDrawerData != null) { - List<Feed> feeds = navDrawerData.feeds; - for (int i = 0; i < feeds.size(); i++) { - if (feeds.get(i).getId() == feedId) { - selectedNavListIndex = navAdapter.getSubscriptionOffset() + i; - break; - } - } - } - } - navAdapter.notifyDataSetChanged(); + preferences.registerOnSharedPreferenceChangeListener(this); + return root; } @Override @@ -135,29 +122,25 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli @Override public void onCreateContextMenu(@NonNull ContextMenu menu, @NonNull View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); - if (v.getId() != R.id.nav_list) { - return; - } - AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; - int position = adapterInfo.position; - if (position < navAdapter.getSubscriptionOffset()) { - return; + if (contextPressedItem.type != NavDrawerData.DrawerItem.Type.FEED) { + return; // Should actually never happen because the context menu is not set up for other items } + MenuInflater inflater = getActivity().getMenuInflater(); inflater.inflate(R.menu.nav_feed_context, menu); - Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); - menu.setHeaderTitle(feed.getTitle()); + menu.setHeaderTitle(((NavDrawerData.FeedDrawerItem) contextPressedItem).feed.getTitle()); // episodes are not loaded, so we cannot check if the podcast has new or unplayed ones! } @Override public boolean onContextItemSelected(@NonNull MenuItem item) { - final int position = this.position; - this.position = -1; // reset - if (position < 0) { - return false; + if (contextPressedItem.type == NavDrawerData.DrawerItem.Type.FEED) { + return onFeedContextMenuClicked(((NavDrawerData.FeedDrawerItem) contextPressedItem).feed, item); } - Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); + return false; + } + + private boolean onFeedContextMenuClicked(Feed feed, MenuItem item) { switch (item.getItemId()) { case R.id.remove_all_new_flags_item: ConfirmationDialog removeAllNewFlagsConfirmationDialog = new ConfirmationDialog(getContext(), @@ -189,13 +172,7 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli return true; case R.id.remove_item: RemoveFeedDialog.show(getContext(), feed, () -> { - if (selectedNavListIndex == position) { - if (getActivity() instanceof MainActivity) { - ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); - } else { - showMainActivity(EpisodesFragment.TAG); - } - } + ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); }); return true; default: @@ -203,12 +180,6 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli } } - private void showMainActivity(String tag) { - Intent intent = new Intent(getActivity(), MainActivity.class); - intent.putExtra(MainActivity.EXTRA_FRAGMENT_TAG, tag); - startActivity(intent); - } - @Subscribe(threadMode = ThreadMode.MAIN) public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { loadData(); @@ -261,7 +232,7 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli }); builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { UserPreferences.setHiddenDrawerItems(hiddenDrawerItems); - updateSelection(); + navAdapter.notifyDataSetChanged(); // Update selection }); builder.setNegativeButton(R.string.cancel_label, null); builder.create().show(); @@ -270,25 +241,39 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli private final NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() { @Override public int getCount() { - if (navDrawerData != null) { - return navDrawerData.feeds.size(); + if (flatItemList != null) { + return flatItemList.size(); } else { return 0; } } @Override - public Feed getItem(int position) { - if (navDrawerData != null && 0 <= position && position < navDrawerData.feeds.size()) { - return navDrawerData.feeds.get(position); + public NavDrawerData.DrawerItem getItem(int position) { + if (flatItemList != null && 0 <= position && position < flatItemList.size()) { + return flatItemList.get(position); } else { return null; } } @Override - public int getSelectedItemIndex() { - return selectedNavListIndex; + public boolean isSelected(int position) { + String lastNavFragment = getLastNavFragment(getContext()); + if (position < navAdapter.getSubscriptionOffset()) { + return navAdapter.getFragmentTags().get(position).equals(lastNavFragment); + } else if (StringUtils.isNumeric(lastNavFragment)) { // last fragment was not a list, but a feed + long feedId = Long.parseLong(lastNavFragment); + if (navDrawerData != null) { + NavDrawerData.DrawerItem itemToCheck = flatItemList.get( + position - navAdapter.getSubscriptionOffset()); + if (itemToCheck.type == NavDrawerData.DrawerItem.Type.FEED) { + // When the same feed is displayed multiple times, it should be highlighted multiple times. + return ((NavDrawerData.FeedDrawerItem) itemToCheck).feed.getId() == feedId; + } + } + } + return false; } @Override @@ -312,11 +297,6 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli } @Override - public int getFeedCounter(long feedId) { - return navDrawerData != null ? navDrawerData.feedCounters.get(feedId) : 0; - } - - @Override public int getFeedCounterSum() { if (navDrawerData == null) { return 0; @@ -328,16 +308,82 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli return sum; } + @Override + public void onItemClick(int position) { + int viewType = navAdapter.getItemViewType(position); + if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER) { + if (position < navAdapter.getSubscriptionOffset()) { + String tag = navAdapter.getFragmentTags().get(position); + ((MainActivity) getActivity()).loadFragment(tag, null); + ((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED); + } else { + int pos = position - navAdapter.getSubscriptionOffset(); + NavDrawerData.DrawerItem clickedItem = flatItemList.get(pos); + + if (clickedItem.type == NavDrawerData.DrawerItem.Type.FEED) { + long feedId = ((NavDrawerData.FeedDrawerItem) clickedItem).feed.getId(); + ((MainActivity) getActivity()).loadFeedFragmentById(feedId, null); + ((MainActivity) getActivity()).getBottomSheet() + .setState(BottomSheetBehavior.STATE_COLLAPSED); + } else { + NavDrawerData.FolderDrawerItem folder = ((NavDrawerData.FolderDrawerItem) clickedItem); + if (openFolders.contains(folder.name)) { + openFolders.remove(folder.name); + } else { + openFolders.add(folder.name); + } + + getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) + .edit() + .putStringSet(PREF_OPEN_FOLDERS, openFolders) + .apply(); + + disposable = Observable.fromCallable(() -> makeFlatDrawerData(navDrawerData.items, 0)) + .subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + result -> { + flatItemList = result; + navAdapter.notifyDataSetChanged(); + }, error -> Log.e(TAG, Log.getStackTraceString(error))); + } + } + } else if (UserPreferences.getSubscriptionsFilter().isEnabled() + && navAdapter.showSubscriptionList) { + SubscriptionsFilterDialog.showDialog(requireContext()); + } + } + + @Override + public boolean onItemLongClick(int position) { + if (position < navAdapter.getFragmentTags().size()) { + showDrawerPreferencesDialog(); + return true; + } else { + contextPressedItem = flatItemList.get(position - navAdapter.getSubscriptionOffset()); + return false; + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + NavDrawerFragment.this.onCreateContextMenu(menu, v, menuInfo); + } }; private void loadData() { - disposable = Observable.fromCallable(DBReader::getNavDrawerData) + progressBar.setVisibility(View.VISIBLE); + disposable = Observable.fromCallable( + () -> { + NavDrawerData data = DBReader.getNavDrawerData(); + return new Pair<>(data, makeFlatDrawerData(data.items, 0)); + }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( result -> { - navDrawerData = result; - updateSelection(); // Selected item might be a feed + navDrawerData = result.first; + flatItemList = result.second; navAdapter.notifyDataSetChanged(); progressBar.setVisibility(View.GONE); // Stays hidden once there is something in the list }, error -> { @@ -346,45 +392,20 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli }); } - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - int viewType = parent.getAdapter().getItemViewType(position); - if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER) { - if (position < navAdapter.getSubscriptionOffset()) { - String tag = navAdapter.getTags().get(position); - if (getActivity() instanceof MainActivity) { - ((MainActivity) getActivity()).loadFragment(tag, null); - ((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED); - } else { - showMainActivity(tag); - } - } else { - int pos = position - navAdapter.getSubscriptionOffset(); - long feedId = navDrawerData.feeds.get(pos).getId(); - if (getActivity() instanceof MainActivity) { - ((MainActivity) getActivity()).loadFeedFragmentById(feedId, null); - ((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED); - } else { - Intent intent = new Intent(getActivity(), MainActivity.class); - intent.putExtra(MainActivity.EXTRA_FEED_ID, feedId); - startActivity(intent); + private List<NavDrawerData.DrawerItem> makeFlatDrawerData(List<NavDrawerData.DrawerItem> items, int layer) { + List<NavDrawerData.DrawerItem> flatItems = new ArrayList<>(); + for (NavDrawerData.DrawerItem item : items) { + item.setLayer(layer); + flatItems.add(item); + if (item.type == NavDrawerData.DrawerItem.Type.FOLDER) { + NavDrawerData.FolderDrawerItem folder = ((NavDrawerData.FolderDrawerItem) item); + folder.isOpen = openFolders.contains(folder.name); + if (folder.isOpen) { + flatItems.addAll(makeFlatDrawerData(((NavDrawerData.FolderDrawerItem) item).children, layer + 1)); } } - } else if (UserPreferences.getSubscriptionsFilter().isEnabled() - && navAdapter.showSubscriptionList) { - SubscriptionsFilterDialog.showDialog(requireContext()); - } - } - - @Override - public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { - if (position < navAdapter.getTags().size()) { - showDrawerPreferencesDialog(); - return true; - } else { - this.position = position; - return false; } + return flatItems; } public static void saveLastNavFragment(Context context, String tag) { @@ -409,8 +430,7 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (PREF_LAST_FRAGMENT_TAG.equals(key)) { - updateSelection(); - navAdapter.notifyDataSetChanged(); + navAdapter.notifyDataSetChanged(); // Update selection } } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java index 9bbc03fba..58cfece14 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -28,6 +28,7 @@ import android.widget.TextView; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.joanzapata.iconify.Iconify; +import java.util.List; import java.util.Locale; import java.util.concurrent.Callable; @@ -44,6 +45,7 @@ import de.danoeh.antennapod.core.service.download.DownloadService; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequester; +import de.danoeh.antennapod.core.storage.NavDrawerData; import de.danoeh.antennapod.core.util.download.AutoUpdateManager; import de.danoeh.antennapod.dialog.RemoveFeedDialog; import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog; @@ -68,6 +70,7 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem private static final String PREFS = "SubscriptionFragment"; private static final String PREF_NUM_COLUMNS = "columns"; private static final String KEY_UP_ARROW = "up_arrow"; + private static final String ARGUMENT_FOLDER = "folder"; private static final int MIN_NUM_COLUMNS = 2; private static final int[] COLUMN_CHECKBOX_IDS = { @@ -77,21 +80,30 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem R.id.subscription_num_columns_5}; private GridView subscriptionGridLayout; - private DBReader.NavDrawerData navDrawerData; + private List<NavDrawerData.DrawerItem> listItems; private SubscriptionsAdapter subscriptionAdapter; private FloatingActionButton subscriptionAddButton; private ProgressBar progressBar; private EmptyViewHandler emptyView; private TextView feedsFilteredMsg; private Toolbar toolbar; + private String displayedFolder = null; - private int mPosition = -1; + private Feed selectedFeed = null; private boolean isUpdatingFeeds = false; private boolean displayUpArrow; private Disposable disposable; private SharedPreferences prefs; + public static SubscriptionFragment newInstance(String folderTitle) { + SubscriptionFragment fragment = new SubscriptionFragment(); + Bundle args = new Bundle(); + args.putString(ARGUMENT_FOLDER, folderTitle); + fragment.setArguments(args); + return fragment; + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -119,6 +131,13 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem } refreshToolbarState(); + if (getArguments() != null) { + displayedFolder = getArguments().getString(ARGUMENT_FOLDER, null); + if (displayedFolder != null) { + toolbar.setTitle(displayedFolder); + } + } + subscriptionGridLayout = root.findViewById(R.id.subscriptions_grid); subscriptionGridLayout.setNumColumns(prefs.getInt(PREF_NUM_COLUMNS, getDefaultNumOfColumns())); registerForContextMenu(subscriptionGridLayout); @@ -231,12 +250,23 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem disposable.dispose(); } emptyView.hide(); - disposable = Observable.fromCallable(DBReader::getNavDrawerData) + disposable = Observable.fromCallable( + () -> { + NavDrawerData data = DBReader.getNavDrawerData(); + List<NavDrawerData.DrawerItem> items = data.items; + for (NavDrawerData.DrawerItem item : items) { + if (item.type == NavDrawerData.DrawerItem.Type.FOLDER + && item.getTitle().equals(displayedFolder)) { + return ((NavDrawerData.FolderDrawerItem) item).children; + } + } + return items; + }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( result -> { - navDrawerData = result; + listItems = result; subscriptionAdapter.notifyDataSetChanged(); emptyView.updateVisibility(); progressBar.setVisibility(View.GONE); // Keep hidden to avoid flickering while refreshing @@ -264,37 +294,24 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo; int position = adapterInfo.position; - Object selectedObject = subscriptionAdapter.getItem(position); - if (selectedObject.equals(SubscriptionsAdapter.ADD_ITEM_OBJ)) { - mPosition = position; - return; - } - - Feed feed = (Feed) selectedObject; - - MenuInflater inflater = requireActivity().getMenuInflater(); - inflater.inflate(R.menu.nav_feed_context, menu); + NavDrawerData.DrawerItem selectedObject = (NavDrawerData.DrawerItem) subscriptionAdapter.getItem(position); - menu.setHeaderTitle(feed.getTitle()); - - mPosition = position; + if (selectedObject.type == NavDrawerData.DrawerItem.Type.FEED) { + MenuInflater inflater = requireActivity().getMenuInflater(); + inflater.inflate(R.menu.nav_feed_context, menu); + selectedFeed = ((NavDrawerData.FeedDrawerItem) selectedObject).feed; + } + menu.setHeaderTitle(selectedObject.getTitle()); } @Override public boolean onContextItemSelected(MenuItem item) { - final int position = mPosition; - mPosition = -1; // reset - if (position < 0) { - return false; - } - - Object selectedObject = subscriptionAdapter.getItem(position); - if (selectedObject.equals(SubscriptionsAdapter.ADD_ITEM_OBJ)) { - // this is the add object, do nothing + if (selectedFeed == null) { return false; } - Feed feed = (Feed) selectedObject; + Feed feed = selectedFeed; + selectedFeed = null; switch (item.getItemId()) { case R.id.remove_all_new_flags_item: displayConfirmationDialog( @@ -359,25 +376,20 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem private final SubscriptionsAdapter.ItemAccess itemAccess = new SubscriptionsAdapter.ItemAccess() { @Override public int getCount() { - if (navDrawerData != null) { - return navDrawerData.feeds.size(); + if (listItems != null) { + return listItems.size(); } else { return 0; } } @Override - public Feed getItem(int position) { - if (navDrawerData != null && 0 <= position && position < navDrawerData.feeds.size()) { - return navDrawerData.feeds.get(position); + public NavDrawerData.DrawerItem getItem(int position) { + if (listItems != null && 0 <= position && position < listItems.size()) { + return listItems.get(position); } else { return null; } } - - @Override - public int getFeedCounter(long feedId) { - return navDrawerData != null ? navDrawerData.feedCounters.get(feedId) : 0; - } }; } diff --git a/app/src/main/res/layout/edit_tags_dialog.xml b/app/src/main/res/layout/edit_tags_dialog.xml new file mode 100644 index 000000000..e83cce9cf --- /dev/null +++ b/app/src/main/res/layout/edit_tags_dialog.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="16dp"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/tagsRecycler" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + <CheckBox + android:id="@+id/rootFolderCheckbox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/feed_folders_include_root" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <EditText + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:inputType="text" + android:ems="10" + android:id="@+id/newTagEditText"/> + + <ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="?attr/content_new" + android:contentDescription="@string/new_label" + android:id="@+id/newTagButton"/> + </LinearLayout> + +</LinearLayout> diff --git a/app/src/main/res/layout/fragment_subscriptions.xml b/app/src/main/res/layout/fragment_subscriptions.xml index d08e0c501..c738300c5 100644 --- a/app/src/main/res/layout/fragment_subscriptions.xml +++ b/app/src/main/res/layout/fragment_subscriptions.xml @@ -12,6 +12,7 @@ android:theme="?attr/actionBarTheme" android:layout_alignParentTop="true" app:title="@string/subscriptions_label" + app:navigationIcon="?homeAsUpIndicator" android:id="@+id/toolbar"/> <TextView diff --git a/app/src/main/res/layout/nav_list.xml b/app/src/main/res/layout/nav_list.xml index ed850cc86..70a71a453 100644 --- a/app/src/main/res/layout/nav_list.xml +++ b/app/src/main/res/layout/nav_list.xml @@ -57,7 +57,7 @@ android:background="?android:attr/listDivider" tools:background="@android:color/holo_red_dark" /> - <ListView + <androidx.recyclerview.widget.RecyclerView android:id="@+id/nav_list" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/app/src/main/res/xml/feed_settings.xml b/app/src/main/res/xml/feed_settings.xml index 13288fbda..8a63ac8e9 100644 --- a/app/src/main/res/xml/feed_settings.xml +++ b/app/src/main/res/xml/feed_settings.xml @@ -23,6 +23,12 @@ android:title="@string/authentication_label" android:summary="@string/authentication_descr"/> + <Preference + android:key="tags" + android:icon="?attr/ic_folder" + android:title="@string/feed_folders_label" + android:summary="@string/feed_folders_summary"/> + <ListPreference android:key="feedPlaybackSpeed" android:icon="?attr/ic_settings_speed" diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFilter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFilter.java index 28161ac9b..020ffb270 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFilter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedFilter.java @@ -1,11 +1,12 @@ package de.danoeh.antennapod.core.feed; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class FeedFilter { +public class FeedFilter implements Serializable { private static final String TAG = "FeedFilter"; diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java index 93efed7e1..3ee91adf5 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java @@ -7,12 +7,19 @@ import android.text.TextUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.PodDBAdapter; +import java.io.Serializable; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + /** * Contains preferences for a single feed. */ -public class FeedPreferences { +public class FeedPreferences implements Serializable { public static final float SPEED_USE_GLOBAL = -1; + public static final String TAG_ROOT = "#root"; + public static final String TAG_SEPARATOR = "\u001e"; public enum AutoDeleteAction { GLOBAL, @@ -33,17 +40,19 @@ public class FeedPreferences { private int feedSkipIntro; private int feedSkipEnding; private boolean showEpisodeNotification; + private final Set<String> tags = new HashSet<>(); public FeedPreferences(long feedID, boolean autoDownload, AutoDeleteAction autoDeleteAction, VolumeAdaptionSetting volumeAdaptionSetting, String username, String password) { this(feedID, autoDownload, true, autoDeleteAction, volumeAdaptionSetting, - username, password, new FeedFilter(), SPEED_USE_GLOBAL, 0, 0, false); + username, password, new FeedFilter(), SPEED_USE_GLOBAL, 0, 0, false, new HashSet<>()); } private FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated, AutoDeleteAction autoDeleteAction, VolumeAdaptionSetting volumeAdaptionSetting, String username, String password, @NonNull FeedFilter filter, float feedPlaybackSpeed, - int feedSkipIntro, int feedSkipEnding, boolean showEpisodeNotification) { + int feedSkipIntro, int feedSkipEnding, boolean showEpisodeNotification, + Set<String> tags) { this.feedID = feedID; this.autoDownload = autoDownload; this.keepUpdated = keepUpdated; @@ -56,6 +65,7 @@ public class FeedPreferences { this.feedSkipIntro = feedSkipIntro; this.feedSkipEnding = feedSkipEnding; this.showEpisodeNotification = showEpisodeNotification; + this.tags.addAll(tags); } public static FeedPreferences fromCursor(Cursor cursor) { @@ -72,6 +82,7 @@ public class FeedPreferences { int indexAutoSkipIntro = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_INTRO); int indexAutoSkipEnding = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_ENDING); int indexEpisodeNotification = cursor.getColumnIndex(PodDBAdapter.KEY_EPISODE_NOTIFICATION); + int indexTags = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_TAGS); long feedId = cursor.getLong(indexId); boolean autoDownload = cursor.getInt(indexAutoDownload) > 0; @@ -88,6 +99,10 @@ public class FeedPreferences { int feedAutoSkipIntro = cursor.getInt(indexAutoSkipIntro); int feedAutoSkipEnding = cursor.getInt(indexAutoSkipEnding); boolean showNotification = cursor.getInt(indexEpisodeNotification) > 0; + String tagsString = cursor.getString(indexTags); + if (TextUtils.isEmpty(tagsString)) { + tagsString = TAG_ROOT; + } return new FeedPreferences(feedId, autoDownload, autoRefresh, @@ -99,8 +114,8 @@ public class FeedPreferences { feedPlaybackSpeed, feedAutoSkipIntro, feedAutoSkipEnding, - showNotification - ); + showNotification, + new HashSet<>(Arrays.asList(tagsString.split(TAG_SEPARATOR)))); } /** @@ -240,6 +255,14 @@ public class FeedPreferences { return feedSkipEnding; } + public Set<String> getTags() { + return tags; + } + + public String getTagsAsString() { + return TextUtils.join(TAG_SEPARATOR, tags); + } + /** * getter for preference if notifications should be display for new episodes. * @return true for displaying notifications diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java index 4899716e9..96e418ef6 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java @@ -11,6 +11,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -874,32 +875,34 @@ public final class DBReader { int numNewItems = adapter.getNumberOfNewItems(); int numDownloadedItems = adapter.getNumberOfDownloadedEpisodes(); - NavDrawerData result = new NavDrawerData(feeds, queueSize, numNewItems, numDownloadedItems, + List<NavDrawerData.DrawerItem> items = new ArrayList<>(); + Map<String, NavDrawerData.FolderDrawerItem> folders = new HashMap<>(); + for (Feed feed : feeds) { + for (String tag : feed.getPreferences().getTags()) { + NavDrawerData.FeedDrawerItem drawerItem = new NavDrawerData.FeedDrawerItem(feed, feed.getId(), + feedCounters.get(feed.getId())); + if (FeedPreferences.TAG_ROOT.equals(tag)) { + items.add(drawerItem); + continue; + } + NavDrawerData.FolderDrawerItem folder; + if (folders.containsKey(tag)) { + folder = folders.get(tag); + } else { + folder = new NavDrawerData.FolderDrawerItem(tag); + folders.put(tag, folder); + } + drawerItem.id |= folder.id; + folder.children.add(drawerItem); + } + } + List<NavDrawerData.FolderDrawerItem> foldersSorted = new ArrayList<>(folders.values()); + Collections.sort(foldersSorted, (o1, o2) -> o1.getTitle().compareToIgnoreCase(o2.getTitle())); + items.addAll(foldersSorted); + + NavDrawerData result = new NavDrawerData(items, queueSize, numNewItems, numDownloadedItems, feedCounters, UserPreferences.getEpisodeCleanupAlgorithm().getReclaimableItems()); adapter.close(); return result; } - - public static class NavDrawerData { - public final List<Feed> feeds; - public final int queueSize; - public final int numNewItems; - public final int numDownloadedItems; - public final LongIntMap feedCounters; - public final int reclaimableSpace; - - public NavDrawerData(List<Feed> feeds, - int queueSize, - int numNewItems, - int numDownloadedItems, - LongIntMap feedIndicatorValues, - int reclaimableSpace) { - this.feeds = feeds; - this.queueSize = queueSize; - this.numNewItems = numNewItems; - this.numDownloadedItems = numDownloadedItems; - this.feedCounters = feedIndicatorValues; - this.reclaimableSpace = reclaimableSpace; - } - } } 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 c3f03a841..4e2eb6e5a 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 @@ -319,6 +319,8 @@ class DBUpgrader { + " SET " + PodDBAdapter.KEY_DESCRIPTION + " = content_encoded, content_encoded = NULL " + "WHERE length(" + PodDBAdapter.KEY_DESCRIPTION + ") < length(content_encoded)"); db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + " SET content_encoded = NULL"); + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + + " ADD COLUMN " + PodDBAdapter.KEY_FEED_TAGS + " TEXT;"); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/NavDrawerData.java b/core/src/main/java/de/danoeh/antennapod/core/storage/NavDrawerData.java new file mode 100644 index 000000000..0a56330a9 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/NavDrawerData.java @@ -0,0 +1,99 @@ +package de.danoeh.antennapod.core.storage; + +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.util.LongIntMap; + +import java.util.ArrayList; +import java.util.List; + +public class NavDrawerData { + public final List<DrawerItem> items; + public final int queueSize; + public final int numNewItems; + public final int numDownloadedItems; + public final LongIntMap feedCounters; + public final int reclaimableSpace; + + public NavDrawerData(List<DrawerItem> feeds, + int queueSize, + int numNewItems, + int numDownloadedItems, + LongIntMap feedIndicatorValues, + int reclaimableSpace) { + this.items = feeds; + this.queueSize = queueSize; + this.numNewItems = numNewItems; + this.numDownloadedItems = numDownloadedItems; + this.feedCounters = feedIndicatorValues; + this.reclaimableSpace = reclaimableSpace; + } + + public abstract static class DrawerItem { + public enum Type { + FOLDER, FEED + } + + public final Type type; + private int layer; + public long id; + + public DrawerItem(Type type, long id) { + this.type = type; + this.id = id; + } + + public int getLayer() { + return layer; + } + + public void setLayer(int layer) { + this.layer = layer; + } + + public abstract String getTitle(); + + public abstract int getCounter(); + } + + public static class FolderDrawerItem extends DrawerItem { + public final List<DrawerItem> children = new ArrayList<>(); + public final String name; + public boolean isOpen; + + public FolderDrawerItem(String name) { + super(DrawerItem.Type.FOLDER, (long) name.hashCode() << 20); // Keep IDs >0 but make room for many feeds + this.name = name; + } + + public String getTitle() { + return name; + } + + public int getCounter() { + int sum = 0; + for (DrawerItem item : children) { + sum += item.getCounter(); + } + return sum; + } + } + + public static class FeedDrawerItem extends DrawerItem { + public final Feed feed; + public final int counter; + + public FeedDrawerItem(Feed feed, long id, int counter) { + super(DrawerItem.Type.FEED, id); + this.feed = feed; + this.counter = counter; + } + + public String getTitle() { + return feed.getTitle(); + } + + public int getCounter() { + return counter; + } + } +} 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 54f5f3fa4..98d5e6310 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 @@ -115,6 +115,7 @@ public class PodDBAdapter { 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"; + public static final String KEY_FEED_TAGS = "tags"; public static final String KEY_EPISODE_NOTIFICATION = "episode_notification"; // Table names @@ -152,6 +153,7 @@ public class PodDBAdapter { + KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0," + KEY_FEED_PLAYBACK_SPEED + " REAL DEFAULT " + SPEED_USE_GLOBAL + "," + KEY_FEED_VOLUME_ADAPTION + " INTEGER DEFAULT 0," + + KEY_FEED_TAGS + " TEXT," + KEY_FEED_SKIP_INTRO + " INTEGER DEFAULT 0," + KEY_FEED_SKIP_ENDING + " INTEGER DEFAULT 0," + KEY_EPISODE_NOTIFICATION + " INTEGER DEFAULT 0)"; @@ -255,6 +257,7 @@ public class PodDBAdapter { TABLE_NAME_FEEDS + "." + KEY_INCLUDE_FILTER, TABLE_NAME_FEEDS + "." + KEY_EXCLUDE_FILTER, TABLE_NAME_FEEDS + "." + KEY_FEED_PLAYBACK_SPEED, + TABLE_NAME_FEEDS + "." + KEY_FEED_TAGS, TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_INTRO, TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_ENDING, TABLE_NAME_FEEDS + "." + KEY_EPISODE_NOTIFICATION @@ -447,6 +450,7 @@ public class PodDBAdapter { values.put(KEY_INCLUDE_FILTER, prefs.getFilter().getIncludeFilter()); values.put(KEY_EXCLUDE_FILTER, prefs.getFilter().getExcludeFilter()); values.put(KEY_FEED_PLAYBACK_SPEED, prefs.getFeedPlaybackSpeed()); + values.put(KEY_FEED_TAGS, prefs.getTagsAsString()); values.put(KEY_FEED_SKIP_INTRO, prefs.getFeedSkipIntro()); values.put(KEY_FEED_SKIP_ENDING, prefs.getFeedSkipEnding()); values.put(KEY_EPISODE_NOTIFICATION, prefs.getShowEpisodeNotification()); diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 8663dd802..6511fa496 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -731,9 +731,12 @@ <string name="apply_action">Apply action</string> <string name="play_chapter">Play chapter</string> - <!-- Feed information screen --> + <!-- Feed settings/information screen --> <string name="authentication_label">Authentication</string> <string name="authentication_descr">Change your username and password for this podcast and its episodes.</string> + <string name="feed_folders_label">Folders</string> + <string name="feed_folders_summary">Change the folders in which this podcast is displayed.</string> + <string name="feed_folders_include_root">Show in main list</string> <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> diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbReaderTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbReaderTest.java index 212b94743..2a1e6f4c8 100644 --- a/core/src/test/java/de/danoeh/antennapod/core/storage/DbReaderTest.java +++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbReaderTest.java @@ -322,8 +322,8 @@ public class DbReaderTest { final int numFeeds = 10; final int numItems = 10; DbTestUtils.saveFeedlist(numFeeds, numItems, true); - DBReader.NavDrawerData navDrawerData = DBReader.getNavDrawerData(); - assertEquals(numFeeds, navDrawerData.feeds.size()); + NavDrawerData navDrawerData = DBReader.getNavDrawerData(); + assertEquals(numFeeds, navDrawerData.items.size()); assertEquals(0, navDrawerData.numNewItems); assertEquals(0, navDrawerData.queueSize); } @@ -351,8 +351,8 @@ public class DbReaderTest { adapter.close(); - DBReader.NavDrawerData navDrawerData = DBReader.getNavDrawerData(); - assertEquals(numFeeds, navDrawerData.feeds.size()); + NavDrawerData navDrawerData = DBReader.getNavDrawerData(); + assertEquals(numFeeds, navDrawerData.items.size()); assertEquals(numNew, navDrawerData.numNewItems); assertEquals(numQueue, navDrawerData.queueSize); } |