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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -122,7 +122,6 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
viewBinding.header.butFilter.setVisibility(View.INVISIBLE);
// https://github.com/bumptech/glide/issues/529
viewBinding.imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000));
viewBinding.urlLabel.setOnClickListener(copyUrlToClipboard);
long feedId = getArguments().getLong(EXTRA_FEED_ID);
@ -237,6 +236,24 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
viewBinding.supportUrl.setText(str.toString());
}
if (feed.getState() == Feed.STATE_SUBSCRIBED) {
long feedId = getArguments().getLong(EXTRA_FEED_ID);
getParentFragmentManager().beginTransaction().replace(R.id.statisticsFragmentContainer,
FeedStatisticsFragment.newInstance(feedId, false), "feed_statistics_fragment")
.commitAllowingStateLoss();
viewBinding.statisticsButton.setOnClickListener(view -> {
StatisticsFragment fragment = new StatisticsFragment();
((MainActivity) getActivity()).loadChildFragment(fragment, TransitionEffect.SLIDE);
});
} else {
viewBinding.statisticsButton.setVisibility(View.GONE);
viewBinding.statisticsFragmentContainer.setVisibility(View.GONE);
viewBinding.statisticsHeadingLabel.setVisibility(View.GONE);
viewBinding.supportHeadingLabel.setVisibility(View.GONE);
viewBinding.supportUrl.setVisibility(View.GONE);
}
refreshToolbarState();
}
@ -249,13 +266,14 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
}
private void refreshToolbarState() {
boolean isSubscribed = feed != null && feed.getState() == Feed.STATE_SUBSCRIBED;
viewBinding.toolbar.getMenu().findItem(R.id.reconnect_local_folder).setVisible(
feed != null && feed.isLocalFeed());
viewBinding.toolbar.getMenu().findItem(R.id.share_item).setVisible(feed != null && !feed.isLocalFeed());
viewBinding.toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(feed != null
isSubscribed && feed.isLocalFeed());
viewBinding.toolbar.getMenu().findItem(R.id.share_item).setVisible(isSubscribed && !feed.isLocalFeed());
viewBinding.toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(isSubscribed
&& feed.getLink() != null
&& IntentUtils.isCallable(getContext(), new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink()))));
viewBinding.toolbar.getMenu().findItem(R.id.edit_feed_url_item).setVisible(feed != null && !feed.isLocalFeed());
viewBinding.toolbar.getMenu().findItem(R.id.edit_feed_url_item).setVisible(isSubscribed && !feed.isLocalFeed());
}
@Override

