aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCamil Staps2015-05-25 19:30:09 +0200
committerCamil Staps2015-05-25 19:30:09 +0200
commit6dc9cf9659e2c4356c2a7eaa1227931458e7a7d9 (patch)
treed80575e868aa050d470b75edd111f36d2be00378
parentCleanup; general Frontend class for displaying user messages (diff)
Add feeds
-rwxr-xr-xapp/src/main/AndroidManifest.xml8
-rw-r--r--app/src/main/java/org/rssin/android/FeedsActivity.java177
-rw-r--r--app/src/main/java/org/rssin/android/FeedsList.java73
-rw-r--r--app/src/main/java/org/rssin/android/FilterSettingsActivity.java4
-rw-r--r--app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java59
-rw-r--r--app/src/main/java/org/rssin/android/UnifiedInboxActivity.java3
-rw-r--r--app/src/main/java/org/rssin/rssin/Feed.java41
-rw-r--r--app/src/main/java/org/rssin/storage/FeedStorageProvider.java14
-rw-r--r--app/src/main/res/layout/activity_feeds.xml13
-rw-r--r--app/src/main/res/layout/activity_filters.xml2
-rw-r--r--app/src/main/res/layout/item_feed.xml19
-rw-r--r--app/src/main/res/menu/menu_feeds.xml13
-rw-r--r--app/src/main/res/menu/menu_unified_inbox.xml2
-rw-r--r--app/src/main/res/values/strings.xml10
14 files changed, 425 insertions, 13 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e4f63c3..7739ada 100755
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -45,6 +45,14 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="org.rssin.android.FiltersActivity" />
</activity>
+ <activity
+ android:name="org.rssin.android.FeedsActivity"
+ android:label="@string/title_activity_feeds"
+ android:parentActivityName="org.rssin.android.UnifiedInboxActivity" >
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value="org.rssin.android.UnifiedInboxActivity" />
+ </activity>
</application>
</manifest>
diff --git a/app/src/main/java/org/rssin/android/FeedsActivity.java b/app/src/main/java/org/rssin/android/FeedsActivity.java
new file mode 100644
index 0000000..fd4ebca
--- /dev/null
+++ b/app/src/main/java/org/rssin/android/FeedsActivity.java
@@ -0,0 +1,177 @@
+package org.rssin.android;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.ActivityInfo;
+import android.support.v7.app.ActionBarActivity;
+import android.os.Bundle;
+import android.text.InputType;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import org.rssin.rssin.Feed;
+import org.rssin.rssin.R;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * List of feeds
+ *
+ * @author Camil Staps
+ */
+public class FeedsActivity extends ActionBarActivity {
+
+ private FeedsList feedsList;
+ private ListView feedsView;
+ private FeedAdapter feedAdapter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_feeds);
+
+ feedsView = (ListView) findViewById(R.id.feeds_list);
+
+ try {
+ feedsList = FeedsList.getInstance(this);
+ } catch (IOException e) {
+ Frontend.error(this, R.string.error_load_feeds, e);
+ finish();
+ }
+
+ feedAdapter = new FeedAdapter(this, R.layout.item_feed, feedsList.getFeeds());
+ feedsView.setAdapter(feedAdapter);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_feeds, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+
+ if (id == R.id.feeds_action_add) {
+ openAddDialog();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * Open dialog to add new feed
+ * For the moment, we temporarily disable rotating because we can't get it working otherwise.
+ * @todo make rotating possible
+ */
+ public void openAddDialog() {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
+
+ AlertDialog.Builder alert = new AlertDialog.Builder(this);
+
+ alert.setTitle("Add feed");
+ alert.setMessage("URL:");
+
+ final EditText input = new EditText(this);
+ input.setFocusable(true);
+ input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
+ input.setMaxLines(1);
+ input.requestFocus();
+
+ AlertDialog dialog = alert
+ .setView(input)
+ .setPositiveButton(getResources().getString(R.string.button_apply), new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ String value = input.getText().toString();
+ try {
+ Feed f = new Feed(value);
+ f.store(DefaultStorageProvider.getInstance(getBaseContext()));
+ feedsList.getFeeds().add(f);
+ feedAdapter.notifyDataSetChanged();
+ } catch (Exception e) {
+ Frontend.error(getBaseContext(), R.string.error_save_feeds, e);
+ }
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
+ }
+ })
+ .setNegativeButton(getResources().getString(R.string.button_cancel), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
+ }
+ })
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
+ }
+ })
+ .create();
+
+ dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ dialog.show();
+ }
+
+ /**
+ * Custom ArrayAdapter to display feeds with our own menu item
+ */
+ private static class FeedAdapter extends ArrayAdapter<Feed> {
+
+ Context context;
+ int layoutResourceId;
+ List<Feed> feeds;
+
+ public FeedAdapter(Context context, int resource, List<Feed> objects) {
+ super(context, resource, objects);
+ this.context = context;
+ layoutResourceId = resource;
+ feeds = objects;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View row = convertView;
+ FeedHolder holder = null;
+
+ if (row == null) {
+ LayoutInflater inflater = ((Activity) context).getLayoutInflater();
+ row = inflater.inflate(layoutResourceId, parent, false);
+
+ holder = new FeedHolder();
+ holder.title = (TextView) row.findViewById(R.id.feed_item_title);
+ holder.url = (TextView) row.findViewById(R.id.feed_item_url);
+
+ row.setTag(holder);
+ } else {
+ holder = (FeedHolder) row.getTag();
+ }
+
+ Feed feed = feeds.get(position);
+ holder.title.setText(feed.getTitle());
+ holder.url.setText(feed.getURL().toString());
+
+ return row;
+ }
+
+ /**
+ * TextViews holder
+ */
+ private static class FeedHolder {
+ TextView title;
+ TextView url;
+ }
+ }
+}
diff --git a/app/src/main/java/org/rssin/android/FeedsList.java b/app/src/main/java/org/rssin/android/FeedsList.java
new file mode 100644
index 0000000..e96342a
--- /dev/null
+++ b/app/src/main/java/org/rssin/android/FeedsList.java
@@ -0,0 +1,73 @@
+package org.rssin.android;
+
+import android.content.Context;
+import android.util.Log;
+
+import org.rssin.rssin.Feed;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * A list of filters that can be saved using the DefaultStorageProvider
+ * @author Camil Staps
+ */
+class FeedsList {
+
+ private static FeedsList instance;
+
+ private final List<Feed> feeds;
+ private final DefaultStorageProvider storageProvider;
+
+ /**
+ * Fetch from storage provider
+ * @param context needed to get the storageprovider
+ * @throws IOException if data is corrupted, and deserializing fails
+ */
+ protected FeedsList(Context context) throws IOException {
+ storageProvider = DefaultStorageProvider.getInstance(context);
+ feeds = storageProvider.allFeeds();
+ }
+
+ public static FeedsList getInstance(Context context) throws IOException {
+ if (instance == null)
+ instance = new FeedsList(context);
+ return instance;
+ }
+
+ public static FeedsList getInstance() {
+ return instance;
+ }
+
+ public List<Feed> getFeeds() {
+ return feeds;
+ }
+
+ /**
+ * Save all filters
+ * @throws Exception if serializing or saving failed
+ */
+ public synchronized void save() throws Exception {
+ Exception e = null;
+ for (Feed feed : feeds) {
+ try {
+ feed.store(storageProvider);
+ } catch (Exception ex) {
+ e = ex;
+ }
+ }
+ if (e != null) {
+ throw e;
+ }
+ }
+
+ public Feed getFeedFromHashCode(int hashcode) {
+ for (Feed feed : feeds) {
+ if (feed.hashCode() == hashcode) {
+ return feed;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/app/src/main/java/org/rssin/android/FilterSettingsActivity.java b/app/src/main/java/org/rssin/android/FilterSettingsActivity.java
index 3de0882..4ff4dd7 100644
--- a/app/src/main/java/org/rssin/android/FilterSettingsActivity.java
+++ b/app/src/main/java/org/rssin/android/FilterSettingsActivity.java
@@ -241,9 +241,7 @@ public class FilterSettingsActivity extends ActionBarActivity {
String value = editText.getText().toString();
try {
URL url = new URL(value);
- Feed feed = new Feed(
- url,
- url.getHost() + " " + value.substring(value.lastIndexOf('/') + 1, value.lastIndexOf('.')));
+ Feed feed = new Feed(url);
filter.getFeeds().add(feed);
try {
diff --git a/app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java b/app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java
index aee7df0..49ddfa8 100644
--- a/app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java
+++ b/app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java
@@ -3,8 +3,11 @@ package org.rssin.android;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
+import android.util.Log;
+import org.rssin.rssin.Feed;
import org.rssin.rssin.Filter;
+import org.rssin.storage.FeedStorageProvider;
import org.rssin.storage.FilterStorageProvider;
import org.rssin.storage.Storable;
import org.rssin.storage.StorageProvider;
@@ -23,7 +26,7 @@ import java.util.Set;
* A StorageProvider using SharedPreferences
* @author Camil Staps
*/
-class SharedPreferencesStorageProvider implements StorageProvider, FilterStorageProvider {
+class SharedPreferencesStorageProvider implements StorageProvider, FilterStorageProvider, FeedStorageProvider {
/**
* Under this key, we will store the administration for the class itself.
@@ -68,10 +71,16 @@ class SharedPreferencesStorageProvider implements StorageProvider, FilterStorage
oos.close();
String string = Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT);
- context.getSharedPreferences(key.toString(), Context.MODE_PRIVATE).edit().putString(element.getClass().getName(), string).commit();
+ context
+ .getSharedPreferences(key.toString(), Context.MODE_PRIVATE)
+ .edit()
+ .putString(element.getClass().getName(), string)
+ .apply();
if (element.getClass() == Filter.class) {
storeFilterKey(key);
+ } else if (element.getClass() == Feed.class) {
+ storeFeedKey(key);
}
}
@@ -87,7 +96,7 @@ class SharedPreferencesStorageProvider implements StorageProvider, FilterStorage
@Override
public Object uniqueKey() {
- return Integer.toString((int) System.currentTimeMillis());
+ return Long.toString(System.currentTimeMillis());
}
@Override
@@ -119,8 +128,48 @@ class SharedPreferencesStorageProvider implements StorageProvider, FilterStorage
*/
protected void storeFilterKey(Object key) throws Exception {
SharedPreferences sharedPreferences = context.getSharedPreferences(ADMIN_PREF_KEY, Context.MODE_PRIVATE);
- Set<String> names = sharedPreferences.getStringSet("filters", new HashSet<String>());
+ Set<String> names = new HashSet<>(sharedPreferences.getStringSet("filters", new HashSet<String>()));
names.add(key.toString());
- sharedPreferences.edit().putStringSet("filters", names).apply();
+ sharedPreferences
+ .edit()
+ .putStringSet("filters", names)
+ .apply();
+ }
+
+ @Override
+ public List<Feed> allFeeds() {
+ Set<String> names = context.getSharedPreferences(ADMIN_PREF_KEY, Context.MODE_PRIVATE).getStringSet("feeds", new HashSet<String>());
+ List<Feed> feeds = new ArrayList<>();
+ for (String name : names) {
+ Feed feed = getFeed(name);
+ if (feed != null) {
+ feeds.add(feed);
+ }
+ }
+ return feeds;
+ }
+
+ @Override
+ public Feed getFeed(Object key) {
+ try {
+ return (Feed) fetch(key.toString(), Feed.class);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Keep track of a new key for a filter in the administration SharedPreferences
+ * @param key
+ * @throws Exception
+ */
+ protected void storeFeedKey(Object key) throws Exception {
+ SharedPreferences sharedPreferences = context.getSharedPreferences(ADMIN_PREF_KEY, Context.MODE_PRIVATE);
+ Set<String> names = new HashSet<>(sharedPreferences.getStringSet("feeds", new HashSet<String>()));
+ names.add(key.toString());
+ sharedPreferences
+ .edit()
+ .putStringSet("feeds", names)
+ .apply();
}
}
diff --git a/app/src/main/java/org/rssin/android/UnifiedInboxActivity.java b/app/src/main/java/org/rssin/android/UnifiedInboxActivity.java
index 6283dae..d99a1e0 100644
--- a/app/src/main/java/org/rssin/android/UnifiedInboxActivity.java
+++ b/app/src/main/java/org/rssin/android/UnifiedInboxActivity.java
@@ -36,6 +36,9 @@ public class UnifiedInboxActivity extends ActionBarActivity {
} else if (id == R.id.action_filters) {
Intent intent = new Intent(this, FiltersActivity.class);
startActivity(intent);
+ } else if (id == R.id.action_feeds) {
+ Intent intent = new Intent(this, FeedsActivity.class);
+ startActivity(intent);
}
return super.onOptionsItemSelected(item);
diff --git a/app/src/main/java/org/rssin/rssin/Feed.java b/app/src/main/java/org/rssin/rssin/Feed.java
index a2009ca..44f3d2e 100644
--- a/app/src/main/java/org/rssin/rssin/Feed.java
+++ b/app/src/main/java/org/rssin/rssin/Feed.java
@@ -1,6 +1,10 @@
package org.rssin.rssin;
-import java.io.Serializable;
+import android.util.Log;
+
+import org.rssin.storage.Storable;
+import org.rssin.storage.StorageProvider;
+
import java.net.MalformedURLException;
import java.net.URL;
@@ -8,7 +12,7 @@ import java.net.URL;
* Feed holder
* @author Camil Staps
*/
-public class Feed implements Serializable {
+public class Feed implements Storable {
private static int serialVersionUID = 0;
/**
@@ -18,14 +22,16 @@ public class Feed implements Serializable {
private String title;
private URL url;
+ private Object storageKey;
+
public Feed(URL url) {
this.url = url;
- this.title = url.toString();
+ setTitleFromURL();
}
public Feed(String url) throws MalformedURLException {
this.url = new URL(url);
- this.title = url;
+ setTitleFromURL();
}
public Feed(URL url, String title) {
@@ -58,9 +64,36 @@ public class Feed implements Serializable {
this.url = new URL(url);
}
+ /**
+ * Cleverly make up a title based on the URL
+ * @todo make it really really clever
+ */
+ public void setTitleFromURL() {
+ String title = url.getHost();
+ String url = this.url.toString();
+ if (url.lastIndexOf('/') < url.lastIndexOf('.') && url.lastIndexOf('/') != -1) {
+ title += " " + url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.'));
+ } else if (url.lastIndexOf('/') != -1) {
+ title += " " + url.substring(url.lastIndexOf('/') + 1);
+ }
+ this.title = title;
+ }
+
@Override
public String toString() {
return title + ": " + url.toString();
}
+ /**
+ * Store the Feed
+ * @param storageProvider
+ * @throws Exception
+ */
+ public synchronized void store(StorageProvider storageProvider) throws Exception {
+ if (storageKey == null) {
+ storageKey = storageProvider.uniqueKey();
+ }
+ storageProvider.store(storageKey, this);
+ }
+
}
diff --git a/app/src/main/java/org/rssin/storage/FeedStorageProvider.java b/app/src/main/java/org/rssin/storage/FeedStorageProvider.java
new file mode 100644
index 0000000..4148308
--- /dev/null
+++ b/app/src/main/java/org/rssin/storage/FeedStorageProvider.java
@@ -0,0 +1,14 @@
+package org.rssin.storage;
+
+import org.rssin.rssin.Feed;
+
+import java.util.List;
+
+/**
+ * The FeedStorageProvider can get a list of all stored feeds, and a specific feed from a key
+ * @author Camil Staps
+ */
+public interface FeedStorageProvider<K> {
+ List<Feed> allFeeds();
+ Feed getFeed(K key);
+}
diff --git a/app/src/main/res/layout/activity_feeds.xml b/app/src/main/res/layout/activity_feeds.xml
new file mode 100644
index 0000000..f71eb1e
--- /dev/null
+++ b/app/src/main/res/layout/activity_feeds.xml
@@ -0,0 +1,13 @@
+<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="org.rssin.android.FeedsActivity">
+
+ <ListView
+ android:id="@+id/feeds_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/activity_filters.xml b/app/src/main/res/layout/activity_filters.xml
index 700e939..c948344 100644
--- a/app/src/main/res/layout/activity_filters.xml
+++ b/app/src/main/res/layout/activity_filters.xml
@@ -12,6 +12,6 @@
<ListView
android:id="@+id/filters_list"
android:layout_width="match_parent"
- android:layout_height="match_parent"></ListView>
+ android:layout_height="match_parent"/>
</RelativeLayout>
diff --git a/app/src/main/res/layout/item_feed.xml b/app/src/main/res/layout/item_feed.xml
new file mode 100644
index 0000000..8c78f62
--- /dev/null
+++ b/app/src/main/res/layout/item_feed.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="10dp">
+
+ <TextView android:id="@+id/feed_item_title"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:textSize="@dimen/font_size_large"/>
+
+ <TextView android:id="@+id/feed_item_url"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:textSize="@dimen/font_size_small"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/menu/menu_feeds.xml b/app/src/main/res/menu/menu_feeds.xml
new file mode 100644
index 0000000..20a4fea
--- /dev/null
+++ b/app/src/main/res/menu/menu_feeds.xml
@@ -0,0 +1,13 @@
+<menu
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="org.rssin.android.FeedsActivity">
+
+ <item
+ android:id="@+id/feeds_action_add"
+ android:title="@string/feeds_action_add"
+ android:orderInCategory="100"
+ app:showAsAction="ifRoom" />
+
+</menu>
diff --git a/app/src/main/res/menu/menu_unified_inbox.xml b/app/src/main/res/menu/menu_unified_inbox.xml
index 2800171..021ceb5 100644
--- a/app/src/main/res/menu/menu_unified_inbox.xml
+++ b/app/src/main/res/menu/menu_unified_inbox.xml
@@ -6,4 +6,6 @@
android:orderInCategory="100" app:showAsAction="never" />
<item android:id="@+id/action_filters" android:title="@string/action_filters"
android:orderInCategory="100" app:showAsAction="never" />
+ <item android:id="@+id/action_feeds" android:title="@string/action_feeds"
+ android:orderInCategory="100" app:showAsAction="never" />
</menu>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 09d325e..0a6e649 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -3,6 +3,7 @@
<string name="action_settings">Settings</string>
<string name="action_filters">Filters</string>
+ <string name="action_feeds">Feeds</string>
<string name="button_ok">OK</string>
<string name="button_apply">Apply</string>
@@ -22,13 +23,22 @@
<string name="filter_settings_action_title">Title</string>
<string name="filter_settings_action_delete">Delete</string>
+ <string name="feeds_action_add">Add</string>
+
<string name="error_save_filters">Couldn\'t save filter</string>
<string name="error_load_filters">Couldn\'t load filters</string>
<string name="error_delete_filter">Couldn\'t delete filter</string>
+ <string name="error_save_feeds">Couldn\'t save feeds</string>
+ <string name="error_load_feeds">Couldn\'t load feeds</string>
+ <string name="error_delete_feed">Couldn\'t delete feed</string>
+
<string name="error_invalid_url">Invalid URL</string>
<string name="error_net_load">Internet problem</string>
<string name="error_save_feedsorter">Couldn\'t store personal preferences</string>
+ <string name="title_activity_feeds">Feeds</string>
+
+ <string name="hello_world">Hello world!</string>
</resources>