diff options
author | ByteHamster <ByteHamster@users.noreply.github.com> | 2024-05-09 11:44:26 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-09 11:44:26 +0200 |
commit | 084723ad76f93822b9a15b24bb55a1fefa155dce (patch) | |
tree | 8c847b937facfae38808f313f427b01d611f7068 | |
parent | 53ce6cd71a766847dfdf345ecd7b96a6dbca8015 (diff) | |
download | AntennaPod-084723ad76f93822b9a15b24bb55a1fefa155dce.zip |
Add episodes without subscribing (#7098)
50 files changed, 677 insertions, 817 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/actionbutton/ItemActionButton.java b/app/src/main/java/de/danoeh/antennapod/actionbutton/ItemActionButton.java index 84b246167..54714be3d 100644 --- a/app/src/main/java/de/danoeh/antennapod/actionbutton/ItemActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/actionbutton/ItemActionButton.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import androidx.annotation.StringRes; import android.view.View; +import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.playback.service.PlaybackStatus; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; @@ -48,6 +49,8 @@ public abstract class ItemActionButton { return new PlayActionButton(item); } else if (isDownloadingMedia) { return new CancelDownloadActionButton(item); + } else if (item.getFeed().getState() != Feed.STATE_SUBSCRIBED) { + return new StreamActionButton(item); } else if (UserPreferences.isStreamOverDownload()) { return new StreamActionButton(item); } else { diff --git a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeItemListAdapter.java b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeItemListAdapter.java index 5082f25ff..db7991261 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeItemListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeItemListAdapter.java @@ -12,6 +12,7 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; import androidx.recyclerview.widget.RecyclerView; import de.danoeh.antennapod.ui.SelectableAdapter; @@ -32,13 +33,13 @@ import de.danoeh.antennapod.ui.screen.episode.ItemPagerFragment; public class EpisodeItemListAdapter extends SelectableAdapter<EpisodeItemViewHolder> implements View.OnCreateContextMenuListener { - private final WeakReference<MainActivity> mainActivityRef; + private final WeakReference<FragmentActivity> mainActivityRef; private List<FeedItem> episodes = new ArrayList<>(); private FeedItem longPressedItem; int longPressedPosition = 0; // used to init actionMode private int dummyViews = 0; - public EpisodeItemListAdapter(MainActivity mainActivity) { + public EpisodeItemListAdapter(FragmentActivity mainActivity) { super(mainActivity); this.mainActivityRef = new WeakReference<>(mainActivity); setHasStableIds(true); @@ -86,9 +87,18 @@ public class EpisodeItemListAdapter extends SelectableAdapter<EpisodeItemViewHol holder.bind(item); holder.itemView.setOnClickListener(v -> { - MainActivity activity = mainActivityRef.get(); - if (activity != null && !inActionMode()) { - activity.loadChildFragment(ItemPagerFragment.newInstance(episodes, item)); + if (!inActionMode()) { + if (mainActivityRef.get() instanceof MainActivity) { + ((MainActivity) mainActivityRef.get()) + .loadChildFragment(ItemPagerFragment.newInstance(episodes, item)); + } else { + ItemPagerFragment fragment = ItemPagerFragment.newInstance(episodes, item); + mainActivityRef.get().getSupportFragmentManager() + .beginTransaction() + .replace(R.id.fragmentContainer, fragment, "Items") + .addToBackStack("Items") + .commitAllowingStateLoss(); + } } else { toggleSelection(holder.getBindingAdapterPosition()); } diff --git a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeItemViewHolder.java b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeItemViewHolder.java index dd1a10450..c5868a945 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeItemViewHolder.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeItemViewHolder.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.ui.episodeslist; +import android.app.Activity; import android.os.Build; import android.text.Layout; import android.text.format.Formatter; @@ -17,7 +18,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.elevation.SurfaceColors; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.ui.CoverLoader; import de.danoeh.antennapod.actionbutton.ItemActionButton; import de.danoeh.antennapod.playback.service.PlaybackStatus; @@ -62,10 +62,10 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder { private final View leftPadding; public final CardView coverHolder; - private final MainActivity activity; + private final Activity activity; private FeedItem item; - public EpisodeItemViewHolder(MainActivity activity, ViewGroup parent) { + public EpisodeItemViewHolder(Activity activity, ViewGroup parent) { super(LayoutInflater.from(activity).inflate(R.layout.feeditemlist_item, parent, false)); this.activity = activity; container = itemView.findViewById(R.id.container); diff --git a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeMultiSelectActionHandler.java b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeMultiSelectActionHandler.java index 76ed47de5..a655982cb 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeMultiSelectActionHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/EpisodeMultiSelectActionHandler.java @@ -1,29 +1,28 @@ package de.danoeh.antennapod.ui.episodeslist; +import android.app.Activity; import android.util.Log; import androidx.annotation.PluralsRes; -import com.google.android.material.snackbar.Snackbar; - import java.util.List; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.event.MessageEvent; import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface; import de.danoeh.antennapod.storage.database.DBWriter; import de.danoeh.antennapod.storage.database.LongList; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.ui.view.LocalDeleteModal; +import org.greenrobot.eventbus.EventBus; public class EpisodeMultiSelectActionHandler { private static final String TAG = "EpisodeSelectHandler"; - private final MainActivity activity; + private final Activity activity; private final int actionId; private int totalNumItems = 0; - private Snackbar snackbar = null; - public EpisodeMultiSelectActionHandler(MainActivity activity, int actionId) { + public EpisodeMultiSelectActionHandler(Activity activity, int actionId) { this.activity = activity; this.actionId = actionId; } @@ -116,12 +115,7 @@ public class EpisodeMultiSelectActionHandler { totalNumItems += numItems; activity.runOnUiThread(() -> { String text = activity.getResources().getQuantityString(msgId, totalNumItems, totalNumItems); - if (snackbar != null) { - snackbar.setText(text); - snackbar.show(); // Resets the timeout - } else { - snackbar = activity.showSnackbarAbovePlayer(text, Snackbar.LENGTH_LONG); - } + EventBus.getDefault().post(new MessageEvent(text)); }); } diff --git a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/FeedItemMenuHandler.java index e1974940c..720e0ccbc 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/FeedItemMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/episodeslist/FeedItemMenuHandler.java @@ -16,6 +16,7 @@ import java.util.Arrays; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink; import de.danoeh.antennapod.storage.preferences.PlaybackPreferences; import de.danoeh.antennapod.playback.service.PlaybackServiceInterface; @@ -84,6 +85,10 @@ public class FeedItemMenuHandler { setItemVisibility(menu, R.id.add_to_favorites_item, !isFavorite); setItemVisibility(menu, R.id.remove_from_favorites_item, isFavorite); setItemVisibility(menu, R.id.remove_item, fileDownloaded || isLocalFile); + + if (selectedItem.getFeed().getState() != Feed.STATE_SUBSCRIBED) { + setItemVisibility(menu, R.id.mark_read_item, false); + } return true; } @@ -158,7 +163,8 @@ public class FeedItemMenuHandler { } else if (menuItemId == R.id.mark_read_item) { selectedItem.setPlayed(true); DBWriter.markItemPlayed(selectedItem, FeedItem.PLAYED, true); - if (!selectedItem.getFeed().isLocalFeed() && SynchronizationSettings.isProviderConnected()) { + if (!selectedItem.getFeed().isLocalFeed() && selectedItem.getFeed().getState() == Feed.STATE_SUBSCRIBED + && SynchronizationSettings.isProviderConnected()) { FeedMedia media = selectedItem.getMedia(); // not all items have media, Gpodder only cares about those that do if (media != null) { @@ -174,7 +180,8 @@ public class FeedItemMenuHandler { } else if (menuItemId == R.id.mark_unread_item) { selectedItem.setPlayed(false); DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, false); - if (!selectedItem.getFeed().isLocalFeed() && selectedItem.getMedia() != null) { + if (!selectedItem.getFeed().isLocalFeed() && selectedItem.getMedia() != null + && selectedItem.getFeed().getState() == Feed.STATE_SUBSCRIBED) { EpisodeAction actionNew = new EpisodeAction.Builder(selectedItem, EpisodeAction.NEW) .currentTimestamp() .build(); diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/AllEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/AllEpisodesFragment.java index eb49c94d9..6e48ade39 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/AllEpisodesFragment.java @@ -66,7 +66,12 @@ public class AllEpisodesFragment extends EpisodesListFragment { @Override protected FeedItemFilter getFilter() { - return new FeedItemFilter(UserPreferences.getPrefFilterAllEpisodes()); + FeedItemFilter filter = new FeedItemFilter(UserPreferences.getPrefFilterAllEpisodes()); + if (filter.showIsFavorite) { + return new FeedItemFilter(filter, FeedItemFilter.INCLUDE_NOT_SUBSCRIBED); + } else { + return filter; + } } @Override @@ -91,6 +96,7 @@ public class AllEpisodesFragment extends EpisodesListFragment { ArrayList<String> filter = new ArrayList<>(getFilter().getValuesList()); if (filter.contains(FeedItemFilter.IS_FAVORITE)) { filter.remove(FeedItemFilter.IS_FAVORITE); + filter.remove(FeedItemFilter.INCLUDE_NOT_SUBSCRIBED); } else { filter.add(FeedItemFilter.IS_FAVORITE); } diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/PlaybackHistoryFragment.java index a02f86bd2..abdf043b1 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/PlaybackHistoryFragment.java @@ -23,7 +23,8 @@ import java.util.List; public class PlaybackHistoryFragment extends EpisodesListFragment { public static final String TAG = "PlaybackHistoryFragment"; - private static final FeedItemFilter FILTER_HISTORY = new FeedItemFilter(FeedItemFilter.IS_IN_HISTORY); + private static final FeedItemFilter FILTER_HISTORY = new FeedItemFilter( + FeedItemFilter.IS_IN_HISTORY, FeedItemFilter.INCLUDE_NOT_SUBSCRIBED); @NonNull @Override diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/download/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/download/CompletedDownloadsFragment.java index 001c2b465..0d82b7133 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/download/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/download/CompletedDownloadsFragment.java @@ -285,7 +285,7 @@ public class CompletedDownloadsFragment extends Fragment disposable = Observable.fromCallable(() -> { SortOrder sortOrder = UserPreferences.getDownloadsSortedOrder(); List<FeedItem> downloadedItems = DBReader.getEpisodes(0, Integer.MAX_VALUE, - new FeedItemFilter(FeedItemFilter.DOWNLOADED), sortOrder); + new FeedItemFilter(FeedItemFilter.DOWNLOADED, FeedItemFilter.INCLUDE_NOT_SUBSCRIBED), sortOrder); List<String> mediaUrls = new ArrayList<>(); if (runningDownloads == null) { diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/episode/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/episode/ItemFragment.java index e31004a57..a5ee78a58 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/episode/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/episode/ItemFragment.java @@ -38,6 +38,7 @@ import de.danoeh.antennapod.actionbutton.PlayLocalActionButton; import de.danoeh.antennapod.actionbutton.StreamActionButton; import de.danoeh.antennapod.actionbutton.VisitWebsiteActionButton; import de.danoeh.antennapod.event.EpisodeDownloadEvent; +import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.playback.service.PlaybackStatus; import de.danoeh.antennapod.event.FeedItemEvent; import de.danoeh.antennapod.event.PlayerStatusEvent; @@ -49,6 +50,7 @@ import de.danoeh.antennapod.storage.preferences.UsageStatistics; import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface; import de.danoeh.antennapod.storage.preferences.UserPreferences; import de.danoeh.antennapod.storage.database.DBReader; +import de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter; import de.danoeh.antennapod.ui.common.Converter; import de.danoeh.antennapod.ui.common.DateFormatter; import de.danoeh.antennapod.ui.common.CircularProgressBar; @@ -114,6 +116,7 @@ public class ItemFragment extends Fragment { private ItemActionButton actionButton1; private ItemActionButton actionButton2; private View noMediaLabel; + private View nonSubscribedWarningLabel; private Disposable disposable; private PlaybackController controller; @@ -164,6 +167,7 @@ public class ItemFragment extends Fragment { butAction1Text = layout.findViewById(R.id.butAction1Text); butAction2Text = layout.findViewById(R.id.butAction2Text); noMediaLabel = layout.findViewById(R.id.noMediaLabel); + nonSubscribedWarningLabel = layout.findViewById(R.id.nonSubscribedWarningLabel); butAction1.setOnClickListener(v -> { if (actionButton1 instanceof StreamActionButton && !UserPreferences.isStreamOverDownload() @@ -287,6 +291,11 @@ public class ItemFragment extends Fragment { txtvPublished.setContentDescription(DateFormatter.formatForAccessibility(item.getPubDate())); } + if (item.getFeed().getState() != Feed.STATE_SUBSCRIBED) { + nonSubscribedWarningLabel.setVisibility(View.VISIBLE); + nonSubscribedWarningLabel.setOnClickListener(v -> openPodcast()); + } + float radius = 8 * getResources().getDisplayMetrics().density; RequestOptions options = new RequestOptions() .error(ImagePlaceholder.getDrawable(getContext(), radius)) @@ -366,8 +375,13 @@ public class ItemFragment extends Fragment { if (item == null) { return; } - Fragment fragment = FeedItemlistFragment.newInstance(item.getFeedId()); - ((MainActivity) getActivity()).loadChildFragment(fragment); + if (item.getFeed().getState() == Feed.STATE_SUBSCRIBED) { + Fragment fragment = FeedItemlistFragment.newInstance(item.getFeedId()); + ((MainActivity) getActivity()).loadChildFragment(fragment); + } else { + startActivity(new OnlineFeedviewActivityStarter(getContext(), item.getFeed().getDownloadUrl()) + .getIntent()); + } } @Subscribe(threadMode = ThreadMode.MAIN) diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/episode/ItemPagerFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/episode/ItemPagerFragment.java index e7f345617..951e5affb 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/episode/ItemPagerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/episode/ItemPagerFragment.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.ui.screen.episode; +import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.MenuItem; @@ -13,7 +14,8 @@ import androidx.fragment.app.Fragment; import androidx.viewpager2.adapter.FragmentStateAdapter; import androidx.viewpager2.widget.ViewPager2; -import de.danoeh.antennapod.ui.screen.feed.FeedItemlistFragment; +import de.danoeh.antennapod.model.feed.Feed; +import de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -173,8 +175,12 @@ public class ItemPagerFragment extends Fragment implements MaterialToolbar.OnMen if (item == null) { return; } - Fragment fragment = FeedItemlistFragment.newInstance(item.getFeedId()); - ((MainActivity) getActivity()).loadChildFragment(fragment); + if (item.getFeed().getState() == Feed.STATE_SUBSCRIBED) { + Intent intent = MainActivity.getIntentToOpenFeed(getContext(), item.getFeedId()); + startActivity(intent); + } else { + startActivity(new OnlineFeedviewActivityStarter(getContext(), item.getFeed().getDownloadUrl()).getIntent()); + } } private class ItemPagerAdapter extends FragmentStateAdapter { diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/feed/FeedInfoFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/feed/FeedInfoFragment.java index aabb0852e..f73b69d75 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/feed/FeedInfoFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/feed/FeedInfoFragment.java @@ -122,7 +122,6 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu viewBinding.header.butFilter.setVisibility(View.INVISIBLE); // https://github.com/bumptech/glide/issues/529 viewBinding.imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000)); - viewBinding.urlLabel.setOnClickListener(copyUrlToClipboard); long feedId = getArguments().getLong(EXTRA_FEED_ID); @@ -237,6 +236,25 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu viewBinding.supportUrl.setText(str.toString()); } + if (feed.getState() == Feed.STATE_SUBSCRIBED) { + long feedId = getArguments().getLong(EXTRA_FEED_ID); + getParentFragmentManager().beginTransaction().replace(R.id.statisticsFragmentContainer, + FeedStatisticsFragment.newInstance(feedId, false), "feed_statistics_fragment") + .commitAllowingStateLoss(); + + viewBinding.statisticsButton.setOnClickListener(view -> { + StatisticsFragment fragment = new StatisticsFragment(); + ((MainActivity) getActivity()).loadChildFragment(fragment, TransitionEffect.SLIDE); + }); + } else { + viewBinding.statisticsButton.setVisibility(View.GONE); + viewBinding.statisticsFragmentContainer.setVisibility(View.GONE); + viewBinding.statisticsHeadingLabel.setVisibility(View.GONE); + viewBinding.supportHeadingLabel.setVisibility(View.GONE); + viewBinding.supportUrl.setVisibility(View.GONE); + viewBinding.descriptionHeadingLabel.setVisibility(View.GONE); + } + refreshToolbarState(); } @@ -249,13 +267,14 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu } private void refreshToolbarState() { + boolean isSubscribed = feed != null && feed.getState() == Feed.STATE_SUBSCRIBED; viewBinding.toolbar.getMenu().findItem(R.id.reconnect_local_folder).setVisible( - feed != null && feed.isLocalFeed()); - viewBinding.toolbar.getMenu().findItem(R.id.share_item).setVisible(feed != null && !feed.isLocalFeed()); - viewBinding.toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(feed != null + isSubscribed && feed.isLocalFeed()); + viewBinding.toolbar.getMenu().findItem(R.id.share_item).setVisible(isSubscribed && !feed.isLocalFeed()); + viewBinding.toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(isSubscribed && feed.getLink() != null && IntentUtils.isCallable(getContext(), new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink())))); - viewBinding.toolbar.getMenu().findItem(R.id.edit_feed_url_item).setVisible(feed != null && !feed.isLocalFeed()); + viewBinding.toolbar.getMenu().findItem(R.id.edit_feed_url_item).setVisible(isSubscribed && !feed.isLocalFeed()); } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/feed/FeedItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/feed/FeedItemlistFragment.java index 3c6931794..fbfb9fa0a 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/feed/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/feed/FeedItemlistFragment.java @@ -12,52 +12,23 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; -import android.widget.Toast; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.content.res.AppCompatResources; import androidx.core.util.Pair; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; import androidx.recyclerview.widget.RecyclerView; - import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.snackbar.Snackbar; - -import de.danoeh.antennapod.ui.CoverLoader; -import de.danoeh.antennapod.ui.screen.episode.ItemPagerFragment; -import de.danoeh.antennapod.ui.screen.SearchFragment; -import de.danoeh.antennapod.ui.TransitionEffect; -import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager; -import de.danoeh.antennapod.ui.screen.download.DownloadLogFragment; -import de.danoeh.antennapod.ui.screen.feed.preferences.FeedSettingsFragment; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutionException; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.ui.episodeslist.EpisodeItemListAdapter; -import de.danoeh.antennapod.event.FeedEvent; -import de.danoeh.antennapod.ui.MenuItemUtils; -import de.danoeh.antennapod.storage.database.DBReader; -import de.danoeh.antennapod.storage.database.DBWriter; -import de.danoeh.antennapod.ui.common.IntentUtils; -import de.danoeh.antennapod.ui.share.ShareUtils; -import de.danoeh.antennapod.ui.episodeslist.MoreContentListFooterUtil; import de.danoeh.antennapod.databinding.FeedItemListFragmentBinding; -import de.danoeh.antennapod.ui.screen.download.DownloadLogDetailsDialog; -import de.danoeh.antennapod.ui.FeedItemFilterDialog; import de.danoeh.antennapod.event.EpisodeDownloadEvent; import de.danoeh.antennapod.event.FavoritesEvent; +import de.danoeh.antennapod.event.FeedEvent; import de.danoeh.antennapod.event.FeedItemEvent; import de.danoeh.antennapod.event.FeedListUpdateEvent; import de.danoeh.antennapod.event.FeedUpdateRunningEvent; @@ -65,21 +36,48 @@ import de.danoeh.antennapod.event.PlayerStatusEvent; import de.danoeh.antennapod.event.QueueEvent; import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; -import de.danoeh.antennapod.ui.episodeslist.EpisodeMultiSelectActionHandler; -import de.danoeh.antennapod.ui.swipeactions.SwipeActions; -import de.danoeh.antennapod.ui.episodeslist.FeedItemMenuHandler; import de.danoeh.antennapod.model.download.DownloadResult; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedItemFilter; +import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager; +import de.danoeh.antennapod.storage.database.DBReader; +import de.danoeh.antennapod.storage.database.DBWriter; import de.danoeh.antennapod.storage.preferences.UserPreferences; -import de.danoeh.antennapod.ui.glide.FastBlurTransformation; +import de.danoeh.antennapod.ui.CoverLoader; +import de.danoeh.antennapod.ui.FeedItemFilterDialog; +import de.danoeh.antennapod.ui.MenuItemUtils; +import de.danoeh.antennapod.ui.TransitionEffect; +import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; +import de.danoeh.antennapod.ui.cleaner.HtmlToPlainText; +import de.danoeh.antennapod.ui.common.IntentUtils; +import de.danoeh.antennapod.ui.episodeslist.EpisodeItemListAdapter; import de.danoeh.antennapod.ui.episodeslist.EpisodeItemViewHolder; +import de.danoeh.antennapod.ui.episodeslist.EpisodeMultiSelectActionHandler; +import de.danoeh.antennapod.ui.episodeslist.FeedItemMenuHandler; +import de.danoeh.antennapod.ui.episodeslist.MoreContentListFooterUtil; +import de.danoeh.antennapod.ui.glide.FastBlurTransformation; +import de.danoeh.antennapod.ui.screen.SearchFragment; +import de.danoeh.antennapod.ui.screen.download.DownloadLogDetailsDialog; +import de.danoeh.antennapod.ui.screen.download.DownloadLogFragment; +import de.danoeh.antennapod.ui.screen.episode.ItemPagerFragment; +import de.danoeh.antennapod.ui.screen.feed.preferences.FeedSettingsFragment; +import de.danoeh.antennapod.ui.share.ShareUtils; +import de.danoeh.antennapod.ui.swipeactions.SwipeActions; import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; /** * Displays a list of FeedItems. @@ -100,7 +98,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem private boolean displayUpArrow; private long feedID; private Feed feed; - private boolean headerCreated = false; private Disposable disposable; private FeedItemListFragmentBinding viewBinding; @@ -145,12 +142,18 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem if (savedInstanceState != null) { displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW); } - ((MainActivity) getActivity()).setupToolbarToggle(viewBinding.toolbar, displayUpArrow); + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).setupToolbarToggle(viewBinding.toolbar, displayUpArrow); + viewBinding.recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool()); + } else { + viewBinding.toolbar.setNavigationIcon(R.drawable.ic_close); + viewBinding.toolbar.setNavigationOnClickListener(view -> getActivity().finish()); + } updateToolbar(); setupLoadMoreScrollListener(); + setupHeaderView(); - viewBinding.recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool()); - adapter = new FeedItemListAdapter((MainActivity) getActivity()); + adapter = new FeedItemListAdapter(getActivity()); adapter.setOnSelectModeListener(this); viewBinding.recyclerView.setAdapter(adapter); swipeActions = new SwipeActions(this, TAG).attachTo(viewBinding.recyclerView); @@ -198,7 +201,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem Snackbar.LENGTH_SHORT); return false; } - new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), menuItem.getItemId()) + new EpisodeMultiSelectActionHandler(getActivity(), menuItem.getItemId()) .handleAction(adapter.getSelectedItems()); adapter.endSelectMode(); return true; @@ -249,6 +252,13 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem if (feed.isLocalFeed()) { viewBinding.toolbar.getMenu().findItem(R.id.share_item).setVisible(false); } + if (feed.getState() == Feed.STATE_NOT_SUBSCRIBED) { + viewBinding.toolbar.getMenu().findItem(R.id.sort_items).setVisible(false); + viewBinding.toolbar.getMenu().findItem(R.id.refresh_item).setVisible(false); + viewBinding.toolbar.getMenu().findItem(R.id.rename_item).setVisible(false); + viewBinding.toolbar.getMenu().findItem(R.id.remove_feed).setVisible(false); + viewBinding.toolbar.getMenu().findItem(R.id.action_search).setVisible(false); + } } @Override @@ -263,8 +273,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem @Override public boolean onMenuItemClick(MenuItem item) { if (feed == null) { - ((MainActivity) getActivity()).showSnackbarAbovePlayer( - R.string.please_wait_for_data, Toast.LENGTH_LONG); + EventBus.getDefault().post(getString(R.string.please_wait_for_data)); return true; } if (item.getItemId() == R.id.visit_website_item) { @@ -443,7 +452,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem } private void refreshHeaderView() { - setupHeaderView(); if (viewBinding == null || feed == null) { Log.e(TAG, "Unable to refresh header view"); return; @@ -454,7 +462,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem } else { viewBinding.header.txtvFailure.setVisibility(View.GONE); } - if (!feed.getPreferences().getKeepUpdated()) { + if (!feed.getPreferences().getKeepUpdated() && feed.getState() == Feed.STATE_SUBSCRIBED) { viewBinding.header.txtvUpdatesDisabled.setText(R.string.updates_disabled_label); viewBinding.header.txtvUpdatesDisabled.setVisibility(View.VISIBLE); } else { @@ -462,7 +470,11 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem } viewBinding.header.txtvTitle.setText(feed.getTitle()); viewBinding.header.txtvAuthor.setText(feed.getAuthor()); - if (feed.getItemFilter() != null) { + viewBinding.header.descriptionContainer.setVisibility(View.GONE); + if (feed.getState() != Feed.STATE_SUBSCRIBED) { + viewBinding.header.descriptionContainer.setVisibility(View.VISIBLE); + viewBinding.header.headerDescriptionLabel.setText(HtmlToPlainText.getPlainText(feed.getDescription())); + } else if (feed.getItemFilter() != null) { FeedItemFilter filter = feed.getItemFilter(); if (filter.getValues().length > 0) { viewBinding.header.txtvInformation.setText(R.string.filtered_label); @@ -475,17 +487,30 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem } else { viewBinding.header.txtvInformation.setVisibility(View.GONE); } - } + boolean isSubscribed = feed.getState() == Feed.STATE_SUBSCRIBED; + viewBinding.header.butShowInfo.setVisibility(isSubscribed ? View.VISIBLE : View.GONE); + viewBinding.header.butFilter.setVisibility(isSubscribed ? View.VISIBLE : View.GONE); + viewBinding.header.butShowSettings.setVisibility(isSubscribed ? View.VISIBLE : View.GONE); + viewBinding.header.butSubscribe.setVisibility(isSubscribed ? View.GONE : View.VISIBLE); - private void setupHeaderView() { - if (feed == null || headerCreated) { - return; + if (!isSubscribed && feed.getLastRefreshAttempt() < System.currentTimeMillis() - 1000L * 3600 * 24) { + FeedUpdateManager.getInstance().runOnce(getContext(), feed, true); } + } + private void setupHeaderView() { // https://github.com/bumptech/glide/issues/529 viewBinding.imgvBackground.setColorFilter(new LightingColorFilter(0xff666666, 0x000000)); viewBinding.header.butShowInfo.setOnClickListener(v -> showFeedInfo()); viewBinding.header.imgvCover.setOnClickListener(v -> showFeedInfo()); + viewBinding.header.headerDescriptionLabel.setOnClickListener(v -> showFeedInfo()); + viewBinding.header.butSubscribe.setOnClickListener(view -> { + DBWriter.setFeedState(getContext(), feed, Feed.STATE_SUBSCRIBED); + MainActivityStarter mainActivityStarter = new MainActivityStarter(getContext()); + mainActivityStarter.withOpenFeed(feed.getId()); + getActivity().finish(); + startActivity(mainActivityStarter.getIntent()); + }); viewBinding.header.butShowSettings.setOnClickListener(v -> { if (feed != null) { FeedSettingsFragment fragment = FeedSettingsFragment.newInstance(feed); @@ -495,7 +520,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem viewBinding.header.butFilter.setOnClickListener(v -> FeedItemFilterDialog.newInstance(feed).show(getChildFragmentManager(), null)); viewBinding.header.txtvFailure.setOnClickListener(v -> showErrorDetails()); - headerCreated = true; } private void showErrorDetails() { @@ -516,9 +540,18 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem } private void showFeedInfo() { - if (feed != null) { - FeedInfoFragment fragment = FeedInfoFragment.newInstance(feed); + if (feed == null) { + return; + } + FeedInfoFragment fragment = FeedInfoFragment.newInstance(feed); + if (getActivity() instanceof MainActivity) { ((MainActivity) getActivity()).loadChildFragment(fragment, TransitionEffect.SLIDE); + } else { + getActivity().getSupportFragmentManager() + .beginTransaction() + .replace(R.id.fragmentContainer, fragment, "Info") + .addToBackStack("Info") + .commitAllowingStateLoss(); } } @@ -625,7 +658,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem } private class FeedItemListAdapter extends EpisodeItemListAdapter { - public FeedItemListAdapter(MainActivity mainActivity) { + public FeedItemListAdapter(FragmentActivity mainActivity) { super(mainActivity); } @@ -648,7 +681,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); - if (!inActionMode()) { + if (!inActionMode() && feed.getState() == Feed.STATE_SUBSCRIBED) { menu.findItem(R.id.multi_select).setVisible(true); } MenuItemUtils.setOnClickListeners(menu, FeedItemlistFragment.this::onContextItemSelected); diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/home/sections/DownloadsSection.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/home/sections/DownloadsSection.java index cc3288e2f..06de7803a 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/home/sections/DownloadsSection.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/home/sections/DownloadsSection.java @@ -39,6 +39,8 @@ import java.util.List; public class DownloadsSection extends HomeSection { public static final String TAG = "DownloadsSection"; private static final int NUM_EPISODES = 2; + private static final FeedItemFilter FILTER_DOWNLOADED = new FeedItemFilter( + FeedItemFilter.DOWNLOADED, FeedItemFilter.INCLUDE_NOT_SUBSCRIBED); private EpisodeItemListAdapter adapter; private List<FeedItem> items; private Disposable disposable; @@ -52,7 +54,7 @@ public class DownloadsSection extends HomeSection { viewBinding.recyclerView.setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER); viewBinding.recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), RecyclerView.VERTICAL, false)); viewBinding.recyclerView.setRecycledViewPool(((MainActivity) requireActivity()).getRecycledViewPool()); - adapter = new EpisodeItemListAdapter((MainActivity) requireActivity()) { + adapter = new EpisodeItemListAdapter(requireActivity()) { @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); @@ -124,8 +126,7 @@ public class DownloadsSection extends HomeSection { disposable.dispose(); } SortOrder sortOrder = UserPreferences.getDownloadsSortedOrder(); - disposable = Observable.fromCallable(() -> DBReader.getEpisodes(0, NUM_EPISODES, - new FeedItemFilter(FeedItemFilter.DOWNLOADED), sortOrder)) + disposable = Observable.fromCallable(() -> DBReader.getEpisodes(0, NUM_EPISODES, FILTER_DOWNLOADED, sortOrder)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(downloads -> { diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/onlinefeedview/FeedItemlistDescriptionAdapter.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/onlinefeedview/FeedItemlistDescriptionAdapter.java deleted file mode 100644 index f46d786cf..000000000 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/onlinefeedview/FeedItemlistDescriptionAdapter.java +++ /dev/null @@ -1,112 +0,0 @@ -package de.danoeh.antennapod.ui.screen.onlinefeedview; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.model.playback.MediaType; -import de.danoeh.antennapod.net.common.NetworkUtils; -import de.danoeh.antennapod.model.playback.RemoteMedia; -import de.danoeh.antennapod.model.feed.FeedItem; -import de.danoeh.antennapod.playback.service.PlaybackService; -import de.danoeh.antennapod.playback.service.PlaybackServiceStarter; -import de.danoeh.antennapod.ui.common.DateFormatter; -import de.danoeh.antennapod.model.playback.Playable; -import de.danoeh.antennapod.ui.cleaner.HtmlToPlainText; -import de.danoeh.antennapod.ui.StreamingConfirmationDialog; - -import java.util.List; - -/** - * List adapter for showing a list of FeedItems with their title and description. - */ -public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> { - private static final int MAX_LINES_COLLAPSED = 2; - - public FeedItemlistDescriptionAdapter(Context context, int resource, List<FeedItem> objects) { - super(context, resource, objects); - } - - @NonNull - @Override - public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - Holder holder; - - FeedItem item = getItem(position); - - // Inflate layout - if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(R.layout.itemdescription_listitem, parent, false); - holder.title = convertView.findViewById(R.id.txtvTitle); - holder.pubDate = convertView.findViewById(R.id.txtvPubDate); - holder.description = convertView.findViewById(R.id.txtvDescription); - holder.preview = convertView.findViewById(R.id.butPreview); - - convertView.setTag(holder); - } else { - holder = (Holder) convertView.getTag(); - } - - holder.title.setText(item.getTitle()); - holder.pubDate.setText(DateFormatter.formatAbbrev(getContext(), item.getPubDate())); - if (item.getDescription() != null) { - String description = HtmlToPlainText.getPlainText(item.getDescription()) - .replaceAll("\n", " ") - .replaceAll("\\s+", " ") - .trim(); - holder.description.setText(description); - holder.description.setMaxLines(MAX_LINES_COLLAPSED); - } - holder.description.setTag(Boolean.FALSE); // not expanded - holder.preview.setVisibility(View.GONE); - holder.preview.setOnClickListener(v -> { - if (item.getMedia() == null) { - return; - } - Playable playable = new RemoteMedia(item); - if (!NetworkUtils.isStreamingAllowed()) { - new StreamingConfirmationDialog(getContext(), playable).show(); - return; - } - - new PlaybackServiceStarter(getContext(), playable) - .callEvenIfRunning(true) - .start(); - - if (playable.getMediaType() == MediaType.VIDEO) { - getContext().startActivity(PlaybackService.getPlayerActivityIntent(getContext(), playable)); - } - }); - convertView.setOnClickListener(v -> { - if (holder.description.getTag() == Boolean.TRUE) { - holder.description.setMaxLines(MAX_LINES_COLLAPSED); - holder.preview.setVisibility(View.GONE); - holder.description.setTag(Boolean.FALSE); - } else { - holder.description.setMaxLines(30); - holder.description.setTag(Boolean.TRUE); - - holder.preview.setVisibility(item.getMedia() != null ? View.VISIBLE : View.GONE); - holder.preview.setText(R.string.preview_episode); - } - }); - return convertView; - } - - static class Holder { - TextView title; - TextView pubDate; - TextView description; - Button preview; - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/onlinefeedview/OnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/onlinefeedview/OnlineFeedViewActivity.java index 7b8558c3d..be601027c 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/onlinefeedview/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/onlinefeedview/OnlineFeedViewActivity.java @@ -4,8 +4,6 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.LightingColorFilter; import android.os.Bundle; import android.text.Spannable; import android.text.SpannableString; @@ -13,68 +11,45 @@ import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.util.Log; import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; import androidx.appcompat.app.AppCompatActivity; -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; -import com.google.android.material.snackbar.Snackbar; - +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.net.download.service.feed.remote.Downloader; -import de.danoeh.antennapod.net.download.service.feed.remote.HttpDownloader; -import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; -import de.danoeh.antennapod.ui.common.ThemeSwitcher; -import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequestCreator; -import de.danoeh.antennapod.net.discovery.FeedUrlNotFoundException; -import de.danoeh.antennapod.storage.database.FeedDatabaseWriter; -import de.danoeh.antennapod.playback.service.PlaybackServiceInterface; -import de.danoeh.antennapod.ui.screen.download.DownloadErrorLabel; import de.danoeh.antennapod.databinding.EditTextDialogBinding; -import de.danoeh.antennapod.databinding.OnlinefeedviewHeaderBinding; -import de.danoeh.antennapod.event.EpisodeDownloadEvent; -import de.danoeh.antennapod.event.FeedListUpdateEvent; -import de.danoeh.antennapod.event.PlayerStatusEvent; -import de.danoeh.antennapod.storage.preferences.PlaybackPreferences; -import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface; -import de.danoeh.antennapod.storage.preferences.UserPreferences; +import de.danoeh.antennapod.databinding.OnlinefeedviewActivityBinding; +import de.danoeh.antennapod.model.download.DownloadError; import de.danoeh.antennapod.model.download.DownloadRequest; import de.danoeh.antennapod.model.download.DownloadResult; -import de.danoeh.antennapod.storage.database.DBReader; -import de.danoeh.antennapod.storage.database.DBWriter; +import de.danoeh.antennapod.model.feed.Feed; +import de.danoeh.antennapod.net.common.UrlChecker; import de.danoeh.antennapod.net.discovery.CombinedSearcher; +import de.danoeh.antennapod.net.discovery.FeedUrlNotFoundException; import de.danoeh.antennapod.net.discovery.PodcastSearchResult; import de.danoeh.antennapod.net.discovery.PodcastSearcherRegistry; +import de.danoeh.antennapod.net.download.service.feed.remote.Downloader; +import de.danoeh.antennapod.net.download.service.feed.remote.HttpDownloader; +import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequestCreator; import de.danoeh.antennapod.parser.feed.FeedHandler; import de.danoeh.antennapod.parser.feed.FeedHandlerResult; -import de.danoeh.antennapod.model.download.DownloadError; -import de.danoeh.antennapod.ui.common.IntentUtils; -import de.danoeh.antennapod.net.common.UrlChecker; -import de.danoeh.antennapod.ui.cleaner.HtmlToPlainText; -import de.danoeh.antennapod.databinding.OnlinefeedviewActivityBinding; -import de.danoeh.antennapod.model.feed.Feed; -import de.danoeh.antennapod.model.feed.FeedPreferences; -import de.danoeh.antennapod.model.playback.RemoteMedia; import de.danoeh.antennapod.parser.feed.UnsupportedFeedtypeException; +import de.danoeh.antennapod.storage.database.DBReader; +import de.danoeh.antennapod.storage.database.DBWriter; +import de.danoeh.antennapod.storage.database.FeedDatabaseWriter; +import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; +import de.danoeh.antennapod.ui.common.ThemeSwitcher; import de.danoeh.antennapod.ui.common.ThemeUtils; -import de.danoeh.antennapod.ui.glide.FastBlurTransformation; import de.danoeh.antennapod.ui.preferences.screen.synchronization.AuthenticationDialog; +import de.danoeh.antennapod.ui.screen.download.DownloadErrorLabel; +import de.danoeh.antennapod.ui.screen.feed.FeedItemlistFragment; import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; -import io.reactivex.observers.DisposableMaybeObserver; import io.reactivex.schedulers.Schedulers; -import org.apache.commons.lang3.StringUtils; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; import java.io.File; import java.io.IOException; @@ -95,30 +70,16 @@ import static de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStart * and the activity will finish as soon as the error dialog is closed. */ public class OnlineFeedViewActivity extends AppCompatActivity { - - private static final int RESULT_ERROR = 2; private static final String TAG = "OnlineFeedViewActivity"; - private static final String PREFS = "OnlineFeedViewActivityPreferences"; - private static final String PREF_LAST_AUTO_DOWNLOAD = "lastAutoDownload"; - private static final int DESCRIPTION_MAX_LINES_COLLAPSED = 4; - - private volatile List<Feed> feeds; private String selectedDownloadUrl; private Downloader downloader; private String username = null; private String password = null; - private boolean isPaused; - private boolean didPressSubscribe = false; private boolean isFeedFoundBySearch = false; - private Dialog dialog; - private Disposable download; private Disposable parser; - private Disposable updater; - - private OnlinefeedviewHeaderBinding headerBinding; private OnlinefeedviewActivityBinding viewBinding; @Override @@ -128,12 +89,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity { viewBinding = OnlinefeedviewActivityBinding.inflate(getLayoutInflater()); setContentView(viewBinding.getRoot()); - viewBinding.transparentBackground.setOnClickListener(v -> finish()); - viewBinding.closeButton.setOnClickListener(view -> finish()); viewBinding.card.setOnClickListener(null); viewBinding.card.setCardBackgroundColor(ThemeUtils.getColorFromAttr(this, R.attr.colorSurface)); - headerBinding = OnlinefeedviewHeaderBinding.inflate(getLayoutInflater()); String feedUrl = null; if (getIntent().hasExtra(ARG_FEEDURL)) { @@ -149,7 +107,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity { showNoPodcastFoundError(); } else { Log.d(TAG, "Activity was started with url " + feedUrl); - setLoadingLayout(); // Remove subscribeonandroid.com from feed URL in order to subscribe to the actual feed URL if (feedUrl.contains("subscribeonandroid.com")) { feedUrl = feedUrl.replaceFirst("((www.)?(subscribeonandroid.com/))", ""); @@ -167,33 +124,20 @@ public class OnlineFeedViewActivity extends AppCompatActivity { .setNeutralButton(android.R.string.ok, (dialog, which) -> finish()) .setTitle(R.string.error_label) .setMessage(R.string.null_value_podcast_error) - .setOnDismissListener(dialog1 -> { - setResult(RESULT_ERROR); - finish(); - }) + .setOnDismissListener(dialog1 -> finish()) .show()); } - /** - * Displays a progress indicator. - */ - private void setLoadingLayout() { - viewBinding.progressBar.setVisibility(View.VISIBLE); - viewBinding.feedDisplayContainer.setVisibility(View.GONE); - } - @Override protected void onStart() { super.onStart(); isPaused = false; - EventBus.getDefault().register(this); } @Override protected void onStop() { super.onStop(); isPaused = true; - EventBus.getDefault().unregister(this); if (downloader != null && !downloader.isFinished()) { downloader.cancel(); } @@ -205,9 +149,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity { @Override public void onDestroy() { super.onDestroy(); - if(updater != null) { - updater.dispose(); - } if(download != null) { download.dispose(); } @@ -239,7 +180,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { download = PodcastSearcherRegistry.lookupUrl(url) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) - .subscribe(this::startFeedDownload, + .subscribe(this::downloadIfNotAlreadySubscribed, error -> { if (error instanceof FeedUrlNotFoundException) { tryToRetrieveFeedUrlBySearch((FeedUrlNotFoundException) error); @@ -256,7 +197,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { if (url != null) { Log.d(TAG, "Successfully retrieve feed url"); isFeedFoundBySearch = true; - startFeedDownload(url); + downloadIfNotAlreadySubscribed(url); } else { showNoPodcastFoundError(); Log.d(TAG, "Failed to retrieve feed url"); @@ -277,6 +218,28 @@ public class OnlineFeedViewActivity extends AppCompatActivity { return null; } + private Feed downloadIfNotAlreadySubscribed(String url) { + download = Maybe.fromCallable(() -> { + List<Feed> feeds = DBReader.getFeedList(); + for (Feed f : feeds) { + if (f.getDownloadUrl().equals(url)) { + return f; + } + } + return null; + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(subscribedFeed -> { + if (subscribedFeed.getState() == Feed.STATE_SUBSCRIBED) { + openFeed(subscribedFeed.getId()); + } else { + showFeedFragment(subscribedFeed.getId()); + } + }, error -> Log.e(TAG, Log.getStackTraceString(error)), () -> startFeedDownload(url)); + return null; + } + private void startFeedDownload(String url) { Log.d(TAG, "Starting feed download"); selectedDownloadUrl = UrlChecker.prepareUrl(url); @@ -286,7 +249,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity { .build(); download = Observable.fromCallable(() -> { - feeds = DBReader.getFeedList(); downloader = new HttpDownloader(request); downloader.call(); return downloader.getResult(); @@ -315,46 +277,25 @@ public class OnlineFeedViewActivity extends AppCompatActivity { } } - @Subscribe - public void onFeedListChanged(FeedListUpdateEvent event) { - updater = Observable.fromCallable(DBReader::getFeedList) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - feeds -> { - OnlineFeedViewActivity.this.feeds = feeds; - handleUpdatedFeedStatus(); - }, error -> Log.e(TAG, Log.getStackTraceString(error)) - ); - } - - @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) - public void onEventMainThread(EpisodeDownloadEvent event) { - handleUpdatedFeedStatus(); - } - private void parseFeed(String destination) { Log.d(TAG, "Parsing feed"); - parser = Maybe.fromCallable(() -> doParseFeed(destination)) - .subscribeOn(Schedulers.computation()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeWith(new DisposableMaybeObserver<FeedHandlerResult>() { - @Override - public void onSuccess(@NonNull FeedHandlerResult result) { - showFeedInformation(result.feed, result.alternateFeedUrls); - } - - @Override - public void onComplete() { - // Ignore null result: We showed the discovery dialog. - } - - @Override - public void onError(@NonNull Throwable error) { - showErrorDialog(error.getMessage(), ""); - Log.d(TAG, "Feed parser exception: " + Log.getStackTraceString(error)); - } - }); + parser = Observable.fromCallable(() -> { + FeedHandlerResult handlerResult = doParseFeed(destination); + Feed feed = handlerResult.feed; + feed.setState(Feed.STATE_NOT_SUBSCRIBED); + feed.setLastRefreshAttempt(System.currentTimeMillis()); + FeedDatabaseWriter.updateFeed(this, feed, false); + Feed feedFromDb = DBReader.getFeed(feed.getId(), false, 0, Integer.MAX_VALUE); + feedFromDb.getPreferences().setKeepUpdated(false); + DBWriter.setFeedPreferences(feedFromDb.getPreferences()); + return feed.getId(); + }) + .subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::showFeedFragment, error -> { + error.printStackTrace(); + showErrorDialog(error.getMessage(), ""); + }); } /** @@ -392,123 +333,23 @@ public class OnlineFeedViewActivity extends AppCompatActivity { } } - /** - * Called when feed parsed successfully. - * This method is executed on the GUI thread. - */ - private void showFeedInformation(final Feed feed, Map<String, String> alternateFeedUrls) { - viewBinding.progressBar.setVisibility(View.GONE); - viewBinding.feedDisplayContainer.setVisibility(View.VISIBLE); + private void showFeedFragment(long id) { if (isFeedFoundBySearch) { - int resId = R.string.no_feed_url_podcast_found_by_search; - Snackbar.make(findViewById(android.R.id.content), resId, Snackbar.LENGTH_LONG).show(); - } - - viewBinding.backgroundImage.setColorFilter(new LightingColorFilter(0xff828282, 0x000000)); - - viewBinding.listView.addHeaderView(headerBinding.getRoot()); - viewBinding.listView.setSelector(android.R.color.transparent); - viewBinding.listView.setAdapter(new FeedItemlistDescriptionAdapter(this, 0, feed.getItems())); - - if (StringUtils.isNotBlank(feed.getImageUrl())) { - Glide.with(this) - .load(feed.getImageUrl()) - .apply(new RequestOptions() - .placeholder(R.color.light_gray) - .error(R.color.light_gray) - .fitCenter() - .dontAnimate()) - .into(viewBinding.coverImage); - Glide.with(this) - .load(feed.getImageUrl()) - .apply(new RequestOptions() - .placeholder(R.color.image_readability_tint) - .error(R.color.image_readability_tint) - .transform(new FastBlurTransformation()) - .dontAnimate()) - .into(viewBinding.backgroundImage); - } - - viewBinding.titleLabel.setText(feed.getTitle()); - viewBinding.authorLabel.setText(feed.getAuthor()); - headerBinding.txtvDescription.setText(HtmlToPlainText.getPlainText(feed.getDescription())); - - viewBinding.subscribeButton.setOnClickListener(v -> { - if (feedInFeedlist()) { - openFeed(); - } else { - FeedDatabaseWriter.updateFeed(this, feed, false); - didPressSubscribe = true; - handleUpdatedFeedStatus(); - } - }); - - viewBinding.stopPreviewButton.setOnClickListener(v -> { - PlaybackPreferences.writeNoMediaPlaying(); - IntentUtils.sendLocalBroadcast(this, PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE); - }); - - if (UserPreferences.isEnableAutodownload()) { - SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE); - viewBinding.autoDownloadCheckBox.setChecked(preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true)); + Toast.makeText(this, R.string.no_feed_url_podcast_found_by_search, Toast.LENGTH_LONG).show(); } - headerBinding.txtvDescription.setMaxLines(DESCRIPTION_MAX_LINES_COLLAPSED); - headerBinding.txtvDescription.setOnClickListener(v -> { - if (headerBinding.txtvDescription.getMaxLines() > DESCRIPTION_MAX_LINES_COLLAPSED) { - headerBinding.txtvDescription.setMaxLines(DESCRIPTION_MAX_LINES_COLLAPSED); - } else { - headerBinding.txtvDescription.setMaxLines(2000); - } - }); - - if (alternateFeedUrls.isEmpty()) { - viewBinding.alternateUrlsSpinner.setVisibility(View.GONE); - } else { - viewBinding.alternateUrlsSpinner.setVisibility(View.VISIBLE); - - final List<String> alternateUrlsList = new ArrayList<>(); - final List<String> alternateUrlsTitleList = new ArrayList<>(); - - alternateUrlsList.add(feed.getDownloadUrl()); - alternateUrlsTitleList.add(feed.getTitle()); - - - alternateUrlsList.addAll(alternateFeedUrls.keySet()); - for (String url : alternateFeedUrls.keySet()) { - alternateUrlsTitleList.add(alternateFeedUrls.get(url)); - } - - ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, - R.layout.alternate_urls_item, alternateUrlsTitleList) { - @Override - public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - // reusing the old view causes a visual bug on Android <= 10 - return super.getDropDownView(position, null, parent); - } - }; - - adapter.setDropDownViewResource(R.layout.alternate_urls_dropdown_item); - viewBinding.alternateUrlsSpinner.setAdapter(adapter); - viewBinding.alternateUrlsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - selectedDownloadUrl = alternateUrlsList.get(position); - } - - @Override - public void onNothingSelected(AdapterView<?> parent) { - - } - }); - } - handleUpdatedFeedStatus(); + viewBinding.progressBar.setVisibility(View.GONE); + FeedItemlistFragment fragment = FeedItemlistFragment.newInstance(id); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.fragmentContainer, fragment, FeedItemlistFragment.TAG) + .commitAllowingStateLoss(); } - private void openFeed() { + private void openFeed(long feedId) { // feed.getId() is always 0, we have to retrieve the id from the feed list from the database MainActivityStarter mainActivityStarter = new MainActivityStarter(this); - mainActivityStarter.withOpenFeed(getFeedId()); + mainActivityStarter.withOpenFeed(feedId); if (getIntent().getBooleanExtra(ARG_STARTED_FROM_SEARCH, false)) { mainActivityStarter.withAddToBackStack(); } @@ -516,60 +357,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity { startActivity(mainActivityStarter.getIntent()); } - private void handleUpdatedFeedStatus() { - if (DownloadServiceInterface.get().isDownloadingEpisode(selectedDownloadUrl)) { - viewBinding.subscribeButton.setEnabled(false); - viewBinding.subscribeButton.setText(R.string.subscribing_label); - } else if (feedInFeedlist()) { - viewBinding.subscribeButton.setEnabled(true); - viewBinding.subscribeButton.setText(R.string.open_podcast); - if (didPressSubscribe) { - didPressSubscribe = false; - - Feed feed1 = DBReader.getFeed(getFeedId(), false, 0, 0); - FeedPreferences feedPreferences = feed1.getPreferences(); - if (UserPreferences.isEnableAutodownload()) { - boolean autoDownload = viewBinding.autoDownloadCheckBox.isChecked(); - feedPreferences.setAutoDownload(autoDownload); - - SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload); - editor.apply(); - } - if (username != null) { - feedPreferences.setUsername(username); - feedPreferences.setPassword(password); - } - DBWriter.setFeedPreferences(feedPreferences); - - openFeed(); - } - } else { - viewBinding.subscribeButton.setEnabled(true); - viewBinding.subscribeButton.setText(R.string.subscribe_label); - if (UserPreferences.isEnableAutodownload()) { - viewBinding.autoDownloadCheckBox.setVisibility(View.VISIBLE); - } - } - } - - private boolean feedInFeedlist() { - return getFeedId() != 0; - } - - private long getFeedId() { - if (feeds == null) { - return 0; - } - for (Feed f : feeds) { - if (f.getDownloadUrl().equals(selectedDownloadUrl)) { - return f.getId(); - } - } - return 0; - } - @UiThread private void showErrorDialog(String errorMsg, String details) { if (!isFinishing() && !isPaused) { @@ -589,7 +376,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity { builder.setNeutralButton(R.string.edit_url_menu, (dialog, which) -> editUrl()); } builder.setOnCancelListener(dialog -> { - setResult(RESULT_ERROR); finish(); }); if (dialog != null && dialog.isShowing()) { @@ -608,24 +394,15 @@ public class OnlineFeedViewActivity extends AppCompatActivity { } builder.setView(dialogBinding.getRoot()); builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { - setLoadingLayout(); lookupUrlAndDownload(dialogBinding.urlEditText.getText().toString()); }); builder.setNegativeButton(R.string.cancel_label, (dialog1, which) -> dialog1.cancel()); builder.setOnCancelListener(dialog1 -> { - setResult(RESULT_ERROR); finish(); }); builder.show(); } - @Subscribe(threadMode = ThreadMode.MAIN) - public void playbackStateChanged(PlayerStatusEvent event) { - boolean isPlayingPreview = - PlaybackPreferences.getCurrentlyPlayingMediaType() == RemoteMedia.PLAYABLE_TYPE_REMOTE_MEDIA; - viewBinding.stopPreviewButton.setVisibility(isPlayingPreview ? View.VISIBLE : View.GONE); - } - /** * * @return true if a FeedDiscoveryDialog is shown, false otherwise (e.g., due to no feed found). @@ -657,7 +434,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { if (urls.size() == 1) { // Skip dialog and display the item directly resetIntent(urls.get(0)); - startFeedDownload(urls.get(0)); + downloadIfNotAlreadySubscribed(urls.get(0)); return true; } @@ -667,7 +444,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { String selectedUrl = urls.get(which); dialog.dismiss(); resetIntent(selectedUrl); - startFeedDownload(selectedUrl); + downloadIfNotAlreadySubscribed(selectedUrl); }; MaterialAlertDialogBuilder ab = new MaterialAlertDialogBuilder(OnlineFeedViewActivity.this) @@ -704,7 +481,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { protected void onConfirmed(String username, String password) { OnlineFeedViewActivity.this.username = username; OnlineFeedViewActivity.this.password = password; - startFeedDownload(feedUrl); + downloadIfNotAlreadySubscribed(feedUrl); } } diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/playback/audio/AudioPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/playback/audio/AudioPlayerFragment.java index e66ed6d3e..7aa9da503 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/playback/audio/AudioPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/playback/audio/AudioPlayerFragment.java @@ -25,8 +25,10 @@ import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.elevation.SurfaceColors; +import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.playback.service.PlaybackController; import de.danoeh.antennapod.ui.appstartintent.MediaButtonStarter; +import de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter; import de.danoeh.antennapod.ui.chapters.ChapterUtils; import de.danoeh.antennapod.ui.episodes.PlaybackSpeedUtils; import de.danoeh.antennapod.ui.episodes.TimeSpeedConverter; @@ -497,14 +499,25 @@ public class AudioPlayerFragment extends Fragment implements return true; } else if (itemId == R.id.open_feed_item) { if (feedItem != null) { - Intent intent = MainActivity.getIntentToOpenFeed(getContext(), feedItem.getFeedId()); - startActivity(intent); + openFeed(feedItem.getFeed()); } return true; } return false; } + private void openFeed(Feed feed) { + if (feed == null) { + return; + } + if (feed.getState() == Feed.STATE_SUBSCRIBED) { + Intent intent = MainActivity.getIntentToOpenFeed(getContext(), feed.getId()); + startActivity(intent); + } else { + startActivity(new OnlineFeedviewActivityStarter(getContext(), feed.getDownloadUrl()).getIntent()); + } + } + public void fadePlayerToToolbar(float slideOffset) { float playerFadeProgress = Math.max(0.0f, Math.min(0.2f, slideOffset - 0.2f)) / 0.2f; View player = getView().findViewById(R.id.playerFragment); diff --git a/app/src/main/java/de/danoeh/antennapod/ui/screen/playback/audio/CoverFragment.java b/app/src/main/java/de/danoeh/antennapod/ui/screen/playback/audio/CoverFragment.java index 073d5b0be..d4931380e 100644 --- a/app/src/main/java/de/danoeh/antennapod/ui/screen/playback/audio/CoverFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/ui/screen/playback/audio/CoverFragment.java @@ -32,6 +32,8 @@ import com.bumptech.glide.request.RequestOptions; import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.model.feed.Feed; +import de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter; import de.danoeh.antennapod.ui.chapters.ChapterUtils; import de.danoeh.antennapod.ui.screen.chapter.ChaptersFragment; import de.danoeh.antennapod.playback.service.PlaybackController; @@ -122,9 +124,7 @@ public class CoverFragment extends Fragment { + "\u00A0" + StringUtils.replace(StringUtils.stripToEmpty(pubDateStr), " ", "\u00A0")); if (media instanceof FeedMedia) { - Intent openFeed = MainActivity.getIntentToOpenFeed(requireContext(), - ((FeedMedia) media).getItem().getFeedId()); - viewBinding.txtvPodcastTitle.setOnClickListener(v -> startActivity(openFeed)); + viewBinding.txtvPodcastTitle.setOnClickListener(v -> openFeed(((FeedMedia) media).getItem().getFeed())); } else { viewBinding.txtvPodcastTitle.setOnClickListener(null); } @@ -164,6 +164,18 @@ public class CoverFragment extends Fragment { updateChapterControlVisibility(); } + private void openFeed(Feed feed) { + if (feed == null) { + return; + } + if (feed.getState() == Feed.STATE_SUBSCRIBED) { + Intent intent = MainActivity.getIntentToOpenFeed(getContext(), feed.getId()); + startActivity(intent); + } else { + startActivity(new OnlineFeedviewActivityStarter(getContext(), feed.getDownloadUrl()).getIntent()); + } + } + private void updateChapterControlVisibility() { boolean chapterControlVisible = false; if (media.getChapters() != null) { diff --git a/app/src/main/res/layout/alternate_urls_dropdown_item.xml b/app/src/main/res/layout/alternate_urls_dropdown_item.xml deleted file mode 100644 index 3c4dcc7fc..000000000 --- a/app/src/main/res/layout/alternate_urls_dropdown_item.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<CheckedTextView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@android:id/text1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingVertical="8dp" - style="?android:attr/spinnerDropDownItemStyle" /> diff --git a/app/src/main/res/layout/alternate_urls_item.xml b/app/src/main/res/layout/alternate_urls_item.xml deleted file mode 100644 index 54e05b49d..000000000 --- a/app/src/main/res/layout/alternate_urls_item.xml +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<TextView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@android:id/text1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:maxLines="3" - android:textAlignment="inherit" - style="?android:attr/spinnerItemStyle" /> diff --git a/app/src/main/res/layout/feedinfo.xml b/app/src/main/res/layout/feedinfo.xml index d1acc1e37..7b7937b9d 100644 --- a/app/src/main/res/layout/feedinfo.xml +++ b/app/src/main/res/layout/feedinfo.xml @@ -65,13 +65,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + android:paddingTop="16dp" android:paddingHorizontal="@dimen/additional_horizontal_spacing"> <TextView android:id="@+id/statisticsHeadingLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="16dp" android:textSize="18sp" android:layout_marginBottom="8dp" android:text="@string/statistics_label" @@ -89,6 +89,7 @@ android:layout_height="wrap_content" android:minWidth="0dp" android:minHeight="0dp" + android:layout_marginBottom="16dp" android:text="@string/statistics_view_all" style="@style/Widget.MaterialComponents.Button.TextButton" /> @@ -96,7 +97,6 @@ android:id="@+id/supportHeadingLabel" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="16dp" android:layout_marginBottom="4dp" android:text="@string/support_funding_label" android:textColor="?android:attr/textColorPrimary" @@ -119,7 +119,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" - android:layout_marginTop="16dp" android:layout_marginBottom="4dp" android:text="@string/description_label" android:textColor="?android:attr/textColorPrimary" @@ -129,7 +128,6 @@ android:id="@+id/descriptionLabel" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/design_time_lorem_ipsum" android:textIsSelectable="true" tools:background="@android:color/holo_green_dark" /> diff --git a/app/src/main/res/layout/feeditem_fragment.xml b/app/src/main/res/layout/feeditem_fragment.xml index 37db9067c..df95b8c15 100644 --- a/app/src/main/res/layout/feeditem_fragment.xml +++ b/app/src/main/res/layout/feeditem_fragment.xml @@ -175,6 +175,20 @@ </LinearLayout> <TextView + android:id="@+id/nonSubscribedWarningLabel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginVertical="8dp" + android:layout_marginBottom="8dp" + android:paddingHorizontal="16dp" + android:paddingVertical="8dp" + android:background="@drawable/bg_message_info" + android:textColor="?attr/colorAccent" + android:visibility="gone" + android:foreground="?attr/selectableItemBackground" + android:text="@string/state_deleted_not_subscribed" /> + + <TextView android:id="@+id/noMediaLabel" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/app/src/main/res/layout/feeditemlist_header.xml b/app/src/main/res/layout/feeditemlist_header.xml index d28f7dae8..f44a3233e 100644 --- a/app/src/main/res/layout/feeditemlist_header.xml +++ b/app/src/main/res/layout/feeditemlist_header.xml @@ -28,6 +28,14 @@ android:layout_width="148dp" android:layout_height="match_parent" /> + <Button + android:id="@+id/butSubscribe" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/subscribe_label" + android:visibility="gone" + tools:visibility="visible" /> + <ImageButton android:id="@+id/butShowInfo" android:layout_width="40dp" @@ -187,4 +195,52 @@ </LinearLayout> + <LinearLayout + android:id="@+id/descriptionContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingHorizontal="16dp" + android:background="?android:attr/colorBackground" + android:orientation="vertical" + android:visibility="gone"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:layout_marginTop="16dp" + android:paddingHorizontal="16dp" + android:paddingVertical="8dp" + android:background="@drawable/bg_message_info" + android:textColor="?attr/colorAccent" + android:text="@string/state_deleted_not_subscribed" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:text="@string/description_label" + style="@style/TextAppearance.Material3.TitleMedium" /> + + <TextView + android:id="@+id/headerDescriptionLabel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:lineHeight="20dp" + android:maxLines="3" + android:layout_marginBottom="16dp" + android:background="?android:attr/selectableItemBackground" + style="@style/AntennaPod.TextView.ListItemBody" + tools:text="@string/design_time_lorem_ipsum" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/preview_episodes" + android:layout_marginBottom="8dp" + style="@style/TextAppearance.Material3.TitleMedium" /> + + </LinearLayout> + </LinearLayout> diff --git a/app/src/main/res/layout/itemdescription_listitem.xml b/app/src/main/res/layout/itemdescription_listitem.xml deleted file mode 100644 index c3f8c24b8..000000000 --- a/app/src/main/res/layout/itemdescription_listitem.xml +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingHorizontal="20dp" - android:paddingVertical="16dp"> - - <TextView - android:id="@+id/txtvPubDate" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:ellipsize="end" - android:lines="1" - style="@android:style/TextAppearance.Small" - tools:text="22 Jan 2016" - tools:background="@android:color/holo_green_dark" /> - - <TextView - android:id="@+id/txtvTitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginVertical="12dp" - android:ellipsize="end" - android:maxLines="2" - style="@style/AntennaPod.TextView.ListItemPrimaryTitle" - tools:text="Feed item title" - tools:background="@android:color/holo_green_dark" /> - - <TextView - android:id="@+id/txtvDescription" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:ellipsize="end" - android:maxLines="2" - style="@style/AntennaPod.TextView.ListItemBody" - tools:text="Feed item description" - tools:background="@android:color/holo_green_dark" /> - - <Button - android:id="@+id/butPreview" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:text="@string/preview_episode" - android:layout_gravity="end|right" - style="@style/Widget.MaterialComponents.Button.OutlinedButton" /> - -</LinearLayout> diff --git a/app/src/main/res/layout/onlinefeedview_activity.xml b/app/src/main/res/layout/onlinefeedview_activity.xml index 88b87da1a..a121c9e0e 100644 --- a/app/src/main/res/layout/onlinefeedview_activity.xml +++ b/app/src/main/res/layout/onlinefeedview_activity.xml @@ -2,7 +2,6 @@ <LinearLayout 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:id="@+id/transparentBackground" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -11,169 +10,22 @@ android:id="@+id/card" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_margin="24dp" + android:layout_marginHorizontal="16dp" + android:layout_marginVertical="56dp" android:elevation="16dp" app:cardCornerRadius="8dp"> - <FrameLayout + <androidx.fragment.app.FragmentContainerView + android:id="@+id/fragmentContainer" android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <ProgressBar - android:id="@+id/progressBar" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - style="?android:attr/progressBarStyle" /> - - <LinearLayout - android:id="@+id/feed_display_container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <RelativeLayout - android:layout_width="match_parent" - android:layout_height="@dimen/feeditemlist_header_height" - android:layout_marginBottom="12dp" - android:background="@color/feed_image_bg"> - - <ImageView - android:id="@+id/backgroundImage" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="centerCrop" /> - - <ImageView - android:id="@+id/coverImage" - android:layout_width="@dimen/thumbnail_length_onlinefeedview" - android:layout_height="@dimen/thumbnail_length_onlinefeedview" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_alignParentTop="true" - android:layout_centerVertical="true" - android:layout_marginBottom="12dp" - android:layout_marginLeft="16dp" - android:layout_marginStart="16dp" - android:layout_marginTop="24dp" - android:background="@drawable/bg_rounded_corners" - android:clipToOutline="true" - android:importantForAccessibility="no" - tools:src="@tools:sample/avatars" /> - - <TextView - android:id="@+id/titleLabel" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:layout_marginBottom="8dp" - android:layout_marginLeft="16dp" - android:layout_marginStart="16dp" - android:layout_marginTop="24dp" - android:layout_marginRight="24dp" - android:layout_marginEnd="24dp" - android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" - android:layout_toRightOf="@id/coverImage" - android:layout_toEndOf="@id/coverImage" - android:ellipsize="end" - android:maxLines="2" - android:shadowColor="@color/black" - android:shadowRadius="3" - android:textColor="@color/white" - android:textSize="20sp" - android:textFontWeight="800" - tools:text="Podcast title" /> - - <TextView - android:id="@+id/author_label" - android:layout_width="0dip" - android:layout_height="wrap_content" - android:layout_below="@id/titleLabel" - android:layout_marginBottom="16dp" - android:layout_marginLeft="16dp" - android:layout_marginStart="16dp" - android:layout_marginRight="16dp" - android:layout_marginEnd="16dp" - android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" - android:layout_toRightOf="@id/coverImage" - android:layout_toEndOf="@id/coverImage" - android:ellipsize="end" - android:lines="1" - android:shadowColor="@color/black" - android:shadowRadius="3" - android:textColor="@color/white" - android:textSize="@dimen/text_size_small" - tools:text="Podcast author" /> - - <ImageButton - android:id="@+id/closeButton" - android:layout_width="16dp" - android:layout_height="16dp" - android:layout_alignParentRight="true" - android:layout_marginTop="12dp" - android:layout_marginRight="12dp" - android:background="@android:color/transparent" - android:src="@drawable/ic_close_white" /> - - </RelativeLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingHorizontal="16dp" - android:orientation="vertical"> - - <Spinner - android:id="@+id/alternate_urls_spinner" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:dropDownWidth="match_parent" - android:padding="8dp" - android:textColor="?android:attr/textColorPrimary" - android:textSize="@dimen/text_size_micro" /> - - <Button - android:id="@+id/subscribeButton" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="4dp" - android:text="@string/subscribe_label" /> - - <CheckBox - android:id="@+id/autoDownloadCheckBox" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="left" - android:checked="true" - android:layout_marginTop="8dp" - android:text="@string/auto_download_label" - android:visibility="gone" - tools:visibility="visible" /> - - <Button - android:id="@+id/stopPreviewButton" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="4dp" - android:text="@string/stop_preview" - android:visibility="gone" - tools:visibility="visible" /> - - </LinearLayout> - - <ListView - android:id="@+id/listView" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingTop="16dp" - android:clipToPadding="false" /> - - </LinearLayout> - - </FrameLayout> + android:layout_height="match_parent" /> + + <ProgressBar + android:id="@+id/progressBar" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + style="?android:attr/progressBarStyle" /> </androidx.cardview.widget.CardView> diff --git a/app/src/main/res/layout/onlinefeedview_header.xml b/app/src/main/res/layout/onlinefeedview_header.xml deleted file mode 100644 index 6d740c772..000000000 --- a/app/src/main/res/layout/onlinefeedview_header.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingLeft="20dp" - android:paddingRight="20dp" - android:layout_marginBottom="8dp"> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="8dp" - android:text="@string/description_label" - style="@style/TextAppearance.Material3.TitleMedium" /> - - <TextView - android:id="@+id/txtvDescription" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="16dp" - android:ellipsize="end" - android:lineHeight="20dp" - style="@style/AntennaPod.TextView.ListItemBody" - tools:text="@string/design_time_lorem_ipsum" /> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="16dp" - android:text="@string/episodes_label" - style="@style/TextAppearance.Material3.TitleMedium" /> - -</LinearLayout> diff --git a/model/src/main/java/de/danoeh/antennapod/model/feed/Feed.java b/model/src/main/java/de/danoeh/antennapod/model/feed/Feed.java index 15d256c24..634d3440d 100644 --- a/model/src/main/java/de/danoeh/antennapod/model/feed/Feed.java +++ b/model/src/main/java/de/danoeh/antennapod/model/feed/Feed.java @@ -15,6 +15,8 @@ import org.apache.commons.lang3.StringUtils; public class Feed { public static final int FEEDFILETYPE_FEED = 0; + public static final int STATE_SUBSCRIBED = 0; + public static final int STATE_NOT_SUBSCRIBED = 1; public static final String TYPE_RSS2 = "rss"; public static final String TYPE_ATOM1 = "atom"; public static final String PREFIX_LOCAL_FOLDER = "antennapod_local:"; @@ -101,6 +103,7 @@ public class Feed { */ @Nullable private SortOrder sortOrder; + private int state; /** * This constructor is used for restoring a feed from the database. @@ -109,7 +112,7 @@ public class Feed { String description, String paymentLinks, String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl, String downloadUrl, long lastRefreshAttempt, boolean paged, String nextPageLink, - String filter, @Nullable SortOrder sortOrder, boolean lastUpdateFailed) { + String filter, @Nullable SortOrder sortOrder, boolean lastUpdateFailed, int state) { this.localFileUrl = fileUrl; this.downloadUrl = downloadUrl; this.lastRefreshAttempt = lastRefreshAttempt; @@ -135,6 +138,7 @@ public class Feed { } setSortOrder(sortOrder); this.lastUpdateFailed = lastUpdateFailed; + this.state = state; } /** @@ -144,7 +148,7 @@ public class Feed { String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl, String downloadUrl, long lastRefreshAttempt) { this(id, lastModified, title, null, link, description, paymentLink, author, language, type, feedIdentifier, - imageUrl, fileUrl, downloadUrl, lastRefreshAttempt, false, null, null, null, false); + imageUrl, fileUrl, downloadUrl, lastRefreshAttempt, false, null, null, null, false, STATE_SUBSCRIBED); } /** @@ -468,4 +472,12 @@ public class Feed { public boolean isLocalFeed() { return downloadUrl.startsWith(PREFIX_LOCAL_FOLDER); } + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = state; + } } diff --git a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItemFilter.java b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItemFilter.java index 95a7a6110..1faeda012 100644 --- a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItemFilter.java +++ b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItemFilter.java @@ -24,6 +24,7 @@ public class FeedItemFilter implements Serializable { public final boolean showIsFavorite; public final boolean showNotFavorite; public final boolean showInHistory; + public final boolean includeNotSubscribed; public static final String PLAYED = "played"; public static final String UNPLAYED = "unplayed"; @@ -39,6 +40,7 @@ public class FeedItemFilter implements Serializable { public static final String DOWNLOADED = "downloaded"; public static final String NOT_DOWNLOADED = "not_downloaded"; public static final String IS_IN_HISTORY = "is_in_history"; + public static final String INCLUDE_NOT_SUBSCRIBED = "include_not_subscribed"; public static FeedItemFilter unfiltered() { return new FeedItemFilter(""); @@ -48,6 +50,10 @@ public class FeedItemFilter implements Serializable { this(TextUtils.split(properties, ",")); } + public FeedItemFilter(FeedItemFilter filter, String... additionalProperties) { + this(TextUtils.join(",", filter.getValues()) + "," + TextUtils.join(",", additionalProperties)); + } + public FeedItemFilter(String... properties) { this.properties = properties; @@ -66,6 +72,7 @@ public class FeedItemFilter implements Serializable { showNotFavorite = hasProperty(NOT_FAVORITE); showNew = hasProperty(NEW); showInHistory = hasProperty(IS_IN_HISTORY); + includeNotSubscribed = hasProperty(INCLUDE_NOT_SUBSCRIBED); } private boolean hasProperty(String property) { @@ -112,6 +119,9 @@ public class FeedItemFilter implements Serializable { } else if (showInHistory && item.getMedia() != null && item.getMedia().getPlaybackCompletionDate().getTime() == 0) { return false; + } else if (!includeNotSubscribed && item.getFeed() != null + && item.getFeed().getState() != Feed.STATE_SUBSCRIBED) { + return false; } return true; } diff --git a/model/src/main/java/de/danoeh/antennapod/model/feed/SubscriptionsFilter.java b/model/src/main/java/de/danoeh/antennapod/model/feed/SubscriptionsFilter.java index 434d474a7..2d584adef 100644 --- a/model/src/main/java/de/danoeh/antennapod/model/feed/SubscriptionsFilter.java +++ b/model/src/main/java/de/danoeh/antennapod/model/feed/SubscriptionsFilter.java @@ -12,6 +12,7 @@ public class SubscriptionsFilter { private final String[] properties; private boolean showIfCounterGreaterZero = false; + private boolean hideNonSubscribedFeeds = true; private boolean showAutoDownloadEnabled = false; private boolean showAutoDownloadDisabled = false; @@ -67,10 +68,6 @@ public class SubscriptionsFilter { * Run a list of feed items through the filter. */ public List<Feed> filter(List<Feed> items, Map<Long, Integer> feedCounters) { - if (properties.length == 0) { - return items; - } - List<Feed> result = new ArrayList<>(); for (Feed item : items) { @@ -95,6 +92,10 @@ public class SubscriptionsFilter { continue; } + if (hideNonSubscribedFeeds && item.getState() != Feed.STATE_SUBSCRIBED) { + continue; + } + // If the item reaches here, it meets all criteria (except counter > 0) result.add(item); } diff --git a/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/ItunesTopListLoader.java b/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/ItunesTopListLoader.java index 128aad593..c837a14ef 100644 --- a/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/ItunesTopListLoader.java +++ b/net/discovery/src/main/java/de/danoeh/antennapod/net/discovery/ItunesTopListLoader.java @@ -58,7 +58,8 @@ public class ItunesTopListLoader { List<PodcastSearchResult> suggestedPodcasts, List<Feed> subscribedFeeds, int limit) { Set<String> subscribedPodcastsSet = new HashSet<>(); for (Feed subscribedFeed : subscribedFeeds) { - if (subscribedFeed.getTitle() != null && subscribedFeed.getAuthor() != null) { + if (subscribedFeed.getTitle() != null && subscribedFeed.getAuthor() != null + && subscribedFeed.getState() == Feed.STATE_SUBSCRIBED) { subscribedPodcastsSet.add(subscribedFeed.getTitle().trim() + " - " + subscribedFeed.getAuthor().trim()); } } diff --git a/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/episode/MediaDownloadedHandler.java b/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/episode/MediaDownloadedHandler.java index 96c1549e2..cf9ec17e1 100644 --- a/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/episode/MediaDownloadedHandler.java +++ b/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/episode/MediaDownloadedHandler.java @@ -7,6 +7,7 @@ import android.util.Log; import androidx.annotation.NonNull; import de.danoeh.antennapod.model.MediaMetadataRetrieverCompat; +import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink; import de.danoeh.antennapod.ui.chapters.ChapterUtils; import org.greenrobot.eventbus.EventBus; @@ -104,7 +105,7 @@ public class MediaDownloadedHandler implements Runnable { FeedMedia.FEEDFILETYPE_FEEDMEDIA, false, DownloadError.ERROR_DB_ACCESS_ERROR, e.getMessage()); } - if (item != null) { + if (item != null && item.getFeed().getState() == Feed.STATE_SUBSCRIBED) { EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DOWNLOAD) .currentTimestamp() .build(); diff --git a/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/feed/FeedUpdateWorker.java b/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/feed/FeedUpdateWorker.java index 9e363b585..b5174dfcb 100644 --- a/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/feed/FeedUpdateWorker.java +++ b/net/download/service/src/main/java/de/danoeh/antennapod/net/download/service/feed/FeedUpdateWorker.java @@ -35,6 +35,7 @@ import de.danoeh.antennapod.model.download.DownloadRequest; import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequestBuilder; import de.danoeh.antennapod.parser.feed.FeedHandlerResult; +import de.danoeh.antennapod.storage.database.NonSubscribedFeedsCleaner; import de.danoeh.antennapod.ui.notifications.NotificationUtils; import java.util.ArrayList; import java.util.Collections; @@ -69,7 +70,7 @@ public class FeedUpdateWorker extends Worker { Iterator<Feed> itr = toUpdate.iterator(); while (itr.hasNext()) { Feed feed = itr.next(); - if (!feed.getPreferences().getKeepUpdated()) { + if (!feed.getPreferences().getKeepUpdated() || feed.getState() != Feed.STATE_SUBSCRIBED) { itr.remove(); continue; } @@ -99,8 +100,9 @@ public class FeedUpdateWorker extends Worker { } refreshFeeds(toUpdate, force); - notificationManager.cancel(R.id.notification_updating_feeds); + NonSubscribedFeedsCleaner.deleteOldNonSubscribedFeeds(getApplicationContext()); AutoDownloadManager.getInstance().autodownloadUndownloadedItems(getApplicationContext()); + notificationManager.cancel(R.id.notification_updating_feeds); return Result.success(); } diff --git a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationQueueSink.java b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationQueueSink.java index ad235130a..e617dfc78 100644 --- a/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationQueueSink.java +++ b/net/sync/service-interface/src/main/java/de/danoeh/antennapod/net/sync/serviceinterface/SynchronizationQueueSink.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.net.sync.serviceinterface; import android.content.Context; +import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.storage.preferences.SynchronizationSettings; import de.danoeh.antennapod.model.feed.FeedMedia; @@ -62,7 +63,8 @@ public class SynchronizationQueueSink { if (!SynchronizationSettings.isProviderConnected()) { return; } - if (media.getItem() == null || media.getItem().getFeed().isLocalFeed()) { + if (media.getItem() == null || media.getItem().getFeed().isLocalFeed() + || media.getItem().getFeed().getState() != Feed.STATE_SUBSCRIBED) { return; } if (media.getStartPosition() < 0 || (!completed && media.getStartPosition() >= media.getPosition())) { diff --git a/playback/service/src/main/java/de/danoeh/antennapod/playback/service/PlaybackService.java b/playback/service/src/main/java/de/danoeh/antennapod/playback/service/PlaybackService.java index 5fa89dd43..7d20124cf 100644 --- a/playback/service/src/main/java/de/danoeh/antennapod/playback/service/PlaybackService.java +++ b/playback/service/src/main/java/de/danoeh/antennapod/playback/service/PlaybackService.java @@ -436,7 +436,9 @@ public class PlaybackService extends MediaBrowserServiceCompat { DBReader.getTotalEpisodeCount(new FeedItemFilter(FeedItemFilter.UNPLAYED)))); List<Feed> feeds = DBReader.getFeedList(); for (Feed feed : feeds) { - mediaItems.add(createBrowsableMediaItemForFeed(feed)); + if (feed.getState() == Feed.STATE_SUBSCRIBED) { + mediaItems.add(createBrowsableMediaItemForFeed(feed)); + } } return mediaItems; } diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBReader.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBReader.java index 432990379..272df5048 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBReader.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBReader.java @@ -356,7 +356,9 @@ public final class DBReader { try (FeedCursor cursor = new FeedCursor(adapter.getFeedCursor(feedId))) { if (cursor.moveToNext()) { feed = cursor.getFeed(); - FeedItemFilter filter = filtered ? feed.getItemFilter() : FeedItemFilter.unfiltered(); + FeedItemFilter filter = (filtered && feed.getItemFilter() != null) + ? feed.getItemFilter() : FeedItemFilter.unfiltered(); + filter = new FeedItemFilter(filter, FeedItemFilter.INCLUDE_NOT_SUBSCRIBED); List<FeedItem> items = getFeedItemList(feed, filter, feed.getSortOrder(), offset, limit); for (FeedItem item : items) { item.setFeed(feed); @@ -668,9 +670,10 @@ public final class DBReader { final Map<Long, Integer> feedCounters = adapter.getFeedCounters(feedCounter); List<Feed> feeds = getFeedList(); - if (subscriptionsFilter != null) { - feeds = subscriptionsFilter.filter(feeds, feedCounters); + if (subscriptionsFilter == null) { + subscriptionsFilter = new SubscriptionsFilter(""); } + feeds = subscriptionsFilter.filter(feeds, feedCounters); Comparator<Feed> comparator; switch (feedOrder) { diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java index 19a02972d..be09a5132 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java @@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase; import android.media.MediaMetadataRetriever; import android.util.Log; +import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedItem; import static de.danoeh.antennapod.model.feed.FeedPreferences.SPEED_USE_GLOBAL; @@ -338,6 +339,10 @@ class DBUpgrader { db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN " + PodDBAdapter.KEY_FEED_SKIP_SILENCE + " INTEGER"); } + if (oldVersion < 3050000) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + + " ADD COLUMN " + PodDBAdapter.KEY_STATE + " INTEGER DEFAULT " + Feed.STATE_SUBSCRIBED); + } } } diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBWriter.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBWriter.java index 5f684b398..11e1ad751 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBWriter.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBWriter.java @@ -14,6 +14,7 @@ import androidx.documentfile.provider.DocumentFile; import com.google.common.util.concurrent.Futures; import de.danoeh.antennapod.event.DownloadLogEvent; +import de.danoeh.antennapod.model.feed.FeedItemFilter; import de.danoeh.antennapod.net.download.serviceinterface.AutoDownloadManager; import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface; import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager; @@ -141,12 +142,13 @@ public class DBWriter { // Do full update of this feed to get rid of the item FeedUpdateManager.getInstance().runOnce(context, media.getItem().getFeed()); } else { - // Gpodder: queue delete action for synchronization - FeedItem item = media.getItem(); - EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DELETE) - .currentTimestamp() - .build(); - SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action); + if (media.getItem().getFeed().getState() == Feed.STATE_SUBSCRIBED) { + FeedItem item = media.getItem(); + EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DELETE) + .currentTimestamp() + .build(); + SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action); + } EventBus.getDefault().post(FeedItemEvent.updated(media.getItem())); } @@ -174,7 +176,7 @@ public class DBWriter { adapter.removeFeed(feed); adapter.close(); - if (!feed.isLocalFeed()) { + if (!feed.isLocalFeed() && feed.getState() == Feed.STATE_SUBSCRIBED) { SynchronizationQueueSink.enqueueFeedRemovedIfSynchronizationIsActive(context, feed.getDownloadUrl()); } EventBus.getDefault().post(new FeedListUpdateEvent(feed)); @@ -786,7 +788,7 @@ public class DBWriter { adapter.close(); for (Feed feed : feeds) { - if (!feed.isLocalFeed()) { + if (!feed.isLocalFeed() && feed.getState() == Feed.STATE_SUBSCRIBED) { SynchronizationQueueSink.enqueueFeedAddedIfSynchronizationIsActive(context, feed.getDownloadUrl()); } } @@ -928,6 +930,32 @@ public class DBWriter { }); } + public static Future<?> setFeedState(Context context, Feed feed, int newState) { + int oldState = feed.getState(); + return runOnDbThread(() -> { + PodDBAdapter adapter = PodDBAdapter.getInstance(); + adapter.open(); + adapter.setFeedState(feed.getId(), newState); + feed.setState(newState); + if (oldState == Feed.STATE_NOT_SUBSCRIBED && newState == Feed.STATE_SUBSCRIBED) { + feed.getPreferences().setKeepUpdated(true); + DBWriter.setFeedPreferences(feed.getPreferences()); + FeedUpdateManager.getInstance().runOnceOrAsk(context, feed); + SynchronizationQueueSink.enqueueFeedAddedIfSynchronizationIsActive(context, feed.getDownloadUrl()); + DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(), + SortOrder.DATE_NEW_OLD, 0, Integer.MAX_VALUE); + for (FeedItem item : feed.getItems()) { + if (item.isPlayed()) { + SynchronizationQueueSink.enqueueEpisodePlayedIfSynchronizationIsActive( + context, item.getMedia(), true); + } + } + } + adapter.close(); + EventBus.getDefault().post(new FeedListUpdateEvent(feed)); + }); + } + /** * Sort the FeedItems in the queue with the given the named sort order. * diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/FeedDatabaseWriter.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/FeedDatabaseWriter.java index 7269fcd55..a62e18809 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/FeedDatabaseWriter.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/FeedDatabaseWriter.java @@ -157,7 +157,8 @@ public abstract class FeedDatabaseWriter { + "\n\nNow the feed contains:\n" + duplicateEpisodeDetails(item))); oldItem.setItemIdentifier(item.getItemIdentifier()); - if (oldItem.isPlayed() && oldItem.getMedia() != null) { + if (oldItem.isPlayed() && oldItem.getMedia() != null + && savedFeed.getState() == Feed.STATE_SUBSCRIBED) { EpisodeAction action = new EpisodeAction.Builder(oldItem, EpisodeAction.PLAY) .currentTimestamp() .started(oldItem.getMedia().getDuration() / 1000) diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/NonSubscribedFeedsCleaner.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/NonSubscribedFeedsCleaner.java new file mode 100644 index 000000000..47a5a5aee --- /dev/null +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/NonSubscribedFeedsCleaner.java @@ -0,0 +1,46 @@ +package de.danoeh.antennapod.storage.database; + +import android.content.Context; +import android.util.Log; +import de.danoeh.antennapod.model.feed.Feed; +import de.danoeh.antennapod.model.feed.FeedItem; +import de.danoeh.antennapod.model.feed.FeedItemFilter; +import de.danoeh.antennapod.model.feed.SortOrder; + +import java.util.List; + +public class NonSubscribedFeedsCleaner { + private static final String TAG = "NonSubscrFeedsCleaner"; + private static final long TIME_TO_KEEP = 1000L * 3600 * 24 * 30; // 30 days + + public static void deleteOldNonSubscribedFeeds(Context context) { + List<Feed> feeds = DBReader.getFeedList(); + for (Feed feed : feeds) { + if (feed.getState() != Feed.STATE_NOT_SUBSCRIBED) { + continue; + } + DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(), SortOrder.DATE_NEW_OLD, 0, Integer.MAX_VALUE); + if (shouldDelete(feed)) { + Log.d(TAG, "Deleting unsubscribed feed " + feed.getTitle()); + DBWriter.deleteFeed(context, feed.getId()); + } + feed.setItems(null); // Let it be garbage collected + } + } + + public static boolean shouldDelete(Feed feed) { + if (feed.getState() != Feed.STATE_NOT_SUBSCRIBED) { + return false; + } else if (feed.getItems() == null) { + return false; + } + for (FeedItem item : feed.getItems()) { + if (item.isTagged(FeedItem.TAG_FAVORITE) + || item.isTagged(FeedItem.TAG_QUEUE) + || item.isDownloaded()) { + return false; + } + } + return feed.getLastRefreshAttempt() < System.currentTimeMillis() - TIME_TO_KEEP; + } +} 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 2cbf27509..aec136457 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 @@ -52,7 +52,7 @@ public class PodDBAdapter { private static final String TAG = "PodDBAdapter"; public static final String DATABASE_NAME = "Antennapod.db"; - public static final int VERSION = 3040000; + public static final int VERSION = 3050000; /** * Maximum number of arguments for IN-operator. @@ -121,6 +121,7 @@ public class PodDBAdapter { public static final String KEY_EPISODE_NOTIFICATION = "episode_notification"; public static final String KEY_NEW_EPISODES_ACTION = "new_episodes_action"; public static final String KEY_PODCASTINDEX_CHAPTER_URL = "podcastindex_chapter_url"; + public static final String KEY_STATE = "state"; // Table names public static final String TABLE_NAME_FEEDS = "Feeds"; @@ -171,6 +172,7 @@ public class PodDBAdapter { + KEY_FEED_SKIP_INTRO + " INTEGER DEFAULT 0," + KEY_FEED_SKIP_ENDING + " INTEGER DEFAULT 0," + KEY_EPISODE_NOTIFICATION + " INTEGER DEFAULT 0," + + KEY_STATE + " INTEGER DEFAULT " + Feed.STATE_SUBSCRIBED + "," + KEY_NEW_EPISODES_ACTION + " INTEGER DEFAULT 0)"; private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE " @@ -323,6 +325,7 @@ public class PodDBAdapter { + TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_INTRO + ", " + TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_ENDING + ", " + TABLE_NAME_FEEDS + "." + KEY_EPISODE_NOTIFICATION + ", " + + TABLE_NAME_FEEDS + "." + KEY_STATE + ", " + TABLE_NAME_FEEDS + "." + KEY_NEW_EPISODES_ACTION; private static final String JOIN_FEED_ITEM_AND_MEDIA = " LEFT JOIN " + TABLE_NAME_FEED_MEDIA @@ -337,6 +340,9 @@ public class PodDBAdapter { "SELECT " + KEYS_FEED_ITEM_WITHOUT_DESCRIPTION + ", " + KEYS_FEED_MEDIA + " FROM " + TABLE_NAME_FEED_ITEMS + JOIN_FEED_ITEM_AND_MEDIA; + public static final String SELECT_WHERE_FEED_IS_SUBSCRIBED = TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + + " IN (SELECT " + KEY_ID + " FROM " + TABLE_NAME_FEEDS + + " WHERE " + KEY_STATE + "=" + Feed.STATE_SUBSCRIBED + ")"; private static Context context; private static PodDBAdapter instance; @@ -431,6 +437,7 @@ public class PodDBAdapter { values.put(KEY_LASTUPDATE, feed.getLastModified()); values.put(KEY_TYPE, feed.getType()); values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier()); + values.put(KEY_STATE, feed.getState()); values.put(KEY_IS_PAGED, feed.isPaged()); values.put(KEY_NEXT_PAGE_LINK, feed.getNextPageLink()); @@ -768,6 +775,12 @@ public class PodDBAdapter { db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feedId)}); } + public void setFeedState(long feedId, int state) { + ContentValues values = new ContentValues(); + values.put(KEY_STATE, state); + db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feedId)}); + } + /** * Inserts or updates a download status. */ @@ -1101,6 +1114,7 @@ public class PodDBAdapter { // Hide episodes that have been played but not completed + " AND (" + KEY_LAST_PLAYED_TIME + " == 0" + " OR " + KEY_LAST_PLAYED_TIME + " > " + (System.currentTimeMillis() - 1000L * 3600L) + ")" + + " AND " + SELECT_WHERE_FEED_IS_SUBSCRIBED + " ORDER BY " + randomEpisodeNumber(seed); final String query = "SELECT * FROM (" + allItemsRandomOrder + ")" + " GROUP BY " + KEY_FEED @@ -1221,6 +1235,7 @@ public class PodDBAdapter { + JOIN_FEED_ITEM_AND_MEDIA + " INNER JOIN " + TABLE_NAME_FEEDS + " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID + + " WHERE " + TABLE_NAME_FEEDS + "." + KEY_STATE + "=" + Feed.STATE_SUBSCRIBED + " GROUP BY " + TABLE_NAME_FEEDS + "." + KEY_ID; return db.rawQuery(query, null); } @@ -1372,7 +1387,7 @@ public class PodDBAdapter { } String queryStart = SELECT_FEED_ITEMS_AND_MEDIA_WITH_DESCRIPTION - + " WHERE " + queryFeedId + " AND ("; + + " WHERE " + queryFeedId + " AND " + SELECT_WHERE_FEED_IS_SUBSCRIBED + " AND ("; StringBuilder sb = new StringBuilder(queryStart); for (int i = 0; i < queryWords.length; i++) { @@ -1401,12 +1416,13 @@ public class PodDBAdapter { public Cursor searchFeeds(String searchQuery) { String[] queryWords = prepareSearchQuery(searchQuery); - String queryStart = "SELECT " + KEYS_FEED + " FROM " + TABLE_NAME_FEEDS + " WHERE "; + String queryStart = "SELECT " + KEYS_FEED + " FROM " + TABLE_NAME_FEEDS + + " WHERE " + KEY_STATE + " = " + Feed.STATE_SUBSCRIBED; StringBuilder sb = new StringBuilder(queryStart); for (int i = 0; i < queryWords.length; i++) { sb - .append("(") + .append(" AND (") .append(KEY_TITLE).append(" LIKE '%").append(queryWords[i]) .append("%' OR ") .append(KEY_CUSTOM_TITLE).append(" LIKE '%").append(queryWords[i]) @@ -1415,13 +1431,9 @@ public class PodDBAdapter { .append("%' OR ") .append(KEY_DESCRIPTION).append(" LIKE '%").append(queryWords[i]) .append("%') "); - - if (i != queryWords.length - 1) { - sb.append("AND "); - } } - sb.append("ORDER BY " + KEY_TITLE + " ASC LIMIT 300"); + sb.append(" ORDER BY " + KEY_TITLE + " ASC LIMIT 300"); return db.rawQuery(sb.toString(), null); } diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedCursor.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedCursor.java index 2707275ca..3aba69106 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedCursor.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedCursor.java @@ -34,6 +34,7 @@ public class FeedCursor extends CursorWrapper { private final int indexSortOrder; private final int indexLastUpdateFailed; private final int indexImageUrl; + private final int indexState; public FeedCursor(Cursor cursor) { super(new FeedPreferencesCursor(cursor)); @@ -58,6 +59,7 @@ public class FeedCursor extends CursorWrapper { indexSortOrder = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_SORT_ORDER); indexLastUpdateFailed = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_LAST_UPDATE_FAILED); indexImageUrl = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_IMAGE_URL); + indexState = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_STATE); } /** @@ -85,7 +87,8 @@ public class FeedCursor extends CursorWrapper { getString(indexNextPageLink), getString(indexHide), SortOrder.fromCodeString(getString(indexSortOrder)), - getInt(indexLastUpdateFailed) > 0); + getInt(indexLastUpdateFailed) > 0, + getInt(indexState)); feed.setPreferences(preferencesCursor.getFeedPreferences()); return feed; } diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java index a4794d324..481107cfa 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemFilterQuery.java @@ -66,6 +66,9 @@ public class FeedItemFilterQuery { if (filter.showInHistory) { statements.add(keyCompletionDate + " > 0 "); } + if (!filter.includeNotSubscribed) { + statements.add(PodDBAdapter.SELECT_WHERE_FEED_IS_SUBSCRIBED); + } if (statements.isEmpty()) { return ""; diff --git a/storage/database/src/test/java/de/danoeh/antennapod/storage/database/NonSubscribedFeedsCleanerTest.java b/storage/database/src/test/java/de/danoeh/antennapod/storage/database/NonSubscribedFeedsCleanerTest.java new file mode 100644 index 000000000..76bbfddc1 --- /dev/null +++ b/storage/database/src/test/java/de/danoeh/antennapod/storage/database/NonSubscribedFeedsCleanerTest.java @@ -0,0 +1,104 @@ +package de.danoeh.antennapod.storage.database; + +import de.danoeh.antennapod.model.feed.Feed; +import de.danoeh.antennapod.model.feed.FeedItem; +import de.danoeh.antennapod.model.feed.FeedMedia; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.util.ArrayList; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(RobolectricTestRunner.class) +public class NonSubscribedFeedsCleanerTest { + + @Test + public void testSubscribed() { + Feed feed = createFeed(); + feed.setLastRefreshAttempt(System.currentTimeMillis() - TimeUnit.MILLISECONDS.convert(200, TimeUnit.DAYS)); + assertFalse(NonSubscribedFeedsCleaner.shouldDelete(feed)); + + feed.setState(Feed.STATE_NOT_SUBSCRIBED); + assertTrue(NonSubscribedFeedsCleaner.shouldDelete(feed)); + } + + @Test + public void testOldDate() { + Feed feed = createFeed(); + feed.setState(Feed.STATE_NOT_SUBSCRIBED); + feed.setLastRefreshAttempt(System.currentTimeMillis()); + assertFalse(NonSubscribedFeedsCleaner.shouldDelete(feed)); + + feed.setLastRefreshAttempt(System.currentTimeMillis() - TimeUnit.MILLISECONDS.convert(10, TimeUnit.DAYS)); + assertFalse(NonSubscribedFeedsCleaner.shouldDelete(feed)); + + feed.setLastRefreshAttempt(System.currentTimeMillis() - TimeUnit.MILLISECONDS.convert(200, TimeUnit.DAYS)); + assertTrue(NonSubscribedFeedsCleaner.shouldDelete(feed)); + + feed.setLastRefreshAttempt(System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(200, TimeUnit.DAYS)); + assertFalse(NonSubscribedFeedsCleaner.shouldDelete(feed)); + } + + @Test + public void testQueuedItem() { + Feed feed = createFeed(); + feed.setState(Feed.STATE_NOT_SUBSCRIBED); + feed.setLastRefreshAttempt(System.currentTimeMillis() - TimeUnit.MILLISECONDS.convert(200, TimeUnit.DAYS)); + feed.getItems().add(createItem(feed)); + assertTrue(NonSubscribedFeedsCleaner.shouldDelete(feed)); + + FeedItem queuedItem = createItem(feed); + queuedItem.addTag(FeedItem.TAG_QUEUE); + feed.getItems().add(queuedItem); + assertFalse(NonSubscribedFeedsCleaner.shouldDelete(feed)); + } + + @Test + public void testFavoriteItem() { + Feed feed = createFeed(); + feed.setState(Feed.STATE_NOT_SUBSCRIBED); + feed.setLastRefreshAttempt(System.currentTimeMillis() - TimeUnit.MILLISECONDS.convert(200, TimeUnit.DAYS)); + feed.getItems().add(createItem(feed)); + assertTrue(NonSubscribedFeedsCleaner.shouldDelete(feed)); + + FeedItem queuedItem = createItem(feed); + queuedItem.addTag(FeedItem.TAG_FAVORITE); + feed.getItems().add(queuedItem); + assertFalse(NonSubscribedFeedsCleaner.shouldDelete(feed)); + } + + @Test + public void testDownloadedItem() { + Feed feed = createFeed(); + feed.setState(Feed.STATE_NOT_SUBSCRIBED); + feed.setLastRefreshAttempt(System.currentTimeMillis() - TimeUnit.MILLISECONDS.convert(200, TimeUnit.DAYS)); + feed.getItems().add(createItem(feed)); + assertTrue(NonSubscribedFeedsCleaner.shouldDelete(feed)); + + FeedItem queuedItem = createItem(feed); + queuedItem.getMedia().setDownloaded(true, System.currentTimeMillis()); + feed.getItems().add(queuedItem); + assertFalse(NonSubscribedFeedsCleaner.shouldDelete(feed)); + } + + private Feed createFeed() { + Feed feed = new Feed(0, null, "title", "http://example.com", "This is the description", + "http://example.com/payment", "Daniel", "en", null, "http://example.com/feed", + "http://example.com/image", null, "http://example.com/feed", System.currentTimeMillis()); + feed.setItems(new ArrayList<>()); + return feed; + } + + private FeedItem createItem(Feed feed) { + FeedItem item = new FeedItem(0, "Item", "ItemId", "url", new Date(), FeedItem.PLAYED, feed); + FeedMedia media = new FeedMedia(item, "http://download.url.net/", 1234567, "audio/mpeg"); + media.setId(item.getId()); + item.setMedia(media); + return item; + } +} diff --git a/storage/importexport/src/main/java/de/danoeh/antennapod/storage/importexport/HtmlWriter.java b/storage/importexport/src/main/java/de/danoeh/antennapod/storage/importexport/HtmlWriter.java index 0b162fab0..d259db43b 100644 --- a/storage/importexport/src/main/java/de/danoeh/antennapod/storage/importexport/HtmlWriter.java +++ b/storage/importexport/src/main/java/de/danoeh/antennapod/storage/importexport/HtmlWriter.java @@ -28,6 +28,9 @@ public class HtmlWriter { writer.append(templateParts[0]); for (Feed feed : feeds) { + if (feed.getState() != Feed.STATE_SUBSCRIBED) { + continue; + } writer.append("<li><div><img src=\""); writer.append(feed.getImageUrl()); writer.append("\" /><p>"); diff --git a/storage/importexport/src/main/java/de/danoeh/antennapod/storage/importexport/OpmlWriter.java b/storage/importexport/src/main/java/de/danoeh/antennapod/storage/importexport/OpmlWriter.java index 9701476f9..d06932a5e 100644 --- a/storage/importexport/src/main/java/de/danoeh/antennapod/storage/importexport/OpmlWriter.java +++ b/storage/importexport/src/main/java/de/danoeh/antennapod/storage/importexport/OpmlWriter.java @@ -48,6 +48,9 @@ public class OpmlWriter { xs.startTag(null, OpmlSymbols.BODY); for (Feed feed : feeds) { + if (feed.getState() != Feed.STATE_SUBSCRIBED) { + continue; + } xs.startTag(null, OpmlSymbols.OUTLINE); xs.attribute(null, OpmlSymbols.TEXT, feed.getTitle()); xs.attribute(null, OpmlSymbols.TITLE, feed.getTitle()); diff --git a/ui/common/src/main/res/drawable/bg_message_info.xml b/ui/common/src/main/res/drawable/bg_message_info.xml new file mode 100644 index 000000000..6543066cf --- /dev/null +++ b/ui/common/src/main/res/drawable/bg_message_info.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <stroke + android:width="2dp" + android:color="?attr/colorPrimary" /> + + <corners android:radius="8dp" /> + + <solid> + <aapt:attr name="android:color" > + <selector> + <item android:alpha="0.1" android:color="?attr/colorPrimary" /> + </selector> + </aapt:attr> + </solid> +</shape>
\ No newline at end of file diff --git a/ui/common/src/main/res/drawable/bg_rounded_corners.xml b/ui/common/src/main/res/drawable/bg_rounded_corners.xml deleted file mode 100644 index 11b7710c4..000000000 --- a/ui/common/src/main/res/drawable/bg_rounded_corners.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<shape - xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <corners android:radius="8dp" /> -</shape> diff --git a/ui/common/src/main/res/drawable/ic_close.xml b/ui/common/src/main/res/drawable/ic_close.xml new file mode 100644 index 000000000..ccba7f4a0 --- /dev/null +++ b/ui/common/src/main/res/drawable/ic_close.xml @@ -0,0 +1,11 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M17.656,6.343L12,12M12,12L6.343,17.656M12,12L17.656,17.656M12,12L6.343,6.343" + android:strokeWidth="2" + android:fillColor="#00000000" + android:strokeColor="?attr/action_icon_color"/> +</vector> diff --git a/ui/common/src/main/res/values/colors.xml b/ui/common/src/main/res/values/colors.xml index ad835faf4..55ba650e7 100644 --- a/ui/common/src/main/res/values/colors.xml +++ b/ui/common/src/main/res/values/colors.xml @@ -8,7 +8,6 @@ <color name="medium_gray">#afafaf</color> <color name="black">#000000</color> <color name="image_readability_tint">#80000000</color> - <color name="feed_image_bg">#50000000</color> <color name="feed_text_bg">#55333333</color> <!-- Theme colors --> diff --git a/ui/common/src/main/res/values/dimens.xml b/ui/common/src/main/res/values/dimens.xml index 5cfcd68b6..82c2b1451 100644 --- a/ui/common/src/main/res/values/dimens.xml +++ b/ui/common/src/main/res/values/dimens.xml @@ -7,8 +7,6 @@ <dimen name="text_size_large">22sp</dimen> <dimen name="thumbnail_length_itemlist">56dp</dimen> <dimen name="thumbnail_length_queue_item">56dp</dimen> - <dimen name="thumbnail_length_onlinefeedview">92dp</dimen> - <dimen name="feeditemlist_header_height">132dp</dimen> <dimen name="thumbnail_length_navlist">40dp</dimen> <dimen name="listitem_iconwithtext_height">48dp</dimen> <dimen name="listitem_iconwithtext_textleftpadding">16dp</dimen> diff --git a/ui/i18n/src/main/res/values/strings.xml b/ui/i18n/src/main/res/values/strings.xml index a840a1abf..f5ac3c306 100644 --- a/ui/i18n/src/main/res/values/strings.xml +++ b/ui/i18n/src/main/res/values/strings.xml @@ -681,9 +681,8 @@ <!-- Online feed view --> <string name="subscribe_label">Subscribe</string> - <string name="subscribing_label">Subscribing…</string> - <string name="preview_episode">Preview</string> - <string name="stop_preview">Stop preview</string> + <string name="preview_episodes">Episodes preview</string> + <string name="state_deleted_not_subscribed">Playback state and history of non-subscribed podcasts are deleted after a while. Subscribe to keep them.</string> <!-- Content descriptions for image buttons --> <string name="toolbar_back_button_content_description">Back</string> |