summaryrefslogtreecommitdiff
path: root/ui/discovery/src/main/java/de/danoeh
diff options
context:
space:
mode:
authorByteHamster <ByteHamster@users.noreply.github.com>2024-03-29 13:38:31 +0100
committerGitHub <noreply@github.com>2024-03-29 13:38:31 +0100
commit8f553f08f0347b73b34c80dddef228302cdf5977 (patch)
tree112ef05988be5a37bb08ec2aa06ed3727234bbae /ui/discovery/src/main/java/de/danoeh
parent5ede21d676af8d879ec8f5d695255ccbf4398e76 (diff)
downloadAntennaPod-8f553f08f0347b73b34c80dddef228302cdf5977.zip
Add :ui:discovery module (#7037)
Diffstat (limited to 'ui/discovery/src/main/java/de/danoeh')
-rw-r--r--ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/DiscoveryFragment.java276
-rw-r--r--ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/FeedDiscoverAdapter.java79
-rw-r--r--ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/OnlineSearchAdapter.java122
-rw-r--r--ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/OnlineSearchFragment.java219
-rw-r--r--ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/QuickFeedDiscoveryFragment.java175
5 files changed, 871 insertions, 0 deletions
diff --git a/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/DiscoveryFragment.java b/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/DiscoveryFragment.java
new file mode 100644
index 000000000..9cdcdbcb9
--- /dev/null
+++ b/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/DiscoveryFragment.java
@@ -0,0 +1,276 @@
+package de.danoeh.antennapod.ui.discovery;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.GridView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.appcompat.widget.Toolbar;
+import androidx.fragment.app.Fragment;
+import com.google.android.material.appbar.MaterialToolbar;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.textfield.MaterialAutoCompleteTextView;
+import com.google.android.material.textfield.TextInputLayout;
+import de.danoeh.antennapod.net.discovery.BuildConfig;
+import de.danoeh.antennapod.storage.database.DBReader;
+import de.danoeh.antennapod.event.DiscoveryDefaultUpdateEvent;
+import de.danoeh.antennapod.net.discovery.ItunesTopListLoader;
+import de.danoeh.antennapod.net.discovery.PodcastSearchResult;
+import de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter;
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+import org.greenrobot.eventbus.EventBus;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Searches iTunes store for top podcasts and displays results in a list.
+ */
+public class DiscoveryFragment extends Fragment implements Toolbar.OnMenuItemClickListener {
+ public static final String TAG = "DiscoveryFragment";
+ private static final int NUM_OF_TOP_PODCASTS = 25;
+ private SharedPreferences prefs;
+
+ /**
+ * Adapter responsible with the search results.
+ */
+ private OnlineSearchAdapter adapter;
+ private GridView gridView;
+ private ProgressBar progressBar;
+ private TextView txtvError;
+ private Button butRetry;
+ private TextView txtvEmpty;
+
+ /**
+ * List of podcasts retreived from the search.
+ */
+ private List<PodcastSearchResult> searchResults;
+ private List<PodcastSearchResult> topList;
+ private Disposable disposable;
+ private String countryCode = "US";
+ private boolean hidden;
+ private boolean needsConfirm;
+ private MaterialToolbar toolbar;
+
+ public DiscoveryFragment() {
+ // Required empty public constructor
+ }
+
+ /**
+ * Replace adapter data with provided search results from SearchTask.
+ *
+ * @param result List of Podcast objects containing search results
+ */
+ private void updateData(List<PodcastSearchResult> result) {
+ this.searchResults = result;
+ adapter.clear();
+ if (result != null && result.size() > 0) {
+ gridView.setVisibility(View.VISIBLE);
+ txtvEmpty.setVisibility(View.GONE);
+ for (PodcastSearchResult p : result) {
+ adapter.add(p);
+ }
+ adapter.notifyDataSetInvalidated();
+ } else {
+ gridView.setVisibility(View.GONE);
+ txtvEmpty.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ prefs = getActivity().getSharedPreferences(ItunesTopListLoader.PREFS, Context.MODE_PRIVATE);
+ countryCode = prefs.getString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, Locale.getDefault().getCountry());
+ hidden = prefs.getBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, false);
+ needsConfirm = prefs.getBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, true);
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View root = inflater.inflate(R.layout.fragment_online_search, container, false);
+ gridView = root.findViewById(R.id.gridView);
+ adapter = new OnlineSearchAdapter(getActivity(), new ArrayList<>());
+ gridView.setAdapter(adapter);
+
+ toolbar = root.findViewById(R.id.toolbar);
+ toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack());
+ toolbar.inflateMenu(R.menu.countries_menu);
+ MenuItem discoverHideItem = toolbar.getMenu().findItem(R.id.discover_hide_item);
+ discoverHideItem.setChecked(hidden);
+ toolbar.setOnMenuItemClickListener(this);
+
+ //Show information about the podcast when the list item is clicked
+ gridView.setOnItemClickListener((parent, view1, position, id) -> {
+ PodcastSearchResult podcast = searchResults.get(position);
+ if (podcast.feedUrl == null) {
+ return;
+ }
+ startActivity(new OnlineFeedviewActivityStarter(getContext(), podcast.feedUrl).getIntent());
+ });
+
+ progressBar = root.findViewById(R.id.progressBar);
+ txtvError = root.findViewById(R.id.txtvError);
+ butRetry = root.findViewById(R.id.butRetry);
+ txtvEmpty = root.findViewById(android.R.id.empty);
+
+ loadToplist(countryCode);
+ return root;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (disposable != null) {
+ disposable.dispose();
+ }
+ adapter = null;
+ }
+
+ private void loadToplist(String country) {
+ if (disposable != null) {
+ disposable.dispose();
+ }
+
+ gridView.setVisibility(View.GONE);
+ txtvError.setVisibility(View.GONE);
+ butRetry.setVisibility(View.GONE);
+ butRetry.setText(R.string.retry_label);
+ txtvEmpty.setVisibility(View.GONE);
+ progressBar.setVisibility(View.VISIBLE);
+
+ if (hidden) {
+ gridView.setVisibility(View.GONE);
+ txtvError.setVisibility(View.VISIBLE);
+ txtvError.setText(getResources().getString(R.string.discover_is_hidden));
+ butRetry.setVisibility(View.GONE);
+ txtvEmpty.setVisibility(View.GONE);
+ progressBar.setVisibility(View.GONE);
+ return;
+ }
+ //noinspection ConstantConditions
+ if (BuildConfig.FLAVOR.equals("free") && needsConfirm) {
+ txtvError.setVisibility(View.VISIBLE);
+ txtvError.setText("");
+ butRetry.setVisibility(View.VISIBLE);
+ butRetry.setText(R.string.discover_confirm);
+ butRetry.setOnClickListener(v -> {
+ prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, false).apply();
+ needsConfirm = false;
+ loadToplist(country);
+ });
+ txtvEmpty.setVisibility(View.GONE);
+ progressBar.setVisibility(View.GONE);
+ return;
+ }
+
+ ItunesTopListLoader loader = new ItunesTopListLoader(getContext());
+ disposable = Observable.fromCallable(() ->
+ loader.loadToplist(country, NUM_OF_TOP_PODCASTS, DBReader.getFeedList()))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ podcasts -> {
+ progressBar.setVisibility(View.GONE);
+ topList = podcasts;
+ updateData(topList);
+ }, error -> {
+ Log.e(TAG, Log.getStackTraceString(error));
+ progressBar.setVisibility(View.GONE);
+ txtvError.setText(error.getMessage());
+ txtvError.setVisibility(View.VISIBLE);
+ butRetry.setOnClickListener(v -> loadToplist(country));
+ butRetry.setVisibility(View.VISIBLE);
+ });
+ }
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ final int itemId = item.getItemId();
+ if (itemId == R.id.discover_hide_item) {
+ item.setChecked(!item.isChecked());
+ hidden = item.isChecked();
+ prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply();
+
+ EventBus.getDefault().post(new DiscoveryDefaultUpdateEvent());
+ loadToplist(countryCode);
+ return true;
+ } else if (itemId == R.id.discover_countries_item) {
+
+ LayoutInflater inflater = getLayoutInflater();
+ View selectCountryDialogView = inflater.inflate(R.layout.select_country_dialog, null);
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext());
+ builder.setView(selectCountryDialogView);
+
+ List<String> countryCodeArray = new ArrayList<>(Arrays.asList(Locale.getISOCountries()));
+ Map<String, String> countryCodeNames = new HashMap<>();
+ Map<String, String> countryNameCodes = new HashMap<>();
+ for (String code : countryCodeArray) {
+ Locale locale = new Locale("", code);
+ String countryName = locale.getDisplayCountry();
+ countryCodeNames.put(code, countryName);
+ countryNameCodes.put(countryName, code);
+ }
+
+ List<String> countryNamesSort = new ArrayList<>(countryCodeNames.values());
+ Collections.sort(countryNamesSort);
+
+ ArrayAdapter<String> dataAdapter =
+ new ArrayAdapter<>(this.getContext(), android.R.layout.simple_list_item_1, countryNamesSort);
+ TextInputLayout textInput = selectCountryDialogView.findViewById(R.id.country_text_input);
+ MaterialAutoCompleteTextView editText = (MaterialAutoCompleteTextView) textInput.getEditText();
+ editText.setAdapter(dataAdapter);
+ editText.setText(countryCodeNames.get(countryCode));
+ editText.setOnClickListener(view -> {
+ if (editText.getText().length() != 0) {
+ editText.setText("");
+ editText.postDelayed(editText::showDropDown, 100);
+ }
+ });
+ editText.setOnFocusChangeListener((v, hasFocus) -> {
+ if (hasFocus) {
+ editText.setText("");
+ editText.postDelayed(editText::showDropDown, 100);
+ }
+ });
+
+ builder.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
+ String countryName = editText.getText().toString();
+ if (countryNameCodes.containsKey(countryName)) {
+ countryCode = countryNameCodes.get(countryName);
+ MenuItem discoverHideItem = toolbar.getMenu().findItem(R.id.discover_hide_item);
+ discoverHideItem.setChecked(false);
+ hidden = false;
+ }
+
+ prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply();
+ prefs.edit().putString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, countryCode).apply();
+
+ EventBus.getDefault().post(new DiscoveryDefaultUpdateEvent());
+ loadToplist(countryCode);
+ });
+ builder.setNegativeButton(R.string.cancel_label, null);
+ builder.show();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/FeedDiscoverAdapter.java b/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/FeedDiscoverAdapter.java
new file mode 100644
index 000000000..ac400da9f
--- /dev/null
+++ b/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/FeedDiscoverAdapter.java
@@ -0,0 +1,79 @@
+package de.danoeh.antennapod.ui.discovery;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.bitmap.FitCenter;
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
+import com.bumptech.glide.request.RequestOptions;
+import de.danoeh.antennapod.net.discovery.PodcastSearchResult;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FeedDiscoverAdapter extends BaseAdapter {
+
+ private final List<PodcastSearchResult> data = new ArrayList<>();
+ private final Context context;
+
+ public FeedDiscoverAdapter(Context context) {
+ this.context = context;
+ }
+
+ public void updateData(List<PodcastSearchResult> newData) {
+ data.clear();
+ data.addAll(newData);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return data.size();
+ }
+
+ @Override
+ public PodcastSearchResult getItem(int position) {
+ return data.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return 0;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Holder holder;
+
+ if (convertView == null) {
+ convertView = View.inflate(context, R.layout.quick_feed_discovery_item, null);
+ holder = new Holder();
+ holder.imageView = convertView.findViewById(R.id.discovery_cover);
+ convertView.setTag(holder);
+ } else {
+ holder = (Holder) convertView.getTag();
+ }
+
+
+ final PodcastSearchResult podcast = getItem(position);
+ holder.imageView.setContentDescription(podcast.title);
+
+ Glide.with(context)
+ .load(podcast.imageUrl)
+ .apply(new RequestOptions()
+ .placeholder(R.color.light_gray)
+ .transform(new FitCenter(), new RoundedCorners((int)
+ (8 * context.getResources().getDisplayMetrics().density)))
+ .dontAnimate())
+ .into(holder.imageView);
+
+ return convertView;
+ }
+
+ static class Holder {
+ ImageView imageView;
+ }
+}
diff --git a/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/OnlineSearchAdapter.java b/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/OnlineSearchAdapter.java
new file mode 100644
index 000000000..a536b8ebd
--- /dev/null
+++ b/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/OnlineSearchAdapter.java
@@ -0,0 +1,122 @@
+package de.danoeh.antennapod.ui.discovery;
+
+import android.content.Context;
+import android.widget.ArrayAdapter;
+import androidx.annotation.NonNull;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+
+import com.bumptech.glide.load.resource.bitmap.FitCenter;
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
+import com.bumptech.glide.request.RequestOptions;
+
+import java.util.List;
+
+import de.danoeh.antennapod.net.discovery.PodcastSearchResult;
+
+public class OnlineSearchAdapter extends ArrayAdapter<PodcastSearchResult> {
+ /**
+ * Related Context
+ */
+ private final Context context;
+
+ /**
+ * List holding the podcasts found in the search
+ */
+ private final List<PodcastSearchResult> data;
+
+ /**
+ * Constructor.
+ *
+ * @param context Related context
+ * @param objects Search result
+ */
+ public OnlineSearchAdapter(Context context, List<PodcastSearchResult> objects) {
+ super(context, 0, objects);
+ this.data = objects;
+ this.context = context;
+ }
+
+ @NonNull
+ @Override
+ public View getView(int position, View convertView, @NonNull ViewGroup parent) {
+ //Current podcast
+ PodcastSearchResult podcast = data.get(position);
+
+ //ViewHolder
+ PodcastViewHolder viewHolder;
+
+ //Resulting view
+ View view;
+
+ //Handle view holder stuff
+ if (convertView == null) {
+ view = View.inflate(context, R.layout.online_search_listitem, null);
+ viewHolder = new PodcastViewHolder(view);
+ view.setTag(viewHolder);
+ } else {
+ view = convertView;
+ viewHolder = (PodcastViewHolder) view.getTag();
+ }
+
+ // Set the title
+ viewHolder.titleView.setText(podcast.title);
+ if (podcast.author != null && ! podcast.author.trim().isEmpty()) {
+ viewHolder.authorView.setText(podcast.author);
+ viewHolder.authorView.setVisibility(View.VISIBLE);
+ } else if (podcast.feedUrl != null && !podcast.feedUrl.contains("itunes.apple.com")) {
+ viewHolder.authorView.setText(podcast.feedUrl);
+ viewHolder.authorView.setVisibility(View.VISIBLE);
+ } else {
+ viewHolder.authorView.setVisibility(View.GONE);
+ }
+
+ //Update the empty imageView with the image from the feed
+ Glide.with(context)
+ .load(podcast.imageUrl)
+ .apply(new RequestOptions()
+ .placeholder(R.color.light_gray)
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .transform(new FitCenter(),
+ new RoundedCorners((int) (4 * context.getResources().getDisplayMetrics().density)))
+ .dontAnimate())
+ .into(viewHolder.coverView);
+
+ //Feed the grid view
+ return view;
+ }
+
+ /**
+ * View holder object for the GridView
+ */
+ static class PodcastViewHolder {
+
+ /**
+ * ImageView holding the Podcast image
+ */
+ final ImageView coverView;
+
+ /**
+ * TextView holding the Podcast title
+ */
+ final TextView titleView;
+
+ final TextView authorView;
+
+
+ /**
+ * Constructor
+ * @param view GridView cell
+ */
+ PodcastViewHolder(View view) {
+ coverView = view.findViewById(R.id.imgvCover);
+ titleView = view.findViewById(R.id.txtvTitle);
+ authorView = view.findViewById(R.id.txtvAuthor);
+ }
+ }
+}
diff --git a/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/OnlineSearchFragment.java b/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/OnlineSearchFragment.java
new file mode 100644
index 000000000..00459a174
--- /dev/null
+++ b/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/OnlineSearchFragment.java
@@ -0,0 +1,219 @@
+package de.danoeh.antennapod.ui.discovery;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AbsListView;
+import android.widget.Button;
+import android.widget.GridView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.appcompat.widget.SearchView;
+import androidx.fragment.app.Fragment;
+
+import com.google.android.material.appbar.MaterialToolbar;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.danoeh.antennapod.net.discovery.PodcastSearchResult;
+import de.danoeh.antennapod.net.discovery.PodcastSearcher;
+import de.danoeh.antennapod.net.discovery.PodcastSearcherRegistry;
+import de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter;
+import io.reactivex.disposables.Disposable;
+
+public class OnlineSearchFragment extends Fragment {
+
+ private static final String TAG = "FyydSearchFragment";
+ private static final String ARG_SEARCHER = "searcher";
+ private static final String ARG_QUERY = "query";
+
+ /**
+ * Adapter responsible with the search results
+ */
+ private OnlineSearchAdapter adapter;
+ private PodcastSearcher searchProvider;
+ private GridView gridView;
+ private ProgressBar progressBar;
+ private TextView txtvError;
+ private Button butRetry;
+ private TextView txtvEmpty;
+
+ /**
+ * List of podcasts retreived from the search
+ */
+ private List<PodcastSearchResult> searchResults;
+ private Disposable disposable;
+
+ public static OnlineSearchFragment newInstance(Class<? extends PodcastSearcher> searchProvider) {
+ return newInstance(searchProvider, null);
+ }
+
+ public static OnlineSearchFragment newInstance(Class<? extends PodcastSearcher> searchProvider, String query) {
+ OnlineSearchFragment fragment = new OnlineSearchFragment();
+ Bundle arguments = new Bundle();
+ arguments.putString(ARG_SEARCHER, searchProvider.getName());
+ arguments.putString(ARG_QUERY, query);
+ fragment.setArguments(arguments);
+ return fragment;
+ }
+
+ /**
+ * Constructor
+ */
+ public OnlineSearchFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ for (PodcastSearcherRegistry.SearcherInfo info : PodcastSearcherRegistry.getSearchProviders()) {
+ if (info.searcher.getClass().getName().equals(getArguments().getString(ARG_SEARCHER))) {
+ searchProvider = info.searcher;
+ break;
+ }
+ }
+ if (searchProvider == null) {
+ throw new IllegalArgumentException("Podcast searcher not found");
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View root = inflater.inflate(R.layout.fragment_online_search, container, false);
+ gridView = root.findViewById(R.id.gridView);
+ adapter = new OnlineSearchAdapter(getActivity(), new ArrayList<>());
+ gridView.setAdapter(adapter);
+
+ //Show information about the podcast when the list item is clicked
+ gridView.setOnItemClickListener((parent, view1, position, id) -> {
+ PodcastSearchResult podcast = searchResults.get(position);
+ startActivity(new OnlineFeedviewActivityStarter(getContext(), podcast.feedUrl)
+ .withStartedFromSearch().getIntent());
+ });
+ progressBar = root.findViewById(R.id.progressBar);
+ txtvError = root.findViewById(R.id.txtvError);
+ butRetry = root.findViewById(R.id.butRetry);
+ txtvEmpty = root.findViewById(android.R.id.empty);
+ TextView txtvPoweredBy = root.findViewById(R.id.search_powered_by);
+ txtvPoweredBy.setText(getString(R.string.search_powered_by, searchProvider.getName()));
+ setupToolbar(root.findViewById(R.id.toolbar));
+
+ gridView.setOnScrollListener(new AbsListView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ if (scrollState == SCROLL_STATE_TOUCH_SCROLL) {
+ InputMethodManager imm = (InputMethodManager)
+ getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ }
+ });
+ return root;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (disposable != null) {
+ disposable.dispose();
+ }
+ adapter = null;
+ }
+
+ private void setupToolbar(MaterialToolbar toolbar) {
+ toolbar.inflateMenu(R.menu.online_search);
+ toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack());
+
+ MenuItem searchItem = toolbar.getMenu().findItem(R.id.action_search);
+ final SearchView sv = (SearchView) searchItem.getActionView();
+ sv.setQueryHint(getString(R.string.search_podcast_hint));
+ sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String s) {
+ sv.clearFocus();
+ search(s);
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String s) {
+ return false;
+ }
+ });
+ sv.setOnQueryTextFocusChangeListener((view, hasFocus) -> {
+ if (hasFocus) {
+ showInputMethod(view.findFocus());
+ }
+ });
+ searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
+ @Override
+ public boolean onMenuItemActionExpand(MenuItem item) {
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(MenuItem item) {
+ getActivity().getSupportFragmentManager().popBackStack();
+ return true;
+ }
+ });
+ searchItem.expandActionView();
+
+ if (getArguments().getString(ARG_QUERY, null) != null) {
+ sv.setQuery(getArguments().getString(ARG_QUERY, null), true);
+ }
+ }
+
+ private void search(String query) {
+ if (disposable != null) {
+ disposable.dispose();
+ }
+ showOnlyProgressBar();
+ disposable = searchProvider.search(query).subscribe(result -> {
+ searchResults = result;
+ progressBar.setVisibility(View.GONE);
+ adapter.clear();
+ adapter.addAll(searchResults);
+ adapter.notifyDataSetInvalidated();
+ gridView.setVisibility(!searchResults.isEmpty() ? View.VISIBLE : View.GONE);
+ txtvEmpty.setVisibility(searchResults.isEmpty() ? View.VISIBLE : View.GONE);
+ txtvEmpty.setText(getString(R.string.no_results_for_query, query));
+ }, error -> {
+ Log.e(TAG, Log.getStackTraceString(error));
+ progressBar.setVisibility(View.GONE);
+ txtvError.setText(error.toString());
+ txtvError.setVisibility(View.VISIBLE);
+ butRetry.setOnClickListener(v -> search(query));
+ butRetry.setVisibility(View.VISIBLE);
+ });
+ }
+
+ private void showOnlyProgressBar() {
+ gridView.setVisibility(View.GONE);
+ txtvError.setVisibility(View.GONE);
+ butRetry.setVisibility(View.GONE);
+ txtvEmpty.setVisibility(View.GONE);
+ progressBar.setVisibility(View.VISIBLE);
+ }
+
+ private void showInputMethod(View view) {
+ InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm != null) {
+ imm.showSoftInput(view, 0);
+ }
+ }
+}
diff --git a/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/QuickFeedDiscoveryFragment.java b/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/QuickFeedDiscoveryFragment.java
new file mode 100644
index 000000000..ff5e328f2
--- /dev/null
+++ b/ui/discovery/src/main/java/de/danoeh/antennapod/ui/discovery/QuickFeedDiscoveryFragment.java
@@ -0,0 +1,175 @@
+package de.danoeh.antennapod.ui.discovery;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.GridView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import androidx.fragment.app.Fragment;
+import de.danoeh.antennapod.net.discovery.BuildConfig;
+import de.danoeh.antennapod.storage.database.DBReader;
+import de.danoeh.antennapod.event.DiscoveryDefaultUpdateEvent;
+import de.danoeh.antennapod.net.discovery.ItunesTopListLoader;
+import de.danoeh.antennapod.net.discovery.PodcastSearchResult;
+import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
+import de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter;
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import static android.content.Context.MODE_PRIVATE;
+
+
+public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView.OnItemClickListener {
+ private static final String TAG = "FeedDiscoveryFragment";
+ private static final int NUM_SUGGESTIONS = 12;
+
+ private Disposable disposable;
+ private FeedDiscoverAdapter adapter;
+ private GridView discoverGridLayout;
+ private TextView errorTextView;
+ private TextView poweredByTextView;
+ private LinearLayout errorView;
+ private Button errorRetry;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+ View root = inflater.inflate(R.layout.quick_feed_discovery, container, false);
+ View discoverMore = root.findViewById(R.id.discover_more);
+ discoverMore.setOnClickListener(v -> startActivity(new MainActivityStarter(getContext())
+ .withFragmentLoaded(DiscoveryFragment.TAG)
+ .withAddToBackStack()
+ .getIntent()));
+
+ discoverGridLayout = root.findViewById(R.id.discover_grid);
+ errorView = root.findViewById(R.id.discover_error);
+ errorTextView = root.findViewById(R.id.discover_error_txtV);
+ errorRetry = root.findViewById(R.id.discover_error_retry_btn);
+ poweredByTextView = root.findViewById(R.id.discover_powered_by_itunes);
+
+ adapter = new FeedDiscoverAdapter(getActivity());
+ discoverGridLayout.setAdapter(adapter);
+ discoverGridLayout.setOnItemClickListener(this);
+
+ DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
+ float screenWidthDp = displayMetrics.widthPixels / displayMetrics.density;
+ if (screenWidthDp > 600) {
+ discoverGridLayout.setNumColumns(6);
+ } else {
+ discoverGridLayout.setNumColumns(4);
+ }
+
+ // Fill with dummy elements to have a fixed height and
+ // prevent the UI elements below from jumping on slow connections
+ List<PodcastSearchResult> dummies = new ArrayList<>();
+ for (int i = 0; i < NUM_SUGGESTIONS; i++) {
+ dummies.add(PodcastSearchResult.dummy());
+ }
+
+ adapter.updateData(dummies);
+ loadToplist();
+
+ EventBus.getDefault().register(this);
+ return root;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ EventBus.getDefault().unregister(this);
+ if (disposable != null) {
+ disposable.dispose();
+ }
+ }
+
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ @SuppressWarnings("unused")
+ public void onDiscoveryDefaultUpdateEvent(DiscoveryDefaultUpdateEvent event) {
+ loadToplist();
+ }
+
+ private void loadToplist() {
+ errorView.setVisibility(View.GONE);
+ errorRetry.setVisibility(View.INVISIBLE);
+ errorRetry.setText(R.string.retry_label);
+ poweredByTextView.setVisibility(View.VISIBLE);
+
+ ItunesTopListLoader loader = new ItunesTopListLoader(getContext());
+ SharedPreferences prefs = getActivity().getSharedPreferences(ItunesTopListLoader.PREFS, MODE_PRIVATE);
+ String countryCode = prefs.getString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE,
+ Locale.getDefault().getCountry());
+ if (prefs.getBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, false)) {
+ errorTextView.setText(R.string.discover_is_hidden);
+ errorView.setVisibility(View.VISIBLE);
+ discoverGridLayout.setVisibility(View.GONE);
+ errorRetry.setVisibility(View.GONE);
+ poweredByTextView.setVisibility(View.GONE);
+ return;
+ }
+ //noinspection ConstantConditions
+ if (BuildConfig.FLAVOR.equals("free") && prefs.getBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, true)) {
+ errorTextView.setText("");
+ errorView.setVisibility(View.VISIBLE);
+ discoverGridLayout.setVisibility(View.VISIBLE);
+ errorRetry.setVisibility(View.VISIBLE);
+ errorRetry.setText(R.string.discover_confirm);
+ poweredByTextView.setVisibility(View.VISIBLE);
+ errorRetry.setOnClickListener(v -> {
+ prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, false).apply();
+ loadToplist();
+ });
+ return;
+ }
+
+ disposable = Observable.fromCallable(() ->
+ loader.loadToplist(countryCode, NUM_SUGGESTIONS, DBReader.getFeedList()))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ podcasts -> {
+ errorView.setVisibility(View.GONE);
+ if (podcasts.size() == 0) {
+ errorTextView.setText(getResources().getText(R.string.search_status_no_results));
+ errorView.setVisibility(View.VISIBLE);
+ discoverGridLayout.setVisibility(View.INVISIBLE);
+ } else {
+ discoverGridLayout.setVisibility(View.VISIBLE);
+ adapter.updateData(podcasts);
+ }
+ }, error -> {
+ Log.e(TAG, Log.getStackTraceString(error));
+ errorTextView.setText(error.getLocalizedMessage());
+ errorView.setVisibility(View.VISIBLE);
+ discoverGridLayout.setVisibility(View.INVISIBLE);
+ errorRetry.setVisibility(View.VISIBLE);
+ errorRetry.setOnClickListener(v -> loadToplist());
+ });
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, final View view, int position, long id) {
+ PodcastSearchResult podcast = adapter.getItem(position);
+ if (TextUtils.isEmpty(podcast.feedUrl)) {
+ return;
+ }
+ startActivity(new OnlineFeedviewActivityStarter(getContext(), podcast.feedUrl).getIntent());
+ }
+}