Merge pull request #3240 from ByteHamster/combined-search
WIP: Combined podcast search
This commit is contained in:
commit
865cb65470
|
@ -2,7 +2,6 @@ package de.danoeh.antennapod.adapter.itunes;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
@ -13,18 +12,14 @@ import com.bumptech.glide.Glide;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
|
||||||
import com.bumptech.glide.request.RequestOptions;
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
import de.danoeh.antennapod.discovery.PodcastSearchResult;
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.activity.MainActivity;
|
import de.danoeh.antennapod.activity.MainActivity;
|
||||||
import de.mfietz.fyydlin.SearchHit;
|
|
||||||
|
|
||||||
public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
|
public class ItunesAdapter extends ArrayAdapter<PodcastSearchResult> {
|
||||||
/**
|
/**
|
||||||
* Related Context
|
* Related Context
|
||||||
*/
|
*/
|
||||||
|
@ -33,7 +28,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
|
||||||
/**
|
/**
|
||||||
* List holding the podcasts found in the search
|
* List holding the podcasts found in the search
|
||||||
*/
|
*/
|
||||||
private final List<Podcast> data;
|
private final List<PodcastSearchResult> data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
|
@ -41,7 +36,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
|
||||||
* @param context Related context
|
* @param context Related context
|
||||||
* @param objects Search result
|
* @param objects Search result
|
||||||
*/
|
*/
|
||||||
public ItunesAdapter(Context context, List<Podcast> objects) {
|
public ItunesAdapter(Context context, List<PodcastSearchResult> objects) {
|
||||||
super(context, 0, objects);
|
super(context, 0, objects);
|
||||||
this.data = objects;
|
this.data = objects;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
@ -51,7 +46,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
|
||||||
@Override
|
@Override
|
||||||
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
|
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
|
||||||
//Current podcast
|
//Current podcast
|
||||||
Podcast podcast = data.get(position);
|
PodcastSearchResult podcast = data.get(position);
|
||||||
|
|
||||||
//ViewHolder
|
//ViewHolder
|
||||||
PodcastViewHolder viewHolder;
|
PodcastViewHolder viewHolder;
|
||||||
|
@ -93,75 +88,6 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* View holder object for the GridView
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
package de.danoeh.antennapod.discovery;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
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.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
public class CombinedSearcher implements PodcastSearcher {
|
||||||
|
private static final String TAG = "CombinedSearcher";
|
||||||
|
|
||||||
|
private final List<Pair<PodcastSearcher, Float>> searchProviders = new ArrayList<>();
|
||||||
|
|
||||||
|
public CombinedSearcher(Context context) {
|
||||||
|
addProvider(new FyydPodcastSearcher(), 1.f);
|
||||||
|
addProvider(new ItunesPodcastSearcher(context), 1.f);
|
||||||
|
addProvider(new GpodnetPodcastSearcher(), 0.6f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addProvider(PodcastSearcher provider, float priority) {
|
||||||
|
searchProviders.add(new Pair<>(provider, priority));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Single<List<PodcastSearchResult>> search(String query) {
|
||||||
|
ArrayList<Disposable> disposables = new ArrayList<>();
|
||||||
|
List<List<PodcastSearchResult>> singleResults = new ArrayList<>(Collections.nCopies(searchProviders.size(), null));
|
||||||
|
CountDownLatch latch = new CountDownLatch(searchProviders.size());
|
||||||
|
for (int i = 0; i < searchProviders.size(); i++) {
|
||||||
|
Pair<PodcastSearcher, Float> searchProviderInfo = searchProviders.get(i);
|
||||||
|
PodcastSearcher searcher = searchProviderInfo.first;
|
||||||
|
final int index = i;
|
||||||
|
disposables.add(searcher.search(query).subscribe(e -> {
|
||||||
|
singleResults.set(index, e);
|
||||||
|
latch.countDown();
|
||||||
|
}, throwable -> {
|
||||||
|
Log.d(TAG, Log.getStackTraceString(throwable));
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
|
||||||
|
latch.await();
|
||||||
|
List<PodcastSearchResult> results = weightSearchResults(singleResults);
|
||||||
|
subscriber.onSuccess(results);
|
||||||
|
})
|
||||||
|
.doOnDispose(() -> {
|
||||||
|
for (Disposable disposable : disposables) {
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PodcastSearchResult> weightSearchResults(List<List<PodcastSearchResult>> singleResults) {
|
||||||
|
HashMap<String, Float> resultRanking = new HashMap<>();
|
||||||
|
HashMap<String, PodcastSearchResult> urlToResult = new HashMap<>();
|
||||||
|
for (int i = 0; i < singleResults.size(); i++) {
|
||||||
|
float providerPriority = searchProviders.get(i).second;
|
||||||
|
List<PodcastSearchResult> providerResults = singleResults.get(i);
|
||||||
|
if (providerResults == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
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 * providerPriority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
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.Single;
|
||||||
|
import io.reactivex.SingleOnSubscribe;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FyydPodcastSearcher implements PodcastSearcher {
|
||||||
|
private final FyydClient client = new FyydClient(AntennapodHttpClient.getHttpClient());
|
||||||
|
|
||||||
|
public Single<List<PodcastSearchResult>> search(String query) {
|
||||||
|
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
|
||||||
|
FyydResponse response = client.searchPodcasts(query, 10)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.blockingGet();
|
||||||
|
|
||||||
|
ArrayList<PodcastSearchResult> searchResults = new ArrayList<>();
|
||||||
|
|
||||||
|
if (!response.getData().isEmpty()) {
|
||||||
|
for (SearchHit searchHit : response.getData()) {
|
||||||
|
PodcastSearchResult podcast = PodcastSearchResult.fromFyyd(searchHit);
|
||||||
|
searchResults.add(podcast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriber.onSuccess(searchResults);
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
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.schedulers.Schedulers;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GpodnetPodcastSearcher implements PodcastSearcher {
|
||||||
|
public Single<List<PodcastSearchResult>> search(String query) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
public ItunesPodcastSearcher(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Single<List<PodcastSearchResult>> search(String query) {
|
||||||
|
return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> {
|
||||||
|
String encodedQuery;
|
||||||
|
try {
|
||||||
|
encodedQuery = URLEncoder.encode(query, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
// this won't ever be thrown
|
||||||
|
encodedQuery = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package de.danoeh.antennapod.discovery;
|
||||||
|
|
||||||
|
import io.reactivex.Single;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import io.reactivex.functions.Consumer;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface PodcastSearcher {
|
||||||
|
Single<List<PodcastSearchResult>> search(String query);
|
||||||
|
}
|
|
@ -42,6 +42,7 @@ public class AddFeedFragment extends Fragment {
|
||||||
Button butSearchITunes = root.findViewById(R.id.butSearchItunes);
|
Button butSearchITunes = root.findViewById(R.id.butSearchItunes);
|
||||||
Button butBrowserGpoddernet = root.findViewById(R.id.butBrowseGpoddernet);
|
Button butBrowserGpoddernet = root.findViewById(R.id.butBrowseGpoddernet);
|
||||||
Button butSearchFyyd = root.findViewById(R.id.butSearchFyyd);
|
Button butSearchFyyd = root.findViewById(R.id.butSearchFyyd);
|
||||||
|
Button butSearchCombined = root.findViewById(R.id.butSearchCombined);
|
||||||
Button butOpmlImport = root.findViewById(R.id.butOpmlImport);
|
Button butOpmlImport = root.findViewById(R.id.butOpmlImport);
|
||||||
Button butConfirm = root.findViewById(R.id.butConfirm);
|
Button butConfirm = root.findViewById(R.id.butConfirm);
|
||||||
|
|
||||||
|
@ -54,6 +55,8 @@ public class AddFeedFragment extends Fragment {
|
||||||
|
|
||||||
butSearchFyyd.setOnClickListener(v -> activity.loadChildFragment(new FyydSearchFragment()));
|
butSearchFyyd.setOnClickListener(v -> activity.loadChildFragment(new FyydSearchFragment()));
|
||||||
|
|
||||||
|
butSearchCombined.setOnClickListener(v -> activity.loadChildFragment(new CombinedSearchFragment()));
|
||||||
|
|
||||||
butOpmlImport.setOnClickListener(v -> startActivity(new Intent(getActivity(),
|
butOpmlImport.setOnClickListener(v -> startActivity(new Intent(getActivity(),
|
||||||
OpmlImportFromPathActivity.class)));
|
OpmlImportFromPathActivity.class)));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
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 de.danoeh.antennapod.R;
|
||||||
|
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
|
||||||
|
import de.danoeh.antennapod.adapter.itunes.ItunesAdapter;
|
||||||
|
import de.danoeh.antennapod.discovery.CombinedSearcher;
|
||||||
|
import de.danoeh.antennapod.discovery.PodcastSearchResult;
|
||||||
|
import de.danoeh.antennapod.menuhandler.MenuItemUtils;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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 Disposable disposable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
if (disposable != null) {
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
if (disposable != null) {
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
showOnlyProgressBar();
|
||||||
|
|
||||||
|
CombinedSearcher searcher = new CombinedSearcher(getContext());
|
||||||
|
disposable = searcher.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);
|
||||||
|
|
||||||
|
}, 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,24 +16,16 @@ import android.widget.Button;
|
||||||
import android.widget.GridView;
|
import android.widget.GridView;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
|
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
|
||||||
import de.danoeh.antennapod.adapter.itunes.ItunesAdapter;
|
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.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.disposables.Disposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
|
|
||||||
import static de.danoeh.antennapod.adapter.itunes.ItunesAdapter.Podcast;
|
import java.util.ArrayList;
|
||||||
import static java.util.Collections.emptyList;
|
import java.util.List;
|
||||||
|
|
||||||
public class FyydSearchFragment extends Fragment {
|
public class FyydSearchFragment extends Fragment {
|
||||||
|
|
||||||
|
@ -49,12 +41,10 @@ public class FyydSearchFragment extends Fragment {
|
||||||
private Button butRetry;
|
private Button butRetry;
|
||||||
private TextView txtvEmpty;
|
private TextView txtvEmpty;
|
||||||
|
|
||||||
private final FyydClient client = new FyydClient(AntennapodHttpClient.getHttpClient());
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of podcasts retreived from the search
|
* List of podcasts retreived from the search
|
||||||
*/
|
*/
|
||||||
private List<Podcast> searchResults;
|
private List<PodcastSearchResult> searchResults;
|
||||||
private Disposable disposable;
|
private Disposable disposable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,7 +71,7 @@ public class FyydSearchFragment extends Fragment {
|
||||||
|
|
||||||
//Show information about the podcast when the list item is clicked
|
//Show information about the podcast when the list item is clicked
|
||||||
gridView.setOnItemClickListener((parent, view1, position, id) -> {
|
gridView.setOnItemClickListener((parent, view1, position, id) -> {
|
||||||
Podcast podcast = searchResults.get(position);
|
PodcastSearchResult podcast = searchResults.get(position);
|
||||||
Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
|
Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
|
||||||
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, podcast.feedUrl);
|
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, podcast.feedUrl);
|
||||||
intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, podcast.title);
|
intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, podcast.title);
|
||||||
|
@ -145,12 +135,18 @@ public class FyydSearchFragment extends Fragment {
|
||||||
disposable.dispose();
|
disposable.dispose();
|
||||||
}
|
}
|
||||||
showOnlyProgressBar();
|
showOnlyProgressBar();
|
||||||
disposable = client.searchPodcasts(query, 10)
|
|
||||||
.subscribeOn(Schedulers.io())
|
FyydPodcastSearcher searcher = new FyydPodcastSearcher();
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
disposable = searcher.search(query).subscribe(result -> {
|
||||||
.subscribe(result -> {
|
searchResults = result;
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
processSearchResult(result);
|
|
||||||
|
adapter.clear();
|
||||||
|
adapter.addAll(searchResults);
|
||||||
|
adapter.notifyDataSetInvalidated();
|
||||||
|
gridView.setVisibility(!searchResults.isEmpty() ? View.VISIBLE : View.GONE);
|
||||||
|
txtvEmpty.setVisibility(searchResults.isEmpty() ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
}, error -> {
|
}, error -> {
|
||||||
Log.e(TAG, Log.getStackTraceString(error));
|
Log.e(TAG, Log.getStackTraceString(error));
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
|
@ -168,25 +164,4 @@ public class FyydSearchFragment extends Fragment {
|
||||||
txtvEmpty.setVisibility(View.GONE);
|
txtvEmpty.setVisibility(View.GONE);
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,13 +20,13 @@ import android.widget.TextView;
|
||||||
|
|
||||||
import com.afollestad.materialdialogs.MaterialDialog;
|
import com.afollestad.materialdialogs.MaterialDialog;
|
||||||
|
|
||||||
|
import de.danoeh.antennapod.discovery.ItunesPodcastSearcher;
|
||||||
|
import de.danoeh.antennapod.discovery.PodcastSearchResult;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -46,15 +46,11 @@ import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
import static de.danoeh.antennapod.adapter.itunes.ItunesAdapter.Podcast;
|
|
||||||
|
|
||||||
//Searches iTunes store for given string and displays results in a list
|
//Searches iTunes store for given string and displays results in a list
|
||||||
public class ItunesSearchFragment extends Fragment {
|
public class ItunesSearchFragment extends Fragment {
|
||||||
|
|
||||||
private static final String TAG = "ItunesSearchFragment";
|
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
|
* Adapter responsible with the search results
|
||||||
|
@ -69,21 +65,21 @@ public class ItunesSearchFragment extends Fragment {
|
||||||
/**
|
/**
|
||||||
* List of podcasts retreived from the search
|
* List of podcasts retreived from the search
|
||||||
*/
|
*/
|
||||||
private List<Podcast> searchResults;
|
private List<PodcastSearchResult> searchResults;
|
||||||
private List<Podcast> topList;
|
private List<PodcastSearchResult> topList;
|
||||||
private Disposable disposable;
|
private Disposable disposable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace adapter data with provided search results from SearchTask.
|
* Replace adapter data with provided search results from SearchTask.
|
||||||
* @param result List of Podcast objects containing search results
|
* @param result List of Podcast objects containing search results
|
||||||
*/
|
*/
|
||||||
private void updateData(List<Podcast> result) {
|
private void updateData(List<PodcastSearchResult> result) {
|
||||||
this.searchResults = result;
|
this.searchResults = result;
|
||||||
adapter.clear();
|
adapter.clear();
|
||||||
if (result != null && result.size() > 0) {
|
if (result != null && result.size() > 0) {
|
||||||
gridView.setVisibility(View.VISIBLE);
|
gridView.setVisibility(View.VISIBLE);
|
||||||
txtvEmpty.setVisibility(View.GONE);
|
txtvEmpty.setVisibility(View.GONE);
|
||||||
for (Podcast p : result) {
|
for (PodcastSearchResult p : result) {
|
||||||
adapter.add(p);
|
adapter.add(p);
|
||||||
}
|
}
|
||||||
adapter.notifyDataSetInvalidated();
|
adapter.notifyDataSetInvalidated();
|
||||||
|
@ -117,7 +113,7 @@ public class ItunesSearchFragment extends Fragment {
|
||||||
|
|
||||||
//Show information about the podcast when the list item is clicked
|
//Show information about the podcast when the list item is clicked
|
||||||
gridView.setOnItemClickListener((parent, view1, position, id) -> {
|
gridView.setOnItemClickListener((parent, view1, position, id) -> {
|
||||||
Podcast podcast = searchResults.get(position);
|
PodcastSearchResult podcast = searchResults.get(position);
|
||||||
if(podcast.feedUrl == null) {
|
if(podcast.feedUrl == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -239,7 +235,7 @@ public class ItunesSearchFragment extends Fragment {
|
||||||
butRetry.setVisibility(View.GONE);
|
butRetry.setVisibility(View.GONE);
|
||||||
txtvEmpty.setVisibility(View.GONE);
|
txtvEmpty.setVisibility(View.GONE);
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
disposable = Single.create((SingleOnSubscribe<List<Podcast>>) emitter -> {
|
disposable = Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) emitter -> {
|
||||||
String lang = Locale.getDefault().getLanguage();
|
String lang = Locale.getDefault().getLanguage();
|
||||||
OkHttpClient client = AntennapodHttpClient.getHttpClient();
|
OkHttpClient client = AntennapodHttpClient.getHttpClient();
|
||||||
String feedString;
|
String feedString;
|
||||||
|
@ -249,7 +245,7 @@ public class ItunesSearchFragment extends Fragment {
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
feedString = getTopListFeed(client, "us");
|
feedString = getTopListFeed(client, "us");
|
||||||
}
|
}
|
||||||
List<Podcast> podcasts = parseFeed(feedString);
|
List<PodcastSearchResult> podcasts = parseFeed(feedString);
|
||||||
emitter.onSuccess(podcasts);
|
emitter.onSuccess(podcasts);
|
||||||
} catch (IOException | JSONException e) {
|
} catch (IOException | JSONException e) {
|
||||||
if (!disposable.isDisposed()) {
|
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 result = new JSONObject(jsonString);
|
||||||
JSONObject feed = result.getJSONObject("feed");
|
JSONObject feed = result.getJSONObject("feed");
|
||||||
JSONArray entries = feed.getJSONArray("entry");
|
JSONArray entries = feed.getJSONArray("entry");
|
||||||
|
|
||||||
List<Podcast> results = new ArrayList<>();
|
List<PodcastSearchResult> results = new ArrayList<>();
|
||||||
for (int i=0; i < entries.length(); i++) {
|
for (int i=0; i < entries.length(); i++) {
|
||||||
JSONObject json = entries.getJSONObject(i);
|
JSONObject json = entries.getJSONObject(i);
|
||||||
results.add(Podcast.fromToplist(json));
|
results.add(PodcastSearchResult.fromItunesToplist(json));
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
|
@ -311,50 +307,9 @@ public class ItunesSearchFragment extends Fragment {
|
||||||
butRetry.setVisibility(View.GONE);
|
butRetry.setVisibility(View.GONE);
|
||||||
txtvEmpty.setVisibility(View.GONE);
|
txtvEmpty.setVisibility(View.GONE);
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
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);
|
ItunesPodcastSearcher searcher = new ItunesPodcastSearcher(getContext());
|
||||||
|
disposable = searcher.search(query).subscribe(podcasts -> {
|
||||||
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);
|
progressBar.setVisibility(View.GONE);
|
||||||
updateData(podcasts);
|
updateData(podcasts);
|
||||||
}, error -> {
|
}, error -> {
|
||||||
|
|
|
@ -29,10 +29,16 @@
|
||||||
android:textSize="@dimen/text_size_medium"/>
|
android:textSize="@dimen/text_size_medium"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/butSearchItunes"
|
android:id="@+id/butSearchCombined"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
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"/>
|
android:text="@string/search_itunes_label"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|
Loading…
Reference in New Issue