summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java84
-rw-r--r--app/src/main/java/de/danoeh/antennapod/discovery/FyydPodcastSearcher.java45
-rw-r--r--app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java47
-rw-r--r--app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java81
-rw-r--r--app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java77
-rw-r--r--app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java9
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java3
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/CombinedSearchFragment.java242
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/FyydSearchFragment.java77
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java93
-rw-r--r--app/src/main/res/layout/addfeed.xml8
11 files changed, 566 insertions, 200 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java
index 2cf17c85f..f5213e4ab 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java
@@ -2,7 +2,6 @@ package de.danoeh.antennapod.adapter.itunes;
import android.content.Context;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
@@ -13,18 +12,14 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;
-import de.danoeh.antennapod.core.glide.ApGlideSettings;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
+import de.danoeh.antennapod.discovery.PodcastSearchResult;
import java.util.List;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
-import de.mfietz.fyydlin.SearchHit;
-public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
+public class ItunesAdapter extends ArrayAdapter<PodcastSearchResult> {
/**
* Related Context
*/
@@ -33,7 +28,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
/**
* List holding the podcasts found in the search
*/
- private final List<Podcast> data;
+ private final List<PodcastSearchResult> data;
/**
* Constructor.
@@ -41,7 +36,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
* @param context Related context
* @param objects Search result
*/
- public ItunesAdapter(Context context, List<Podcast> objects) {
+ public ItunesAdapter(Context context, List<PodcastSearchResult> objects) {
super(context, 0, objects);
this.data = objects;
this.context = context;
@@ -51,7 +46,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
//Current podcast
- Podcast podcast = data.get(position);
+ PodcastSearchResult podcast = data.get(position);
//ViewHolder
PodcastViewHolder viewHolder;
@@ -94,75 +89,6 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
}
/**
- * Represents an individual podcast on the iTunes Store.
- */
- public static class Podcast { //TODO: Move this out eventually. Possibly to core.itunes.model
-
- /**
- * The name of the podcast
- */
- public final String title;
-
- /**
- * URL of the podcast image
- */
- @Nullable
- public final String imageUrl;
- /**
- * URL of the podcast feed
- */
- @Nullable
- public final String feedUrl;
-
-
- private Podcast(String title, @Nullable String imageUrl, @Nullable String feedUrl) {
- this.title = title;
- this.imageUrl = imageUrl;
- this.feedUrl = feedUrl;
- }
-
- /**
- * Constructs a Podcast instance from a iTunes search result
- *
- * @param json object holding the podcast information
- * @throws JSONException
- */
- public static Podcast fromSearch(JSONObject json) {
- String title = json.optString("collectionName", "");
- String imageUrl = json.optString("artworkUrl100", null);
- String feedUrl = json.optString("feedUrl", null);
- return new Podcast(title, imageUrl, feedUrl);
- }
-
- public static Podcast fromSearch(SearchHit searchHit) {
- return new Podcast(searchHit.getTitle(), searchHit.getThumbImageURL(), searchHit.getXmlUrl());
- }
-
- /**
- * Constructs a Podcast instance from iTunes toplist entry
- *
- * @param json object holding the podcast information
- * @throws JSONException
- */
- public static Podcast fromToplist(JSONObject json) throws JSONException {
- String title = json.getJSONObject("title").getString("label");
- String imageUrl = null;
- JSONArray images = json.getJSONArray("im:image");
- for(int i=0; imageUrl == null && i < images.length(); i++) {
- JSONObject image = images.getJSONObject(i);
- String height = image.getJSONObject("attributes").getString("height");
- if(Integer.parseInt(height) >= 100) {
- imageUrl = image.getString("label");
- }
- }
- String feedUrl = "https://itunes.apple.com/lookup?id=" +
- json.getJSONObject("id").getJSONObject("attributes").getString("im:id");
- return new Podcast(title, imageUrl, feedUrl);
- }
-
- }
-
- /**
* View holder object for the GridView
*/
static class PodcastViewHolder {
diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/FyydPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/FyydPodcastSearcher.java
new file mode 100644
index 000000000..af0597a07
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/discovery/FyydPodcastSearcher.java
@@ -0,0 +1,45 @@
+package de.danoeh.antennapod.discovery;
+
+import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
+import de.mfietz.fyydlin.FyydClient;
+import de.mfietz.fyydlin.FyydResponse;
+import de.mfietz.fyydlin.SearchHit;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Consumer;
+import io.reactivex.schedulers.Schedulers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FyydPodcastSearcher implements PodcastSearcher {
+ private final String query;
+ private final FyydClient client = new FyydClient(AntennapodHttpClient.getHttpClient());
+
+ public FyydPodcastSearcher(String query) {
+ this.query = query;
+ }
+
+ public Disposable search(Consumer<? super List<PodcastSearchResult>> successHandler, Consumer<? super Throwable> errorHandler) {
+ return client.searchPodcasts(query, 10)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(result -> {
+ ArrayList<PodcastSearchResult> results = processSearchResult(result);
+ successHandler.accept(results);
+ }, errorHandler);
+ }
+
+ private ArrayList<PodcastSearchResult> processSearchResult(FyydResponse response) {
+ ArrayList<PodcastSearchResult> searchResults = new ArrayList<>();
+
+ if (!response.getData().isEmpty()) {
+ for (SearchHit searchHit : response.getData()) {
+ PodcastSearchResult podcast = PodcastSearchResult.fromFyyd(searchHit);
+ searchResults.add(podcast);
+ }
+ }
+
+ return searchResults;
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java
new file mode 100644
index 000000000..530f234fd
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java
@@ -0,0 +1,47 @@
+package de.danoeh.antennapod.discovery;
+
+import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
+import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast;
+import io.reactivex.Single;
+import io.reactivex.SingleOnSubscribe;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Consumer;
+import io.reactivex.schedulers.Schedulers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GpodnetPodcastSearcher implements PodcastSearcher {
+ private final String query;
+
+ public GpodnetPodcastSearcher(String query) {
+ this.query = query;
+ }
+
+ public Disposable search(Consumer<? super List<PodcastSearchResult>> successHandler, Consumer<? super Throwable> errorHandler) {
+ return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
+ GpodnetService service = null;
+ try {
+ service = new GpodnetService();
+ List<GpodnetPodcast> gpodnetPodcasts = service.searchPodcasts(query, 0);
+ List<PodcastSearchResult> results = new ArrayList<>();
+ for (GpodnetPodcast podcast : gpodnetPodcasts) {
+ results.add(PodcastSearchResult.fromGpodder(podcast));
+ }
+ subscriber.onSuccess(results);
+ } catch (GpodnetServiceException e) {
+ e.printStackTrace();
+ subscriber.onError(e);
+ } finally {
+ if (service != null) {
+ service.shutdown();
+ }
+ }
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(successHandler, errorHandler);
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java
new file mode 100644
index 000000000..5d5837e18
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java
@@ -0,0 +1,81 @@
+package de.danoeh.antennapod.discovery;
+
+import android.content.Context;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.ClientConfig;
+import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
+import io.reactivex.Single;
+import io.reactivex.SingleOnSubscribe;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Consumer;
+import io.reactivex.schedulers.Schedulers;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ItunesPodcastSearcher implements PodcastSearcher {
+ private static final String ITUNES_API_URL = "https://itunes.apple.com/search?media=podcast&term=%s";
+ private final Context context;
+ private final String query;
+
+ public ItunesPodcastSearcher(Context context, String query) {
+ this.context = context;
+ this.query = query;
+ }
+
+ public Disposable search(Consumer<? super List<PodcastSearchResult>> successHandler, Consumer<? super Throwable> errorHandler) {
+ return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
+ String encodedQuery = null;
+ try {
+ encodedQuery = URLEncoder.encode(query, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // this won't ever be thrown
+ }
+ if (encodedQuery == null) {
+ encodedQuery = query; // failsafe
+ }
+
+ String formattedUrl = String.format(ITUNES_API_URL, encodedQuery);
+
+ OkHttpClient client = AntennapodHttpClient.getHttpClient();
+ Request.Builder httpReq = new Request.Builder()
+ .url(formattedUrl)
+ .header("User-Agent", ClientConfig.USER_AGENT);
+ List<PodcastSearchResult> podcasts = new ArrayList<>();
+ try {
+ Response response = client.newCall(httpReq.build()).execute();
+
+ if (response.isSuccessful()) {
+ String resultString = response.body().string();
+ JSONObject result = new JSONObject(resultString);
+ JSONArray j = result.getJSONArray("results");
+
+ for (int i = 0; i < j.length(); i++) {
+ JSONObject podcastJson = j.getJSONObject(i);
+ PodcastSearchResult podcast = PodcastSearchResult.fromItunes(podcastJson);
+ podcasts.add(podcast);
+ }
+ } else {
+ String prefix = context.getString(R.string.error_msg_prefix);
+ subscriber.onError(new IOException(prefix + response));
+ }
+ } catch (IOException | JSONException e) {
+ subscriber.onError(e);
+ }
+ subscriber.onSuccess(podcasts);
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(successHandler, errorHandler);
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java
new file mode 100644
index 000000000..39c5b8dee
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java
@@ -0,0 +1,77 @@
+package de.danoeh.antennapod.discovery;
+
+import android.support.annotation.Nullable;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast;
+import de.mfietz.fyydlin.SearchHit;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class PodcastSearchResult {
+
+ /**
+ * The name of the podcast
+ */
+ public final String title;
+
+ /**
+ * URL of the podcast image
+ */
+ @Nullable
+ public final String imageUrl;
+ /**
+ * URL of the podcast feed
+ */
+ @Nullable
+ public final String feedUrl;
+
+
+ private PodcastSearchResult(String title, @Nullable String imageUrl, @Nullable String feedUrl) {
+ this.title = title;
+ this.imageUrl = imageUrl;
+ this.feedUrl = feedUrl;
+ }
+
+ /**
+ * Constructs a Podcast instance from a iTunes search result
+ *
+ * @param json object holding the podcast information
+ * @throws JSONException
+ */
+ public static PodcastSearchResult fromItunes(JSONObject json) {
+ String title = json.optString("collectionName", "");
+ String imageUrl = json.optString("artworkUrl100", null);
+ String feedUrl = json.optString("feedUrl", null);
+ return new PodcastSearchResult(title, imageUrl, feedUrl);
+ }
+
+ /**
+ * Constructs a Podcast instance from iTunes toplist entry
+ *
+ * @param json object holding the podcast information
+ * @throws JSONException
+ */
+ public static PodcastSearchResult fromItunesToplist(JSONObject json) throws JSONException {
+ String title = json.getJSONObject("title").getString("label");
+ String imageUrl = null;
+ JSONArray images = json.getJSONArray("im:image");
+ for(int i=0; imageUrl == null && i < images.length(); i++) {
+ JSONObject image = images.getJSONObject(i);
+ String height = image.getJSONObject("attributes").getString("height");
+ if(Integer.parseInt(height) >= 100) {
+ imageUrl = image.getString("label");
+ }
+ }
+ String feedUrl = "https://itunes.apple.com/lookup?id=" +
+ json.getJSONObject("id").getJSONObject("attributes").getString("im:id");
+ return new PodcastSearchResult(title, imageUrl, feedUrl);
+ }
+
+ public static PodcastSearchResult fromFyyd(SearchHit searchHit) {
+ return new PodcastSearchResult(searchHit.getTitle(), searchHit.getThumbImageURL(), searchHit.getXmlUrl());
+ }
+
+ public static PodcastSearchResult fromGpodder(GpodnetPodcast searchHit) {
+ return new PodcastSearchResult(searchHit.getTitle(), searchHit.getLogoUrl(), searchHit.getUrl());
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java
new file mode 100644
index 000000000..2a4937e28
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java
@@ -0,0 +1,9 @@
+package de.danoeh.antennapod.discovery;
+
+import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Consumer;
+import java.util.List;
+
+public interface PodcastSearcher {
+ Disposable search(Consumer<? super List<PodcastSearchResult>> successHandler, Consumer<? super Throwable> errorHandler);
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
index ee2373da8..ad8849a3e 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
@@ -42,6 +42,7 @@ public class AddFeedFragment extends Fragment {
Button butSearchITunes = root.findViewById(R.id.butSearchItunes);
Button butBrowserGpoddernet = root.findViewById(R.id.butBrowseGpoddernet);
Button butSearchFyyd = root.findViewById(R.id.butSearchFyyd);
+ Button butSearchCombined = root.findViewById(R.id.butSearchCombined);
Button butOpmlImport = root.findViewById(R.id.butOpmlImport);
Button butConfirm = root.findViewById(R.id.butConfirm);
@@ -54,6 +55,8 @@ public class AddFeedFragment extends Fragment {
butSearchFyyd.setOnClickListener(v -> activity.loadChildFragment(new FyydSearchFragment()));
+ butSearchCombined.setOnClickListener(v -> activity.loadChildFragment(new CombinedSearchFragment()));
+
butOpmlImport.setOnClickListener(v -> startActivity(new Intent(getActivity(),
OpmlImportFromPathActivity.class)));
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CombinedSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CombinedSearchFragment.java
new file mode 100644
index 000000000..33f63fa96
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/CombinedSearchFragment.java
@@ -0,0 +1,242 @@
+package de.danoeh.antennapod.fragment;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.widget.SearchView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.GridView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
+import de.danoeh.antennapod.adapter.itunes.ItunesAdapter;
+import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
+import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast;
+import de.danoeh.antennapod.discovery.FyydPodcastSearcher;
+import de.danoeh.antennapod.discovery.GpodnetPodcastSearcher;
+import de.danoeh.antennapod.discovery.ItunesPodcastSearcher;
+import de.danoeh.antennapod.discovery.PodcastSearchResult;
+import de.danoeh.antennapod.discovery.PodcastSearcher;
+import de.danoeh.antennapod.menuhandler.MenuItemUtils;
+import io.reactivex.Single;
+import io.reactivex.SingleOnSubscribe;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+public class CombinedSearchFragment extends Fragment {
+
+ private static final String TAG = "CombinedSearchFragment";
+
+ /**
+ * Adapter responsible with the search results
+ */
+ private ItunesAdapter 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 = new ArrayList<>();
+ private List<Disposable> disposables = new ArrayList<>();
+
+ /**
+ * Constructor
+ */
+ public CombinedSearchFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View root = inflater.inflate(R.layout.fragment_itunes_search, container, false);
+ gridView = root.findViewById(R.id.gridView);
+ adapter = new ItunesAdapter(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);
+ Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
+ intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, podcast.feedUrl);
+ intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, podcast.title);
+ startActivity(intent);
+ });
+ 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);
+
+ return root;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ disposeAll();
+ adapter = null;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.itunes_search, menu);
+ MenuItem searchItem = menu.findItem(R.id.action_search);
+ final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem);
+ MenuItemUtils.adjustTextColor(getActivity(), sv);
+ sv.setQueryHint(getString(R.string.search_label));
+ 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;
+ }
+ });
+ MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() {
+ @Override
+ public boolean onMenuItemActionExpand(MenuItem item) {
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(MenuItem item) {
+ getActivity().getSupportFragmentManager().popBackStack();
+ return true;
+ }
+ });
+ MenuItemCompat.expandActionView(searchItem);
+ }
+
+ private void search(String query) {
+ disposeAll();
+
+ showOnlyProgressBar();
+
+ List<PodcastSearcher> searchProviders = new ArrayList<>();
+ searchProviders.add(new FyydPodcastSearcher(query));
+ searchProviders.add(new ItunesPodcastSearcher(getContext(), query));
+ searchProviders.add(new GpodnetPodcastSearcher(query));
+
+ List<List<PodcastSearchResult>> singleResults = new ArrayList<>();
+ CountDownLatch latch = new CountDownLatch(searchProviders.size());
+ for (PodcastSearcher searchProvider : searchProviders) {
+ disposables.add(searchProvider.search(e -> {
+ singleResults.add(e);
+ latch.countDown();
+ }, throwable -> {
+ Toast.makeText(getContext(), throwable.getLocalizedMessage(), Toast.LENGTH_LONG).show();
+ latch.countDown();
+ }
+ ));
+ }
+
+ disposables.add(Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
+ latch.await();
+
+ HashMap<String, Float> resultRanking = new HashMap<>();
+ HashMap<String, PodcastSearchResult> urlToResult = new HashMap<>();
+ for (List<PodcastSearchResult> providerResults : singleResults) {
+ for (int position = 0; position < providerResults.size(); position++) {
+ PodcastSearchResult result = providerResults.get(position);
+ urlToResult.put(result.feedUrl, result);
+
+ float ranking = 0;
+ if (resultRanking.containsKey(result.feedUrl)) {
+ ranking = resultRanking.get(result.feedUrl);
+ }
+ ranking += 1.f / (position + 1.f);
+ resultRanking.put(result.feedUrl, ranking);
+ }
+ }
+ List<Map.Entry<String, Float>> sortedResults = new ArrayList<>(resultRanking.entrySet());
+ Collections.sort(sortedResults, (o1, o2) -> Double.compare(o2.getValue(), o1.getValue()));
+
+ List<PodcastSearchResult> results = new ArrayList<>();
+ for (Map.Entry<String, Float> res : sortedResults) {
+ results.add(urlToResult.get(res.getKey()));
+ }
+ subscriber.onSuccess(results);
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .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);
+
+ }, 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 disposeAll() {
+ for (Disposable d : disposables) {
+ d.dispose();
+ }
+ disposables.clear();
+ }
+
+ private void showOnlyProgressBar() {
+ gridView.setVisibility(View.GONE);
+ txtvError.setVisibility(View.GONE);
+ butRetry.setVisibility(View.GONE);
+ txtvEmpty.setVisibility(View.GONE);
+ progressBar.setVisibility(View.VISIBLE);
+ }
+
+ public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K, V>> comparingByValue() {
+ return (Comparator<Map.Entry<K, V>> & Serializable)
+ (c1, c2) -> c1.getValue().compareTo(c2.getValue());
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FyydSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FyydSearchFragment.java
index dadc596e2..d51db8b07 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/FyydSearchFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/FyydSearchFragment.java
@@ -16,24 +16,16 @@ import android.widget.Button;
import android.widget.GridView;
import android.widget.ProgressBar;
import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.List;
-
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
import de.danoeh.antennapod.adapter.itunes.ItunesAdapter;
-import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
+import de.danoeh.antennapod.discovery.FyydPodcastSearcher;
+import de.danoeh.antennapod.discovery.PodcastSearchResult;
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
-import de.mfietz.fyydlin.FyydClient;
-import de.mfietz.fyydlin.FyydResponse;
-import de.mfietz.fyydlin.SearchHit;
-import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
-import io.reactivex.schedulers.Schedulers;
-import static de.danoeh.antennapod.adapter.itunes.ItunesAdapter.Podcast;
-import static java.util.Collections.emptyList;
+import java.util.ArrayList;
+import java.util.List;
public class FyydSearchFragment extends Fragment {
@@ -49,12 +41,10 @@ public class FyydSearchFragment extends Fragment {
private Button butRetry;
private TextView txtvEmpty;
- private final FyydClient client = new FyydClient(AntennapodHttpClient.getHttpClient());
-
/**
* List of podcasts retreived from the search
*/
- private List<Podcast> searchResults;
+ private List<PodcastSearchResult> searchResults;
private Disposable disposable;
/**
@@ -81,7 +71,7 @@ public class FyydSearchFragment extends Fragment {
//Show information about the podcast when the list item is clicked
gridView.setOnItemClickListener((parent, view1, position, id) -> {
- Podcast podcast = searchResults.get(position);
+ PodcastSearchResult podcast = searchResults.get(position);
Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, podcast.feedUrl);
intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, podcast.title);
@@ -145,20 +135,26 @@ public class FyydSearchFragment extends Fragment {
disposable.dispose();
}
showOnlyProgressBar();
- disposable = client.searchPodcasts(query, 10)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(result -> {
- progressBar.setVisibility(View.GONE);
- processSearchResult(result);
- }, 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);
- });
+
+ FyydPodcastSearcher searcher = new FyydPodcastSearcher(query);
+ disposable = searcher.search(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);
+
+ }, 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() {
@@ -168,25 +164,4 @@ public class FyydSearchFragment extends Fragment {
txtvEmpty.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}
-
- private void processSearchResult(FyydResponse response) {
- adapter.clear();
- if (!response.getData().isEmpty()) {
- adapter.clear();
- searchResults = new ArrayList<>();
- for (SearchHit searchHit : response.getData()) {
- Podcast podcast = Podcast.fromSearch(searchHit);
- searchResults.add(podcast);
- }
- } else {
- searchResults = emptyList();
- }
- for(Podcast podcast : searchResults) {
- adapter.add(podcast);
- }
- adapter.notifyDataSetInvalidated();
- gridView.setVisibility(!searchResults.isEmpty() ? View.VISIBLE : View.GONE);
- txtvEmpty.setVisibility(searchResults.isEmpty() ? View.VISIBLE : View.GONE);
- }
-
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java
index a9f56d317..4f28b650e 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java
@@ -20,13 +20,13 @@ import android.widget.TextView;
import com.afollestad.materialdialogs.MaterialDialog;
+import de.danoeh.antennapod.discovery.ItunesPodcastSearcher;
+import de.danoeh.antennapod.discovery.PodcastSearchResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -46,15 +46,11 @@ import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
-import static de.danoeh.antennapod.adapter.itunes.ItunesAdapter.Podcast;
-
//Searches iTunes store for given string and displays results in a list
public class ItunesSearchFragment extends Fragment {
private static final String TAG = "ItunesSearchFragment";
- private static final String API_URL = "https://itunes.apple.com/search?media=podcast&term=%s";
-
/**
* Adapter responsible with the search results
@@ -69,21 +65,21 @@ public class ItunesSearchFragment extends Fragment {
/**
* List of podcasts retreived from the search
*/
- private List<Podcast> searchResults;
- private List<Podcast> topList;
+ private List<PodcastSearchResult> searchResults;
+ private List<PodcastSearchResult> topList;
private Disposable disposable;
/**
* Replace adapter data with provided search results from SearchTask.
* @param result List of Podcast objects containing search results
*/
- private void updateData(List<Podcast> result) {
+ 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 (Podcast p : result) {
+ for (PodcastSearchResult p : result) {
adapter.add(p);
}
adapter.notifyDataSetInvalidated();
@@ -117,7 +113,7 @@ public class ItunesSearchFragment extends Fragment {
//Show information about the podcast when the list item is clicked
gridView.setOnItemClickListener((parent, view1, position, id) -> {
- Podcast podcast = searchResults.get(position);
+ PodcastSearchResult podcast = searchResults.get(position);
if(podcast.feedUrl == null) {
return;
}
@@ -239,7 +235,7 @@ public class ItunesSearchFragment extends Fragment {
butRetry.setVisibility(View.GONE);
txtvEmpty.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
- disposable = Single.create((SingleOnSubscribe<List<Podcast>>) emitter -> {
+ disposable = Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) emitter -> {
String lang = Locale.getDefault().getLanguage();
OkHttpClient client = AntennapodHttpClient.getHttpClient();
String feedString;
@@ -249,7 +245,7 @@ public class ItunesSearchFragment extends Fragment {
} catch (IOException e) {
feedString = getTopListFeed(client, "us");
}
- List<Podcast> podcasts = parseFeed(feedString);
+ List<PodcastSearchResult> podcasts = parseFeed(feedString);
emitter.onSuccess(podcasts);
} catch (IOException | JSONException e) {
if (!disposable.isDisposed()) {
@@ -288,15 +284,15 @@ public class ItunesSearchFragment extends Fragment {
}
}
- private List<Podcast> parseFeed(String jsonString) throws JSONException {
+ private List<PodcastSearchResult> parseFeed(String jsonString) throws JSONException {
JSONObject result = new JSONObject(jsonString);
JSONObject feed = result.getJSONObject("feed");
JSONArray entries = feed.getJSONArray("entry");
- List<Podcast> results = new ArrayList<>();
+ List<PodcastSearchResult> results = new ArrayList<>();
for (int i=0; i < entries.length(); i++) {
JSONObject json = entries.getJSONObject(i);
- results.add(Podcast.fromToplist(json));
+ results.add(PodcastSearchResult.fromItunesToplist(json));
}
return results;
@@ -311,60 +307,19 @@ public class ItunesSearchFragment extends Fragment {
butRetry.setVisibility(View.GONE);
txtvEmpty.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
- disposable = Single.create((SingleOnSubscribe<List<Podcast>>) subscriber -> {
- String encodedQuery = null;
- try {
- encodedQuery = URLEncoder.encode(query, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- // this won't ever be thrown
- }
- if (encodedQuery == null) {
- encodedQuery = query; // failsafe
- }
-
- String formattedUrl = String.format(API_URL, encodedQuery);
- OkHttpClient client = AntennapodHttpClient.getHttpClient();
- Request.Builder httpReq = new Request.Builder()
- .url(formattedUrl)
- .header("User-Agent", ClientConfig.USER_AGENT);
- List<Podcast> podcasts = new ArrayList<>();
- try {
- Response response = client.newCall(httpReq.build()).execute();
-
- if(response.isSuccessful()) {
- String resultString = response.body().string();
- JSONObject result = new JSONObject(resultString);
- JSONArray j = result.getJSONArray("results");
-
- for (int i = 0; i < j.length(); i++) {
- JSONObject podcastJson = j.getJSONObject(i);
- Podcast podcast = Podcast.fromSearch(podcastJson);
- podcasts.add(podcast);
- }
- }
- else {
- String prefix = getString(R.string.error_msg_prefix);
- subscriber.onError(new IOException(prefix + response));
- }
- } catch (IOException | JSONException e) {
- subscriber.onError(e);
- }
- subscriber.onSuccess(podcasts);
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(podcasts -> {
- progressBar.setVisibility(View.GONE);
- updateData(podcasts);
- }, 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);
- });
+ ItunesPodcastSearcher searcher = new ItunesPodcastSearcher(getContext(), query);
+ disposable = searcher.search(podcasts -> {
+ progressBar.setVisibility(View.GONE);
+ updateData(podcasts);
+ }, 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);
+ });
}
}
diff --git a/app/src/main/res/layout/addfeed.xml b/app/src/main/res/layout/addfeed.xml
index 33951e060..92169e4a8 100644
--- a/app/src/main/res/layout/addfeed.xml
+++ b/app/src/main/res/layout/addfeed.xml
@@ -29,10 +29,16 @@
android:textSize="@dimen/text_size_medium"/>
<Button
- android:id="@+id/butSearchItunes"
+ android:id="@+id/butSearchCombined"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
+ android:text="Search all providers"/>
+
+ <Button
+ android:id="@+id/butSearchItunes"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:text="@string/search_itunes_label"/>
<Button