Open non-subscribed feeds in popup
This commit is contained in:
parent
2ab4ab9638
commit
4eeefe07b8
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package de.danoeh.antennapod.ui.episodeslist;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.PluralsRes;
|
||||
|
@ -9,21 +10,22 @@ 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;
|
||||
}
|
||||
|
@ -120,7 +122,7 @@ public class EpisodeMultiSelectActionHandler {
|
|||
snackbar.setText(text);
|
||||
snackbar.show(); // Resets the timeout
|
||||
} else {
|
||||
snackbar = activity.showSnackbarAbovePlayer(text, Snackbar.LENGTH_LONG);
|
||||
EventBus.getDefault().post(new MessageEvent(text));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 com.leinardi.android.speeddial.SpeedDialView;
|
||||
|
||||
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.databinding.MultiSelectSpeedDialBinding;
|
||||
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.MessageEvent;
|
||||
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.FeedItemFilterDialog;
|
||||
import de.danoeh.antennapod.ui.MenuItemUtils;
|
||||
import de.danoeh.antennapod.ui.TransitionEffect;
|
||||
import de.danoeh.antennapod.ui.common.IntentUtils;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
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,45 +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.databinding.MultiSelectSpeedDialBinding;
|
||||
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.
|
||||
*/
|
||||
|
@ -148,12 +145,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(
|
||||
ThemeUtils.getDrawableFromAttr(getContext(), R.attr.homeAsUpIndicator));
|
||||
viewBinding.toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack());
|
||||
}
|
||||
updateToolbar();
|
||||
setupLoadMoreScrollListener();
|
||||
|
||||
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);
|
||||
|
@ -211,14 +214,13 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
|||
@Override
|
||||
public void onToggleChanged(boolean open) {
|
||||
if (open && adapter.getSelectedCount() == 0) {
|
||||
((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.no_items_selected,
|
||||
Snackbar.LENGTH_SHORT);
|
||||
EventBus.getDefault().post(new MessageEvent(getString(R.string.no_items_selected)));
|
||||
speedDialBinding.fabSD.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
speedDialBinding.fabSD.setOnActionSelectedListener(actionItem -> {
|
||||
new EpisodeMultiSelectActionHandler(((MainActivity) getActivity()), actionItem.getId())
|
||||
new EpisodeMultiSelectActionHandler(getActivity(), actionItem.getId())
|
||||
.handleAction(adapter.getSelectedItems());
|
||||
adapter.endSelectMode();
|
||||
return true;
|
||||
|
@ -255,6 +257,12 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -269,8 +277,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) {
|
||||
|
@ -641,7 +648,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
|||
}
|
||||
|
||||
private class FeedItemListAdapter extends EpisodeItemListAdapter {
|
||||
public FeedItemListAdapter(MainActivity mainActivity) {
|
||||
public FeedItemListAdapter(FragmentActivity mainActivity) {
|
||||
super(mainActivity);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,74 +1,15 @@
|
|||
package de.danoeh.antennapod.ui.screen.onlinefeedview;
|
||||
|
||||
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;
|
||||
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 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.ui.screen.download.DownloadErrorLabel;
|
||||
import de.danoeh.antennapod.databinding.EditTextDialogBinding;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
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.net.discovery.CombinedSearcher;
|
||||
import de.danoeh.antennapod.net.discovery.PodcastSearchResult;
|
||||
import de.danoeh.antennapod.net.discovery.PodcastSearcherRegistry;
|
||||
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.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.parser.feed.UnsupportedFeedtypeException;
|
||||
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 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 java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter.ARG_FEEDURL;
|
||||
import static de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter.ARG_STARTED_FROM_SEARCH;
|
||||
|
@ -83,38 +24,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 volatile List<Feed> feeds;
|
||||
private String selectedDownloadUrl;
|
||||
private Downloader downloader;
|
||||
private String username = null;
|
||||
private String password = null;
|
||||
|
||||
private boolean isPaused;
|
||||
private boolean isFeedFoundBySearch = false;
|
||||
|
||||
private Dialog dialog;
|
||||
|
||||
private Disposable download;
|
||||
private Disposable parser;
|
||||
|
||||
private OnlinefeedviewActivityBinding viewBinding;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(ThemeSwitcher.getTranslucentTheme(this));
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
viewBinding = OnlinefeedviewActivityBinding.inflate(getLayoutInflater());
|
||||
OnlinefeedviewActivityBinding 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));
|
||||
|
||||
|
@ -129,526 +48,28 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
|
|||
|
||||
if (feedUrl == null) {
|
||||
Log.e(TAG, "feedUrl is null.");
|
||||
showNoPodcastFoundError();
|
||||
Toast.makeText(this, R.string.null_value_podcast_error, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
} 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/))", "");
|
||||
}
|
||||
if (savedInstanceState != null) {
|
||||
username = savedInstanceState.getString("username");
|
||||
password = savedInstanceState.getString("password");
|
||||
}
|
||||
lookupUrlAndDownload(feedUrl);
|
||||
|
||||
OnlineFeedViewFragment fragment = OnlineFeedViewFragment.newInstance(feedUrl,
|
||||
getIntent().getBooleanExtra(ARG_STARTED_FROM_SEARCH, false),
|
||||
getIntent().getBooleanExtra(ARG_WAS_MANUAL_URL, false));
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(viewBinding.fragmentContainer.getId(), fragment, OnlineFeedViewFragment.TAG)
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
}
|
||||
|
||||
private void showNoPodcastFoundError() {
|
||||
runOnUiThread(() -> new MaterialAlertDialogBuilder(OnlineFeedViewActivity.this)
|
||||
.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();
|
||||
})
|
||||
.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
isPaused = true;
|
||||
if (downloader != null && !downloader.isFinished()) {
|
||||
downloader.cancel();
|
||||
}
|
||||
if(dialog != null && dialog.isShowing()) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if(download != null) {
|
||||
download.dispose();
|
||||
}
|
||||
if(parser != null) {
|
||||
parser.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString("username", username);
|
||||
outState.putString("password", password);
|
||||
}
|
||||
|
||||
private void resetIntent(String url) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(ARG_FEEDURL, url);
|
||||
setIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
}
|
||||
|
||||
private void lookupUrlAndDownload(String url) {
|
||||
download = PodcastSearcherRegistry.lookupUrl(url)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(this::startFeedDownload,
|
||||
error -> {
|
||||
if (error instanceof FeedUrlNotFoundException) {
|
||||
tryToRetrieveFeedUrlBySearch((FeedUrlNotFoundException) error);
|
||||
} else {
|
||||
showNoPodcastFoundError();
|
||||
Log.e(TAG, Log.getStackTraceString(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void tryToRetrieveFeedUrlBySearch(FeedUrlNotFoundException error) {
|
||||
Log.d(TAG, "Unable to retrieve feed url, trying to retrieve feed url from search");
|
||||
String url = searchFeedUrlByTrackName(error.getTrackName(), error.getArtistName());
|
||||
if (url != null) {
|
||||
Log.d(TAG, "Successfully retrieve feed url");
|
||||
isFeedFoundBySearch = true;
|
||||
startFeedDownload(url);
|
||||
} else {
|
||||
showNoPodcastFoundError();
|
||||
Log.d(TAG, "Failed to retrieve feed url");
|
||||
}
|
||||
}
|
||||
|
||||
private String searchFeedUrlByTrackName(String trackName, String artistName) {
|
||||
CombinedSearcher searcher = new CombinedSearcher();
|
||||
String query = trackName + " " + artistName;
|
||||
List<PodcastSearchResult> results = searcher.search(query).blockingGet();
|
||||
for (PodcastSearchResult result : results) {
|
||||
if (result.feedUrl != null && result.author != null
|
||||
&& result.author.equalsIgnoreCase(artistName) && result.title.equalsIgnoreCase(trackName)) {
|
||||
return result.feedUrl;
|
||||
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void startFeedDownload(String url) {
|
||||
Log.d(TAG, "Starting feed download");
|
||||
selectedDownloadUrl = UrlChecker.prepareUrl(url);
|
||||
DownloadRequest request = DownloadRequestCreator.create(new Feed(selectedDownloadUrl, null))
|
||||
.withAuthentication(username, password)
|
||||
.withInitiatedByUser(true)
|
||||
.build();
|
||||
|
||||
download = Observable.fromCallable(() -> {
|
||||
feeds = DBReader.getFeedList();
|
||||
downloader = new HttpDownloader(request);
|
||||
downloader.call();
|
||||
return downloader.getResult();
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(status -> checkDownloadResult(status, request.getDestination()),
|
||||
error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
}
|
||||
|
||||
private void checkDownloadResult(@NonNull DownloadResult status, String destination) {
|
||||
if (status.isSuccessful()) {
|
||||
parseFeed(destination);
|
||||
} else if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) {
|
||||
if (!isFinishing() && !isPaused) {
|
||||
if (username != null && password != null) {
|
||||
Toast.makeText(this, R.string.download_error_unauthorized, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
dialog = new FeedViewAuthenticationDialog(OnlineFeedViewActivity.this,
|
||||
R.string.authentication_notification_title,
|
||||
downloader.getDownloadRequest().getSource()).create();
|
||||
dialog.show();
|
||||
}
|
||||
} else {
|
||||
showErrorDialog(getString(DownloadErrorLabel.from(status.getReason())), status.getReasonDetailed());
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse the feed.
|
||||
* @return The FeedHandlerResult if successful.
|
||||
* Null if unsuccessful but we started another attempt.
|
||||
* @throws Exception If unsuccessful but we do not know a resolution.
|
||||
*/
|
||||
@Nullable
|
||||
private FeedHandlerResult doParseFeed(String destination) throws Exception {
|
||||
FeedHandler handler = new FeedHandler();
|
||||
Feed feed = new Feed(selectedDownloadUrl, null);
|
||||
feed.setLocalFileUrl(destination);
|
||||
File destinationFile = new File(destination);
|
||||
try {
|
||||
return handler.parseFeed(feed);
|
||||
} catch (UnsupportedFeedtypeException e) {
|
||||
Log.d(TAG, "Unsupported feed type detected");
|
||||
if ("html".equalsIgnoreCase(e.getRootElement())) {
|
||||
boolean dialogShown = showFeedDiscoveryDialog(destinationFile, selectedDownloadUrl);
|
||||
if (dialogShown) {
|
||||
return null; // Should not display an error message
|
||||
} else {
|
||||
throw new UnsupportedFeedtypeException(getString(R.string.download_error_unsupported_type_html));
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
throw e;
|
||||
} finally {
|
||||
boolean rc = destinationFile.delete();
|
||||
Log.d(TAG, "Deleted feed source file. Result: " + rc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when feed parsed successfully.
|
||||
* This method is executed on the GUI thread.
|
||||
*/
|
||||
private void showFeedInformation(final Feed feed, Map<String, String> alternateFeedUrls) {
|
||||
viewBinding.progressBar.setVisibility(View.GONE);
|
||||
viewBinding.feedDisplayContainer.setVisibility(View.VISIBLE);
|
||||
if (isFeedFoundBySearch) {
|
||||
int resId = R.string.no_feed_url_podcast_found_by_search;
|
||||
Snackbar.make(findViewById(android.R.id.content), resId, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
viewBinding.backgroundImage.setColorFilter(new LightingColorFilter(0xff828282, 0x000000));
|
||||
|
||||
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());
|
||||
viewBinding.txtvDescription.setText(HtmlToPlainText.getPlainText(feed.getDescription()));
|
||||
|
||||
Feed feedInSubscriptions = findFeedInSubscriptions();
|
||||
if (feedInSubscriptions != null) {
|
||||
if (feedInSubscriptions.getState() == Feed.STATE_SUBSCRIBED) {
|
||||
viewBinding.subscribeButton.setVisibility(View.GONE);
|
||||
viewBinding.previewEpisodesButton.setVisibility(View.GONE);
|
||||
viewBinding.openPodcastButton.setVisibility(View.VISIBLE);
|
||||
viewBinding.openPodcastButton.setOnClickListener(view -> openFeed(feedInSubscriptions.getId()));
|
||||
} else {
|
||||
viewBinding.openPodcastButton.setVisibility(View.GONE);
|
||||
viewBinding.subscribeButton.setVisibility(View.VISIBLE);
|
||||
viewBinding.subscribeButton.setOnClickListener(view -> {
|
||||
DBWriter.setFeedState(this, feedInSubscriptions, Feed.STATE_SUBSCRIBED);
|
||||
openFeed(feedInSubscriptions.getId());
|
||||
});
|
||||
viewBinding.previewEpisodesButton.setVisibility(View.VISIBLE);
|
||||
viewBinding.previewEpisodesButton.setOnClickListener(view -> {
|
||||
feed.setId(feedInSubscriptions.getId());
|
||||
FeedDatabaseWriter.updateFeed(this, feed, false);
|
||||
openFeed(feedInSubscriptions.getId());
|
||||
});
|
||||
}
|
||||
} else {
|
||||
viewBinding.openPodcastButton.setVisibility(View.GONE);
|
||||
viewBinding.subscribeButton.setVisibility(View.VISIBLE);
|
||||
viewBinding.previewEpisodesButton.setVisibility(View.VISIBLE);
|
||||
|
||||
viewBinding.previewEpisodesButton.setOnClickListener(v -> {
|
||||
feed.setState(Feed.STATE_NOT_SUBSCRIBED);
|
||||
FeedDatabaseWriter.updateFeed(this, feed, false);
|
||||
Feed feedFromDb = DBReader.getFeed(feed.getId(), false, 0, Integer.MAX_VALUE);
|
||||
feedFromDb.getPreferences().setKeepUpdated(false);
|
||||
DBWriter.setFeedPreferences(feedFromDb.getPreferences());
|
||||
setAutodownloadPreferenceAndOpen(feedFromDb);
|
||||
});
|
||||
viewBinding.subscribeButton.setOnClickListener(v -> {
|
||||
Feed feedFromDb = FeedDatabaseWriter.updateFeed(this, feed, false);
|
||||
setAutodownloadPreferenceAndOpen(feedFromDb);
|
||||
});
|
||||
if (UserPreferences.isEnableAutodownload()) {
|
||||
viewBinding.autoDownloadCheckBox.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
if (UserPreferences.isEnableAutodownload()) {
|
||||
SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
viewBinding.autoDownloadCheckBox.setChecked(preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true));
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void openFeed(long id) {
|
||||
MainActivityStarter mainActivityStarter = new MainActivityStarter(this);
|
||||
mainActivityStarter.withOpenFeed(id);
|
||||
if (getIntent().getBooleanExtra(ARG_STARTED_FROM_SEARCH, false)) {
|
||||
mainActivityStarter.withAddToBackStack();
|
||||
}
|
||||
finish();
|
||||
startActivity(mainActivityStarter.getIntent());
|
||||
}
|
||||
|
||||
private void setAutodownloadPreferenceAndOpen(Feed feed) {
|
||||
FeedPreferences feedPreferences = feed.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(feed.getId());
|
||||
}
|
||||
|
||||
private Feed findFeedInSubscriptions() {
|
||||
if (feeds == null) {
|
||||
return null;
|
||||
}
|
||||
for (Feed f : feeds) {
|
||||
if (f.getDownloadUrl().equals(selectedDownloadUrl)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void showErrorDialog(String errorMsg, String details) {
|
||||
if (!isFinishing() && !isPaused) {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle(R.string.error_label);
|
||||
if (errorMsg != null) {
|
||||
String total = errorMsg + "\n\n" + details;
|
||||
SpannableString errorMessage = new SpannableString(total);
|
||||
errorMessage.setSpan(new ForegroundColorSpan(0x88888888),
|
||||
errorMsg.length(), total.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
builder.setMessage(errorMessage);
|
||||
} else {
|
||||
builder.setMessage(R.string.download_error_error_unknown);
|
||||
}
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.cancel());
|
||||
if (getIntent().getBooleanExtra(ARG_WAS_MANUAL_URL, false)) {
|
||||
builder.setNeutralButton(R.string.edit_url_menu, (dialog, which) -> editUrl());
|
||||
}
|
||||
builder.setOnCancelListener(dialog -> {
|
||||
setResult(RESULT_ERROR);
|
||||
finish();
|
||||
});
|
||||
if (dialog != null && dialog.isShowing()) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
dialog = builder.show();
|
||||
}
|
||||
}
|
||||
|
||||
private void editUrl() {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle(R.string.edit_url_menu);
|
||||
final EditTextDialogBinding dialogBinding = EditTextDialogBinding.inflate(getLayoutInflater());
|
||||
if (downloader != null) {
|
||||
dialogBinding.urlEditText.setText(downloader.getDownloadRequest().getSource());
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return true if a FeedDiscoveryDialog is shown, false otherwise (e.g., due to no feed found).
|
||||
*/
|
||||
private boolean showFeedDiscoveryDialog(File feedFile, String baseUrl) {
|
||||
FeedDiscoverer fd = new FeedDiscoverer();
|
||||
final Map<String, String> urlsMap;
|
||||
try {
|
||||
urlsMap = fd.findLinks(feedFile, baseUrl);
|
||||
if (urlsMap == null || urlsMap.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPaused || isFinishing()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<String> titles = new ArrayList<>();
|
||||
|
||||
final List<String> urls = new ArrayList<>(urlsMap.keySet());
|
||||
for (String url : urls) {
|
||||
titles.add(urlsMap.get(url));
|
||||
}
|
||||
|
||||
if (urls.size() == 1) {
|
||||
// Skip dialog and display the item directly
|
||||
resetIntent(urls.get(0));
|
||||
startFeedDownload(urls.get(0));
|
||||
return true;
|
||||
}
|
||||
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<>(OnlineFeedViewActivity.this,
|
||||
R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles);
|
||||
DialogInterface.OnClickListener onClickListener = (dialog, which) -> {
|
||||
String selectedUrl = urls.get(which);
|
||||
dialog.dismiss();
|
||||
resetIntent(selectedUrl);
|
||||
startFeedDownload(selectedUrl);
|
||||
};
|
||||
|
||||
MaterialAlertDialogBuilder ab = new MaterialAlertDialogBuilder(OnlineFeedViewActivity.this)
|
||||
.setTitle(R.string.feeds_label)
|
||||
.setCancelable(true)
|
||||
.setOnCancelListener(dialog -> finish())
|
||||
.setAdapter(adapter, onClickListener);
|
||||
|
||||
runOnUiThread(() -> {
|
||||
if(dialog != null && dialog.isShowing()) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
dialog = ab.show();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private class FeedViewAuthenticationDialog extends AuthenticationDialog {
|
||||
|
||||
private final String feedUrl;
|
||||
|
||||
FeedViewAuthenticationDialog(Context context, int titleRes, String feedUrl) {
|
||||
super(context, titleRes, true, username, password);
|
||||
this.feedUrl = feedUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
super.onCancelled();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfirmed(String username, String password) {
|
||||
OnlineFeedViewActivity.this.username = username;
|
||||
OnlineFeedViewActivity.this.password = password;
|
||||
startFeedDownload(feedUrl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,594 @@
|
|||
package de.danoeh.antennapod.ui.screen.onlinefeedview;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.LightingColorFilter;
|
||||
import android.os.Bundle;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
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 androidx.fragment.app.Fragment;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.databinding.EditTextDialogBinding;
|
||||
import de.danoeh.antennapod.databinding.OnlinefeedviewFragmentBinding;
|
||||
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.model.feed.Feed;
|
||||
import de.danoeh.antennapod.model.feed.FeedPreferences;
|
||||
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.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.storage.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
|
||||
import de.danoeh.antennapod.ui.cleaner.HtmlToPlainText;
|
||||
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 java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter.ARG_FEEDURL;
|
||||
import static de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter.ARG_STARTED_FROM_SEARCH;
|
||||
import static de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter.ARG_WAS_MANUAL_URL;
|
||||
|
||||
public class OnlineFeedViewFragment extends Fragment {
|
||||
|
||||
static final String TAG = "OnlineFeedViewActivity";
|
||||
private static final String PREFS = "OnlineFeedViewActivityPreferences";
|
||||
private static final String PREF_LAST_AUTO_DOWNLOAD = "lastAutoDownload";
|
||||
|
||||
private volatile List<Feed> feeds;
|
||||
private String selectedDownloadUrl;
|
||||
private Downloader downloader;
|
||||
private String username = null;
|
||||
private String password = null;
|
||||
private boolean isFeedFoundBySearch = false;
|
||||
private Dialog dialog;
|
||||
private Disposable download;
|
||||
private Disposable parser;
|
||||
private OnlinefeedviewFragmentBinding viewBinding;
|
||||
|
||||
public static OnlineFeedViewFragment newInstance(String url, boolean startedFromSearch, boolean wasManualUrl) {
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(ARG_FEEDURL, url);
|
||||
arguments.putBoolean(ARG_STARTED_FROM_SEARCH, startedFromSearch);
|
||||
arguments.putBoolean(ARG_WAS_MANUAL_URL, wasManualUrl);
|
||||
OnlineFeedViewFragment fragment = new OnlineFeedViewFragment();
|
||||
fragment.setArguments(arguments);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
viewBinding = OnlinefeedviewFragmentBinding.inflate(getLayoutInflater());
|
||||
viewBinding.closeButton.setOnClickListener(view -> getActivity().finish());
|
||||
setLoadingLayout();
|
||||
if (savedInstanceState != null) {
|
||||
username = savedInstanceState.getString("username");
|
||||
password = savedInstanceState.getString("password");
|
||||
}
|
||||
lookupUrlAndDownload(getArguments().getString(ARG_FEEDURL));
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
private void showNoPodcastFoundError() {
|
||||
getActivity().runOnUiThread(() -> new MaterialAlertDialogBuilder(getContext())
|
||||
.setNeutralButton(android.R.string.ok, (dialog, which) -> getActivity().finish())
|
||||
.setTitle(R.string.error_label)
|
||||
.setMessage(R.string.null_value_podcast_error)
|
||||
.show());
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a progress indicator.
|
||||
*/
|
||||
private void setLoadingLayout() {
|
||||
viewBinding.progressBar.setVisibility(View.VISIBLE);
|
||||
viewBinding.feedDisplayContainer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (downloader != null && !downloader.isFinished()) {
|
||||
downloader.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (download != null) {
|
||||
download.dispose();
|
||||
}
|
||||
if (parser != null) {
|
||||
parser.dispose();
|
||||
}
|
||||
viewBinding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString("username", username);
|
||||
outState.putString("password", password);
|
||||
}
|
||||
|
||||
private void lookupUrlAndDownload(String url) {
|
||||
download = PodcastSearcherRegistry.lookupUrl(url)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(this::startFeedDownload,
|
||||
error -> {
|
||||
if (error instanceof FeedUrlNotFoundException) {
|
||||
tryToRetrieveFeedUrlBySearch((FeedUrlNotFoundException) error);
|
||||
} else {
|
||||
showNoPodcastFoundError();
|
||||
Log.e(TAG, Log.getStackTraceString(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void tryToRetrieveFeedUrlBySearch(FeedUrlNotFoundException error) {
|
||||
Log.d(TAG, "Unable to retrieve feed url, trying to retrieve feed url from search");
|
||||
String url = searchFeedUrlByTrackName(error.getTrackName(), error.getArtistName());
|
||||
if (url != null) {
|
||||
Log.d(TAG, "Successfully retrieve feed url");
|
||||
isFeedFoundBySearch = true;
|
||||
startFeedDownload(url);
|
||||
} else {
|
||||
showNoPodcastFoundError();
|
||||
Log.d(TAG, "Failed to retrieve feed url");
|
||||
}
|
||||
}
|
||||
|
||||
private String searchFeedUrlByTrackName(String trackName, String artistName) {
|
||||
CombinedSearcher searcher = new CombinedSearcher();
|
||||
String query = trackName + " " + artistName;
|
||||
List<PodcastSearchResult> results = searcher.search(query).blockingGet();
|
||||
for (PodcastSearchResult result : results) {
|
||||
if (result.feedUrl != null && result.author != null
|
||||
&& result.author.equalsIgnoreCase(artistName) && result.title.equalsIgnoreCase(trackName)) {
|
||||
return result.feedUrl;
|
||||
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void startFeedDownload(String url) {
|
||||
Log.d(TAG, "Starting feed download");
|
||||
selectedDownloadUrl = UrlChecker.prepareUrl(url);
|
||||
DownloadRequest request = DownloadRequestCreator.create(new Feed(selectedDownloadUrl, null))
|
||||
.withAuthentication(username, password)
|
||||
.withInitiatedByUser(true)
|
||||
.build();
|
||||
|
||||
download = Observable.fromCallable(() -> {
|
||||
feeds = DBReader.getFeedList();
|
||||
downloader = new HttpDownloader(request);
|
||||
downloader.call();
|
||||
return downloader.getResult();
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(status -> checkDownloadResult(status, request.getDestination()),
|
||||
error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
}
|
||||
|
||||
private void checkDownloadResult(@NonNull DownloadResult status, String destination) {
|
||||
if (status.isSuccessful()) {
|
||||
parseFeed(destination);
|
||||
} else if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) {
|
||||
if (username != null && password != null) {
|
||||
Toast.makeText(getContext(), R.string.download_error_unauthorized, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
dialog = new FeedViewAuthenticationDialog(getContext(),
|
||||
R.string.authentication_notification_title,
|
||||
downloader.getDownloadRequest().getSource()).create();
|
||||
dialog.show();
|
||||
} else {
|
||||
showErrorDialog(getString(DownloadErrorLabel.from(status.getReason())), status.getReasonDetailed());
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse the feed.
|
||||
* @return The FeedHandlerResult if successful.
|
||||
* Null if unsuccessful but we started another attempt.
|
||||
* @throws Exception If unsuccessful but we do not know a resolution.
|
||||
*/
|
||||
@Nullable
|
||||
private FeedHandlerResult doParseFeed(String destination) throws Exception {
|
||||
FeedHandler handler = new FeedHandler();
|
||||
Feed feed = new Feed(selectedDownloadUrl, null);
|
||||
feed.setLocalFileUrl(destination);
|
||||
File destinationFile = new File(destination);
|
||||
try {
|
||||
return handler.parseFeed(feed);
|
||||
} catch (UnsupportedFeedtypeException e) {
|
||||
Log.d(TAG, "Unsupported feed type detected");
|
||||
if ("html".equalsIgnoreCase(e.getRootElement())) {
|
||||
boolean dialogShown = showFeedDiscoveryDialog(destinationFile, selectedDownloadUrl);
|
||||
if (dialogShown) {
|
||||
return null; // Should not display an error message
|
||||
} else {
|
||||
throw new UnsupportedFeedtypeException(getString(R.string.download_error_unsupported_type_html));
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
throw e;
|
||||
} finally {
|
||||
boolean rc = destinationFile.delete();
|
||||
Log.d(TAG, "Deleted feed source file. Result: " + rc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when feed parsed successfully.
|
||||
* This method is executed on the GUI thread.
|
||||
*/
|
||||
private void showFeedInformation(final Feed feed, Map<String, String> alternateFeedUrls) {
|
||||
viewBinding.progressBar.setVisibility(View.GONE);
|
||||
viewBinding.feedDisplayContainer.setVisibility(View.VISIBLE);
|
||||
if (isFeedFoundBySearch) {
|
||||
int resId = R.string.no_feed_url_podcast_found_by_search;
|
||||
Snackbar.make(viewBinding.getRoot(), resId, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
viewBinding.backgroundImage.setColorFilter(new LightingColorFilter(0xff828282, 0x000000));
|
||||
|
||||
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());
|
||||
viewBinding.txtvDescription.setText(HtmlToPlainText.getPlainText(feed.getDescription()));
|
||||
|
||||
Feed feedInSubscriptions = findFeedInSubscriptions();
|
||||
if (feedInSubscriptions != null) {
|
||||
if (feedInSubscriptions.getState() == Feed.STATE_SUBSCRIBED) {
|
||||
viewBinding.subscribeButton.setVisibility(View.GONE);
|
||||
viewBinding.previewEpisodesButton.setVisibility(View.GONE);
|
||||
viewBinding.openPodcastButton.setVisibility(View.VISIBLE);
|
||||
viewBinding.openPodcastButton.setOnClickListener(view -> openFeed(feedInSubscriptions.getId()));
|
||||
} else {
|
||||
viewBinding.openPodcastButton.setVisibility(View.GONE);
|
||||
viewBinding.subscribeButton.setVisibility(View.VISIBLE);
|
||||
viewBinding.subscribeButton.setOnClickListener(view -> {
|
||||
DBWriter.setFeedState(getContext(), feedInSubscriptions, Feed.STATE_SUBSCRIBED);
|
||||
openFeed(feedInSubscriptions.getId());
|
||||
});
|
||||
viewBinding.previewEpisodesButton.setVisibility(View.VISIBLE);
|
||||
viewBinding.previewEpisodesButton.setOnClickListener(view -> {
|
||||
feed.setId(feedInSubscriptions.getId());
|
||||
FeedDatabaseWriter.updateFeed(getContext(), feed, false);
|
||||
previewFeed(feedInSubscriptions.getId());
|
||||
});
|
||||
}
|
||||
} else {
|
||||
viewBinding.openPodcastButton.setVisibility(View.GONE);
|
||||
viewBinding.subscribeButton.setVisibility(View.VISIBLE);
|
||||
viewBinding.previewEpisodesButton.setVisibility(View.VISIBLE);
|
||||
|
||||
viewBinding.previewEpisodesButton.setOnClickListener(v -> {
|
||||
feed.setState(Feed.STATE_NOT_SUBSCRIBED);
|
||||
FeedDatabaseWriter.updateFeed(getContext(), feed, false);
|
||||
Feed feedFromDb = DBReader.getFeed(feed.getId(), false, 0, Integer.MAX_VALUE);
|
||||
feedFromDb.getPreferences().setKeepUpdated(false);
|
||||
DBWriter.setFeedPreferences(feedFromDb.getPreferences());
|
||||
previewFeed(feedFromDb.getId());
|
||||
});
|
||||
viewBinding.subscribeButton.setOnClickListener(v -> {
|
||||
Feed feedFromDb = FeedDatabaseWriter.updateFeed(getContext(), feed, false);
|
||||
setAutodownloadPreferenceAndOpen(feedFromDb);
|
||||
});
|
||||
if (UserPreferences.isEnableAutodownload()) {
|
||||
viewBinding.autoDownloadCheckBox.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
if (UserPreferences.isEnableAutodownload()) {
|
||||
SharedPreferences preferences = getContext().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
||||
viewBinding.autoDownloadCheckBox.setChecked(preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true));
|
||||
}
|
||||
|
||||
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>(getContext(),
|
||||
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) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void previewFeed(long id) {
|
||||
FeedItemlistFragment fragment = FeedItemlistFragment.newInstance(id);
|
||||
getParentFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.fragmentContainer, fragment, OnlineFeedViewFragment.TAG)
|
||||
.addToBackStack(TAG)
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
|
||||
private void openFeed(long id) {
|
||||
MainActivityStarter mainActivityStarter = new MainActivityStarter(getContext());
|
||||
mainActivityStarter.withOpenFeed(id);
|
||||
if (getArguments().getBoolean(ARG_STARTED_FROM_SEARCH, false)) {
|
||||
mainActivityStarter.withAddToBackStack();
|
||||
}
|
||||
getActivity().finish();
|
||||
startActivity(mainActivityStarter.getIntent());
|
||||
}
|
||||
|
||||
private void setAutodownloadPreferenceAndOpen(Feed feed) {
|
||||
FeedPreferences feedPreferences = feed.getPreferences();
|
||||
if (UserPreferences.isEnableAutodownload()) {
|
||||
boolean autoDownload = viewBinding.autoDownloadCheckBox.isChecked();
|
||||
feedPreferences.setAutoDownload(autoDownload);
|
||||
|
||||
SharedPreferences preferences = getContext().getSharedPreferences(PREFS, Context.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(feed.getId());
|
||||
}
|
||||
|
||||
private Feed findFeedInSubscriptions() {
|
||||
if (feeds == null) {
|
||||
return null;
|
||||
}
|
||||
for (Feed f : feeds) {
|
||||
if (f.getDownloadUrl().equals(selectedDownloadUrl)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void showErrorDialog(String errorMsg, String details) {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext());
|
||||
builder.setTitle(R.string.error_label);
|
||||
if (errorMsg != null) {
|
||||
String total = errorMsg + "\n\n" + details;
|
||||
SpannableString errorMessage = new SpannableString(total);
|
||||
errorMessage.setSpan(new ForegroundColorSpan(0x88888888),
|
||||
errorMsg.length(), total.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
builder.setMessage(errorMessage);
|
||||
} else {
|
||||
builder.setMessage(R.string.download_error_error_unknown);
|
||||
}
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.cancel());
|
||||
if (getArguments().getBoolean(ARG_WAS_MANUAL_URL, false)) {
|
||||
builder.setNeutralButton(R.string.edit_url_menu, (dialog, which) -> editUrl());
|
||||
}
|
||||
builder.setOnCancelListener(dialog -> getActivity().finish());
|
||||
if (dialog != null && dialog.isShowing()) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
dialog = builder.show();
|
||||
}
|
||||
|
||||
private void editUrl() {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext());
|
||||
builder.setTitle(R.string.edit_url_menu);
|
||||
final EditTextDialogBinding dialogBinding = EditTextDialogBinding.inflate(getLayoutInflater());
|
||||
if (downloader != null) {
|
||||
dialogBinding.urlEditText.setText(downloader.getDownloadRequest().getSource());
|
||||
}
|
||||
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 -> {
|
||||
getActivity().finish();
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return true if a FeedDiscoveryDialog is shown, false otherwise (e.g., due to no feed found).
|
||||
*/
|
||||
private boolean showFeedDiscoveryDialog(File feedFile, String baseUrl) {
|
||||
FeedDiscoverer fd = new FeedDiscoverer();
|
||||
final Map<String, String> urlsMap;
|
||||
try {
|
||||
urlsMap = fd.findLinks(feedFile, baseUrl);
|
||||
if (urlsMap == null || urlsMap.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<String> titles = new ArrayList<>();
|
||||
|
||||
final List<String> urls = new ArrayList<>(urlsMap.keySet());
|
||||
for (String url : urls) {
|
||||
titles.add(urlsMap.get(url));
|
||||
}
|
||||
|
||||
if (urls.size() == 1) {
|
||||
// Skip dialog and display the item directly
|
||||
getArguments().putString(ARG_FEEDURL, urls.get(0));
|
||||
startFeedDownload(urls.get(0));
|
||||
return true;
|
||||
}
|
||||
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<>(getContext(),
|
||||
R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles);
|
||||
DialogInterface.OnClickListener onClickListener = (dialog, which) -> {
|
||||
String selectedUrl = urls.get(which);
|
||||
dialog.dismiss();
|
||||
getArguments().putString(ARG_FEEDURL, selectedUrl);
|
||||
startFeedDownload(selectedUrl);
|
||||
};
|
||||
|
||||
MaterialAlertDialogBuilder ab = new MaterialAlertDialogBuilder(getContext())
|
||||
.setTitle(R.string.feeds_label)
|
||||
.setCancelable(true)
|
||||
.setOnCancelListener(dialog -> getActivity().finish())
|
||||
.setAdapter(adapter, onClickListener);
|
||||
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if(dialog != null && dialog.isShowing()) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
dialog = ab.show();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private class FeedViewAuthenticationDialog extends AuthenticationDialog {
|
||||
|
||||
private final String feedUrl;
|
||||
|
||||
FeedViewAuthenticationDialog(Context context, int titleRes, String feedUrl) {
|
||||
super(context, titleRes, true, username, password);
|
||||
this.feedUrl = feedUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
super.onCancelled();
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfirmed(String username, String password) {
|
||||
OnlineFeedViewFragment.this.username = username;
|
||||
OnlineFeedViewFragment.this.password = password;
|
||||
startFeedDownload(feedUrl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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,200 +10,15 @@
|
|||
android:id="@+id/card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="56dp"
|
||||
android:elevation="16dp"
|
||||
app:cardCornerRadius="8dp">
|
||||
|
||||
<FrameLayout
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragmentContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
style="?android:attr/progressBarStyle" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/feed_display_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/feeditemlist_header_height"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="@color/feed_image_bg">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/backgroundImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/coverImage"
|
||||
android:layout_width="@dimen/thumbnail_length_onlinefeedview"
|
||||
android:layout_height="@dimen/thumbnail_length_onlinefeedview"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="@drawable/bg_rounded_corners"
|
||||
android:clipToOutline="true"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/titleLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_toRightOf="@id/coverImage"
|
||||
android:layout_toEndOf="@id/coverImage"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:shadowColor="@color/black"
|
||||
android:shadowRadius="3"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textFontWeight="800"
|
||||
tools:text="Podcast title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author_label"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/titleLabel"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_toRightOf="@id/coverImage"
|
||||
android:layout_toEndOf="@id/coverImage"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:shadowColor="@color/black"
|
||||
android:shadowRadius="3"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
tools:text="Podcast author" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/closeButton"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:src="@drawable/ic_close_white" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/alternate_urls_spinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:dropDownWidth="match_parent"
|
||||
android:padding="8dp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="@dimen/text_size_micro" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/subscribeButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/subscribe_label" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/openPodcastButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/open_podcast" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/previewEpisodesButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/preview_episodes"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton" />
|
||||
|
||||
<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" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<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:ellipsize="end"
|
||||
android:lineHeight="20dp"
|
||||
style="@style/AntennaPod.TextView.ListItemBody"
|
||||
tools:text="@string/design_time_lorem_ipsum" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
style="?android:attr/progressBarStyle" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/feed_display_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/feeditemlist_header_height"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="@color/feed_image_bg">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/backgroundImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/coverImage"
|
||||
android:layout_width="@dimen/thumbnail_length_onlinefeedview"
|
||||
android:layout_height="@dimen/thumbnail_length_onlinefeedview"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="@drawable/bg_rounded_corners"
|
||||
android:clipToOutline="true"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/titleLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_toRightOf="@id/coverImage"
|
||||
android:layout_toEndOf="@id/coverImage"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:shadowColor="@color/black"
|
||||
android:shadowRadius="3"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textFontWeight="800"
|
||||
tools:text="Podcast title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author_label"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/titleLabel"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_toRightOf="@id/coverImage"
|
||||
android:layout_toEndOf="@id/coverImage"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:shadowColor="@color/black"
|
||||
android:shadowRadius="3"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
tools:text="Podcast author" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/closeButton"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:src="@drawable/ic_close_white" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/alternate_urls_spinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:dropDownWidth="match_parent"
|
||||
android:padding="8dp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="@dimen/text_size_micro" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/subscribeButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/subscribe_label" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/openPodcastButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/open_podcast" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/previewEpisodesButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/preview_episodes"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton" />
|
||||
|
||||
<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" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<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:ellipsize="end"
|
||||
android:lineHeight="20dp"
|
||||
style="@style/AntennaPod.TextView.ListItemBody"
|
||||
tools:text="@string/design_time_lorem_ipsum" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
Loading…
Reference in New Issue