Merge pull request #677 from eerden/itunes-search-pull
Add ability to search iTunes for podcasts
This commit is contained in:
commit
c680c10c91
@ -0,0 +1,187 @@
|
||||
package de.danoeh.antennapod.adapter.itunes;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.AsyncTask;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
|
||||
public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> {
|
||||
/**
|
||||
* Related Context
|
||||
*/
|
||||
private final Context context;
|
||||
|
||||
/**
|
||||
* List holding the podcasts found in the search
|
||||
*/
|
||||
private final List<Podcast> data;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param context Related context
|
||||
* @param objects Search result
|
||||
*/
|
||||
public ItunesAdapter(Context context, List<Podcast> objects) {
|
||||
super(context, 0, objects);
|
||||
this.data = objects;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given ImageView with the image in the given Podcast's imageUrl
|
||||
*/
|
||||
class FetchImageTask extends AsyncTask<Void,Void,Bitmap>{
|
||||
/**
|
||||
* Current podcast
|
||||
*/
|
||||
private final Podcast podcast;
|
||||
|
||||
/**
|
||||
* ImageView to be updated
|
||||
*/
|
||||
private final ImageView imageView;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param podcast Podcast that has the image
|
||||
* @param imageView UI image to be updated
|
||||
*/
|
||||
FetchImageTask(Podcast podcast, ImageView imageView){
|
||||
this.podcast = podcast;
|
||||
this.imageView = imageView;
|
||||
}
|
||||
|
||||
//Get the image from the url
|
||||
@Override
|
||||
protected Bitmap doInBackground(Void... params) {
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
HttpGet get = new HttpGet(podcast.imageUrl);
|
||||
try {
|
||||
HttpResponse response = client.execute(get);
|
||||
return BitmapFactory.decodeStream(response.getEntity().getContent());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Set the background image for the podcast
|
||||
@Override
|
||||
protected void onPostExecute(Bitmap img) {
|
||||
super.onPostExecute(img);
|
||||
if(img!=null) {
|
||||
imageView.setImageBitmap(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
//Current podcast
|
||||
Podcast podcast = data.get(position);
|
||||
|
||||
//ViewHolder
|
||||
PodcastViewHolder viewHolder;
|
||||
|
||||
//Resulting view
|
||||
View view;
|
||||
|
||||
//Handle view holder stuff
|
||||
if(convertView == null) {
|
||||
view = ((MainActivity) context).getLayoutInflater()
|
||||
.inflate(R.layout.itunes_podcast_listitem, parent, false);
|
||||
viewHolder = new PodcastViewHolder(view);
|
||||
view.setTag(viewHolder);
|
||||
} else {
|
||||
view = convertView;
|
||||
viewHolder = (PodcastViewHolder) view.getTag();
|
||||
}
|
||||
|
||||
//Set the title
|
||||
viewHolder.titleView.setText(podcast.title);
|
||||
|
||||
//Update the empty imageView with the image from the feed
|
||||
new FetchImageTask(podcast,viewHolder.coverView).execute();
|
||||
|
||||
//Feed the grid view
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* View holder object for the GridView
|
||||
*/
|
||||
class PodcastViewHolder {
|
||||
|
||||
/**
|
||||
* ImageView holding the Podcast image
|
||||
*/
|
||||
public final ImageView coverView;
|
||||
|
||||
/**
|
||||
* TextView holding the Podcast title
|
||||
*/
|
||||
public final TextView titleView;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param view GridView cell
|
||||
*/
|
||||
PodcastViewHolder(View view){
|
||||
coverView = (ImageView) view.findViewById(R.id.imgvCover);
|
||||
titleView = (TextView) view.findViewById(R.id.txtvTitle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public final String imageUrl;
|
||||
/**
|
||||
* URL of the podcast feed
|
||||
*/
|
||||
public final String feedUrl;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param json object holding the podcast information
|
||||
* @throws JSONException
|
||||
*/
|
||||
public Podcast(JSONObject json) throws JSONException {
|
||||
title = json.getString("collectionName");
|
||||
imageUrl = json.getString("artworkUrl100");
|
||||
feedUrl = json.getString("feedUrl");
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
@ -41,10 +42,18 @@ public class AddFeedFragment extends Fragment {
|
||||
Button butBrowserGpoddernet = (Button) root.findViewById(R.id.butBrowseGpoddernet);
|
||||
Button butOpmlImport = (Button) root.findViewById(R.id.butOpmlImport);
|
||||
Button butConfirm = (Button) root.findViewById(R.id.butConfirm);
|
||||
Button butSearchITunes = (Button) root.findViewById(R.id.butSearchItunes);
|
||||
|
||||
final MainActivity activity = (MainActivity) getActivity();
|
||||
activity.getMainActivtyActionBar().setTitle(R.string.add_feed_label);
|
||||
|
||||
butSearchITunes.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
activity.loadChildFragment(new ItunesSearchFragment());
|
||||
}
|
||||
});
|
||||
|
||||
butBrowserGpoddernet.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@ -53,7 +62,6 @@ public class AddFeedFragment extends Fragment {
|
||||
});
|
||||
|
||||
butOpmlImport.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(new Intent(getActivity(),
|
||||
|
@ -0,0 +1,193 @@
|
||||
package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.GridView;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
|
||||
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
|
||||
import de.danoeh.antennapod.adapter.itunes.ItunesAdapter;
|
||||
|
||||
import static de.danoeh.antennapod.adapter.itunes.ItunesAdapter.*;
|
||||
|
||||
//Searches iTunes store for given string and displays results in a list
|
||||
public class ItunesSearchFragment extends Fragment {
|
||||
final String TAG = "ItunesSearchFragment";
|
||||
/**
|
||||
* Search input field
|
||||
*/
|
||||
private SearchView searchView;
|
||||
|
||||
/**
|
||||
* Adapter responsible with the search results
|
||||
*/
|
||||
private ItunesAdapter adapter;
|
||||
|
||||
/**
|
||||
* List of podcasts retreived from the search
|
||||
*/
|
||||
private List<Podcast> searchResults;
|
||||
|
||||
/**
|
||||
* Replace adapter data with provided search results from SearchTask.
|
||||
* @param result List of Podcast objects containing search results
|
||||
*/
|
||||
void updateData(List<Podcast> result) {
|
||||
this.searchResults = result;
|
||||
adapter.clear();
|
||||
|
||||
//ArrayAdapter.addAll() requires minsdk > 10
|
||||
for(Podcast p: result) {
|
||||
adapter.add(p);
|
||||
}
|
||||
|
||||
adapter.notifyDataSetInvalidated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public ItunesSearchFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
adapter = new ItunesAdapter(getActivity(), new ArrayList<Podcast>());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
View view = inflater.inflate(R.layout.fragment_itunes_search, container, false);
|
||||
GridView gridView = (GridView) view.findViewById(R.id.gridView);
|
||||
gridView.setAdapter(adapter);
|
||||
|
||||
//Show information about the podcast when the list item is clicked
|
||||
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Intent intent = new Intent(getActivity(),
|
||||
DefaultOnlineFeedViewActivity.class);
|
||||
|
||||
//Tell the OnlineFeedViewActivity where to go
|
||||
String url = searchResults.get(position).feedUrl;
|
||||
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, url);
|
||||
|
||||
intent.putExtra(DefaultOnlineFeedViewActivity.ARG_TITLE, "iTunes");
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
//Configure search input view to be expanded by default with a visible submit button
|
||||
searchView = (SearchView) view.findViewById(R.id.itunes_search_view);
|
||||
searchView.setIconifiedByDefault(false);
|
||||
searchView.setIconified(false);
|
||||
searchView.setSubmitButtonEnabled(true);
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String s) {
|
||||
//This prevents onQueryTextSubmit() from being called twice when keyboard is used
|
||||
//to submit the query.
|
||||
searchView.clearFocus();
|
||||
new SearchTask(s).execute();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String s) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the iTunes store for podcasts using the given query
|
||||
*/
|
||||
class SearchTask extends AsyncTask<Void,Void,Void> {
|
||||
/**
|
||||
* Incomplete iTunes API search URL
|
||||
*/
|
||||
final String apiUrl = "https://itunes.apple.com/search?media=podcast&term=%s";
|
||||
|
||||
/**
|
||||
* Search terms
|
||||
*/
|
||||
final String query;
|
||||
|
||||
/**
|
||||
* Search result
|
||||
*/
|
||||
final List<Podcast> taskData = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param query Search string
|
||||
*/
|
||||
public SearchTask(String query){
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
//Get the podcast data
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
|
||||
//Spaces in the query need to be replaced with '+' character.
|
||||
String formattedUrl = String.format(apiUrl, query).replace(' ', '+');
|
||||
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
HttpGet get = new HttpGet(formattedUrl);
|
||||
|
||||
try {
|
||||
HttpResponse response = client.execute(get);
|
||||
String resultString = EntityUtils.toString(response.getEntity());
|
||||
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 = new Podcast(podcastJson);
|
||||
taskData.add(podcast);
|
||||
}
|
||||
|
||||
} catch (IOException | JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Save the data and update the list
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
super.onPostExecute(aVoid);
|
||||
updateData(taskData);
|
||||
}
|
||||
}
|
||||
}
|
@ -66,12 +66,19 @@
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/browse_gpoddernet_label"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/butSearchItunes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/butBrowseGpoddernet"
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/search_itunes_label"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvOpmlImport"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/butBrowseGpoddernet"
|
||||
android:layout_below="@id/butSearchItunes"
|
||||
android:layout_margin="8dp"
|
||||
style="@style/AntennaPod.TextView.Heading"
|
||||
android:text="@string/opml_import_label"/>
|
||||
|
26
app/src/main/res/layout/fragment_itunes_search.xml
Normal file
26
app/src/main/res/layout/fragment_itunes_search.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="de.danoeh.antennapod.activity.ITunesSearchActivity">
|
||||
<android.support.v7.widget.SearchView
|
||||
android:id="@+id/itunes_search_view"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
/>
|
||||
<GridView
|
||||
android:id="@+id/gridView"
|
||||
android:layout_below="@id/itunes_search_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:columnWidth="200dp"
|
||||
android:gravity="center"
|
||||
android:horizontalSpacing="8dp"
|
||||
android:numColumns="auto_fit"
|
||||
android:paddingBottom="@dimen/list_vertical_padding"
|
||||
android:paddingTop="@dimen/list_vertical_padding"
|
||||
android:stretchMode="columnWidth"
|
||||
android:verticalSpacing="8dp"
|
||||
tools:listitem="@layout/gpodnet_podcast_listitem" />
|
||||
</RelativeLayout>
|
38
app/src/main/res/layout/itunes_podcast_listitem.xml
Normal file
38
app/src/main/res/layout/itunes_podcast_listitem.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/listitem_threeline_height"
|
||||
tools:background="@android:color/darker_gray">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgvCover"
|
||||
android:layout_width="@dimen/thumbnail_length_itemlist"
|
||||
android:layout_height="@dimen/thumbnail_length_itemlist"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
|
||||
android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginTop="@dimen/listitem_threeline_verticalpadding"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@string/cover_label"
|
||||
android:cropToPadding="true"
|
||||
android:scaleType="fitXY"
|
||||
tools:src="@drawable/ic_stat_antenna_default"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvTitle"
|
||||
style="@style/AntennaPod.TextView.ListItemPrimaryTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding"
|
||||
android:layout_marginRight="@dimen/listitem_threeline_horizontalpadding"
|
||||
android:layout_toRightOf="@id/imgvCover"
|
||||
android:maxLines="1"
|
||||
tools:text="Podcast title"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
</RelativeLayout>
|
@ -74,7 +74,7 @@
|
||||
<string name="etxtFeedurlHint">URL of feed or website</string>
|
||||
<string name="txtvfeedurl_label">Add Podcast by URL</string>
|
||||
<string name="podcastdirectories_label">Find podcast in directory</string>
|
||||
<string name="podcastdirectories_descr">You can search for new podcasts by name, category or popularity in the gpodder.net directory.</string>
|
||||
<string name="podcastdirectories_descr">You can search for new podcasts by name, category or popularity in the gpodder.net directory, or search the iTunes store.</string>
|
||||
<string name="browse_gpoddernet_label">Browse gpodder.net</string>
|
||||
|
||||
<!-- Actions on feeds -->
|
||||
@ -398,4 +398,5 @@
|
||||
<!-- AntennaPodSP -->
|
||||
|
||||
<string name="sp_apps_importing_feeds_msg">Importing subscriptions from single-purpose apps…</string>
|
||||
<string name="search_itunes_label">Search iTunes</string>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user