diff options
author | Camil Staps | 2015-05-25 19:30:09 +0200 |
---|---|---|
committer | Camil Staps | 2015-05-25 19:30:09 +0200 |
commit | 6dc9cf9659e2c4356c2a7eaa1227931458e7a7d9 (patch) | |
tree | d80575e868aa050d470b75edd111f36d2be00378 | |
parent | Cleanup; general Frontend class for displaying user messages (diff) |
Add feeds
-rwxr-xr-x | app/src/main/AndroidManifest.xml | 8 | ||||
-rw-r--r-- | app/src/main/java/org/rssin/android/FeedsActivity.java | 177 | ||||
-rw-r--r-- | app/src/main/java/org/rssin/android/FeedsList.java | 73 | ||||
-rw-r--r-- | app/src/main/java/org/rssin/android/FilterSettingsActivity.java | 4 | ||||
-rw-r--r-- | app/src/main/java/org/rssin/android/SharedPreferencesStorageProvider.java | 59 | ||||
-rw-r--r-- | app/src/main/java/org/rssin/android/UnifiedInboxActivity.java | 3 | ||||
-rw-r--r-- | app/src/main/java/org/rssin/rssin/Feed.java | 41 | ||||
-rw-r--r-- | app/src/main/java/org/rssin/storage/FeedStorageProvider.java | 14 | ||||
-rw-r--r-- | app/src/main/res/layout/activity_feeds.xml | 13 | ||||
-rw-r--r-- | app/src/main/res/layout/activity_filters.xml | 2 | ||||
-rw-r--r-- | app/src/main/res/layout/item_feed.xml | 19 | ||||
-rw-r--r-- | app/src/main/res/menu/menu_feeds.xml | 13 | ||||
-rw-r--r-- | app/src/main/res/menu/menu_unified_inbox.xml | 2 | ||||
-rw-r--r-- | app/src/main/res/values/strings.xml | 10 |
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> |