Compare commits

...

25 Commits

Author SHA1 Message Date
ByteHamster 2f888e646d
Merge 8eac76a4ba into a61f548792 2024-05-08 07:55:08 +02:00
ByteHamster a61f548792
Fix settings toolbar having color (#7169) 2024-05-08 07:46:25 +02:00
ByteHamster 8eac76a4ba Fixes 2024-05-03 22:17:11 +02:00
ByteHamster bfc5738268 Show warning message also on item details page 2024-05-03 22:09:37 +02:00
ByteHamster aac4fd5b88 Refresh feed when visiting details page 2024-05-03 21:59:47 +02:00
ByteHamster cf9e782726 Fix going back to view 2024-05-03 21:53:46 +02:00
ByteHamster 1b369abe74 Hide things on info fragment 2024-05-03 21:47:43 +02:00
ByteHamster 2406453730 Merge branch 'develop' into add-feed-no-subscribe 2024-05-03 21:45:36 +02:00
ByteHamster 76afb09d07 Review feedback 2024-05-03 18:19:10 +02:00
ByteHamster 9e780fab40 Fix crash 2024-05-02 23:52:14 +02:00
ByteHamster 0939dcfd10 Cleanup 2024-05-02 20:55:11 +02:00
ByteHamster ef3c038033 Merge branch 'develop' into add-feed-no-subscribe 2024-05-02 20:51:15 +02:00
ByteHamster bf7fe4901f Cleanup 2024-05-02 20:47:34 +02:00
ByteHamster 6ec8d42532 Reduce diff 2024-05-02 20:37:59 +02:00
ByteHamster d8b57365f6 Show item list directly 2024-05-02 20:26:48 +02:00
ByteHamster 3e6a3fe54e Remove delay, make nag message a bit stronger 2024-04-21 20:20:23 +02:00
ByteHamster 0ecdac5fed Delay loading when using online view multiple times to nudge people into subscribing 2024-04-21 00:17:46 +02:00
ByteHamster f0b0c68374 Subscribe to change settings text 2024-04-21 00:01:38 +02:00
ByteHamster 0e6a7ea206 More consistent menu button hiding 2024-04-19 23:39:03 +02:00
ByteHamster a424ca3219 Merge branch 'develop' into add-feed-no-subscribe 2024-04-19 18:46:07 +02:00
ByteHamster 1654699eff Show non-subscribed feed details 2024-04-18 23:00:59 +02:00
ByteHamster 4eeefe07b8 Open non-subscribed feeds in popup 2024-04-18 22:36:34 +02:00
ByteHamster 2ab4ab9638 Sync feeds that have their state changed to subscribed 2024-04-18 21:47:55 +02:00
ByteHamster 2037978dd3 Clean up non-subscribed feeds 2024-04-16 23:42:31 +02:00
ByteHamster 2bc755abba Add episodes without subscribing
Adding episodes to the queue without subscribing is a long running
feature request. Doing this does not really make sense
on a distributed podcast app. However, to make these people happy,
subscribe to the feed internally anyway and just tell them that
it is not subscribed.
2024-04-14 19:50:56 +02:00
48 changed files with 676 additions and 818 deletions

View File

@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import android.view.View; import android.view.View;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.playback.service.PlaybackStatus; import de.danoeh.antennapod.playback.service.PlaybackStatus;
import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.model.feed.FeedMedia;
@ -48,6 +49,8 @@ public abstract class ItemActionButton {
return new PlayActionButton(item); return new PlayActionButton(item);
} else if (isDownloadingMedia) { } else if (isDownloadingMedia) {
return new CancelDownloadActionButton(item); return new CancelDownloadActionButton(item);
} else if (item.getFeed().getState() != Feed.STATE_SUBSCRIBED) {
return new StreamActionButton(item);
} else if (UserPreferences.isStreamOverDownload()) { } else if (UserPreferences.isStreamOverDownload()) {
return new StreamActionButton(item); return new StreamActionButton(item);
} else { } else {

View File

@ -12,6 +12,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import de.danoeh.antennapod.ui.SelectableAdapter; import de.danoeh.antennapod.ui.SelectableAdapter;
@ -32,13 +33,13 @@ import de.danoeh.antennapod.ui.screen.episode.ItemPagerFragment;
public class EpisodeItemListAdapter extends SelectableAdapter<EpisodeItemViewHolder> public class EpisodeItemListAdapter extends SelectableAdapter<EpisodeItemViewHolder>
implements View.OnCreateContextMenuListener { implements View.OnCreateContextMenuListener {
private final WeakReference<MainActivity> mainActivityRef; private final WeakReference<FragmentActivity> mainActivityRef;
private List<FeedItem> episodes = new ArrayList<>(); private List<FeedItem> episodes = new ArrayList<>();
private FeedItem longPressedItem; private FeedItem longPressedItem;
int longPressedPosition = 0; // used to init actionMode int longPressedPosition = 0; // used to init actionMode
private int dummyViews = 0; private int dummyViews = 0;
public EpisodeItemListAdapter(MainActivity mainActivity) { public EpisodeItemListAdapter(FragmentActivity mainActivity) {
super(mainActivity); super(mainActivity);
this.mainActivityRef = new WeakReference<>(mainActivity); this.mainActivityRef = new WeakReference<>(mainActivity);
setHasStableIds(true); setHasStableIds(true);
@ -86,9 +87,18 @@ public class EpisodeItemListAdapter extends SelectableAdapter<EpisodeItemViewHol
holder.bind(item); holder.bind(item);
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
MainActivity activity = mainActivityRef.get(); if (!inActionMode()) {
if (activity != null && !inActionMode()) { if (mainActivityRef.get() instanceof MainActivity) {
activity.loadChildFragment(ItemPagerFragment.newInstance(episodes, item)); ((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 { } else {
toggleSelection(holder.getBindingAdapterPosition()); toggleSelection(holder.getBindingAdapterPosition());
} }

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.ui.episodeslist; package de.danoeh.antennapod.ui.episodeslist;
import android.app.Activity;
import android.os.Build; import android.os.Build;
import android.text.Layout; import android.text.Layout;
import android.text.format.Formatter; import android.text.format.Formatter;
@ -17,7 +18,6 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.elevation.SurfaceColors; import com.google.android.material.elevation.SurfaceColors;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.ui.CoverLoader; import de.danoeh.antennapod.ui.CoverLoader;
import de.danoeh.antennapod.actionbutton.ItemActionButton; import de.danoeh.antennapod.actionbutton.ItemActionButton;
import de.danoeh.antennapod.playback.service.PlaybackStatus; import de.danoeh.antennapod.playback.service.PlaybackStatus;
@ -62,10 +62,10 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
private final View leftPadding; private final View leftPadding;
public final CardView coverHolder; public final CardView coverHolder;
private final MainActivity activity; private final Activity activity;
private FeedItem item; 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)); super(LayoutInflater.from(activity).inflate(R.layout.feeditemlist_item, parent, false));
this.activity = activity; this.activity = activity;
container = itemView.findViewById(R.id.container); container = itemView.findViewById(R.id.container);

View File

@ -1,29 +1,28 @@
package de.danoeh.antennapod.ui.episodeslist; package de.danoeh.antennapod.ui.episodeslist;
import android.app.Activity;
import android.util.Log; import android.util.Log;
import androidx.annotation.PluralsRes; import androidx.annotation.PluralsRes;
import com.google.android.material.snackbar.Snackbar;
import java.util.List; import java.util.List;
import de.danoeh.antennapod.R; 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.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.storage.database.DBWriter; import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.storage.database.LongList; import de.danoeh.antennapod.storage.database.LongList;
import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.ui.view.LocalDeleteModal; import de.danoeh.antennapod.ui.view.LocalDeleteModal;
import org.greenrobot.eventbus.EventBus;
public class EpisodeMultiSelectActionHandler { public class EpisodeMultiSelectActionHandler {
private static final String TAG = "EpisodeSelectHandler"; private static final String TAG = "EpisodeSelectHandler";
private final MainActivity activity; private final Activity activity;
private final int actionId; private final int actionId;
private int totalNumItems = 0; private int totalNumItems = 0;
private Snackbar snackbar = null;
public EpisodeMultiSelectActionHandler(MainActivity activity, int actionId) { public EpisodeMultiSelectActionHandler(Activity activity, int actionId) {
this.activity = activity; this.activity = activity;
this.actionId = actionId; this.actionId = actionId;
} }
@ -116,12 +115,7 @@ public class EpisodeMultiSelectActionHandler {
totalNumItems += numItems; totalNumItems += numItems;
activity.runOnUiThread(() -> { activity.runOnUiThread(() -> {
String text = activity.getResources().getQuantityString(msgId, totalNumItems, totalNumItems); String text = activity.getResources().getQuantityString(msgId, totalNumItems, totalNumItems);
if (snackbar != null) { EventBus.getDefault().post(new MessageEvent(text));
snackbar.setText(text);
snackbar.show(); // Resets the timeout
} else {
snackbar = activity.showSnackbarAbovePlayer(text, Snackbar.LENGTH_LONG);
}
}); });
} }

View File

@ -16,6 +16,7 @@ import java.util.Arrays;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity; 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.net.sync.serviceinterface.SynchronizationQueueSink;
import de.danoeh.antennapod.storage.preferences.PlaybackPreferences; import de.danoeh.antennapod.storage.preferences.PlaybackPreferences;
import de.danoeh.antennapod.playback.service.PlaybackServiceInterface; import de.danoeh.antennapod.playback.service.PlaybackServiceInterface;
@ -84,6 +85,11 @@ public class FeedItemMenuHandler {
setItemVisibility(menu, R.id.add_to_favorites_item, !isFavorite); setItemVisibility(menu, R.id.add_to_favorites_item, !isFavorite);
setItemVisibility(menu, R.id.remove_from_favorites_item, isFavorite); setItemVisibility(menu, R.id.remove_from_favorites_item, isFavorite);
setItemVisibility(menu, R.id.remove_item, fileDownloaded || isLocalFile); setItemVisibility(menu, R.id.remove_item, fileDownloaded || isLocalFile);
if (selectedItem.getFeed().getState() != Feed.STATE_SUBSCRIBED) {
setItemVisibility(menu, R.id.mark_read_item, false);
setItemVisibility(menu, R.id.add_to_favorites_item, false);
}
return true; return true;
} }
@ -158,7 +164,8 @@ public class FeedItemMenuHandler {
} else if (menuItemId == R.id.mark_read_item) { } else if (menuItemId == R.id.mark_read_item) {
selectedItem.setPlayed(true); selectedItem.setPlayed(true);
DBWriter.markItemPlayed(selectedItem, FeedItem.PLAYED, 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(); FeedMedia media = selectedItem.getMedia();
// not all items have media, Gpodder only cares about those that do // not all items have media, Gpodder only cares about those that do
if (media != null) { if (media != null) {
@ -174,7 +181,8 @@ public class FeedItemMenuHandler {
} else if (menuItemId == R.id.mark_unread_item) { } else if (menuItemId == R.id.mark_unread_item) {
selectedItem.setPlayed(false); selectedItem.setPlayed(false);
DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, 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) EpisodeAction actionNew = new EpisodeAction.Builder(selectedItem, EpisodeAction.NEW)
.currentTimestamp() .currentTimestamp()
.build(); .build();

View File

@ -285,7 +285,8 @@ public class CompletedDownloadsFragment extends Fragment
disposable = Observable.fromCallable(() -> { disposable = Observable.fromCallable(() -> {
SortOrder sortOrder = UserPreferences.getDownloadsSortedOrder(); SortOrder sortOrder = UserPreferences.getDownloadsSortedOrder();
List<FeedItem> downloadedItems = DBReader.getEpisodes(0, Integer.MAX_VALUE, 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<>(); List<String> mediaUrls = new ArrayList<>();
if (runningDownloads == null) { if (runningDownloads == null) {

View File

@ -38,6 +38,7 @@ import de.danoeh.antennapod.actionbutton.PlayLocalActionButton;
import de.danoeh.antennapod.actionbutton.StreamActionButton; import de.danoeh.antennapod.actionbutton.StreamActionButton;
import de.danoeh.antennapod.actionbutton.VisitWebsiteActionButton; import de.danoeh.antennapod.actionbutton.VisitWebsiteActionButton;
import de.danoeh.antennapod.event.EpisodeDownloadEvent; import de.danoeh.antennapod.event.EpisodeDownloadEvent;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.playback.service.PlaybackStatus; import de.danoeh.antennapod.playback.service.PlaybackStatus;
import de.danoeh.antennapod.event.FeedItemEvent; import de.danoeh.antennapod.event.FeedItemEvent;
import de.danoeh.antennapod.event.PlayerStatusEvent; 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.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.storage.preferences.UserPreferences; import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.storage.database.DBReader; 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.Converter;
import de.danoeh.antennapod.ui.common.DateFormatter; import de.danoeh.antennapod.ui.common.DateFormatter;
import de.danoeh.antennapod.ui.common.CircularProgressBar; import de.danoeh.antennapod.ui.common.CircularProgressBar;
@ -114,6 +116,7 @@ public class ItemFragment extends Fragment {
private ItemActionButton actionButton1; private ItemActionButton actionButton1;
private ItemActionButton actionButton2; private ItemActionButton actionButton2;
private View noMediaLabel; private View noMediaLabel;
private View nonSubscribedWarningLabel;
private Disposable disposable; private Disposable disposable;
private PlaybackController controller; private PlaybackController controller;
@ -164,6 +167,7 @@ public class ItemFragment extends Fragment {
butAction1Text = layout.findViewById(R.id.butAction1Text); butAction1Text = layout.findViewById(R.id.butAction1Text);
butAction2Text = layout.findViewById(R.id.butAction2Text); butAction2Text = layout.findViewById(R.id.butAction2Text);
noMediaLabel = layout.findViewById(R.id.noMediaLabel); noMediaLabel = layout.findViewById(R.id.noMediaLabel);
nonSubscribedWarningLabel = layout.findViewById(R.id.nonSubscribedWarningLabel);
butAction1.setOnClickListener(v -> { butAction1.setOnClickListener(v -> {
if (actionButton1 instanceof StreamActionButton && !UserPreferences.isStreamOverDownload() if (actionButton1 instanceof StreamActionButton && !UserPreferences.isStreamOverDownload()
@ -287,6 +291,11 @@ public class ItemFragment extends Fragment {
txtvPublished.setContentDescription(DateFormatter.formatForAccessibility(item.getPubDate())); 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; float radius = 8 * getResources().getDisplayMetrics().density;
RequestOptions options = new RequestOptions() RequestOptions options = new RequestOptions()
.error(ImagePlaceholder.getDrawable(getContext(), radius)) .error(ImagePlaceholder.getDrawable(getContext(), radius))
@ -366,8 +375,13 @@ public class ItemFragment extends Fragment {
if (item == null) { if (item == null) {
return; return;
} }
Fragment fragment = FeedItemlistFragment.newInstance(item.getFeedId()); if (item.getFeed().getState() == Feed.STATE_SUBSCRIBED) {
((MainActivity) getActivity()).loadChildFragment(fragment); Fragment fragment = FeedItemlistFragment.newInstance(item.getFeedId());
((MainActivity) getActivity()).loadChildFragment(fragment);
} else {
startActivity(new OnlineFeedviewActivityStarter(getContext(), item.getFeed().getDownloadUrl())
.getIntent());
}
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.ui.screen.episode; package de.danoeh.antennapod.ui.screen.episode;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
@ -13,7 +14,8 @@ import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter; import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2; 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.EventBus;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.eventbus.ThreadMode;
@ -173,8 +175,12 @@ public class ItemPagerFragment extends Fragment implements MaterialToolbar.OnMen
if (item == null) { if (item == null) {
return; return;
} }
Fragment fragment = FeedItemlistFragment.newInstance(item.getFeedId()); if (item.getFeed().getState() == Feed.STATE_SUBSCRIBED) {
((MainActivity) getActivity()).loadChildFragment(fragment); Intent intent = MainActivity.getIntentToOpenFeed(getContext(), item.getFeedId());
startActivity(intent);
} else {
startActivity(new OnlineFeedviewActivityStarter(getContext(), item.getFeed().getDownloadUrl()).getIntent());
}
} }
private class ItemPagerAdapter extends FragmentStateAdapter { private class ItemPagerAdapter extends FragmentStateAdapter {

View File

@ -122,7 +122,6 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
viewBinding.header.butFilter.setVisibility(View.INVISIBLE); viewBinding.header.butFilter.setVisibility(View.INVISIBLE);
// https://github.com/bumptech/glide/issues/529 // https://github.com/bumptech/glide/issues/529
viewBinding.imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000)); viewBinding.imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000));
viewBinding.urlLabel.setOnClickListener(copyUrlToClipboard); viewBinding.urlLabel.setOnClickListener(copyUrlToClipboard);
long feedId = getArguments().getLong(EXTRA_FEED_ID); long feedId = getArguments().getLong(EXTRA_FEED_ID);
@ -237,6 +236,24 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
viewBinding.supportUrl.setText(str.toString()); 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);
}
refreshToolbarState(); refreshToolbarState();
} }
@ -249,13 +266,14 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
} }
private void refreshToolbarState() { private void refreshToolbarState() {
boolean isSubscribed = feed != null && feed.getState() == Feed.STATE_SUBSCRIBED;
viewBinding.toolbar.getMenu().findItem(R.id.reconnect_local_folder).setVisible( viewBinding.toolbar.getMenu().findItem(R.id.reconnect_local_folder).setVisible(
feed != null && feed.isLocalFeed()); isSubscribed && feed.isLocalFeed());
viewBinding.toolbar.getMenu().findItem(R.id.share_item).setVisible(feed != null && !feed.isLocalFeed()); viewBinding.toolbar.getMenu().findItem(R.id.share_item).setVisible(isSubscribed && !feed.isLocalFeed());
viewBinding.toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(feed != null viewBinding.toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(isSubscribed
&& feed.getLink() != null && feed.getLink() != null
&& IntentUtils.isCallable(getContext(), new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink())))); && 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 @Override

View File

@ -12,27 +12,63 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.util.Pair; import androidx.core.util.Pair;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.ui.CoverLoader; import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.ui.screen.episode.ItemPagerFragment; import de.danoeh.antennapod.databinding.FeedItemListFragmentBinding;
import de.danoeh.antennapod.ui.screen.SearchFragment; import de.danoeh.antennapod.event.EpisodeDownloadEvent;
import de.danoeh.antennapod.ui.TransitionEffect; 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;
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.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.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.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.download.DownloadLogFragment;
import de.danoeh.antennapod.ui.screen.episode.ItemPagerFragment;
import de.danoeh.antennapod.ui.screen.feed.preferences.FeedSettingsFragment; 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.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
@ -43,44 +79,6 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException; 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.FeedItemEvent;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.FeedUpdateRunningEvent;
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.storage.preferences.UserPreferences;
import de.danoeh.antennapod.ui.glide.FastBlurTransformation;
import de.danoeh.antennapod.ui.episodeslist.EpisodeItemViewHolder;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
/** /**
* Displays a list of FeedItems. * Displays a list of FeedItems.
*/ */
@ -100,7 +98,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
private boolean displayUpArrow; private boolean displayUpArrow;
private long feedID; private long feedID;
private Feed feed; private Feed feed;
private boolean headerCreated = false;
private Disposable disposable; private Disposable disposable;
private FeedItemListFragmentBinding viewBinding; private FeedItemListFragmentBinding viewBinding;
@ -145,12 +142,18 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
if (savedInstanceState != null) { if (savedInstanceState != null) {
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW); 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(); updateToolbar();
setupLoadMoreScrollListener(); setupLoadMoreScrollListener();
setupHeaderView();
viewBinding.recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool()); adapter = new FeedItemListAdapter(getActivity());
adapter = new FeedItemListAdapter((MainActivity) getActivity());
adapter.setOnSelectModeListener(this); adapter.setOnSelectModeListener(this);
viewBinding.recyclerView.setAdapter(adapter); viewBinding.recyclerView.setAdapter(adapter);
swipeActions = new SwipeActions(this, TAG).attachTo(viewBinding.recyclerView); swipeActions = new SwipeActions(this, TAG).attachTo(viewBinding.recyclerView);
@ -198,7 +201,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
Snackbar.LENGTH_SHORT); Snackbar.LENGTH_SHORT);
return false; return false;
} }
new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), menuItem.getItemId()) new EpisodeMultiSelectActionHandler(getActivity(), menuItem.getItemId())
.handleAction(adapter.getSelectedItems()); .handleAction(adapter.getSelectedItems());
adapter.endSelectMode(); adapter.endSelectMode();
return true; return true;
@ -249,6 +252,13 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
if (feed.isLocalFeed()) { if (feed.isLocalFeed()) {
viewBinding.toolbar.getMenu().findItem(R.id.share_item).setVisible(false); 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 @Override
@ -263,8 +273,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
@Override @Override
public boolean onMenuItemClick(MenuItem item) { public boolean onMenuItemClick(MenuItem item) {
if (feed == null) { if (feed == null) {
((MainActivity) getActivity()).showSnackbarAbovePlayer( EventBus.getDefault().post(getString(R.string.please_wait_for_data));
R.string.please_wait_for_data, Toast.LENGTH_LONG);
return true; return true;
} }
if (item.getItemId() == R.id.visit_website_item) { if (item.getItemId() == R.id.visit_website_item) {
@ -443,7 +452,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
} }
private void refreshHeaderView() { private void refreshHeaderView() {
setupHeaderView();
if (viewBinding == null || feed == null) { if (viewBinding == null || feed == null) {
Log.e(TAG, "Unable to refresh header view"); Log.e(TAG, "Unable to refresh header view");
return; return;
@ -454,7 +462,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
} else { } else {
viewBinding.header.txtvFailure.setVisibility(View.GONE); 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.setText(R.string.updates_disabled_label);
viewBinding.header.txtvUpdatesDisabled.setVisibility(View.VISIBLE); viewBinding.header.txtvUpdatesDisabled.setVisibility(View.VISIBLE);
} else { } else {
@ -462,7 +470,11 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
} }
viewBinding.header.txtvTitle.setText(feed.getTitle()); viewBinding.header.txtvTitle.setText(feed.getTitle());
viewBinding.header.txtvAuthor.setText(feed.getAuthor()); 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(); FeedItemFilter filter = feed.getItemFilter();
if (filter.getValues().length > 0) { if (filter.getValues().length > 0) {
viewBinding.header.txtvInformation.setText(R.string.filtered_label); viewBinding.header.txtvInformation.setText(R.string.filtered_label);
@ -475,17 +487,30 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
} else { } else {
viewBinding.header.txtvInformation.setVisibility(View.GONE); 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);
if (!isSubscribed && feed.getLastRefreshAttempt() < System.currentTimeMillis() - 1000L * 3600 * 24) {
FeedUpdateManager.getInstance().runOnce(getContext(), feed, true);
}
} }
private void setupHeaderView() { private void setupHeaderView() {
if (feed == null || headerCreated) {
return;
}
// https://github.com/bumptech/glide/issues/529 // https://github.com/bumptech/glide/issues/529
viewBinding.imgvBackground.setColorFilter(new LightingColorFilter(0xff666666, 0x000000)); viewBinding.imgvBackground.setColorFilter(new LightingColorFilter(0xff666666, 0x000000));
viewBinding.header.butShowInfo.setOnClickListener(v -> showFeedInfo()); viewBinding.header.butShowInfo.setOnClickListener(v -> showFeedInfo());
viewBinding.header.imgvCover.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 -> { viewBinding.header.butShowSettings.setOnClickListener(v -> {
if (feed != null) { if (feed != null) {
FeedSettingsFragment fragment = FeedSettingsFragment.newInstance(feed); FeedSettingsFragment fragment = FeedSettingsFragment.newInstance(feed);
@ -495,7 +520,6 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
viewBinding.header.butFilter.setOnClickListener(v -> viewBinding.header.butFilter.setOnClickListener(v ->
FeedItemFilterDialog.newInstance(feed).show(getChildFragmentManager(), null)); FeedItemFilterDialog.newInstance(feed).show(getChildFragmentManager(), null));
viewBinding.header.txtvFailure.setOnClickListener(v -> showErrorDetails()); viewBinding.header.txtvFailure.setOnClickListener(v -> showErrorDetails());
headerCreated = true;
} }
private void showErrorDetails() { private void showErrorDetails() {
@ -516,9 +540,18 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
} }
private void showFeedInfo() { private void showFeedInfo() {
if (feed != null) { if (feed == null) {
FeedInfoFragment fragment = FeedInfoFragment.newInstance(feed); return;
}
FeedInfoFragment fragment = FeedInfoFragment.newInstance(feed);
if (getActivity() instanceof MainActivity) {
((MainActivity) getActivity()).loadChildFragment(fragment, TransitionEffect.SLIDE); ((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 { private class FeedItemListAdapter extends EpisodeItemListAdapter {
public FeedItemListAdapter(MainActivity mainActivity) { public FeedItemListAdapter(FragmentActivity mainActivity) {
super(mainActivity); super(mainActivity);
} }
@ -648,7 +681,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
@Override @Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo); super.onCreateContextMenu(menu, v, menuInfo);
if (!inActionMode()) { if (!inActionMode() && feed.getState() == Feed.STATE_SUBSCRIBED) {
menu.findItem(R.id.multi_select).setVisible(true); menu.findItem(R.id.multi_select).setVisible(true);
} }
MenuItemUtils.setOnClickListeners(menu, FeedItemlistFragment.this::onContextItemSelected); MenuItemUtils.setOnClickListeners(menu, FeedItemlistFragment.this::onContextItemSelected);

View File

@ -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;
}
}

View File

@ -4,8 +4,6 @@ import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.LightingColorFilter;
import android.os.Bundle; import android.os.Bundle;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
@ -13,68 +11,45 @@ import android.text.TextUtils;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import com.bumptech.glide.Glide; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.bumptech.glide.request.RequestOptions;
import com.google.android.material.snackbar.Snackbar;
import de.danoeh.antennapod.R; 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.EditTextDialogBinding;
import de.danoeh.antennapod.databinding.OnlinefeedviewHeaderBinding; import de.danoeh.antennapod.databinding.OnlinefeedviewActivityBinding;
import de.danoeh.antennapod.event.EpisodeDownloadEvent; import de.danoeh.antennapod.model.download.DownloadError;
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.model.download.DownloadRequest; import de.danoeh.antennapod.model.download.DownloadRequest;
import de.danoeh.antennapod.model.download.DownloadResult; import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.storage.database.DBReader; import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.storage.database.DBWriter; import de.danoeh.antennapod.net.common.UrlChecker;
import de.danoeh.antennapod.net.discovery.CombinedSearcher; 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.PodcastSearchResult;
import de.danoeh.antennapod.net.discovery.PodcastSearcherRegistry; 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.FeedHandler;
import de.danoeh.antennapod.parser.feed.FeedHandlerResult; 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.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.common.ThemeUtils;
import de.danoeh.antennapod.ui.glide.FastBlurTransformation;
import de.danoeh.antennapod.ui.preferences.screen.synchronization.AuthenticationDialog; 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.Maybe;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import io.reactivex.observers.DisposableMaybeObserver;
import io.reactivex.schedulers.Schedulers; 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.File;
import java.io.IOException; 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. * and the activity will finish as soon as the error dialog is closed.
*/ */
public class OnlineFeedViewActivity extends AppCompatActivity { public class OnlineFeedViewActivity extends AppCompatActivity {
private static final int RESULT_ERROR = 2;
private static final String TAG = "OnlineFeedViewActivity"; 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 String selectedDownloadUrl;
private Downloader downloader; private Downloader downloader;
private String username = null; private String username = null;
private String password = null; private String password = null;
private boolean isPaused; private boolean isPaused;
private boolean didPressSubscribe = false;
private boolean isFeedFoundBySearch = false; private boolean isFeedFoundBySearch = false;
private Dialog dialog; private Dialog dialog;
private Disposable download; private Disposable download;
private Disposable parser; private Disposable parser;
private Disposable updater;
private OnlinefeedviewHeaderBinding headerBinding;
private OnlinefeedviewActivityBinding viewBinding; private OnlinefeedviewActivityBinding viewBinding;
@Override @Override
@ -128,12 +89,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
viewBinding = OnlinefeedviewActivityBinding.inflate(getLayoutInflater()); viewBinding = OnlinefeedviewActivityBinding.inflate(getLayoutInflater());
setContentView(viewBinding.getRoot()); setContentView(viewBinding.getRoot());
viewBinding.transparentBackground.setOnClickListener(v -> finish()); viewBinding.transparentBackground.setOnClickListener(v -> finish());
viewBinding.closeButton.setOnClickListener(view -> finish());
viewBinding.card.setOnClickListener(null); viewBinding.card.setOnClickListener(null);
viewBinding.card.setCardBackgroundColor(ThemeUtils.getColorFromAttr(this, R.attr.colorSurface)); viewBinding.card.setCardBackgroundColor(ThemeUtils.getColorFromAttr(this, R.attr.colorSurface));
headerBinding = OnlinefeedviewHeaderBinding.inflate(getLayoutInflater());
String feedUrl = null; String feedUrl = null;
if (getIntent().hasExtra(ARG_FEEDURL)) { if (getIntent().hasExtra(ARG_FEEDURL)) {
@ -149,7 +107,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
showNoPodcastFoundError(); showNoPodcastFoundError();
} else { } else {
Log.d(TAG, "Activity was started with url " + feedUrl); 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 // Remove subscribeonandroid.com from feed URL in order to subscribe to the actual feed URL
if (feedUrl.contains("subscribeonandroid.com")) { if (feedUrl.contains("subscribeonandroid.com")) {
feedUrl = feedUrl.replaceFirst("((www.)?(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()) .setNeutralButton(android.R.string.ok, (dialog, which) -> finish())
.setTitle(R.string.error_label) .setTitle(R.string.error_label)
.setMessage(R.string.null_value_podcast_error) .setMessage(R.string.null_value_podcast_error)
.setOnDismissListener(dialog1 -> { .setOnDismissListener(dialog1 -> finish())
setResult(RESULT_ERROR);
finish();
})
.show()); .show());
} }
/**
* Displays a progress indicator.
*/
private void setLoadingLayout() {
viewBinding.progressBar.setVisibility(View.VISIBLE);
viewBinding.feedDisplayContainer.setVisibility(View.GONE);
}
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
isPaused = false; isPaused = false;
EventBus.getDefault().register(this);
} }
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
isPaused = true; isPaused = true;
EventBus.getDefault().unregister(this);
if (downloader != null && !downloader.isFinished()) { if (downloader != null && !downloader.isFinished()) {
downloader.cancel(); downloader.cancel();
} }
@ -205,9 +149,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if(updater != null) {
updater.dispose();
}
if(download != null) { if(download != null) {
download.dispose(); download.dispose();
} }
@ -239,7 +180,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
download = PodcastSearcherRegistry.lookupUrl(url) download = PodcastSearcherRegistry.lookupUrl(url)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.io()) .observeOn(Schedulers.io())
.subscribe(this::startFeedDownload, .subscribe(this::downloadIfNotAlreadySubscribed,
error -> { error -> {
if (error instanceof FeedUrlNotFoundException) { if (error instanceof FeedUrlNotFoundException) {
tryToRetrieveFeedUrlBySearch((FeedUrlNotFoundException) error); tryToRetrieveFeedUrlBySearch((FeedUrlNotFoundException) error);
@ -256,7 +197,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
if (url != null) { if (url != null) {
Log.d(TAG, "Successfully retrieve feed url"); Log.d(TAG, "Successfully retrieve feed url");
isFeedFoundBySearch = true; isFeedFoundBySearch = true;
startFeedDownload(url); downloadIfNotAlreadySubscribed(url);
} else { } else {
showNoPodcastFoundError(); showNoPodcastFoundError();
Log.d(TAG, "Failed to retrieve feed url"); Log.d(TAG, "Failed to retrieve feed url");
@ -277,6 +218,28 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
return null; 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) { private void startFeedDownload(String url) {
Log.d(TAG, "Starting feed download"); Log.d(TAG, "Starting feed download");
selectedDownloadUrl = UrlChecker.prepareUrl(url); selectedDownloadUrl = UrlChecker.prepareUrl(url);
@ -286,7 +249,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
.build(); .build();
download = Observable.fromCallable(() -> { download = Observable.fromCallable(() -> {
feeds = DBReader.getFeedList();
downloader = new HttpDownloader(request); downloader = new HttpDownloader(request);
downloader.call(); downloader.call();
return downloader.getResult(); 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) { private void parseFeed(String destination) {
Log.d(TAG, "Parsing feed"); Log.d(TAG, "Parsing feed");
parser = Maybe.fromCallable(() -> doParseFeed(destination)) parser = Observable.fromCallable(() -> {
.subscribeOn(Schedulers.computation()) FeedHandlerResult handlerResult = doParseFeed(destination);
.observeOn(AndroidSchedulers.mainThread()) Feed feed = handlerResult.feed;
.subscribeWith(new DisposableMaybeObserver<FeedHandlerResult>() { feed.setState(Feed.STATE_NOT_SUBSCRIBED);
@Override feed.setLastRefreshAttempt(System.currentTimeMillis());
public void onSuccess(@NonNull FeedHandlerResult result) { FeedDatabaseWriter.updateFeed(this, feed, false);
showFeedInformation(result.feed, result.alternateFeedUrls); Feed feedFromDb = DBReader.getFeed(feed.getId(), false, 0, Integer.MAX_VALUE);
} feedFromDb.getPreferences().setKeepUpdated(false);
DBWriter.setFeedPreferences(feedFromDb.getPreferences());
@Override return feed.getId();
public void onComplete() { })
// Ignore null result: We showed the discovery dialog. .subscribeOn(Schedulers.computation())
} .observeOn(AndroidSchedulers.mainThread())
.subscribe(this::showFeedFragment, error -> {
@Override error.printStackTrace();
public void onError(@NonNull Throwable error) { showErrorDialog(error.getMessage(), "");
showErrorDialog(error.getMessage(), ""); });
Log.d(TAG, "Feed parser exception: " + Log.getStackTraceString(error));
}
});
} }
/** /**
@ -392,123 +333,23 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
} }
} }
/** private void showFeedFragment(long id) {
* 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);
if (isFeedFoundBySearch) { if (isFeedFoundBySearch) {
int resId = R.string.no_feed_url_podcast_found_by_search; Toast.makeText(this, R.string.no_feed_url_podcast_found_by_search, Toast.LENGTH_LONG).show();
Snackbar.make(findViewById(android.R.id.content), resId, Snackbar.LENGTH_LONG).show();
} }
viewBinding.backgroundImage.setColorFilter(new LightingColorFilter(0xff828282, 0x000000)); viewBinding.progressBar.setVisibility(View.GONE);
FeedItemlistFragment fragment = FeedItemlistFragment.newInstance(id);
viewBinding.listView.addHeaderView(headerBinding.getRoot()); getSupportFragmentManager()
viewBinding.listView.setSelector(android.R.color.transparent); .beginTransaction()
viewBinding.listView.setAdapter(new FeedItemlistDescriptionAdapter(this, 0, feed.getItems())); .replace(R.id.fragmentContainer, fragment, FeedItemlistFragment.TAG)
.commitAllowingStateLoss();
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));
}
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();
} }
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 // feed.getId() is always 0, we have to retrieve the id from the feed list from the database
MainActivityStarter mainActivityStarter = new MainActivityStarter(this); MainActivityStarter mainActivityStarter = new MainActivityStarter(this);
mainActivityStarter.withOpenFeed(getFeedId()); mainActivityStarter.withOpenFeed(feedId);
if (getIntent().getBooleanExtra(ARG_STARTED_FROM_SEARCH, false)) { if (getIntent().getBooleanExtra(ARG_STARTED_FROM_SEARCH, false)) {
mainActivityStarter.withAddToBackStack(); mainActivityStarter.withAddToBackStack();
} }
@ -516,60 +357,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
startActivity(mainActivityStarter.getIntent()); 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 @UiThread
private void showErrorDialog(String errorMsg, String details) { private void showErrorDialog(String errorMsg, String details) {
if (!isFinishing() && !isPaused) { if (!isFinishing() && !isPaused) {
@ -589,7 +376,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
builder.setNeutralButton(R.string.edit_url_menu, (dialog, which) -> editUrl()); builder.setNeutralButton(R.string.edit_url_menu, (dialog, which) -> editUrl());
} }
builder.setOnCancelListener(dialog -> { builder.setOnCancelListener(dialog -> {
setResult(RESULT_ERROR);
finish(); finish();
}); });
if (dialog != null && dialog.isShowing()) { if (dialog != null && dialog.isShowing()) {
@ -608,24 +394,15 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
} }
builder.setView(dialogBinding.getRoot()); builder.setView(dialogBinding.getRoot());
builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
setLoadingLayout();
lookupUrlAndDownload(dialogBinding.urlEditText.getText().toString()); lookupUrlAndDownload(dialogBinding.urlEditText.getText().toString());
}); });
builder.setNegativeButton(R.string.cancel_label, (dialog1, which) -> dialog1.cancel()); builder.setNegativeButton(R.string.cancel_label, (dialog1, which) -> dialog1.cancel());
builder.setOnCancelListener(dialog1 -> { builder.setOnCancelListener(dialog1 -> {
setResult(RESULT_ERROR);
finish(); finish();
}); });
builder.show(); 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). * @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) { if (urls.size() == 1) {
// Skip dialog and display the item directly // Skip dialog and display the item directly
resetIntent(urls.get(0)); resetIntent(urls.get(0));
startFeedDownload(urls.get(0)); downloadIfNotAlreadySubscribed(urls.get(0));
return true; return true;
} }
@ -667,7 +444,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
String selectedUrl = urls.get(which); String selectedUrl = urls.get(which);
dialog.dismiss(); dialog.dismiss();
resetIntent(selectedUrl); resetIntent(selectedUrl);
startFeedDownload(selectedUrl); downloadIfNotAlreadySubscribed(selectedUrl);
}; };
MaterialAlertDialogBuilder ab = new MaterialAlertDialogBuilder(OnlineFeedViewActivity.this) MaterialAlertDialogBuilder ab = new MaterialAlertDialogBuilder(OnlineFeedViewActivity.this)
@ -704,7 +481,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
protected void onConfirmed(String username, String password) { protected void onConfirmed(String username, String password) {
OnlineFeedViewActivity.this.username = username; OnlineFeedViewActivity.this.username = username;
OnlineFeedViewActivity.this.password = password; OnlineFeedViewActivity.this.password = password;
startFeedDownload(feedUrl); downloadIfNotAlreadySubscribed(feedUrl);
} }
} }

View File

@ -25,8 +25,10 @@ import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.elevation.SurfaceColors; 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.playback.service.PlaybackController;
import de.danoeh.antennapod.ui.appstartintent.MediaButtonStarter; 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.chapters.ChapterUtils;
import de.danoeh.antennapod.ui.episodes.PlaybackSpeedUtils; import de.danoeh.antennapod.ui.episodes.PlaybackSpeedUtils;
import de.danoeh.antennapod.ui.episodes.TimeSpeedConverter; import de.danoeh.antennapod.ui.episodes.TimeSpeedConverter;
@ -497,14 +499,25 @@ public class AudioPlayerFragment extends Fragment implements
return true; return true;
} else if (itemId == R.id.open_feed_item) { } else if (itemId == R.id.open_feed_item) {
if (feedItem != null) { if (feedItem != null) {
Intent intent = MainActivity.getIntentToOpenFeed(getContext(), feedItem.getFeedId()); openFeed(feedItem.getFeed());
startActivity(intent);
} }
return true; return true;
} }
return false; 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) { public void fadePlayerToToolbar(float slideOffset) {
float playerFadeProgress = Math.max(0.0f, Math.min(0.2f, slideOffset - 0.2f)) / 0.2f; float playerFadeProgress = Math.max(0.0f, Math.min(0.2f, slideOffset - 0.2f)) / 0.2f;
View player = getView().findViewById(R.id.playerFragment); View player = getView().findViewById(R.id.playerFragment);

View File

@ -32,6 +32,8 @@ import com.bumptech.glide.request.RequestOptions;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity; 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.chapters.ChapterUtils;
import de.danoeh.antennapod.ui.screen.chapter.ChaptersFragment; import de.danoeh.antennapod.ui.screen.chapter.ChaptersFragment;
import de.danoeh.antennapod.playback.service.PlaybackController; import de.danoeh.antennapod.playback.service.PlaybackController;
@ -122,9 +124,7 @@ public class CoverFragment extends Fragment {
+ "\u00A0" + "\u00A0"
+ StringUtils.replace(StringUtils.stripToEmpty(pubDateStr), " ", "\u00A0")); + StringUtils.replace(StringUtils.stripToEmpty(pubDateStr), " ", "\u00A0"));
if (media instanceof FeedMedia) { if (media instanceof FeedMedia) {
Intent openFeed = MainActivity.getIntentToOpenFeed(requireContext(), viewBinding.txtvPodcastTitle.setOnClickListener(v -> openFeed(((FeedMedia) media).getItem().getFeed()));
((FeedMedia) media).getItem().getFeedId());
viewBinding.txtvPodcastTitle.setOnClickListener(v -> startActivity(openFeed));
} else { } else {
viewBinding.txtvPodcastTitle.setOnClickListener(null); viewBinding.txtvPodcastTitle.setOnClickListener(null);
} }
@ -164,6 +164,18 @@ public class CoverFragment extends Fragment {
updateChapterControlVisibility(); 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() { private void updateChapterControlVisibility() {
boolean chapterControlVisible = false; boolean chapterControlVisible = false;
if (media.getChapters() != null) { if (media.getChapters() != null) {

View File

@ -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" />

View File

@ -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" />

View File

@ -129,7 +129,6 @@
android:id="@+id/descriptionLabel" android:id="@+id/descriptionLabel"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/design_time_lorem_ipsum"
android:textIsSelectable="true" android:textIsSelectable="true"
tools:background="@android:color/holo_green_dark" /> tools:background="@android:color/holo_green_dark" />

View File

@ -174,6 +174,20 @@
</LinearLayout> </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 <TextView
android:id="@+id/noMediaLabel" android:id="@+id/noMediaLabel"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -28,6 +28,14 @@
android:layout_width="148dp" android:layout_width="148dp"
android:layout_height="match_parent" /> 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 <ImageButton
android:id="@+id/butShowInfo" android:id="@+id/butShowInfo"
android:layout_width="40dp" android:layout_width="40dp"
@ -178,4 +186,52 @@
tools:visibility="visible" tools:visibility="visible"
tools:text="Updates disabled" /> tools:text="Updates disabled" />
<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> </LinearLayout>

View File

@ -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>

View File

@ -2,7 +2,6 @@
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/transparentBackground" android:id="@+id/transparentBackground"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -11,169 +10,22 @@
android:id="@+id/card" android:id="@+id/card"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_margin="24dp" android:layout_marginHorizontal="16dp"
android:layout_marginVertical="56dp"
android:elevation="16dp" android:elevation="16dp"
app:cardCornerRadius="8dp"> app:cardCornerRadius="8dp">
<FrameLayout <androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent" />
android:orientation="vertical">
<ProgressBar <ProgressBar
android:id="@+id/progressBar" android:id="@+id/progressBar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
style="?android:attr/progressBarStyle" /> 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>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>

View File

@ -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>

View File

@ -15,6 +15,8 @@ import org.apache.commons.lang3.StringUtils;
public class Feed { public class Feed {
public static final int FEEDFILETYPE_FEED = 0; 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_RSS2 = "rss";
public static final String TYPE_ATOM1 = "atom"; public static final String TYPE_ATOM1 = "atom";
public static final String PREFIX_LOCAL_FOLDER = "antennapod_local:"; public static final String PREFIX_LOCAL_FOLDER = "antennapod_local:";
@ -101,6 +103,7 @@ public class Feed {
*/ */
@Nullable @Nullable
private SortOrder sortOrder; private SortOrder sortOrder;
private int state;
/** /**
* This constructor is used for restoring a feed from the database. * 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 description, String paymentLinks, String author, String language,
String type, String feedIdentifier, String imageUrl, String fileUrl, String type, String feedIdentifier, String imageUrl, String fileUrl,
String downloadUrl, long lastRefreshAttempt, boolean paged, String nextPageLink, 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.localFileUrl = fileUrl;
this.downloadUrl = downloadUrl; this.downloadUrl = downloadUrl;
this.lastRefreshAttempt = lastRefreshAttempt; this.lastRefreshAttempt = lastRefreshAttempt;
@ -135,6 +138,7 @@ public class Feed {
} }
setSortOrder(sortOrder); setSortOrder(sortOrder);
this.lastUpdateFailed = lastUpdateFailed; 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 author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl,
String downloadUrl, long lastRefreshAttempt) { String downloadUrl, long lastRefreshAttempt) {
this(id, lastModified, title, null, link, description, paymentLink, author, language, type, feedIdentifier, 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() { public boolean isLocalFeed() {
return downloadUrl.startsWith(PREFIX_LOCAL_FOLDER); return downloadUrl.startsWith(PREFIX_LOCAL_FOLDER);
} }
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
} }

View File

@ -24,6 +24,7 @@ public class FeedItemFilter implements Serializable {
public final boolean showIsFavorite; public final boolean showIsFavorite;
public final boolean showNotFavorite; public final boolean showNotFavorite;
public final boolean showInHistory; public final boolean showInHistory;
public final boolean includeNotSubscribed;
public static final String PLAYED = "played"; public static final String PLAYED = "played";
public static final String UNPLAYED = "unplayed"; 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 DOWNLOADED = "downloaded";
public static final String NOT_DOWNLOADED = "not_downloaded"; public static final String NOT_DOWNLOADED = "not_downloaded";
public static final String IS_IN_HISTORY = "is_in_history"; public static final String IS_IN_HISTORY = "is_in_history";
public static final String INCLUDE_NOT_SUBSCRIBED = "include_not_subscribed";
public static FeedItemFilter unfiltered() { public static FeedItemFilter unfiltered() {
return new FeedItemFilter(""); return new FeedItemFilter("");
@ -66,6 +68,7 @@ public class FeedItemFilter implements Serializable {
showNotFavorite = hasProperty(NOT_FAVORITE); showNotFavorite = hasProperty(NOT_FAVORITE);
showNew = hasProperty(NEW); showNew = hasProperty(NEW);
showInHistory = hasProperty(IS_IN_HISTORY); showInHistory = hasProperty(IS_IN_HISTORY);
includeNotSubscribed = hasProperty(INCLUDE_NOT_SUBSCRIBED);
} }
private boolean hasProperty(String property) { private boolean hasProperty(String property) {
@ -112,6 +115,9 @@ public class FeedItemFilter implements Serializable {
} else if (showInHistory && item.getMedia() != null } else if (showInHistory && item.getMedia() != null
&& item.getMedia().getPlaybackCompletionDate().getTime() == 0) { && item.getMedia().getPlaybackCompletionDate().getTime() == 0) {
return false; return false;
} else if (!includeNotSubscribed && item.getFeed() != null
&& item.getFeed().getState() != Feed.STATE_SUBSCRIBED) {
return false;
} }
return true; return true;
} }

View File

@ -12,6 +12,7 @@ public class SubscriptionsFilter {
private final String[] properties; private final String[] properties;
private boolean showIfCounterGreaterZero = false; private boolean showIfCounterGreaterZero = false;
private boolean hideNonSubscribedFeeds = true;
private boolean showAutoDownloadEnabled = false; private boolean showAutoDownloadEnabled = false;
private boolean showAutoDownloadDisabled = false; private boolean showAutoDownloadDisabled = false;
@ -67,10 +68,6 @@ public class SubscriptionsFilter {
* Run a list of feed items through the filter. * Run a list of feed items through the filter.
*/ */
public List<Feed> filter(List<Feed> items, Map<Long, Integer> feedCounters) { public List<Feed> filter(List<Feed> items, Map<Long, Integer> feedCounters) {
if (properties.length == 0) {
return items;
}
List<Feed> result = new ArrayList<>(); List<Feed> result = new ArrayList<>();
for (Feed item : items) { for (Feed item : items) {
@ -95,6 +92,10 @@ public class SubscriptionsFilter {
continue; continue;
} }
if (hideNonSubscribedFeeds && item.getState() != Feed.STATE_SUBSCRIBED) {
continue;
}
// If the item reaches here, it meets all criteria (except counter > 0) // If the item reaches here, it meets all criteria (except counter > 0)
result.add(item); result.add(item);
} }

View File

@ -58,7 +58,8 @@ public class ItunesTopListLoader {
List<PodcastSearchResult> suggestedPodcasts, List<Feed> subscribedFeeds, int limit) { List<PodcastSearchResult> suggestedPodcasts, List<Feed> subscribedFeeds, int limit) {
Set<String> subscribedPodcastsSet = new HashSet<>(); Set<String> subscribedPodcastsSet = new HashSet<>();
for (Feed subscribedFeed : subscribedFeeds) { 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()); subscribedPodcastsSet.add(subscribedFeed.getTitle().trim() + " - " + subscribedFeed.getAuthor().trim());
} }
} }

View File

@ -7,6 +7,7 @@ import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import de.danoeh.antennapod.model.MediaMetadataRetrieverCompat; 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.net.sync.serviceinterface.SynchronizationQueueSink;
import de.danoeh.antennapod.ui.chapters.ChapterUtils; import de.danoeh.antennapod.ui.chapters.ChapterUtils;
import org.greenrobot.eventbus.EventBus; 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()); 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) EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DOWNLOAD)
.currentTimestamp() .currentTimestamp()
.build(); .build();

View File

@ -35,6 +35,7 @@ import de.danoeh.antennapod.model.download.DownloadRequest;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequestBuilder; import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequestBuilder;
import de.danoeh.antennapod.parser.feed.FeedHandlerResult; import de.danoeh.antennapod.parser.feed.FeedHandlerResult;
import de.danoeh.antennapod.storage.database.NonSubscribedFeedsCleaner;
import de.danoeh.antennapod.ui.notifications.NotificationUtils; import de.danoeh.antennapod.ui.notifications.NotificationUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -69,7 +70,7 @@ public class FeedUpdateWorker extends Worker {
Iterator<Feed> itr = toUpdate.iterator(); Iterator<Feed> itr = toUpdate.iterator();
while (itr.hasNext()) { while (itr.hasNext()) {
Feed feed = itr.next(); Feed feed = itr.next();
if (!feed.getPreferences().getKeepUpdated()) { if (!feed.getPreferences().getKeepUpdated() || feed.getState() != Feed.STATE_SUBSCRIBED) {
itr.remove(); itr.remove();
continue; continue;
} }
@ -99,8 +100,9 @@ public class FeedUpdateWorker extends Worker {
} }
refreshFeeds(toUpdate, force); refreshFeeds(toUpdate, force);
notificationManager.cancel(R.id.notification_updating_feeds); NonSubscribedFeedsCleaner.deleteOldNonSubscribedFeeds(getApplicationContext());
AutoDownloadManager.getInstance().autodownloadUndownloadedItems(getApplicationContext()); AutoDownloadManager.getInstance().autodownloadUndownloadedItems(getApplicationContext());
notificationManager.cancel(R.id.notification_updating_feeds);
return Result.success(); return Result.success();
} }

View File

@ -2,6 +2,7 @@ package de.danoeh.antennapod.net.sync.serviceinterface;
import android.content.Context; import android.content.Context;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.storage.preferences.SynchronizationSettings; import de.danoeh.antennapod.storage.preferences.SynchronizationSettings;
import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.model.feed.FeedMedia;
@ -62,7 +63,8 @@ public class SynchronizationQueueSink {
if (!SynchronizationSettings.isProviderConnected()) { if (!SynchronizationSettings.isProviderConnected()) {
return; 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; return;
} }
if (media.getStartPosition() < 0 || (!completed && media.getStartPosition() >= media.getPosition())) { if (media.getStartPosition() < 0 || (!completed && media.getStartPosition() >= media.getPosition())) {

View File

@ -436,7 +436,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
DBReader.getTotalEpisodeCount(new FeedItemFilter(FeedItemFilter.UNPLAYED)))); DBReader.getTotalEpisodeCount(new FeedItemFilter(FeedItemFilter.UNPLAYED))));
List<Feed> feeds = DBReader.getFeedList(); List<Feed> feeds = DBReader.getFeedList();
for (Feed feed : feeds) { for (Feed feed : feeds) {
mediaItems.add(createBrowsableMediaItemForFeed(feed)); if (feed.getState() == Feed.STATE_SUBSCRIBED) {
mediaItems.add(createBrowsableMediaItemForFeed(feed));
}
} }
return mediaItems; return mediaItems;
} }

View File

@ -1,6 +1,7 @@
package de.danoeh.antennapod.storage.database; package de.danoeh.antennapod.storage.database;
import android.database.Cursor; import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -357,6 +358,8 @@ public final class DBReader {
if (cursor.moveToNext()) { if (cursor.moveToNext()) {
feed = cursor.getFeed(); feed = cursor.getFeed();
FeedItemFilter filter = filtered ? feed.getItemFilter() : FeedItemFilter.unfiltered(); FeedItemFilter filter = filtered ? feed.getItemFilter() : FeedItemFilter.unfiltered();
filter = new FeedItemFilter(
TextUtils.join(",", filter.getValues()) + "," + FeedItemFilter.INCLUDE_NOT_SUBSCRIBED);
List<FeedItem> items = getFeedItemList(feed, filter, feed.getSortOrder(), offset, limit); List<FeedItem> items = getFeedItemList(feed, filter, feed.getSortOrder(), offset, limit);
for (FeedItem item : items) { for (FeedItem item : items) {
item.setFeed(feed); item.setFeed(feed);
@ -668,9 +671,10 @@ public final class DBReader {
final Map<Long, Integer> feedCounters = adapter.getFeedCounters(feedCounter); final Map<Long, Integer> feedCounters = adapter.getFeedCounters(feedCounter);
List<Feed> feeds = getFeedList(); List<Feed> feeds = getFeedList();
if (subscriptionsFilter != null) { if (subscriptionsFilter == null) {
feeds = subscriptionsFilter.filter(feeds, feedCounters); subscriptionsFilter = new SubscriptionsFilter("");
} }
feeds = subscriptionsFilter.filter(feeds, feedCounters);
Comparator<Feed> comparator; Comparator<Feed> comparator;
switch (feedOrder) { switch (feedOrder) {

View File

@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.media.MediaMetadataRetriever; import android.media.MediaMetadataRetriever;
import android.util.Log; import android.util.Log;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedItem;
import static de.danoeh.antennapod.model.feed.FeedPreferences.SPEED_USE_GLOBAL; 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 db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_FEED_SKIP_SILENCE + " INTEGER"); + " 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);
}
} }
} }

View File

@ -14,6 +14,7 @@ import androidx.documentfile.provider.DocumentFile;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import de.danoeh.antennapod.event.DownloadLogEvent; 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.AutoDownloadManager;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface; import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager; 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 // Do full update of this feed to get rid of the item
FeedUpdateManager.getInstance().runOnce(context, media.getItem().getFeed()); FeedUpdateManager.getInstance().runOnce(context, media.getItem().getFeed());
} else { } else {
// Gpodder: queue delete action for synchronization if (media.getItem().getFeed().getState() == Feed.STATE_SUBSCRIBED) {
FeedItem item = media.getItem(); FeedItem item = media.getItem();
EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DELETE) EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DELETE)
.currentTimestamp() .currentTimestamp()
.build(); .build();
SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action); SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action);
}
EventBus.getDefault().post(FeedItemEvent.updated(media.getItem())); EventBus.getDefault().post(FeedItemEvent.updated(media.getItem()));
} }
@ -174,7 +176,7 @@ public class DBWriter {
adapter.removeFeed(feed); adapter.removeFeed(feed);
adapter.close(); adapter.close();
if (!feed.isLocalFeed()) { if (!feed.isLocalFeed() && feed.getState() == Feed.STATE_SUBSCRIBED) {
SynchronizationQueueSink.enqueueFeedRemovedIfSynchronizationIsActive(context, feed.getDownloadUrl()); SynchronizationQueueSink.enqueueFeedRemovedIfSynchronizationIsActive(context, feed.getDownloadUrl());
} }
EventBus.getDefault().post(new FeedListUpdateEvent(feed)); EventBus.getDefault().post(new FeedListUpdateEvent(feed));
@ -786,7 +788,7 @@ public class DBWriter {
adapter.close(); adapter.close();
for (Feed feed : feeds) { for (Feed feed : feeds) {
if (!feed.isLocalFeed()) { if (!feed.isLocalFeed() && feed.getState() == Feed.STATE_SUBSCRIBED) {
SynchronizationQueueSink.enqueueFeedAddedIfSynchronizationIsActive(context, feed.getDownloadUrl()); 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. * Sort the FeedItems in the queue with the given the named sort order.
* *

View File

@ -157,7 +157,8 @@ public abstract class FeedDatabaseWriter {
+ "\n\nNow the feed contains:\n" + duplicateEpisodeDetails(item))); + "\n\nNow the feed contains:\n" + duplicateEpisodeDetails(item)));
oldItem.setItemIdentifier(item.getItemIdentifier()); 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) EpisodeAction action = new EpisodeAction.Builder(oldItem, EpisodeAction.PLAY)
.currentTimestamp() .currentTimestamp()
.started(oldItem.getMedia().getDuration() / 1000) .started(oldItem.getMedia().getDuration() / 1000)

View File

@ -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;
}
}

View File

@ -52,7 +52,7 @@ public class PodDBAdapter {
private static final String TAG = "PodDBAdapter"; private static final String TAG = "PodDBAdapter";
public static final String DATABASE_NAME = "Antennapod.db"; 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. * 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_EPISODE_NOTIFICATION = "episode_notification";
public static final String KEY_NEW_EPISODES_ACTION = "new_episodes_action"; 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_PODCASTINDEX_CHAPTER_URL = "podcastindex_chapter_url";
public static final String KEY_STATE = "state";
// Table names // Table names
public static final String TABLE_NAME_FEEDS = "Feeds"; 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_INTRO + " INTEGER DEFAULT 0,"
+ KEY_FEED_SKIP_ENDING + " INTEGER DEFAULT 0," + KEY_FEED_SKIP_ENDING + " INTEGER DEFAULT 0,"
+ KEY_EPISODE_NOTIFICATION + " INTEGER DEFAULT 0," + KEY_EPISODE_NOTIFICATION + " INTEGER DEFAULT 0,"
+ KEY_STATE + " INTEGER DEFAULT " + Feed.STATE_SUBSCRIBED + ","
+ KEY_NEW_EPISODES_ACTION + " INTEGER DEFAULT 0)"; + KEY_NEW_EPISODES_ACTION + " INTEGER DEFAULT 0)";
private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE " 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_INTRO + ", "
+ TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_ENDING + ", " + TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_ENDING + ", "
+ TABLE_NAME_FEEDS + "." + KEY_EPISODE_NOTIFICATION + ", " + TABLE_NAME_FEEDS + "." + KEY_EPISODE_NOTIFICATION + ", "
+ TABLE_NAME_FEEDS + "." + KEY_STATE + ", "
+ TABLE_NAME_FEEDS + "." + KEY_NEW_EPISODES_ACTION; + TABLE_NAME_FEEDS + "." + KEY_NEW_EPISODES_ACTION;
private static final String JOIN_FEED_ITEM_AND_MEDIA = " LEFT JOIN " + TABLE_NAME_FEED_MEDIA 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 "SELECT " + KEYS_FEED_ITEM_WITHOUT_DESCRIPTION + ", " + KEYS_FEED_MEDIA
+ " FROM " + TABLE_NAME_FEED_ITEMS + " FROM " + TABLE_NAME_FEED_ITEMS
+ JOIN_FEED_ITEM_AND_MEDIA; + 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 Context context;
private static PodDBAdapter instance; private static PodDBAdapter instance;
@ -431,6 +437,7 @@ public class PodDBAdapter {
values.put(KEY_LASTUPDATE, feed.getLastModified()); values.put(KEY_LASTUPDATE, feed.getLastModified());
values.put(KEY_TYPE, feed.getType()); values.put(KEY_TYPE, feed.getType());
values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier()); values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier());
values.put(KEY_STATE, feed.getState());
values.put(KEY_IS_PAGED, feed.isPaged()); values.put(KEY_IS_PAGED, feed.isPaged());
values.put(KEY_NEXT_PAGE_LINK, feed.getNextPageLink()); 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)}); 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. * Inserts or updates a download status.
*/ */
@ -1101,6 +1114,7 @@ public class PodDBAdapter {
// Hide episodes that have been played but not completed // Hide episodes that have been played but not completed
+ " AND (" + KEY_LAST_PLAYED_TIME + " == 0" + " AND (" + KEY_LAST_PLAYED_TIME + " == 0"
+ " OR " + KEY_LAST_PLAYED_TIME + " > " + (System.currentTimeMillis() - 1000L * 3600L) + ")" + " OR " + KEY_LAST_PLAYED_TIME + " > " + (System.currentTimeMillis() - 1000L * 3600L) + ")"
+ " AND " + SELECT_WHERE_FEED_IS_SUBSCRIBED
+ " ORDER BY " + randomEpisodeNumber(seed); + " ORDER BY " + randomEpisodeNumber(seed);
final String query = "SELECT * FROM (" + allItemsRandomOrder + ")" final String query = "SELECT * FROM (" + allItemsRandomOrder + ")"
+ " GROUP BY " + KEY_FEED + " GROUP BY " + KEY_FEED
@ -1221,6 +1235,7 @@ public class PodDBAdapter {
+ JOIN_FEED_ITEM_AND_MEDIA + JOIN_FEED_ITEM_AND_MEDIA
+ " INNER JOIN " + TABLE_NAME_FEEDS + " INNER JOIN " + TABLE_NAME_FEEDS
+ " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID + " 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; + " GROUP BY " + TABLE_NAME_FEEDS + "." + KEY_ID;
return db.rawQuery(query, null); return db.rawQuery(query, null);
} }
@ -1372,7 +1387,7 @@ public class PodDBAdapter {
} }
String queryStart = SELECT_FEED_ITEMS_AND_MEDIA_WITH_DESCRIPTION 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); StringBuilder sb = new StringBuilder(queryStart);
for (int i = 0; i < queryWords.length; i++) { for (int i = 0; i < queryWords.length; i++) {
@ -1401,12 +1416,13 @@ public class PodDBAdapter {
public Cursor searchFeeds(String searchQuery) { public Cursor searchFeeds(String searchQuery) {
String[] queryWords = prepareSearchQuery(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); StringBuilder sb = new StringBuilder(queryStart);
for (int i = 0; i < queryWords.length; i++) { for (int i = 0; i < queryWords.length; i++) {
sb sb
.append("(") .append(" AND (")
.append(KEY_TITLE).append(" LIKE '%").append(queryWords[i]) .append(KEY_TITLE).append(" LIKE '%").append(queryWords[i])
.append("%' OR ") .append("%' OR ")
.append(KEY_CUSTOM_TITLE).append(" LIKE '%").append(queryWords[i]) .append(KEY_CUSTOM_TITLE).append(" LIKE '%").append(queryWords[i])
@ -1415,13 +1431,9 @@ public class PodDBAdapter {
.append("%' OR ") .append("%' OR ")
.append(KEY_DESCRIPTION).append(" LIKE '%").append(queryWords[i]) .append(KEY_DESCRIPTION).append(" LIKE '%").append(queryWords[i])
.append("%') "); .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); return db.rawQuery(sb.toString(), null);
} }

View File

@ -34,6 +34,7 @@ public class FeedCursor extends CursorWrapper {
private final int indexSortOrder; private final int indexSortOrder;
private final int indexLastUpdateFailed; private final int indexLastUpdateFailed;
private final int indexImageUrl; private final int indexImageUrl;
private final int indexState;
public FeedCursor(Cursor cursor) { public FeedCursor(Cursor cursor) {
super(new FeedPreferencesCursor(cursor)); super(new FeedPreferencesCursor(cursor));
@ -58,6 +59,7 @@ public class FeedCursor extends CursorWrapper {
indexSortOrder = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_SORT_ORDER); indexSortOrder = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_SORT_ORDER);
indexLastUpdateFailed = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_LAST_UPDATE_FAILED); indexLastUpdateFailed = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_LAST_UPDATE_FAILED);
indexImageUrl = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_IMAGE_URL); 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(indexNextPageLink),
getString(indexHide), getString(indexHide),
SortOrder.fromCodeString(getString(indexSortOrder)), SortOrder.fromCodeString(getString(indexSortOrder)),
getInt(indexLastUpdateFailed) > 0); getInt(indexLastUpdateFailed) > 0,
getInt(indexState));
feed.setPreferences(preferencesCursor.getFeedPreferences()); feed.setPreferences(preferencesCursor.getFeedPreferences());
return feed; return feed;
} }

View File

@ -66,6 +66,9 @@ public class FeedItemFilterQuery {
if (filter.showInHistory) { if (filter.showInHistory) {
statements.add(keyCompletionDate + " > 0 "); statements.add(keyCompletionDate + " > 0 ");
} }
if (!filter.includeNotSubscribed) {
statements.add(PodDBAdapter.SELECT_WHERE_FEED_IS_SUBSCRIBED);
}
if (statements.isEmpty()) { if (statements.isEmpty()) {
return ""; return "";

View File

@ -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;
}
}

View File

@ -28,6 +28,9 @@ public class HtmlWriter {
writer.append(templateParts[0]); writer.append(templateParts[0]);
for (Feed feed : feeds) { for (Feed feed : feeds) {
if (feed.getState() != Feed.STATE_SUBSCRIBED) {
continue;
}
writer.append("<li><div><img src=\""); writer.append("<li><div><img src=\"");
writer.append(feed.getImageUrl()); writer.append(feed.getImageUrl());
writer.append("\" /><p>"); writer.append("\" /><p>");

View File

@ -48,6 +48,9 @@ public class OpmlWriter {
xs.startTag(null, OpmlSymbols.BODY); xs.startTag(null, OpmlSymbols.BODY);
for (Feed feed : feeds) { for (Feed feed : feeds) {
if (feed.getState() != Feed.STATE_SUBSCRIBED) {
continue;
}
xs.startTag(null, OpmlSymbols.OUTLINE); xs.startTag(null, OpmlSymbols.OUTLINE);
xs.attribute(null, OpmlSymbols.TEXT, feed.getTitle()); xs.attribute(null, OpmlSymbols.TEXT, feed.getTitle());
xs.attribute(null, OpmlSymbols.TITLE, feed.getTitle()); xs.attribute(null, OpmlSymbols.TITLE, feed.getTitle());

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -8,7 +8,6 @@
<color name="medium_gray">#afafaf</color> <color name="medium_gray">#afafaf</color>
<color name="black">#000000</color> <color name="black">#000000</color>
<color name="image_readability_tint">#80000000</color> <color name="image_readability_tint">#80000000</color>
<color name="feed_image_bg">#50000000</color>
<color name="feed_text_bg">#55333333</color> <color name="feed_text_bg">#55333333</color>
<!-- Theme colors --> <!-- Theme colors -->

View File

@ -7,8 +7,6 @@
<dimen name="text_size_large">22sp</dimen> <dimen name="text_size_large">22sp</dimen>
<dimen name="thumbnail_length_itemlist">56dp</dimen> <dimen name="thumbnail_length_itemlist">56dp</dimen>
<dimen name="thumbnail_length_queue_item">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="thumbnail_length_navlist">40dp</dimen>
<dimen name="listitem_iconwithtext_height">48dp</dimen> <dimen name="listitem_iconwithtext_height">48dp</dimen>
<dimen name="listitem_iconwithtext_textleftpadding">16dp</dimen> <dimen name="listitem_iconwithtext_textleftpadding">16dp</dimen>

View File

@ -12,6 +12,7 @@
<style name="Theme.Base.AntennaPod.Dynamic.Light" parent="Theme.Material3.DynamicColors.Light"> <style name="Theme.Base.AntennaPod.Dynamic.Light" parent="Theme.Material3.DynamicColors.Light">
<item name="progressBarTheme">@style/ProgressBarLight</item> <item name="progressBarTheme">@style/ProgressBarLight</item>
<item name="background_color">@color/background_light</item> <item name="background_color">@color/background_light</item>
<item name="actionBarStyle">@style/Widget.AntennaPod.ActionBar</item>
<item name="background_elevated">@color/background_elevated_light</item> <item name="background_elevated">@color/background_elevated_light</item>
<item name="action_icon_color">@color/black</item> <item name="action_icon_color">@color/black</item>
<item name="android:textAllCaps">false</item> <item name="android:textAllCaps">false</item>
@ -54,6 +55,7 @@
<item name="background_color">@color/background_darktheme</item> <item name="background_color">@color/background_darktheme</item>
<item name="background_elevated">@color/background_elevated_darktheme</item> <item name="background_elevated">@color/background_elevated_darktheme</item>
<item name="action_icon_color">@color/white</item> <item name="action_icon_color">@color/white</item>
<item name="actionBarStyle">@style/Widget.AntennaPod.ActionBar</item>
<item name="android:textAllCaps">false</item> <item name="android:textAllCaps">false</item>
<item name="seek_background">@color/seek_background_dark</item> <item name="seek_background">@color/seek_background_dark</item>
<item name="dragview_background">@drawable/ic_drag_darktheme</item> <item name="dragview_background">@drawable/ic_drag_darktheme</item>
@ -289,6 +291,11 @@
<item name="fastScrollVerticalTrackDrawable">@drawable/scrollbar_track</item> <item name="fastScrollVerticalTrackDrawable">@drawable/scrollbar_track</item>
</style> </style>
<style name="Widget.AntennaPod.ActionBar" parent="Widget.Material3.Light.ActionBar.Solid">
<item name="background">?android:attr/colorBackground</item>
<item name="elevation">0dp</item>
</style>
<style name="AddPodcastTextView"> <style name="AddPodcastTextView">
<item name="android:drawablePadding">8dp</item> <item name="android:drawablePadding">8dp</item>
<item name="android:paddingTop">8dp</item> <item name="android:paddingTop">8dp</item>

View File

@ -681,9 +681,8 @@
<!-- Online feed view --> <!-- Online feed view -->
<string name="subscribe_label">Subscribe</string> <string name="subscribe_label">Subscribe</string>
<string name="subscribing_label">Subscribing&#8230;</string> <string name="preview_episodes">Episodes preview</string>
<string name="preview_episode">Preview</string> <string name="state_deleted_not_subscribed">Data like playback state of non-subscribed podcasts is deleted after a while. Subscribe to keep it.</string>
<string name="stop_preview">Stop preview</string>
<!-- Content descriptions for image buttons --> <!-- Content descriptions for image buttons -->
<string name="toolbar_back_button_content_description">Back</string> <string name="toolbar_back_button_content_description">Back</string>