From 4a57a217d002f75d3cd10b081c5f4cb73d2ea275 Mon Sep 17 00:00:00 2001 From: Camil Staps Date: Wed, 3 Jun 2015 11:51:55 +0200 Subject: Nicer filter settings --- .../org/rssin/android/FilterSettingsActivity.java | 270 ++++++++++----------- .../android/SharedPreferencesStorageProvider.java | 20 +- app/src/main/java/org/rssin/rssin/Feed.java | 5 +- .../org/rssin/storage/FeedStorageProvider.java | 13 + 4 files changed, 145 insertions(+), 163 deletions(-) (limited to 'app/src/main/java') diff --git a/app/src/main/java/org/rssin/android/FilterSettingsActivity.java b/app/src/main/java/org/rssin/android/FilterSettingsActivity.java index 296d2e5..931a86c 100755 --- a/app/src/main/java/org/rssin/android/FilterSettingsActivity.java +++ b/app/src/main/java/org/rssin/android/FilterSettingsActivity.java @@ -18,9 +18,11 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.AdapterView; +import android.widget.CheckBox; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ListView; +import android.widget.RelativeLayout; import android.widget.TextView; import org.rssin.rssin.Feed; @@ -55,6 +57,82 @@ public class FilterSettingsActivity extends ActionBarActivity { filter = filtersList.getFilterFromHashCode(filterHashCode); setTitle(); + + try { + setupFeedsList(); + } catch (IOException e) { + Frontend.error(this, R.string.error_load_feeds, e); + } + } + + /** + * Setup the list of feeds + * @throws IOException + */ + protected void setupFeedsList() throws IOException { + final FeedAdapter feedAdapter = new FeedAdapter( + this, + R.layout.item_filter_settings_feed, + FeedsList.getInstance(this).getFeeds(), + filter); + + final ListView feedsListView = (ListView) findViewById(R.id.filter_settings_feeds_list); + feedsListView.setAdapter(feedAdapter); + + feedsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Feed feed = feedAdapter.getItem(position); + List hashCodes = filter.getFeedHashCodes(); + if (hashCodes.contains(feed.hashCode())) { + hashCodes.remove((Integer) feed.hashCode()); + } else { + hashCodes.add(feed.hashCode()); + } + try { + filter.store(DefaultStorageProvider.getInstance()); + feedAdapter.notifyDataSetChanged(); + } catch (Exception e) { + Frontend.error(getBaseContext(), R.string.error_save_filters, e); + filter.getFeeds().add(feed); + } + } + }); + + /** + * @todo Allow users to enter links without scheme (http:// ...) + */ + findViewById(R.id.filter_settings_add_feed_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + EditText editText = (EditText) findViewById(R.id.filter_settings_add_feed); + String value = editText.getText().toString(); + try { + URL url = new URL(value); + Feed feed = new Feed(url); + try { + feed.store(DefaultStorageProvider.getInstance()); + FeedsList.getInstance().getFeeds().add(feed); + filter.getFeedHashCodes().add(feed.hashCode()); + + try { + filter.store(DefaultStorageProvider.getInstance()); + feed.store(DefaultStorageProvider.getInstance()); + feedAdapter.notifyDataSetChanged(); + editText.setText(""); + } catch (Exception e) { + Frontend.error(getBaseContext(), R.string.error_save_filters, e); + filter.getFeeds().remove(feed); + } + } catch (Exception e) { + Frontend.error(getBaseContext(), R.string.error_save_feeds, e); + FeedsList.getInstance().getFeeds().remove(feed); + } + } catch (MalformedURLException e) { + Frontend.info(getBaseContext(), R.string.error_invalid_url, e); + } + } + }); } @Override @@ -68,9 +146,6 @@ public class FilterSettingsActivity extends ActionBarActivity { int id = item.getItemId(); switch (id) { - case R.id.filter_settings_action_feeds: - openFeedsDialog(); - return true; case R.id.filter_settings_action_title: openTitleDialog(); return true; @@ -97,17 +172,6 @@ public class FilterSettingsActivity extends ActionBarActivity { } } - /** - * Open dialog to edit feeds - */ - public void openFeedsDialog() { - FeedsDialogFragment f = new FeedsDialogFragment(); - Bundle bundle = new Bundle(); - bundle.putInt("filter", filter.hashCode()); - f.setArguments(bundle); - f.show(getFragmentManager(), ""); - } - /** * Open dialog to edit title * For the moment, we temporarily disable rotating because we can't get it working otherwise. @@ -160,148 +224,58 @@ public class FilterSettingsActivity extends ActionBarActivity { } /** - * Custom Dialog to display & edit feeds + * Custom ArrayAdapter to display feeds */ - public static class FeedsDialogFragment extends DialogFragment { - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_filter_settings_feeds, null); - - final FiltersList filtersList = FiltersList.getInstance(); - final Filter filter = filtersList.getFilterFromHashCode(getArguments().getInt("filter")); - - try { - final FeedAdapter feedAdapter = new FeedAdapter( - getActivity(), - R.layout.item_filter_settings_feed, - FeedsList.getInstance(getActivity()).getFeeds(), - filter); - - ListView feedsListView = (ListView) view.findViewById(R.id.filter_settings_feeds_list); - feedsListView.setAdapter(feedAdapter); - - /** - * @todo nicer selected feed layout - * @todo deselecting feeds doesn't work - */ - feedsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - Feed feed = feedAdapter.getItem(position); - List hashCodes = filter.getFeedHashCodes(); - if (hashCodes.contains(feed.hashCode())) { - hashCodes.remove(Integer.valueOf(feed.hashCode())); - } else { - hashCodes.add(feed.hashCode()); - } - try { - filter.store(DefaultStorageProvider.getInstance()); - feedAdapter.notifyDataSetChanged(); - } catch (Exception e) { - Frontend.error(getActivity(), R.string.error_save_filters, e); - filter.getFeeds().add(feed); - } - } - }); + private static class FeedAdapter extends SortedArrayAdapter { + + Context context; + int layoutResourceId; + Filter filter; + + public FeedAdapter(Context context, int resource, List objects, Filter filter) { + super(context, resource, objects); + this.context = context; + layoutResourceId = resource; + items = objects; + this.filter = filter; + } - view.findViewById(R.id.filter_settings_add_feed_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - EditText editText = (EditText) view.findViewById(R.id.filter_settings_add_feed); - String value = editText.getText().toString(); - try { - URL url = new URL(value); - Feed feed = new Feed(url); - try { - feed.store(DefaultStorageProvider.getInstance()); - FeedsList.getInstance().getFeeds().add(feed); - filter.getFeedHashCodes().add(feed.hashCode()); - - try { - filter.store(DefaultStorageProvider.getInstance()); - feed.store(DefaultStorageProvider.getInstance()); - feedAdapter.notifyDataSetChanged(); - editText.setText(""); - } catch (Exception e) { - Frontend.error(getActivity(), R.string.error_save_filters, e); - filter.getFeeds().remove(feed); - } - } catch (Exception e) { - Frontend.error(getActivity(), R.string.error_save_feeds, e); - FeedsList.getInstance().getFeeds().remove(feed); - } - } catch (MalformedURLException e) { - Frontend.info(getActivity(), R.string.error_invalid_url, e); - } - } - }); + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = convertView; + FeedHolder holder; + + if (row == null) { + LayoutInflater inflater = ((Activity) context).getLayoutInflater(); + row = inflater.inflate(layoutResourceId, parent, false); + + holder = new FeedHolder(); + holder.layout = (RelativeLayout) row.findViewById(R.id.filter_settings_feed_item); + holder.title = (TextView) row.findViewById(R.id.filter_settings_feed_item_title); + holder.url = (TextView) row.findViewById(R.id.filter_settings_feed_item_url); + holder.checkBox = (CheckBox) row.findViewById(R.id.filter_settings_feed_item_checkbox); + + row.setTag(holder); + } else { + holder = (FeedHolder) row.getTag(); + } - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setMessage(R.string.filter_settings_feeds).setPositiveButton(R.string.button_ok, null); - builder.setView(view); + Feed feed = items.get(position); + holder.title.setText(feed.getTitle()); + holder.url.setText(feed.getURL().toString()); + holder.checkBox.setChecked(filter.getFeedHashCodes().contains(feed.hashCode())); - return builder.create(); - } catch (IOException e) { - dismiss(); - Frontend.error(getActivity(), R.string.error_load_feeds, e); - return null; - } + return row; } /** - * Custom ArrayAdapter to display feeds + * TextViews holder */ - private static class FeedAdapter extends SortedArrayAdapter { - - Context context; - int layoutResourceId; - Filter filter; - - public FeedAdapter(Context context, int resource, List objects, Filter filter) { - super(context, resource, objects); - this.context = context; - layoutResourceId = resource; - items = objects; - this.filter = filter; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View row = convertView; - FeedHolder holder; - - if (row == null) { - LayoutInflater inflater = ((Activity) context).getLayoutInflater(); - row = inflater.inflate(layoutResourceId, parent, false); - - holder = new FeedHolder(); - holder.layout = (LinearLayout) row.findViewById(R.id.filter_settings_feed_item); - holder.title = (TextView) row.findViewById(R.id.filter_settings_feed_item_title); - holder.url = (TextView) row.findViewById(R.id.filter_settings_feed_item_url); - - row.setTag(holder); - } else { - holder = (FeedHolder) row.getTag(); - } - - Feed feed = items.get(position); - holder.title.setText(feed.getTitle()); - holder.url.setText(feed.getURL().toString()); - if (filter.getFeedHashCodes().contains(feed.hashCode())) { - holder.layout.setBackgroundColor(Color.RED); - } - - return row; - } - - /** - * TextViews holder - */ - private static class FeedHolder { - LinearLayout layout; - TextView title; - TextView url; - } + private static class FeedHolder { + RelativeLayout layout; + TextView title; + TextView url; + CheckBox checkBox; } } diff --git a/app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java b/app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java index 4cc9386..61f28cd 100644 --- a/app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java +++ b/app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java @@ -75,12 +75,6 @@ class SharedPreferencesStorageProvider implements StorageProvider, FilterStorage .edit() .putString(element.getClass().getName(), string) .apply(); - - if (element.getClass() == Filter.class) { - storeFilterKey(key); - } else if (element.getClass() == Feed.class) { - storeFeedKey(key); - } } @Override @@ -145,12 +139,7 @@ class SharedPreferencesStorageProvider implements StorageProvider, FilterStorage public void removeFilter(Object key) { SharedPreferences sharedPreferences = context.getSharedPreferences(ADMIN_PREF_KEY, Context.MODE_PRIVATE); Set names = new HashSet<>(sharedPreferences.getStringSet("filters", new HashSet())); - for (String name : names) - Log.d("SPSP", "Old name: " + name); - Log.d("SPSP", "Removing name " + key.toString()); names.remove(key.toString()); - for (String name : names) - Log.d("SPSP", "New name: " + name); sharedPreferences .edit() .putStringSet("filters", names) @@ -161,9 +150,8 @@ class SharedPreferencesStorageProvider implements StorageProvider, FilterStorage /** * Keep track of a new key for a filter in the administration SharedPreferences * @param key - * @throws Exception */ - protected void storeFilterKey(Object key) throws Exception { + protected void storeFilterKey(Object key) { SharedPreferences sharedPreferences = context.getSharedPreferences(ADMIN_PREF_KEY, Context.MODE_PRIVATE); Set names = new HashSet<>(sharedPreferences.getStringSet("filters", new HashSet())); names.add(key.toString()); @@ -186,6 +174,12 @@ class SharedPreferencesStorageProvider implements StorageProvider, FilterStorage return feeds; } + @Override + public void storeFeed(Object key, Feed feed) throws Exception { + store(key, feed); + storeFeedKey(key); + } + @Override public Feed getFeed(Object key) { try { diff --git a/app/src/main/java/org/rssin/rssin/Feed.java b/app/src/main/java/org/rssin/rssin/Feed.java index 55d0e5d..ae8b5f2 100755 --- a/app/src/main/java/org/rssin/rssin/Feed.java +++ b/app/src/main/java/org/rssin/rssin/Feed.java @@ -5,6 +5,7 @@ import android.util.Log; import org.json.JSONException; import org.json.JSONObject; import org.rssin.serialization.Jsonable; +import org.rssin.storage.FeedStorageProvider; import org.rssin.storage.Storable; import org.rssin.storage.StorageProvider; @@ -110,12 +111,12 @@ public class Feed implements Storable, Comparable, Jsonable { * @param storageProvider * @throws Exception */ - public synchronized void store(StorageProvider storageProvider) throws Exception { + public synchronized void store(FeedStorageProvider storageProvider) throws Exception { if (storageKey == null) { storageKey = storageProvider.uniqueKey(); } - storageProvider.store(storageKey, this); + storageProvider.storeFeed(storageKey, this); } @Override diff --git a/app/src/main/java/org/rssin/storage/FeedStorageProvider.java b/app/src/main/java/org/rssin/storage/FeedStorageProvider.java index eac78ce..13932a6 100644 --- a/app/src/main/java/org/rssin/storage/FeedStorageProvider.java +++ b/app/src/main/java/org/rssin/storage/FeedStorageProvider.java @@ -16,6 +16,13 @@ public interface FeedStorageProvider { */ List allFeeds(); + /** + * Save a Filter + * @param key + * @param feed + */ + void storeFeed(K key, Feed feed) throws Exception; + /** * Get the feed for a specific key * @param key @@ -29,4 +36,10 @@ public interface FeedStorageProvider { */ void removeFeed(K key); + /** + * Get a new, unique, usable key + * @return + */ + K uniqueKey(); + } -- cgit v1.2.3 From d67bf7492f9c573199e32679b36265c75641dfb5 Mon Sep 17 00:00:00 2001 From: Camil Staps Date: Wed, 3 Jun 2015 12:19:26 +0200 Subject: Removed JSON write&readObject as it was too buggy --- .../org/rssin/android/SharedPreferencesStorageProvider.java | 13 ++++++++++--- app/src/main/java/org/rssin/rssin/Filter.java | 8 -------- 2 files changed, 10 insertions(+), 11 deletions(-) (limited to 'app/src/main/java') diff --git a/app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java b/app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java index 61f28cd..4c23fa9 100644 --- a/app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java +++ b/app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java @@ -69,12 +69,15 @@ class SharedPreferencesStorageProvider implements StorageProvider, FilterStorage ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(element); oos.close(); - String string = Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT); + baos.close(); + String base64 = Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT); context.getSharedPreferences(key.toString(), Context.MODE_PRIVATE) .edit() - .putString(element.getClass().getName(), string) + .putString(element.getClass().getName(), base64) .apply(); + + Log.v("SPSP", "Store to " + key.toString() + ":\n" + base64); } @Override @@ -83,8 +86,9 @@ class SharedPreferencesStorageProvider implements StorageProvider, FilterStorage if (serialized == null) { throw new IOException("No sharedPreference with key " + key.toString() + " and class " + className.getName()); } - ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.decode(serialized, Base64.DEFAULT))); + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.decode(serialized.getBytes(), Base64.DEFAULT))); Object obj = ois.readObject(); + Log.v("SPSP", "Fetch " + className.toString() + " from " + key.toString() + ": " + obj.toString()); return (Storable) className.cast(obj); } @@ -112,8 +116,10 @@ class SharedPreferencesStorageProvider implements StorageProvider, FilterStorage .getStringSet("filters", new HashSet()); List filters = new ArrayList<>(); for (String name : names) { + Log.v("SPSP", "allFilters: " + name); Filter filter = getFilter(name); if (filter != null) { + Log.v("SPSP", "allFilters: not null, returning"); filters.add(filter); } } @@ -131,6 +137,7 @@ class SharedPreferencesStorageProvider implements StorageProvider, FilterStorage try { return (Filter) fetch(key.toString(), Filter.class); } catch (Exception e) { + Log.w("SPSP", "Fetch filter " + key.toString(), e); return null; } } diff --git a/app/src/main/java/org/rssin/rssin/Filter.java b/app/src/main/java/org/rssin/rssin/Filter.java index 7f1d95e..be4ed4d 100755 --- a/app/src/main/java/org/rssin/rssin/Filter.java +++ b/app/src/main/java/org/rssin/rssin/Filter.java @@ -73,14 +73,6 @@ public class Filter implements Storable, Comparable, Jsonable { setTitle(title); } - private void writeObject(ObjectOutputStream stream) throws JSONException, IOException { - stream.writeObject(toJson().toString()); - } - - private void readObject(ObjectInputStream stream) throws JSONException, IOException, ClassNotFoundException { - fromJson(new JSONObject((String) stream.readObject())); - } - public List getFeedHashCodes() { return feedHashCodes; } -- cgit v1.2.3 From 6944cf9f088e8026fbaa583d6a4e934c1fa4660d Mon Sep 17 00:00:00 2001 From: Camil Staps Date: Wed, 3 Jun 2015 12:25:46 +0200 Subject: InternalStorageProvider for the feedsorter --- app/src/main/java/org/rssin/android/FilterActivity.java | 4 ++-- app/src/main/java/org/rssin/android/UnifiedInboxActivity.java | 2 +- app/src/main/java/org/rssin/rssin/Filter.java | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'app/src/main/java') diff --git a/app/src/main/java/org/rssin/android/FilterActivity.java b/app/src/main/java/org/rssin/android/FilterActivity.java index 5003cfb..189fd55 100755 --- a/app/src/main/java/org/rssin/android/FilterActivity.java +++ b/app/src/main/java/org/rssin/android/FilterActivity.java @@ -45,7 +45,7 @@ public class FilterActivity extends ActionBarActivity { // @todo Check on -1? Shouldn't happen anyway. filter = filtersList.getFilterFromHashCode(filterHashCode); - filter.ensureFeedSorter(DefaultStorageProvider.getInstance(this)); + filter.ensureFeedSorter(InternalStorageProvider.getInstance(this)); setTitle(filter.getTitle()); @@ -90,7 +90,7 @@ public class FilterActivity extends ActionBarActivity { protected void onDestroy() { super.onDestroy(); try { - filter.storeFeedSorter(DefaultStorageProvider.getInstance(this)); + filter.storeFeedSorter(InternalStorageProvider.getInstance(this)); } catch (Exception e) { Frontend.warning(this, R.string.error_save_feedsorter, e); } diff --git a/app/src/main/java/org/rssin/android/UnifiedInboxActivity.java b/app/src/main/java/org/rssin/android/UnifiedInboxActivity.java index ec8dce7..5a11621 100755 --- a/app/src/main/java/org/rssin/android/UnifiedInboxActivity.java +++ b/app/src/main/java/org/rssin/android/UnifiedInboxActivity.java @@ -41,7 +41,7 @@ public class UnifiedInboxActivity extends ActionBarActivity { filtersList = FiltersList.getInstance(this); for(Filter filter : filtersList.getFilters()) { - filter.ensureFeedSorter(DefaultStorageProvider.getInstance(this)); + filter.ensureFeedSorter(InternalStorageProvider.getInstance(this)); filter.ensureFeeds(DefaultStorageProvider.getInstance(this)); } diff --git a/app/src/main/java/org/rssin/rssin/Filter.java b/app/src/main/java/org/rssin/rssin/Filter.java index be4ed4d..e7e0158 100755 --- a/app/src/main/java/org/rssin/rssin/Filter.java +++ b/app/src/main/java/org/rssin/rssin/Filter.java @@ -1,5 +1,6 @@ package org.rssin.rssin; +import android.support.annotation.Nullable; import android.util.Log; import org.json.JSONArray; @@ -173,8 +174,8 @@ public class Filter implements Storable, Comparable, Jsonable { } @Override - public int compareTo(Filter another) { - return title.compareTo(another.title); + public int compareTo(@Nullable Filter another) { + return another == null ? -1 : title.compareTo(another.title); } @Override @@ -190,7 +191,6 @@ public class Filter implements Storable, Comparable, Jsonable { public void fromJson(JSONObject json) throws JSONException { title = json.getString("title"); feedHashCodes = JsonSerializer.integersListFromJson(json.getJSONArray("feedHashCodes")); - JSONArray keywordsJson = json.getJSONArray("keywords"); storageKey = json.getString("storageKey"); } -- cgit v1.2.3 From 81e3502f1ceb336e6f75ac7daccc0f0b1724dde7 Mon Sep 17 00:00:00 2001 From: Camil Staps Date: Wed, 3 Jun 2015 12:33:19 +0200 Subject: Error handling ArticleActivity --- .../java/org/rssin/android/ArticleActivity.java | 37 ++++++++++++++++------ app/src/main/res/values/strings.xml | 14 ++++++-- 2 files changed, 38 insertions(+), 13 deletions(-) (limited to 'app/src/main/java') diff --git a/app/src/main/java/org/rssin/android/ArticleActivity.java b/app/src/main/java/org/rssin/android/ArticleActivity.java index 2e5460c..7d9fa2b 100755 --- a/app/src/main/java/org/rssin/android/ArticleActivity.java +++ b/app/src/main/java/org/rssin/android/ArticleActivity.java @@ -24,18 +24,35 @@ public class ArticleActivity extends ActionBarActivity { Intent intent = getIntent(); Bundle arguments = intent.getExtras(); container = (SortedFeedItemContainer) arguments.getSerializable("item"); - FeedItem item = container.getFeeditem(); + try { + FeedItem item = container.getFeeditem(); - TextView title = (TextView) findViewById(R.id.article_title); - title.setText(item.getTitle()); - TextView description = (TextView) findViewById(R.id.article_description); - description.setText(Html.fromHtml(item.getDescription())); - TextView author = (TextView) findViewById(R.id.article_author); - author.setText("Written by: " + item.getAuthor()); - TextView date = (TextView) findViewById(R.id.article_date); - date.setText("Published on: " + item.getPubDate().toString()); + TextView title = (TextView) findViewById(R.id.article_title); + title.setText(item.getTitle()); + setTitle(item.getTitle()); - new Thread(new FeedSorterTrainer(container.getSorter())).start(); + TextView description = (TextView) findViewById(R.id.article_description); + description.setText(Html.fromHtml(item.getDescription())); + + TextView author = (TextView) findViewById(R.id.article_author); + if (item.getAuthor() != null) { + author.setText(getResources().getString(R.string.article_author) + " " + item.getAuthor()); + } else { + author.setText(R.string.article_author_unknown); + } + + TextView date = (TextView) findViewById(R.id.article_date); + if (item.getPubDate() != null) { + date.setText(getResources().getString(R.string.article_published_on) + " " + item.getPubDate().toString()); + } else { + date.setText(R.string.article_published_on_unknown); + } + + new Thread(new FeedSorterTrainer(container.getSorter())).start(); + } catch (NullPointerException e) { + Frontend.error(this, R.string.error_load_article, e); + finish(); + } } @Override diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 77a55f0..5b051c8 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,6 +13,8 @@ Filters Filter Filter + Article + Feeds Add @@ -39,11 +41,17 @@ Internet problem Couldn\'t store personal preferences - Feeds - Hello world! - ArticleActivity + Couldn\'t load article + feeds + Dislike Like + + Written by: + Unknown author + Published on: + + -- cgit v1.2.3