From 2e54af07b4ccada483388722b6cc93d48de9d6be Mon Sep 17 00:00:00 2001 From: Camil Staps Date: Thu, 21 May 2015 19:45:01 +0200 Subject: Volley; org.rssin.http; org.rssin.listener; fetching XML with listeners; FeedLoader serializable; working FilterActivity --- app/src/main/AndroidManifest.xml | 10 ++ .../java/org/rssin/android/FilterActivity.java | 139 +++++++++++++++++++++ .../java/org/rssin/android/FiltersActivity.java | 8 +- .../main/java/org/rssin/android/VolleyFetcher.java | 59 +++++++++ app/src/main/java/org/rssin/http/Fetcher.java | 13 ++ app/src/main/java/org/rssin/http/Request.java | 20 +++ .../java/org/rssin/listener/DismissListener.java | 15 +++ .../java/org/rssin/listener/ErrorListener.java | 8 ++ .../java/org/rssin/listener/FallibleListener.java | 7 ++ app/src/main/java/org/rssin/listener/Listener.java | 8 ++ .../java/org/rssin/listener/RealtimeListener.java | 8 ++ .../java/org/rssin/neurons/SentenceSplitter.java | 5 +- app/src/main/java/org/rssin/rss/FeedLoader.java | 49 ++++---- .../java/org/rssin/rssin/FeedLoaderAndSorter.java | 68 +++++++--- app/src/main/java/org/rssin/rssin/Filter.java | 2 +- app/src/main/res/layout/activity_filter.xml | 13 ++ app/src/main/res/layout/item_feeditem.xml | 19 +++ app/src/main/res/menu/menu_filter.xml | 6 + app/src/main/res/values/strings.xml | 3 + 19 files changed, 422 insertions(+), 38 deletions(-) create mode 100644 app/src/main/java/org/rssin/android/FilterActivity.java create mode 100644 app/src/main/java/org/rssin/android/VolleyFetcher.java create mode 100644 app/src/main/java/org/rssin/http/Fetcher.java create mode 100644 app/src/main/java/org/rssin/http/Request.java create mode 100644 app/src/main/java/org/rssin/listener/DismissListener.java create mode 100644 app/src/main/java/org/rssin/listener/ErrorListener.java create mode 100644 app/src/main/java/org/rssin/listener/FallibleListener.java create mode 100644 app/src/main/java/org/rssin/listener/Listener.java create mode 100644 app/src/main/java/org/rssin/listener/RealtimeListener.java create mode 100644 app/src/main/res/layout/activity_filter.xml create mode 100644 app/src/main/res/layout/item_feeditem.xml create mode 100644 app/src/main/res/menu/menu_filter.xml (limited to 'app/src') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6d7e57d..e4f63c3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + + + + diff --git a/app/src/main/java/org/rssin/android/FilterActivity.java b/app/src/main/java/org/rssin/android/FilterActivity.java new file mode 100644 index 0000000..145a60b --- /dev/null +++ b/app/src/main/java/org/rssin/android/FilterActivity.java @@ -0,0 +1,139 @@ +package org.rssin.android; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.support.v7.app.ActionBarActivity; +import android.os.Bundle; +import android.text.Html; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.volley.VolleyError; + +import org.rssin.listener.FallibleListener; +import org.rssin.rss.FeedItem; +import org.rssin.rssin.FeedLoaderAndSorter; +import org.rssin.rssin.Filter; +import org.rssin.rssin.Keyword; +import org.rssin.rssin.R; + +import java.io.IOException; +import java.util.List; + +public class FilterActivity extends ActionBarActivity { + + private FiltersList filtersList; + private Filter filter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_filter); + + try { + filtersList = FiltersList.getInstance(this); + } catch (IOException e) { + Toast.makeText(this, getResources().getString(R.string.error_load_filters), Toast.LENGTH_SHORT).show(); + } + + Intent intent = getIntent(); + int filterHashCode = intent.getIntExtra("filter", -1); + + // @todo Check on -1? Shouldn't happen anyway. + filter = filtersList.getFilterFromHashCode(filterHashCode); + + final Activity activity = this; + final ListView itemsListView = (ListView) findViewById(R.id.filter_items_list); + FeedLoaderAndSorter loaderAndSorter = new FeedLoaderAndSorter(filter); + loaderAndSorter.getFilteredFeedItems(new VolleyFetcher(this), new FallibleListener,VolleyError>() { + @Override + public void onReceive(List data) { + FeedItemAdapter feedItemAdapter = new FeedItemAdapter(activity, R.layout.item_feeditem, data); + itemsListView.setAdapter(feedItemAdapter); + } + + @Override + public void onError(VolleyError error) { + Log.e("FA", "VolleyError", error); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_filter, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == R.id.action_settings) { + Intent intent = new Intent(getApplicationContext(), FilterSettingsActivity.class); + intent.putExtra("filter", filter.hashCode()); + startActivity(intent); + return true; + } + + return super.onOptionsItemSelected(item); + } + + /** + * Custom ArrayAdapter to display Keywords + */ + private static class FeedItemAdapter extends ArrayAdapter { + Context context; + int layoutResourceId; + List feedItems; + + public FeedItemAdapter(Context context, int resource, List objects) { + super(context, resource, objects); + this.context = context; + layoutResourceId = resource; + feedItems = objects; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = convertView; + KeywordHolder holder = null; + + if (row == null) { + LayoutInflater inflater = ((Activity) context).getLayoutInflater(); + row = inflater.inflate(layoutResourceId, parent, false); + + holder = new KeywordHolder(); + holder.title = (TextView) row.findViewById(R.id.feeditem_title); + holder.summary = (TextView) row.findViewById(R.id.feeditem_summary); + + row.setTag(holder); + } else { + holder = (KeywordHolder) row.getTag(); + } + + FeedItem feedItem = feedItems.get(position); + holder.title.setText(feedItem.getTitle()); + holder.summary.setText(Html.fromHtml(feedItem.getDescription())); + + return row; + } + + /** + * TextViews holder + */ + private static class KeywordHolder { + TextView title; + TextView summary; + } + } +} diff --git a/app/src/main/java/org/rssin/android/FiltersActivity.java b/app/src/main/java/org/rssin/android/FiltersActivity.java index b7de210..b5f8760 100644 --- a/app/src/main/java/org/rssin/android/FiltersActivity.java +++ b/app/src/main/java/org/rssin/android/FiltersActivity.java @@ -102,7 +102,7 @@ public class FiltersActivity extends ActionBarActivity { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { Filter item = (Filter) parent.getItemAtPosition(position); - // @Todo open filter + openFilter(item); } }; onFilterLongClickListener = new AdapterView.OnItemLongClickListener() { @@ -175,6 +175,12 @@ public class FiltersActivity extends ActionBarActivity { startActivity(intent); } + public void openFilter(Filter f) { + Intent intent = new Intent(getApplicationContext(), FilterActivity.class); + intent.putExtra("filter", f.hashCode()); + startActivity(intent); + } + /** * Custom ArrayAdapter to display filters with our own menu item */ diff --git a/app/src/main/java/org/rssin/android/VolleyFetcher.java b/app/src/main/java/org/rssin/android/VolleyFetcher.java new file mode 100644 index 0000000..d853255 --- /dev/null +++ b/app/src/main/java/org/rssin/android/VolleyFetcher.java @@ -0,0 +1,59 @@ +package org.rssin.android; + +import android.content.Context; + +import com.android.volley.Request.Method; +import com.android.volley.RequestQueue; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.StringRequest; +import com.android.volley.toolbox.Volley; + +import org.rssin.http.Fetcher; +import org.rssin.http.Request; +import org.rssin.listener.ErrorListener; +import org.rssin.listener.Listener; + +/** + * Created by camilstaps on 21-5-15. + */ +public class VolleyFetcher implements Fetcher { + + private final Context context; + private final RequestQueue requestQueue; + + public VolleyFetcher(Context context) { + this.context = context; + requestQueue = Volley.newRequestQueue(context); + } + + @Override + public void fetch(Request request) { + fetch(request, null); + } + + @Override + public void fetch(Request request, final Listener listener) { + StringRequest stringRequest = new StringRequest( + Method.GET, + request.getURL().toString(), + new com.android.volley.Response.Listener() { + @Override + public void onResponse(String s) { + try { + listener.onReceive(s); + } catch (ClassCastException e) {} + } + }, + new com.android.volley.Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError volleyError) { + try { + ((ErrorListener) listener).onError(volleyError); + } catch (ClassCastException e) {} + } + }); + + requestQueue.add(stringRequest); + } + +} diff --git a/app/src/main/java/org/rssin/http/Fetcher.java b/app/src/main/java/org/rssin/http/Fetcher.java new file mode 100644 index 0000000..0052e38 --- /dev/null +++ b/app/src/main/java/org/rssin/http/Fetcher.java @@ -0,0 +1,13 @@ +package org.rssin.http; + +import org.rssin.listener.Listener; + +/** + * Load an HTTP request + */ +public interface Fetcher { + + void fetch(Request request); + void fetch(Request request, Listener listener); + +} diff --git a/app/src/main/java/org/rssin/http/Request.java b/app/src/main/java/org/rssin/http/Request.java new file mode 100644 index 0000000..b9861a5 --- /dev/null +++ b/app/src/main/java/org/rssin/http/Request.java @@ -0,0 +1,20 @@ +package org.rssin.http; + +import java.net.URL; + +/** + * Created by camilstaps on 21-5-15. + */ +public class Request { + + URL url; + + public Request(URL url) { + this.url = url; + } + + public URL getURL() { + return url; + } + +} diff --git a/app/src/main/java/org/rssin/listener/DismissListener.java b/app/src/main/java/org/rssin/listener/DismissListener.java new file mode 100644 index 0000000..cfdc51d --- /dev/null +++ b/app/src/main/java/org/rssin/listener/DismissListener.java @@ -0,0 +1,15 @@ +package org.rssin.listener; + +/** + * Created by camilstaps on 21-5-15. + */ +public class DismissListener implements Listener, RealtimeListener, ErrorListener, FallibleListener { + @Override + public void onError(Object error) {} + + @Override + public void finish() {} + + @Override + public void onReceive(Object data) {} +} diff --git a/app/src/main/java/org/rssin/listener/ErrorListener.java b/app/src/main/java/org/rssin/listener/ErrorListener.java new file mode 100644 index 0000000..5ad4f1b --- /dev/null +++ b/app/src/main/java/org/rssin/listener/ErrorListener.java @@ -0,0 +1,8 @@ +package org.rssin.listener; + +/** + * Created by camilstaps on 21-5-15. + */ +public interface ErrorListener { + void onError(T error); +} diff --git a/app/src/main/java/org/rssin/listener/FallibleListener.java b/app/src/main/java/org/rssin/listener/FallibleListener.java new file mode 100644 index 0000000..4e1d113 --- /dev/null +++ b/app/src/main/java/org/rssin/listener/FallibleListener.java @@ -0,0 +1,7 @@ +package org.rssin.listener; + +/** + * Created by camilstaps on 21-5-15. + */ +public interface FallibleListener extends Listener, ErrorListener { +} diff --git a/app/src/main/java/org/rssin/listener/Listener.java b/app/src/main/java/org/rssin/listener/Listener.java new file mode 100644 index 0000000..db1764c --- /dev/null +++ b/app/src/main/java/org/rssin/listener/Listener.java @@ -0,0 +1,8 @@ +package org.rssin.listener; + +/** + * Created by camilstaps on 21-5-15. + */ +public interface Listener { + void onReceive(T data); +} diff --git a/app/src/main/java/org/rssin/listener/RealtimeListener.java b/app/src/main/java/org/rssin/listener/RealtimeListener.java new file mode 100644 index 0000000..82a4c91 --- /dev/null +++ b/app/src/main/java/org/rssin/listener/RealtimeListener.java @@ -0,0 +1,8 @@ +package org.rssin.listener; + +/** + * Created by camilstaps on 21-5-15. + */ +public interface RealtimeListener extends Listener { + void finish(); +} diff --git a/app/src/main/java/org/rssin/neurons/SentenceSplitter.java b/app/src/main/java/org/rssin/neurons/SentenceSplitter.java index 887439d..29e34bc 100755 --- a/app/src/main/java/org/rssin/neurons/SentenceSplitter.java +++ b/app/src/main/java/org/rssin/neurons/SentenceSplitter.java @@ -1,5 +1,6 @@ package org.rssin.neurons; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; @@ -8,7 +9,9 @@ import java.util.regex.Pattern; /** * @author Jos. */ -public class SentenceSplitter { +public class SentenceSplitter implements Serializable { + private static final long serialVersionUID = 0; + private final Pattern wordMatch = Pattern.compile("[\\w-]+");//For unicode support, add the Pattern.UNICODE_CHARACTER_CLASS flag. Works only in Java 7+. public SentenceSplitter() { diff --git a/app/src/main/java/org/rssin/rss/FeedLoader.java b/app/src/main/java/org/rssin/rss/FeedLoader.java index eabfebd..3220826 100644 --- a/app/src/main/java/org/rssin/rss/FeedLoader.java +++ b/app/src/main/java/org/rssin/rss/FeedLoader.java @@ -1,11 +1,13 @@ package org.rssin.rss; -import java.io.InputStream; -import java.net.HttpURLConnection; +import java.io.ByteArrayInputStream; import java.net.URL; import java.util.Date; import java.util.LinkedList; +import org.rssin.http.Fetcher; +import org.rssin.http.Request; +import org.rssin.listener.FallibleListener; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; @@ -214,25 +216,29 @@ public class FeedLoader { /** * Retrieves the XML and parses it. */ - public void fetchXML() { - try { - URL url = urlString; - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setReadTimeout(10000 /* milliseconds */); - conn.setConnectTimeout(15000 /* milliseconds */); - conn.setRequestMethod("GET"); - conn.setDoInput(true); - // Starts the query - conn.connect(); - InputStream stream = conn.getInputStream(); - xmlFactoryObject = XmlPullParserFactory.newInstance(); - XmlPullParser myparser = xmlFactoryObject.newPullParser(); - myparser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); - myparser.setInput(stream, null); - parseXMLAndStoreIt(myparser); - stream.close(); - } catch (Exception ignored) { - } + public void fetchXML(Fetcher fetcher, final FallibleListener listener) { + fetcher.fetch( + new Request(urlString), + new FallibleListener() { + @Override + public void onReceive(String data) { + try { + xmlFactoryObject = XmlPullParserFactory.newInstance(); + XmlPullParser parser = xmlFactoryObject.newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + parser.setInput(new ByteArrayInputStream(data.getBytes()), null); + parseXMLAndStoreIt(parser); + listener.onReceive(null); + } catch (XmlPullParserException e) { + listener.onError(e); + } + } + + @Override + public void onError(Object error) { + listener.onError(error); + } + }); } public void setFeed(Feed feed) { @@ -246,4 +252,5 @@ public class FeedLoader { public void setUrlString(URL urlString) { this.urlString = urlString; } + } diff --git a/app/src/main/java/org/rssin/rssin/FeedLoaderAndSorter.java b/app/src/main/java/org/rssin/rssin/FeedLoaderAndSorter.java index e9d3e5d..0739e52 100755 --- a/app/src/main/java/org/rssin/rssin/FeedLoaderAndSorter.java +++ b/app/src/main/java/org/rssin/rssin/FeedLoaderAndSorter.java @@ -1,5 +1,9 @@ package org.rssin.rssin; +import org.rssin.http.Fetcher; +import org.rssin.listener.FallibleListener; +import org.rssin.listener.Listener; +import org.rssin.listener.RealtimeListener; import org.rssin.neurons.FeedSorter; import org.rssin.rss.FeedItem; import org.rssin.rss.FeedLoader; @@ -19,26 +23,62 @@ public class FeedLoaderAndSorter { /** * Loads the feed(s), filters it, sorts it, and returns the result. + * @param fetcher + * @param listener * @return The filtered & sorted list of feedtems. */ - public List getFilteredFeedItems() + public void getFilteredFeedItems(Fetcher fetcher, final Listener> listener) { - List resultingItems = new ArrayList(); - for(Feed feed : filter.getFeeds()) - { - FeedLoader loader = new FeedLoader(feed.getURL()); - loader.fetchXML(); - for(FeedItem item : loader.getFeed().getPosts()) - { - if(matchesKeyword(item)) - { - resultingItems.add(item); + final List resultingItems = new ArrayList<>(); + final Counter counter = new Counter(filter.getFeeds().size()); + final FeedSorter sorter = filter.getSorter(); + + for (Feed feed : filter.getFeeds()) { + final FeedLoader loader = new FeedLoader(feed.getURL()); + loader.fetchXML(fetcher, new FallibleListener() { + @Override + public void onReceive(Object data) { + for (FeedItem item : loader.getFeed().getPosts()) { + if(matchesKeyword(item)) { + resultingItems.add(item); + } + } + + sorter.sortItems(resultingItems); + + if (counter.decr().isZero() || listener.getClass() == RealtimeListener.class) { + listener.onReceive(resultingItems); + if (counter.decr().isZero() && listener.getClass() == RealtimeListener.class) { + ((RealtimeListener) listener).finish(); + } + } } - } + + @Override + public void onError(Object error) { + try { + ((FallibleListener) listener).onError(error); + } catch (ClassCastException e) {} + } + }); + } + } + + private static class Counter { + int count; + + Counter (int initial) { + count = initial; } - FeedSorter sorter = filter.getSorter(); - return sorter.sortItems(resultingItems); + public Counter decr() { + count--; + return this; + } + + public boolean isZero() { + return count == 0; + } } private boolean matchesKeyword(FeedItem item) diff --git a/app/src/main/java/org/rssin/rssin/Filter.java b/app/src/main/java/org/rssin/rssin/Filter.java index 271b67d..50e3351 100755 --- a/app/src/main/java/org/rssin/rssin/Filter.java +++ b/app/src/main/java/org/rssin/rssin/Filter.java @@ -22,7 +22,7 @@ public class Filter implements Serializable { private final List feeds; private final List keywords; private String title = ""; - private FeedSorter sorter; + private FeedSorter sorter = new FeedSorter(); public Filter() { feeds = new ArrayList<>(); diff --git a/app/src/main/res/layout/activity_filter.xml b/app/src/main/res/layout/activity_filter.xml new file mode 100644 index 0000000..b144d08 --- /dev/null +++ b/app/src/main/res/layout/activity_filter.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/layout/item_feeditem.xml b/app/src/main/res/layout/item_feeditem.xml new file mode 100644 index 0000000..9f4c553 --- /dev/null +++ b/app/src/main/res/layout/item_feeditem.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_filter.xml b/app/src/main/res/menu/menu_filter.xml new file mode 100644 index 0000000..aaf1ca1 --- /dev/null +++ b/app/src/main/res/menu/menu_filter.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9b5ffb4..3c6a364 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,4 +26,7 @@ Couldn\'t delete filter Invalid URL + FilterActivity + + Hello world! -- cgit v1.2.3