Merge pull request #677 from eerden/itunes-search-pull

Add ability to search iTunes for podcasts
This commit is contained in:
Tom Hennen 2015-03-15 15:46:20 -04:00
commit c680c10c91
7 changed files with 463 additions and 3 deletions

View File

@ -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");
}
}
}

View File

@ -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(),

View File

@ -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);
}
}
}

View File

@ -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"/>

View 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>

View 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>

View File

@ -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&#8230;</string>
<string name="search_itunes_label">Search iTunes</string>
</resources>