View File

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

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.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.LightingColorFilter;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableString;
@ -13,68 +11,45 @@ import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.appcompat.app.AppCompatActivity;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.net.download.service.feed.remote.Downloader;
import de.danoeh.antennapod.net.download.service.feed.remote.HttpDownloader;
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
import de.danoeh.antennapod.ui.common.ThemeSwitcher;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequestCreator;
import de.danoeh.antennapod.net.discovery.FeedUrlNotFoundException;
import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
import de.danoeh.antennapod.playback.service.PlaybackServiceInterface;
import de.danoeh.antennapod.ui.screen.download.DownloadErrorLabel;
import de.danoeh.antennapod.databinding.EditTextDialogBinding;
import de.danoeh.antennapod.databinding.OnlinefeedviewHeaderBinding;
import de.danoeh.antennapod.event.EpisodeDownloadEvent;
import de.danoeh.antennapod.event.FeedListUpdateEvent;
import de.danoeh.antennapod.event.PlayerStatusEvent;
import de.danoeh.antennapod.storage.preferences.PlaybackPreferences;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.databinding.OnlinefeedviewActivityBinding;
import de.danoeh.antennapod.model.download.DownloadError;
import de.danoeh.antennapod.model.download.DownloadRequest;
import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.net.common.UrlChecker;
import de.danoeh.antennapod.net.discovery.CombinedSearcher;
import de.danoeh.antennapod.net.discovery.FeedUrlNotFoundException;
import de.danoeh.antennapod.net.discovery.PodcastSearchResult;
import de.danoeh.antennapod.net.discovery.PodcastSearcherRegistry;
import de.danoeh.antennapod.net.download.service.feed.remote.Downloader;
import de.danoeh.antennapod.net.download.service.feed.remote.HttpDownloader;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequestCreator;
import de.danoeh.antennapod.parser.feed.FeedHandler;
import de.danoeh.antennapod.parser.feed.FeedHandlerResult;
import de.danoeh.antennapod.model.download.DownloadError;
import de.danoeh.antennapod.ui.common.IntentUtils;
import de.danoeh.antennapod.net.common.UrlChecker;
import de.danoeh.antennapod.ui.cleaner.HtmlToPlainText;
import de.danoeh.antennapod.databinding.OnlinefeedviewActivityBinding;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedPreferences;
import de.danoeh.antennapod.model.playback.RemoteMedia;
import de.danoeh.antennapod.parser.feed.UnsupportedFeedtypeException;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
import de.danoeh.antennapod.ui.common.ThemeSwitcher;
import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.ui.glide.FastBlurTransformation;
import de.danoeh.antennapod.ui.preferences.screen.synchronization.AuthenticationDialog;
import de.danoeh.antennapod.ui.screen.download.DownloadErrorLabel;
import de.danoeh.antennapod.ui.screen.feed.FeedItemlistFragment;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.observers.DisposableMaybeObserver;
import io.reactivex.schedulers.Schedulers;
import org.apache.commons.lang3.StringUtils;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.io.File;
import java.io.IOException;
@ -95,30 +70,16 @@ import static de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStart
* and the activity will finish as soon as the error dialog is closed.
*/
public class OnlineFeedViewActivity extends AppCompatActivity {
private static final int RESULT_ERROR = 2;
private static final String TAG = "OnlineFeedViewActivity";
private static final String PREFS = "OnlineFeedViewActivityPreferences";
private static final String PREF_LAST_AUTO_DOWNLOAD = "lastAutoDownload";
private static final int DESCRIPTION_MAX_LINES_COLLAPSED = 4;
private volatile List<Feed> feeds;
private String selectedDownloadUrl;
private Downloader downloader;
private String username = null;
private String password = null;
private boolean isPaused;
private boolean didPressSubscribe = false;
private boolean isFeedFoundBySearch = false;
private Dialog dialog;
private Disposable download;
private Disposable parser;
private Disposable updater;
private OnlinefeedviewHeaderBinding headerBinding;
private OnlinefeedviewActivityBinding viewBinding;
@Override
@ -128,12 +89,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
viewBinding = OnlinefeedviewActivityBinding.inflate(getLayoutInflater());
setContentView(viewBinding.getRoot());
viewBinding.transparentBackground.setOnClickListener(v -> finish());
viewBinding.closeButton.setOnClickListener(view -> finish());
viewBinding.card.setOnClickListener(null);
viewBinding.card.setCardBackgroundColor(ThemeUtils.getColorFromAttr(this, R.attr.colorSurface));
headerBinding = OnlinefeedviewHeaderBinding.inflate(getLayoutInflater());
String feedUrl = null;
if (getIntent().hasExtra(ARG_FEEDURL)) {
@ -149,7 +107,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
showNoPodcastFoundError();
} else {
Log.d(TAG, "Activity was started with url " + feedUrl);
setLoadingLayout();
// Remove subscribeonandroid.com from feed URL in order to subscribe to the actual feed URL
if (feedUrl.contains("subscribeonandroid.com")) {
feedUrl = feedUrl.replaceFirst("((www.)?(subscribeonandroid.com/))", "");
@ -167,33 +124,20 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
.setNeutralButton(android.R.string.ok, (dialog, which) -> finish())
.setTitle(R.string.error_label)
.setMessage(R.string.null_value_podcast_error)
.setOnDismissListener(dialog1 -> {
setResult(RESULT_ERROR);
finish();
})
.setOnDismissListener(dialog1 -> finish())
.show());
}
/**
* Displays a progress indicator.
*/
private void setLoadingLayout() {
viewBinding.progressBar.setVisibility(View.VISIBLE);
viewBinding.feedDisplayContainer.setVisibility(View.GONE);
}
@Override
protected void onStart() {
super.onStart();
isPaused = false;
EventBus.getDefault().register(this);
}
@Override
protected void onStop() {
super.onStop();
isPaused = true;
EventBus.getDefault().unregister(this);
if (downloader != null && !downloader.isFinished()) {
downloader.cancel();
}
@ -205,9 +149,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
@Override
public void onDestroy() {
super.onDestroy();
if(updater != null) {
updater.dispose();
}
if(download != null) {
download.dispose();
}
@ -239,7 +180,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
download = PodcastSearcherRegistry.lookupUrl(url)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(this::startFeedDownload,
.subscribe(this::downloadIfNotAlreadySubscribed,
error -> {
if (error instanceof FeedUrlNotFoundException) {
tryToRetrieveFeedUrlBySearch((FeedUrlNotFoundException) error);
@ -256,7 +197,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
if (url != null) {
Log.d(TAG, "Successfully retrieve feed url");
isFeedFoundBySearch = true;
startFeedDownload(url);
downloadIfNotAlreadySubscribed(url);
} else {
showNoPodcastFoundError();
Log.d(TAG, "Failed to retrieve feed url");
@ -277,6 +218,28 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
return null;
}
private Feed downloadIfNotAlreadySubscribed(String url) {
download = Maybe.fromCallable(() -> {
List<Feed> feeds = DBReader.getFeedList();
for (Feed f : feeds) {
if (f.getDownloadUrl().equals(url)) {
return f;
}
}
return null;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscribedFeed -> {
if (subscribedFeed.getState() == Feed.STATE_SUBSCRIBED) {
openFeed(subscribedFeed.getId());
} else {
showFeedFragment(subscribedFeed.getId());
}
}, error -> Log.e(TAG, Log.getStackTraceString(error)), () -> startFeedDownload(url));
return null;
}
private void startFeedDownload(String url) {
Log.d(TAG, "Starting feed download");
selectedDownloadUrl = UrlChecker.prepareUrl(url);
@ -286,7 +249,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
.build();
download = Observable.fromCallable(() -> {
feeds = DBReader.getFeedList();
downloader = new HttpDownloader(request);
downloader.call();
return downloader.getResult();
@ -315,46 +277,25 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
}
}
@Subscribe
public void onFeedListChanged(FeedListUpdateEvent event) {
updater = Observable.fromCallable(DBReader::getFeedList)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
feeds -> {
OnlineFeedViewActivity.this.feeds = feeds;
handleUpdatedFeedStatus();
}, error -> Log.e(TAG, Log.getStackTraceString(error))
);
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventMainThread(EpisodeDownloadEvent event) {
handleUpdatedFeedStatus();
}
private void parseFeed(String destination) {
Log.d(TAG, "Parsing feed");
parser = Maybe.fromCallable(() -> doParseFeed(destination))
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableMaybeObserver<FeedHandlerResult>() {
@Override
public void onSuccess(@NonNull FeedHandlerResult result) {
showFeedInformation(result.feed, result.alternateFeedUrls);
}
@Override
public void onComplete() {
// Ignore null result: We showed the discovery dialog.
}
@Override
public void onError(@NonNull Throwable error) {
showErrorDialog(error.getMessage(), "");
Log.d(TAG, "Feed parser exception: " + Log.getStackTraceString(error));
}
});
parser = Observable.fromCallable(() -> {
FeedHandlerResult handlerResult = doParseFeed(destination);
Feed feed = handlerResult.feed;
feed.setState(Feed.STATE_NOT_SUBSCRIBED);
feed.setLastRefreshAttempt(System.currentTimeMillis());
FeedDatabaseWriter.updateFeed(this, feed, false);
Feed feedFromDb = DBReader.getFeed(feed.getId(), false, 0, Integer.MAX_VALUE);
feedFromDb.getPreferences().setKeepUpdated(false);
DBWriter.setFeedPreferences(feedFromDb.getPreferences());
return feed.getId();
})
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::showFeedFragment, error -> {
error.printStackTrace();
showErrorDialog(error.getMessage(), "");
});
}
/**
@ -392,123 +333,23 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
}
}
/**
* Called when feed parsed successfully.
* This method is executed on the GUI thread.
*/
private void showFeedInformation(final Feed feed, Map<String, String> alternateFeedUrls) {
viewBinding.progressBar.setVisibility(View.GONE);
viewBinding.feedDisplayContainer.setVisibility(View.VISIBLE);
private void showFeedFragment(long id) {
if (isFeedFoundBySearch) {
int resId = R.string.no_feed_url_podcast_found_by_search;
Snackbar.make(findViewById(android.R.id.content), resId, Snackbar.LENGTH_LONG).show();
Toast.makeText(this, R.string.no_feed_url_podcast_found_by_search, Toast.LENGTH_LONG).show();
}
viewBinding.backgroundImage.setColorFilter(new LightingColorFilter(0xff828282, 0x000000));
viewBinding.listView.addHeaderView(headerBinding.getRoot());
viewBinding.listView.setSelector(android.R.color.transparent);
viewBinding.listView.setAdapter(new FeedItemlistDescriptionAdapter(this, 0, feed.getItems()));
if (StringUtils.isNotBlank(feed.getImageUrl())) {
Glide.with(this)
.load(feed.getImageUrl())
.apply(new RequestOptions()
.placeholder(R.color.light_gray)
.error(R.color.light_gray)
.fitCenter()
.dontAnimate())
.into(viewBinding.coverImage);
Glide.with(this)
.load(feed.getImageUrl())
.apply(new RequestOptions()
.placeholder(R.color.image_readability_tint)
.error(R.color.image_readability_tint)
.transform(new FastBlurTransformation())
.dontAnimate())
.into(viewBinding.backgroundImage);
}
viewBinding.titleLabel.setText(feed.getTitle());
viewBinding.authorLabel.setText(feed.getAuthor());
headerBinding.txtvDescription.setText(HtmlToPlainText.getPlainText(feed.getDescription()));
viewBinding.subscribeButton.setOnClickListener(v -> {
if (feedInFeedlist()) {
openFeed();
} else {
FeedDatabaseWriter.updateFeed(this, feed, false);
didPressSubscribe = true;
handleUpdatedFeedStatus();
}
});
viewBinding.stopPreviewButton.setOnClickListener(v -> {
PlaybackPreferences.writeNoMediaPlaying();
IntentUtils.sendLocalBroadcast(this, PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
});
if (UserPreferences.isEnableAutodownload()) {
SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE);
viewBinding.autoDownloadCheckBox.setChecked(preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true));
}
headerBinding.txtvDescription.setMaxLines(DESCRIPTION_MAX_LINES_COLLAPSED);
headerBinding.txtvDescription.setOnClickListener(v -> {
if (headerBinding.txtvDescription.getMaxLines() > DESCRIPTION_MAX_LINES_COLLAPSED) {
headerBinding.txtvDescription.setMaxLines(DESCRIPTION_MAX_LINES_COLLAPSED);
} else {
headerBinding.txtvDescription.setMaxLines(2000);
}
});
if (alternateFeedUrls.isEmpty()) {
viewBinding.alternateUrlsSpinner.setVisibility(View.GONE);
} else {
viewBinding.alternateUrlsSpinner.setVisibility(View.VISIBLE);
final List<String> alternateUrlsList = new ArrayList<>();
final List<String> alternateUrlsTitleList = new ArrayList<>();
alternateUrlsList.add(feed.getDownloadUrl());
alternateUrlsTitleList.add(feed.getTitle());
alternateUrlsList.addAll(alternateFeedUrls.keySet());
for (String url : alternateFeedUrls.keySet()) {
alternateUrlsTitleList.add(alternateFeedUrls.get(url));
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
R.layout.alternate_urls_item, alternateUrlsTitleList) {
@Override
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
// reusing the old view causes a visual bug on Android <= 10
return super.getDropDownView(position, null, parent);
}
};
adapter.setDropDownViewResource(R.layout.alternate_urls_dropdown_item);
viewBinding.alternateUrlsSpinner.setAdapter(adapter);
viewBinding.alternateUrlsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
selectedDownloadUrl = alternateUrlsList.get(position);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
handleUpdatedFeedStatus();
viewBinding.progressBar.setVisibility(View.GONE);
FeedItemlistFragment fragment = FeedItemlistFragment.newInstance(id);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragmentContainer, fragment, FeedItemlistFragment.TAG)
.commitAllowingStateLoss();
}
private void openFeed() {
private void openFeed(long feedId) {
// feed.getId() is always 0, we have to retrieve the id from the feed list from the database
MainActivityStarter mainActivityStarter = new MainActivityStarter(this);
mainActivityStarter.withOpenFeed(getFeedId());
mainActivityStarter.withOpenFeed(feedId);
if (getIntent().getBooleanExtra(ARG_STARTED_FROM_SEARCH, false)) {
mainActivityStarter.withAddToBackStack();
}
@ -516,60 +357,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
startActivity(mainActivityStarter.getIntent());
}
private void handleUpdatedFeedStatus() {
if (DownloadServiceInterface.get().isDownloadingEpisode(selectedDownloadUrl)) {
viewBinding.subscribeButton.setEnabled(false);
viewBinding.subscribeButton.setText(R.string.subscribing_label);
} else if (feedInFeedlist()) {
viewBinding.subscribeButton.setEnabled(true);
viewBinding.subscribeButton.setText(R.string.open_podcast);
if (didPressSubscribe) {
didPressSubscribe = false;
Feed feed1 = DBReader.getFeed(getFeedId(), false, 0, 0);
FeedPreferences feedPreferences = feed1.getPreferences();
if (UserPreferences.isEnableAutodownload()) {
boolean autoDownload = viewBinding.autoDownloadCheckBox.isChecked();
feedPreferences.setAutoDownload(autoDownload);
SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload);
editor.apply();
}
if (username != null) {
feedPreferences.setUsername(username);
feedPreferences.setPassword(password);
}
DBWriter.setFeedPreferences(feedPreferences);
openFeed();
}
} else {
viewBinding.subscribeButton.setEnabled(true);
viewBinding.subscribeButton.setText(R.string.subscribe_label);
if (UserPreferences.isEnableAutodownload()) {
viewBinding.autoDownloadCheckBox.setVisibility(View.VISIBLE);
}
}
}
private boolean feedInFeedlist() {
return getFeedId() != 0;
}
private long getFeedId() {
if (feeds == null) {
return 0;
}
for (Feed f : feeds) {
if (f.getDownloadUrl().equals(selectedDownloadUrl)) {
return f.getId();
}
}
return 0;
}
@UiThread
private void showErrorDialog(String errorMsg, String details) {
if (!isFinishing() && !isPaused) {
@ -589,7 +376,6 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
builder.setNeutralButton(R.string.edit_url_menu, (dialog, which) -> editUrl());
}
builder.setOnCancelListener(dialog -> {
setResult(RESULT_ERROR);
finish();
});
if (dialog != null && dialog.isShowing()) {
@ -608,24 +394,15 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
}
builder.setView(dialogBinding.getRoot());
builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
setLoadingLayout();
lookupUrlAndDownload(dialogBinding.urlEditText.getText().toString());
});
builder.setNegativeButton(R.string.cancel_label, (dialog1, which) -> dialog1.cancel());
builder.setOnCancelListener(dialog1 -> {
setResult(RESULT_ERROR);
finish();
});
builder.show();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void playbackStateChanged(PlayerStatusEvent event) {
boolean isPlayingPreview =
PlaybackPreferences.getCurrentlyPlayingMediaType() == RemoteMedia.PLAYABLE_TYPE_REMOTE_MEDIA;
viewBinding.stopPreviewButton.setVisibility(isPlayingPreview ? View.VISIBLE : View.GONE);
}
/**
*
* @return true if a FeedDiscoveryDialog is shown, false otherwise (e.g., due to no feed found).
@ -657,7 +434,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
if (urls.size() == 1) {
// Skip dialog and display the item directly
resetIntent(urls.get(0));
startFeedDownload(urls.get(0));
downloadIfNotAlreadySubscribed(urls.get(0));
return true;
}
@ -667,7 +444,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
String selectedUrl = urls.get(which);
dialog.dismiss();
resetIntent(selectedUrl);
startFeedDownload(selectedUrl);
downloadIfNotAlreadySubscribed(selectedUrl);
};
MaterialAlertDialogBuilder ab = new MaterialAlertDialogBuilder(OnlineFeedViewActivity.this)
@ -704,7 +481,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
protected void onConfirmed(String username, String password) {
OnlineFeedViewActivity.this.username = username;
OnlineFeedViewActivity.this.password = password;
startFeedDownload(feedUrl);
downloadIfNotAlreadySubscribed(feedUrl);
}
}

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.elevation.SurfaceColors;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.playback.service.PlaybackController;
import de.danoeh.antennapod.ui.appstartintent.MediaButtonStarter;
import de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter;
import de.danoeh.antennapod.ui.chapters.ChapterUtils;
import de.danoeh.antennapod.ui.episodes.PlaybackSpeedUtils;
import de.danoeh.antennapod.ui.episodes.TimeSpeedConverter;
@ -497,14 +499,25 @@ public class AudioPlayerFragment extends Fragment implements
return true;
} else if (itemId == R.id.open_feed_item) {
if (feedItem != null) {
Intent intent = MainActivity.getIntentToOpenFeed(getContext(), feedItem.getFeedId());
startActivity(intent);
openFeed(feedItem.getFeed());
}
return true;
}
return false;
}
private void openFeed(Feed feed) {
if (feed == null) {
return;
}
if (feed.getState() == Feed.STATE_SUBSCRIBED) {
Intent intent = MainActivity.getIntentToOpenFeed(getContext(), feed.getId());
startActivity(intent);
} else {
startActivity(new OnlineFeedviewActivityStarter(getContext(), feed.getDownloadUrl()).getIntent());
}
}
public void fadePlayerToToolbar(float slideOffset) {
float playerFadeProgress = Math.max(0.0f, Math.min(0.2f, slideOffset - 0.2f)) / 0.2f;
View player = getView().findViewById(R.id.playerFragment);

View File

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

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:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/design_time_lorem_ipsum"
android:textIsSelectable="true"
tools:background="@android:color/holo_green_dark" />

View File

@ -174,6 +174,20 @@
</LinearLayout>
<TextView
android:id="@+id/nonSubscribedWarningLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:layout_marginBottom="8dp"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
android:background="@drawable/bg_message_info"
android:textColor="?attr/colorAccent"
android:visibility="gone"
android:foreground="?attr/selectableItemBackground"
android:text="@string/state_deleted_not_subscribed" />
<TextView
android:id="@+id/noMediaLabel"
android:layout_width="match_parent"

View File

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

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

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 static final int FEEDFILETYPE_FEED = 0;
public static final int STATE_SUBSCRIBED = 0;
public static final int STATE_NOT_SUBSCRIBED = 1;
public static final String TYPE_RSS2 = "rss";
public static final String TYPE_ATOM1 = "atom";
public static final String PREFIX_LOCAL_FOLDER = "antennapod_local:";
@ -101,6 +103,7 @@ public class Feed {
*/
@Nullable
private SortOrder sortOrder;
private int state;
/**
* This constructor is used for restoring a feed from the database.
@ -109,7 +112,7 @@ public class Feed {
String description, String paymentLinks, String author, String language,
String type, String feedIdentifier, String imageUrl, String fileUrl,
String downloadUrl, long lastRefreshAttempt, boolean paged, String nextPageLink,
String filter, @Nullable SortOrder sortOrder, boolean lastUpdateFailed) {
String filter, @Nullable SortOrder sortOrder, boolean lastUpdateFailed, int state) {
this.localFileUrl = fileUrl;
this.downloadUrl = downloadUrl;
this.lastRefreshAttempt = lastRefreshAttempt;
@ -135,6 +138,7 @@ public class Feed {
}
setSortOrder(sortOrder);
this.lastUpdateFailed = lastUpdateFailed;
this.state = state;
}
/**
@ -144,7 +148,7 @@ public class Feed {
String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl,
String downloadUrl, long lastRefreshAttempt) {
this(id, lastModified, title, null, link, description, paymentLink, author, language, type, feedIdentifier,
imageUrl, fileUrl, downloadUrl, lastRefreshAttempt, false, null, null, null, false);
imageUrl, fileUrl, downloadUrl, lastRefreshAttempt, false, null, null, null, false, STATE_SUBSCRIBED);
}
/**
@ -468,4 +472,12 @@ public class Feed {
public boolean isLocalFeed() {
return downloadUrl.startsWith(PREFIX_LOCAL_FOLDER);
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}

View File

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

View File

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

View File

@ -58,7 +58,8 @@ public class ItunesTopListLoader {
List<PodcastSearchResult> suggestedPodcasts, List<Feed> subscribedFeeds, int limit) {
Set<String> subscribedPodcastsSet = new HashSet<>();
for (Feed subscribedFeed : subscribedFeeds) {
if (subscribedFeed.getTitle() != null && subscribedFeed.getAuthor() != null) {
if (subscribedFeed.getTitle() != null && subscribedFeed.getAuthor() != null
&& subscribedFeed.getState() == Feed.STATE_SUBSCRIBED) {
subscribedPodcastsSet.add(subscribedFeed.getTitle().trim() + " - " + subscribedFeed.getAuthor().trim());
}
}

View File

@ -7,6 +7,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.model.MediaMetadataRetrieverCompat;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueSink;
import de.danoeh.antennapod.ui.chapters.ChapterUtils;
import org.greenrobot.eventbus.EventBus;
@ -104,7 +105,7 @@ public class MediaDownloadedHandler implements Runnable {
FeedMedia.FEEDFILETYPE_FEEDMEDIA, false, DownloadError.ERROR_DB_ACCESS_ERROR, e.getMessage());
}
if (item != null) {
if (item != null && item.getFeed().getState() == Feed.STATE_SUBSCRIBED) {
EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DOWNLOAD)
.currentTimestamp()
.build();

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.media.MediaMetadataRetriever;
import android.util.Log;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
import static de.danoeh.antennapod.model.feed.FeedPreferences.SPEED_USE_GLOBAL;
@ -338,6 +339,10 @@ class DBUpgrader {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_FEED_SKIP_SILENCE + " INTEGER");
}
if (oldVersion < 3050000) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_STATE + " INTEGER DEFAULT " + Feed.STATE_SUBSCRIBED);
}
}
}

View File

@ -14,6 +14,7 @@ import androidx.documentfile.provider.DocumentFile;
import com.google.common.util.concurrent.Futures;
import de.danoeh.antennapod.event.DownloadLogEvent;
import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.net.download.serviceinterface.AutoDownloadManager;
import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface;
import de.danoeh.antennapod.net.download.serviceinterface.FeedUpdateManager;
@ -141,12 +142,13 @@ public class DBWriter {
// Do full update of this feed to get rid of the item
FeedUpdateManager.getInstance().runOnce(context, media.getItem().getFeed());
} else {
// Gpodder: queue delete action for synchronization
FeedItem item = media.getItem();
EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DELETE)
.currentTimestamp()
.build();
SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action);
if (media.getItem().getFeed().getState() == Feed.STATE_SUBSCRIBED) {
FeedItem item = media.getItem();
EpisodeAction action = new EpisodeAction.Builder(item, EpisodeAction.DELETE)
.currentTimestamp()
.build();
SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action);
}
EventBus.getDefault().post(FeedItemEvent.updated(media.getItem()));
}
@ -174,7 +176,7 @@ public class DBWriter {
adapter.removeFeed(feed);
adapter.close();
if (!feed.isLocalFeed()) {
if (!feed.isLocalFeed() && feed.getState() == Feed.STATE_SUBSCRIBED) {
SynchronizationQueueSink.enqueueFeedRemovedIfSynchronizationIsActive(context, feed.getDownloadUrl());
}
EventBus.getDefault().post(new FeedListUpdateEvent(feed));
@ -786,7 +788,7 @@ public class DBWriter {
adapter.close();
for (Feed feed : feeds) {
if (!feed.isLocalFeed()) {
if (!feed.isLocalFeed() && feed.getState() == Feed.STATE_SUBSCRIBED) {
SynchronizationQueueSink.enqueueFeedAddedIfSynchronizationIsActive(context, feed.getDownloadUrl());
}
}
@ -928,6 +930,32 @@ public class DBWriter {
});
}
public static Future<?> setFeedState(Context context, Feed feed, int newState) {
int oldState = feed.getState();
return runOnDbThread(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setFeedState(feed.getId(), newState);
feed.setState(newState);
if (oldState == Feed.STATE_NOT_SUBSCRIBED && newState == Feed.STATE_SUBSCRIBED) {
feed.getPreferences().setKeepUpdated(true);
DBWriter.setFeedPreferences(feed.getPreferences());
FeedUpdateManager.getInstance().runOnceOrAsk(context, feed);
SynchronizationQueueSink.enqueueFeedAddedIfSynchronizationIsActive(context, feed.getDownloadUrl());
DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(),
SortOrder.DATE_NEW_OLD, 0, Integer.MAX_VALUE);
for (FeedItem item : feed.getItems()) {
if (item.isPlayed()) {
SynchronizationQueueSink.enqueueEpisodePlayedIfSynchronizationIsActive(
context, item.getMedia(), true);
}
}
}
adapter.close();
EventBus.getDefault().post(new FeedListUpdateEvent(feed));
});
}
/**
* Sort the FeedItems in the queue with the given the named sort order.
*

View File

@ -157,7 +157,8 @@ public abstract class FeedDatabaseWriter {
+ "\n\nNow the feed contains:\n" + duplicateEpisodeDetails(item)));
oldItem.setItemIdentifier(item.getItemIdentifier());
if (oldItem.isPlayed() && oldItem.getMedia() != null) {
if (oldItem.isPlayed() && oldItem.getMedia() != null
&& savedFeed.getState() == Feed.STATE_SUBSCRIBED) {
EpisodeAction action = new EpisodeAction.Builder(oldItem, EpisodeAction.PLAY)
.currentTimestamp()
.started(oldItem.getMedia().getDuration() / 1000)

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";
public static final String DATABASE_NAME = "Antennapod.db";
public static final int VERSION = 3040000;
public static final int VERSION = 3050000;
/**
* Maximum number of arguments for IN-operator.
@ -121,6 +121,7 @@ public class PodDBAdapter {
public static final String KEY_EPISODE_NOTIFICATION = "episode_notification";
public static final String KEY_NEW_EPISODES_ACTION = "new_episodes_action";
public static final String KEY_PODCASTINDEX_CHAPTER_URL = "podcastindex_chapter_url";
public static final String KEY_STATE = "state";
// Table names
public static final String TABLE_NAME_FEEDS = "Feeds";
@ -171,6 +172,7 @@ public class PodDBAdapter {
+ KEY_FEED_SKIP_INTRO + " INTEGER DEFAULT 0,"
+ KEY_FEED_SKIP_ENDING + " INTEGER DEFAULT 0,"
+ KEY_EPISODE_NOTIFICATION + " INTEGER DEFAULT 0,"
+ KEY_STATE + " INTEGER DEFAULT " + Feed.STATE_SUBSCRIBED + ","
+ KEY_NEW_EPISODES_ACTION + " INTEGER DEFAULT 0)";
private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
@ -323,6 +325,7 @@ public class PodDBAdapter {
+ TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_INTRO + ", "
+ TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_ENDING + ", "
+ TABLE_NAME_FEEDS + "." + KEY_EPISODE_NOTIFICATION + ", "
+ TABLE_NAME_FEEDS + "." + KEY_STATE + ", "
+ TABLE_NAME_FEEDS + "." + KEY_NEW_EPISODES_ACTION;
private static final String JOIN_FEED_ITEM_AND_MEDIA = " LEFT JOIN " + TABLE_NAME_FEED_MEDIA
@ -337,6 +340,9 @@ public class PodDBAdapter {
"SELECT " + KEYS_FEED_ITEM_WITHOUT_DESCRIPTION + ", " + KEYS_FEED_MEDIA
+ " FROM " + TABLE_NAME_FEED_ITEMS
+ JOIN_FEED_ITEM_AND_MEDIA;
public static final String SELECT_WHERE_FEED_IS_SUBSCRIBED = TABLE_NAME_FEED_ITEMS + "." + KEY_FEED
+ " IN (SELECT " + KEY_ID + " FROM " + TABLE_NAME_FEEDS
+ " WHERE " + KEY_STATE + "=" + Feed.STATE_SUBSCRIBED + ")";
private static Context context;
private static PodDBAdapter instance;
@ -431,6 +437,7 @@ public class PodDBAdapter {
values.put(KEY_LASTUPDATE, feed.getLastModified());
values.put(KEY_TYPE, feed.getType());
values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier());
values.put(KEY_STATE, feed.getState());
values.put(KEY_IS_PAGED, feed.isPaged());
values.put(KEY_NEXT_PAGE_LINK, feed.getNextPageLink());
@ -768,6 +775,12 @@ public class PodDBAdapter {
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feedId)});
}
public void setFeedState(long feedId, int state) {
ContentValues values = new ContentValues();
values.put(KEY_STATE, state);
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feedId)});
}
/**
* Inserts or updates a download status.
*/
@ -1101,6 +1114,7 @@ public class PodDBAdapter {
// Hide episodes that have been played but not completed
+ " AND (" + KEY_LAST_PLAYED_TIME + " == 0"
+ " OR " + KEY_LAST_PLAYED_TIME + " > " + (System.currentTimeMillis() - 1000L * 3600L) + ")"
+ " AND " + SELECT_WHERE_FEED_IS_SUBSCRIBED
+ " ORDER BY " + randomEpisodeNumber(seed);
final String query = "SELECT * FROM (" + allItemsRandomOrder + ")"
+ " GROUP BY " + KEY_FEED
@ -1221,6 +1235,7 @@ public class PodDBAdapter {
+ JOIN_FEED_ITEM_AND_MEDIA
+ " INNER JOIN " + TABLE_NAME_FEEDS
+ " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID
+ " WHERE " + TABLE_NAME_FEEDS + "." + KEY_STATE + "=" + Feed.STATE_SUBSCRIBED
+ " GROUP BY " + TABLE_NAME_FEEDS + "." + KEY_ID;
return db.rawQuery(query, null);
}
@ -1372,7 +1387,7 @@ public class PodDBAdapter {
}
String queryStart = SELECT_FEED_ITEMS_AND_MEDIA_WITH_DESCRIPTION
+ " WHERE " + queryFeedId + " AND (";
+ " WHERE " + queryFeedId + " AND " + SELECT_WHERE_FEED_IS_SUBSCRIBED + " AND (";
StringBuilder sb = new StringBuilder(queryStart);
for (int i = 0; i < queryWords.length; i++) {
@ -1401,12 +1416,13 @@ public class PodDBAdapter {
public Cursor searchFeeds(String searchQuery) {
String[] queryWords = prepareSearchQuery(searchQuery);
String queryStart = "SELECT " + KEYS_FEED + " FROM " + TABLE_NAME_FEEDS + " WHERE ";
String queryStart = "SELECT " + KEYS_FEED + " FROM " + TABLE_NAME_FEEDS
+ " WHERE " + KEY_STATE + " = " + Feed.STATE_SUBSCRIBED;
StringBuilder sb = new StringBuilder(queryStart);
for (int i = 0; i < queryWords.length; i++) {
sb
.append("(")
.append(" AND (")
.append(KEY_TITLE).append(" LIKE '%").append(queryWords[i])
.append("%' OR ")
.append(KEY_CUSTOM_TITLE).append(" LIKE '%").append(queryWords[i])
@ -1415,13 +1431,9 @@ public class PodDBAdapter {
.append("%' OR ")
.append(KEY_DESCRIPTION).append(" LIKE '%").append(queryWords[i])
.append("%') ");
if (i != queryWords.length - 1) {
sb.append("AND ");
}
}
sb.append("ORDER BY " + KEY_TITLE + " ASC LIMIT 300");
sb.append(" ORDER BY " + KEY_TITLE + " ASC LIMIT 300");
return db.rawQuery(sb.toString(), null);
}

View File

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

View File

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

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]);
for (Feed feed : feeds) {
if (feed.getState() != Feed.STATE_SUBSCRIBED) {
continue;
}
writer.append("<li><div><img src=\"");
writer.append(feed.getImageUrl());
writer.append("\" /><p>");

View File

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

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="black">#000000</color>
<color name="image_readability_tint">#80000000</color>
<color name="feed_image_bg">#50000000</color>
<color name="feed_text_bg">#55333333</color>
<!-- Theme colors -->

View File

@ -7,8 +7,6 @@
<dimen name="text_size_large">22sp</dimen>
<dimen name="thumbnail_length_itemlist">56dp</dimen>
<dimen name="thumbnail_length_queue_item">56dp</dimen>
<dimen name="thumbnail_length_onlinefeedview">92dp</dimen>
<dimen name="feeditemlist_header_height">132dp</dimen>
<dimen name="thumbnail_length_navlist">40dp</dimen>
<dimen name="listitem_iconwithtext_height">48dp</dimen>
<dimen name="listitem_iconwithtext_textleftpadding">16dp</dimen>

View File

@ -12,6 +12,7 @@
<style name="Theme.Base.AntennaPod.Dynamic.Light" parent="Theme.Material3.DynamicColors.Light">
<item name="progressBarTheme">@style/ProgressBarLight</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="action_icon_color">@color/black</item>
<item name="android:textAllCaps">false</item>
@ -54,6 +55,7 @@
<item name="background_color">@color/background_darktheme</item>
<item name="background_elevated">@color/background_elevated_darktheme</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="seek_background">@color/seek_background_dark</item>
<item name="dragview_background">@drawable/ic_drag_darktheme</item>
@ -289,6 +291,11 @@
<item name="fastScrollVerticalTrackDrawable">@drawable/scrollbar_track</item>
</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">
<item name="android:drawablePadding">8dp</item>
<item name="android:paddingTop">8dp</item>

View File

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