From bd817d5568051395496c1b5704841d05ab0fdc55 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Wed, 10 Apr 2013 15:04:48 -0700 Subject: [PATCH 001/172] WPActionBarActivity refactor managing menu state - If the selected menu item is gone after a blog switch, select the first available menu item - For example the ReaderActivity and NotificationsActivity are only available for dotcom blogs - Move activity restoration to WPActionBarActivity and re-use the MenuDrawerItem objects to correctly launch the necessary activity --- .../android/ui/WPActionBarActivity.java | 75 +++++++++++++++---- .../android/ui/posts/PostsActivity.java | 41 ---------- .../android/ui/reader/ReaderActivity.java | 11 --- 3 files changed, 60 insertions(+), 67 deletions(-) diff --git a/src/org/wordpress/android/ui/WPActionBarActivity.java b/src/org/wordpress/android/ui/WPActionBarActivity.java index 1011d79dc7f6..0b435ad59b79 100644 --- a/src/org/wordpress/android/ui/WPActionBarActivity.java +++ b/src/org/wordpress/android/ui/WPActionBarActivity.java @@ -102,13 +102,47 @@ public abstract class WPActionBarActivity extends SherlockFragmentActivity { private List mMenuItems = new ArrayList(); private ListView mListView; private IcsSpinner mBlogSpinner; - + private boolean mFirstLaunch = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == 4) mIsXLargeDevice = true; + // configure all the available menu items + mMenuItems.add(new ReaderMenuItem()); + mMenuItems.add(new PostsMenuItem()); + mMenuItems.add(new PagesMenuItem()); + mMenuItems.add(new CommentsMenuItem()); + mMenuItems.add(new StatsMenuItem()); + mMenuItems.add(new QuickPhotoMenuItem()); + mMenuItems.add(new QuickVideoMenuItem()); + mMenuItems.add(new ViewSiteMenuItem()); + mMenuItems.add(new AdminMenuItem()); + mMenuItems.add(new SettingsMenuItem()); + + // Restore last selection on app creation + // TODO: This more likely belongs in WPActionBarActivity + if (WordPress.shouldRestoreSelectedActivity && WordPress.getCurrentBlog() != null + && !(this instanceof PagesActivity)) { + WordPress.shouldRestoreSelectedActivity = false; + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); + int lastActivitySelection = settings.getInt(LAST_ACTIVITY_PREFERENCE, -1); + if (lastActivitySelection > MenuDrawerItem.NO_ITEM_ID) { + Iterator itemIterator = mMenuItems.iterator(); + while(itemIterator.hasNext()){ + MenuDrawerItem item = itemIterator.next(); + // if we have a matching item id, and it's not selected and it's visible, call it + if (item.hasItemId() && item.getItemId() == lastActivitySelection && !item.isSelected() && item.isVisible()) { + mFirstLaunch = true; + Log.d(TAG, String.format("Switch to %d", item.getItemId())); + item.selectItem(); + finish(); + break; + } + } + } + } } @Override @@ -252,20 +286,6 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun mMenuDrawer.setMenuView(mListView); mListView.setAdapter(mAdapter); - - // configure all the available menu items - // mMenuItems.add(new NotificationsMenuItem()); - mMenuItems.add(new ReaderMenuItem()); - mMenuItems.add(new PostsMenuItem()); - mMenuItems.add(new PagesMenuItem()); - mMenuItems.add(new CommentsMenuItem()); - mMenuItems.add(new StatsMenuItem()); - mMenuItems.add(new QuickPhotoMenuItem()); - mMenuItems.add(new QuickVideoMenuItem()); - mMenuItems.add(new ViewSiteMenuItem()); - mMenuItems.add(new AdminMenuItem()); - mMenuItems.add(new SettingsMenuItem()); - updateMenuDrawer(); } @@ -298,6 +318,11 @@ protected void startActivityWithDelay(final Intent i) { // Tablets in landscape don't need a delay because the menu drawer doesn't close startActivity(i); } else { + // When switching to LAST_ACTIVITY_PREFERENCE onCreate we don't need to delay + if (mFirstLaunch) { + startActivity(i); + return; + } // Let the menu animation finish before starting a new activity new Handler().postDelayed(new Runnable() { @Override @@ -513,6 +538,26 @@ public boolean onOptionsItemSelected(MenuItem item) { */ public void onBlogChanged() { WordPress.wpDB.updateLastBlogId(WordPress.currentBlog.getId()); + // the menu may have changed, we need to change the selection if the selected item + // is not available in the menu anymore + Iterator itemIterator = mMenuItems.iterator(); + while(itemIterator.hasNext()){ + MenuDrawerItem item = itemIterator.next(); + // if the item is selected, but it's no longer visible we need to + // select the first available item from the adapter + if (item.isSelected() && !item.isVisible()) { + // then select the first item and activate it + mAdapter.getItem(0).selectItem(); + // if it has an item id save it to the preferences + if (item.hasItemId()){ + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(WPActionBarActivity.this); + SharedPreferences.Editor editor = settings.edit(); + editor.putInt(LAST_ACTIVITY_PREFERENCE, item.getItemId()); + editor.commit(); + } + break; + } + } } public void startAnimatingRefreshButton(MenuItem refreshItem) { diff --git a/src/org/wordpress/android/ui/posts/PostsActivity.java b/src/org/wordpress/android/ui/posts/PostsActivity.java index a7cc3ceda6a0..a9666096ea20 100644 --- a/src/org/wordpress/android/ui/posts/PostsActivity.java +++ b/src/org/wordpress/android/ui/posts/PostsActivity.java @@ -31,16 +31,11 @@ import org.wordpress.android.R; import org.wordpress.android.WordPress; import org.wordpress.android.models.Post; -import org.wordpress.android.ui.DashboardActivity; -import org.wordpress.android.ui.StatsActivity; -import org.wordpress.android.ui.ViewSiteActivity; import org.wordpress.android.ui.WPActionBarActivity; -import org.wordpress.android.ui.comments.CommentsActivity; import org.wordpress.android.ui.posts.ViewPostFragment.OnDetailPostActionListener; import org.wordpress.android.ui.posts.ViewPostsFragment.OnPostActionListener; import org.wordpress.android.ui.posts.ViewPostsFragment.OnPostSelectedListener; import org.wordpress.android.ui.posts.ViewPostsFragment.OnRefreshListener; -import org.wordpress.android.ui.reader.ReaderActivity; import org.wordpress.android.util.WPAlertDialogFragment.OnDialogConfirmListener; public class PostsActivity extends WPActionBarActivity implements OnPostSelectedListener, @@ -60,42 +55,6 @@ public class PostsActivity extends WPActionBarActivity implements OnPostSelected public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Restore last selection on app creation - if (WordPress.shouldRestoreSelectedActivity && WordPress.getCurrentBlog() != null - && !(this instanceof PagesActivity)) { - WordPress.shouldRestoreSelectedActivity = false; - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); - int lastActivitySelection = settings.getInt("wp_pref_last_activity", -1); - if (lastActivitySelection >= 0) { - Intent i = null; - switch (lastActivitySelection) { - case READER_ACTIVITY: - i = new Intent(this, ReaderActivity.class); - break; - case PAGES_ACTIVITY: - i = new Intent(this, PagesActivity.class); - i.putExtra("viewPages", true); - break; - case COMMENTS_ACTIVITY: - i = new Intent(this, CommentsActivity.class); - break; - case STATS_ACTIVITY: - i = new Intent(this, StatsActivity.class); - break; - case VIEW_SITE_ACTIVITY: - i = new Intent(this, ViewSiteActivity.class); - break; - case DASHBOARD_ACTIVITY: - i = new Intent(this, DashboardActivity.class); - break; - } - if (i != null) { - startActivity(i); - finish(); - } - } - } - createMenuDrawer(R.layout.posts); ActionBar actionBar = getSupportActionBar(); diff --git a/src/org/wordpress/android/ui/reader/ReaderActivity.java b/src/org/wordpress/android/ui/reader/ReaderActivity.java index c26b1a629eb1..8a2fd5f6a820 100644 --- a/src/org/wordpress/android/ui/reader/ReaderActivity.java +++ b/src/org/wordpress/android/ui/reader/ReaderActivity.java @@ -449,17 +449,6 @@ public void onBlogChanged() { if (WordPress.currentBlog.isDotcomFlag()) { ReaderImplFragment readerPageFragment = (ReaderImplFragment) readerPage; readerPageFragment.reloadReader(); - } else { - // Self-hosted blogs do not have Reader access, send to posts instead - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(ReaderActivity.this); - SharedPreferences.Editor editor = settings.edit(); - editor.putInt("wp_pref_last_activity", POSTS_ACTIVITY); - editor.commit(); - mShouldFinish = true; - Intent i = new Intent(ReaderActivity.this, PostsActivity.class); - i.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - mMenuDrawer.closeMenu(); - startActivityWithDelay(i); } } From 9c5d3227643ebf06a5bc6a0e47b80025ad9919c9 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 8 Apr 2013 13:33:07 -0700 Subject: [PATCH 002/172] Ignoring local.properties file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a22392ade60e..3228c89989ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /bin /gen +local.properties From 32c410c4b20082dd353f08cadfd35b066540c4a4 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Tue, 9 Apr 2013 09:28:38 -0700 Subject: [PATCH 003/172] Adding REST notifications configuration --- .gitignore | 1 + src/org/wordpress/android/WordPress.java | 17 +++++++++++++++++ src/org/wordpress/android/models/Note.java | 14 ++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 src/org/wordpress/android/models/Note.java diff --git a/.gitignore b/.gitignore index 3228c89989ec..ea5d367f0609 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /bin /gen local.properties +res/raw/config.properties diff --git a/src/org/wordpress/android/WordPress.java b/src/org/wordpress/android/WordPress.java index 72886d5a8124..89c195e4af7b 100644 --- a/src/org/wordpress/android/WordPress.java +++ b/src/org/wordpress/android/WordPress.java @@ -14,6 +14,8 @@ import org.wordpress.android.models.Comment; import org.wordpress.android.models.Post; +import com.wordpress.rest.Oauth; + public class WordPress extends Application { public static String versionName; @@ -24,6 +26,7 @@ public class WordPress extends Application { public static OnPostUploadedListener onPostUploadedListener = null; public static boolean postsShouldRefresh; public static boolean shouldRestoreSelectedActivity; + public static Oauth oauth; @Override public void onCreate() { @@ -153,4 +156,18 @@ public static Blog setCurrentBlog(int id) { return currentBlog; } + + protected Properties getConfigProperties(){ + if (mConfig == null) { + mConfig = new Properties(); + InputStream stream = getResources().openRawResource(R.raw.config); + try { + mConfig.load(stream); + } catch(java.io.IOException e){ + mConfig = null; + Log.e(TAG, "Could not load config", e); + } + } + return mConfig; + } } diff --git a/src/org/wordpress/android/models/Note.java b/src/org/wordpress/android/models/Note.java new file mode 100644 index 000000000000..913fe4cd10bc --- /dev/null +++ b/src/org/wordpress/android/models/Note.java @@ -0,0 +1,14 @@ +/** + * Note represents a single WordPress.com notification + */ +package org.wordpress.android.models; + +public class Note { + /** + * Create a note using JSON from REST API + */ + public Note(JSONObject noteJSON){ + + } + +} \ No newline at end of file From 8f21de36b4a96774b7eda62eae2cd1adb7a29dd8 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Wed, 10 Apr 2013 10:35:42 -0700 Subject: [PATCH 004/172] Configuring notifications --- AndroidManifest.xml | 4 ++ res/layout/notifications.xml | 13 ++++ res/values/strings.xml | 3 + src/org/wordpress/android/WordPress.java | 42 ++++++------ src/org/wordpress/android/models/Note.java | 2 + .../notifications/NotificationsActivity.java | 27 ++++++++ .../wordpress/android/util/WPRestClient.java | 64 +++++++++++++++++++ 7 files changed, 137 insertions(+), 18 deletions(-) create mode 100644 res/layout/notifications.xml create mode 100644 src/org/wordpress/android/ui/notifications/NotificationsActivity.java create mode 100644 src/org/wordpress/android/util/WPRestClient.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index b1c515c2c280..88d02bb254f9 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -150,6 +150,10 @@ android:configChanges="orientation|keyboardHidden|screenSize" android:launchMode="singleTask" > + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 403456921072..f6e17426a0fa 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -317,6 +317,9 @@ Reader Topics + + + Notifications diff --git a/src/org/wordpress/android/WordPress.java b/src/org/wordpress/android/WordPress.java index 89c195e4af7b..4fa2fd80143d 100644 --- a/src/org/wordpress/android/WordPress.java +++ b/src/org/wordpress/android/WordPress.java @@ -1,7 +1,9 @@ package org.wordpress.android; +import java.io.InputStream; import java.util.List; import java.util.Map; +import java.util.Properties; import android.app.Application; import android.content.SharedPreferences; @@ -9,12 +11,12 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.preference.PreferenceManager; +import android.util.Log; import org.wordpress.android.models.Blog; import org.wordpress.android.models.Comment; import org.wordpress.android.models.Post; - -import com.wordpress.rest.Oauth; +import org.wordpress.android.util.WPRestClient; public class WordPress extends Application { @@ -26,17 +28,22 @@ public class WordPress extends Application { public static OnPostUploadedListener onPostUploadedListener = null; public static boolean postsShouldRefresh; public static boolean shouldRestoreSelectedActivity; - public static Oauth oauth; + public static WPRestClient restClient; + public static Properties config; + + public static final String TAG="WordPress"; @Override public void onCreate() { + loadProperties(); versionName = getVersionName(); wpDB = new WordPressDB(this); SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); if (settings.getInt("wp_pref_last_activity", -1) >= 0) shouldRestoreSelectedActivity = true; - + + restClient = new WPRestClient(config, settings); super.onCreate(); } @@ -156,18 +163,17 @@ public static Blog setCurrentBlog(int id) { return currentBlog; } - - protected Properties getConfigProperties(){ - if (mConfig == null) { - mConfig = new Properties(); - InputStream stream = getResources().openRawResource(R.raw.config); - try { - mConfig.load(stream); - } catch(java.io.IOException e){ - mConfig = null; - Log.e(TAG, "Could not load config", e); - } - } - return mConfig; - } + /** + * Load res/raw/config.properties into a Properties object + */ + private void loadProperties(){ + config = new Properties(); + InputStream stream = getResources().openRawResource(R.raw.config); + try { + config.load(stream); + } catch(java.io.IOException error){ + config = null; + Log.e(TAG, "Could not load config.properties", error); + } + } } diff --git a/src/org/wordpress/android/models/Note.java b/src/org/wordpress/android/models/Note.java index 913fe4cd10bc..95acbdfc7e9f 100644 --- a/src/org/wordpress/android/models/Note.java +++ b/src/org/wordpress/android/models/Note.java @@ -3,6 +3,8 @@ */ package org.wordpress.android.models; +import org.json.JSONObject; + public class Note { /** * Create a note using JSON from REST API diff --git a/src/org/wordpress/android/ui/notifications/NotificationsActivity.java b/src/org/wordpress/android/ui/notifications/NotificationsActivity.java new file mode 100644 index 000000000000..d24ffc68fcec --- /dev/null +++ b/src/org/wordpress/android/ui/notifications/NotificationsActivity.java @@ -0,0 +1,27 @@ +package org.wordpress.android.ui.notifications; + +import android.os.Bundle; +import android.util.Log; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +import org.wordpress.android.R; +import org.wordpress.android.ui.WPActionBarActivity; + +public class NotificationsActivity extends WPActionBarActivity { + public final String TAG="WPNotifications"; + @Override + public void onCreate(Bundle savedInstanceState){ + Log.d(TAG, "Launching notifications activity"); + super.onCreate(savedInstanceState); + createMenuDrawer(R.layout.notifications); + + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + setTitle(getString(R.string.notifications)); + + } +} \ No newline at end of file diff --git a/src/org/wordpress/android/util/WPRestClient.java b/src/org/wordpress/android/util/WPRestClient.java new file mode 100644 index 000000000000..2d900b68ffd2 --- /dev/null +++ b/src/org/wordpress/android/util/WPRestClient.java @@ -0,0 +1,64 @@ +/** + * Wraps the Oauth and RestClient classes and configures them with application specific settings + */ +package org.wordpress.android.util; + +import com.wordpress.rest.Oauth; +import com.wordpress.rest.OauthToken; +import com.wordpress.rest.OauthTokenResponseHandler; +import com.wordpress.rest.RestClient; + +import android.content.SharedPreferences; + +import java.util.Properties; + +import org.json.JSONObject; + +public class WPRestClient { + + private static final String ACCESS_TOKEN_PREFERNCE="wpcom-access-token"; + private static final String APP_ID_PROPERTY="oauth.app_id"; + private static final String APP_SECRET_PROPERTY="oauth.app_secret"; + private static final String APP_REDIRECT_PROPERTY="oauth.redirect_uri"; + + private Oauth mOauth; + private RestClient mRestClient; + private SharedPreferences mPrefs; + + public WPRestClient(Properties config, SharedPreferences prefs){ + mPrefs = prefs; + // configure Oauth with app credentials + mOauth = new Oauth(config.getProperty(APP_ID_PROPERTY), + config.getProperty(APP_SECRET_PROPERTY), + config.getProperty(APP_REDIRECT_PROPERTY)); + // load an existing access token from prefs if we have one + mRestClient = new RestClient(prefs.getString(ACCESS_TOKEN_PREFERNCE, null)); + } + /** + * Authenticate the user using WordPress.com Oauth + */ + public void requestAccessToken(String username, String password, OauthTokenResponseHandler handler){ + mOauth.requestAccessToken(username, password, new OauthTokenResponseHandler() { + @Override + public void onStart(){} + /** + * Save the token to a preference + */ + @Override + public void onSuccess(OauthToken token){ + + } + + @Override + public void onFailure(Throwable e, JSONObject respose){ + + } + + @Override + public void onFinish(){ + + } + }); + } + +} \ No newline at end of file From 8ad11a3c359aebe5a6b05203eeb9b8f53dc680a4 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Wed, 10 Apr 2013 15:06:23 -0700 Subject: [PATCH 005/172] Adding notifications menu item --- .../android/ui/WPActionBarActivity.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/org/wordpress/android/ui/WPActionBarActivity.java b/src/org/wordpress/android/ui/WPActionBarActivity.java index 0b435ad59b79..7ed784834832 100644 --- a/src/org/wordpress/android/ui/WPActionBarActivity.java +++ b/src/org/wordpress/android/ui/WPActionBarActivity.java @@ -53,6 +53,7 @@ import org.wordpress.android.ui.posts.PostsActivity; import org.wordpress.android.ui.prefs.PreferencesActivity; import org.wordpress.android.ui.reader.ReaderActivity; +import org.wordpress.android.ui.notifications.NotificationsActivity; import org.wordpress.android.util.EscapeUtils; /** @@ -85,6 +86,7 @@ public abstract class WPActionBarActivity extends SherlockFragmentActivity { protected static final int VIEW_SITE_ACTIVITY = 7; protected static final int DASHBOARD_ACTIVITY = 8; protected static final int SETTINGS_ACTIVITY = 9; + protected static final int NOTIFICATIONS_ACTIVITY = 10; protected static final String LAST_ACTIVITY_PREFERENCE = "wp_pref_last_activity"; @@ -110,6 +112,7 @@ public void onCreate(Bundle savedInstanceState) { mIsXLargeDevice = true; // configure all the available menu items + mMenuItems.add(new NotificationsMenuItem()); mMenuItems.add(new ReaderMenuItem()); mMenuItems.add(new PostsMenuItem()); mMenuItems.add(new PagesMenuItem()); @@ -807,4 +810,25 @@ public void onSelectItem(){ startActivityForResult(settingsIntent, SETTINGS_REQUEST); } } + private class NotificationsMenuItem extends MenuDrawerItem { + NotificationsMenuItem(){ + super(NOTIFICATIONS_ACTIVITY, R.string.notifications, R.drawable.dashboard_icon_comments); + } + @Override + public Boolean isVisible(){ + return WordPress.currentBlog != null && WordPress.currentBlog.isDotcomFlag(); + } + @Override + public Boolean isSelected(){ + return WPActionBarActivity.this instanceof NotificationsActivity; + } + @Override + public void onSelectItem(){ + if (!(WPActionBarActivity.this instanceof NotificationsActivity)) + mShouldFinish = true; + Intent intent = new Intent(WPActionBarActivity.this, NotificationsActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + startActivityWithDelay(intent); + } + } } From 894f0eb266903539ed62b476100804527dc649f1 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Wed, 10 Apr 2013 15:17:35 -0700 Subject: [PATCH 006/172] Add library reference to wordpress-rest-android while notifications under development --- project.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/project.properties b/project.properties index fef3618e9755..f9fbf06fed89 100644 --- a/project.properties +++ b/project.properties @@ -18,3 +18,5 @@ apk-configurations= android.library=false android.library.reference.1=../ActionBarSherlock/library android.library.reference.2=../../../Downloads/android-menudrawer-master/library +android.library.reference.3=../wordpress-rest-android + From 4e4f8b9d8db17384ac0a931d26c54d39444f1f0d Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Wed, 10 Apr 2013 17:58:18 -0700 Subject: [PATCH 007/172] Displaying an initial list of notes pulled straight from the REST api --- res/layout/note_list_item.xml | 15 ++++ res/layout/notifications.xml | 12 ++- res/raw/config.properties.sample | 5 ++ src/org/wordpress/android/models/Note.java | 17 +++- .../notifications/NotificationsActivity.java | 80 ++++++++++++++++++- .../NotificationsListFragment.java | 11 +++ .../wordpress/android/util/WPRestClient.java | 38 +++++++-- 7 files changed, 166 insertions(+), 12 deletions(-) create mode 100644 res/layout/note_list_item.xml create mode 100644 res/raw/config.properties.sample create mode 100644 src/org/wordpress/android/ui/notifications/NotificationsListFragment.java diff --git a/res/layout/note_list_item.xml b/res/layout/note_list_item.xml new file mode 100644 index 000000000000..a9e3fd0e7e1c --- /dev/null +++ b/res/layout/note_list_item.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/res/layout/notifications.xml b/res/layout/notifications.xml index 11414f1b03a0..7141b33eaaf1 100644 --- a/res/layout/notifications.xml +++ b/res/layout/notifications.xml @@ -3,11 +3,15 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:orientation="vertical" - android:background="#FF0000"> + android:orientation="vertical" > + android:layout_height="wrap_content" > + \ No newline at end of file diff --git a/res/raw/config.properties.sample b/res/raw/config.properties.sample new file mode 100644 index 000000000000..68c5bb09209f --- /dev/null +++ b/res/raw/config.properties.sample @@ -0,0 +1,5 @@ +# Add your WordPress App credentials here +# Get you App ID from http://developer.wordpress.com +oauth.app_id= +oauth.app_secret= +oauth.redirect_uri= diff --git a/src/org/wordpress/android/models/Note.java b/src/org/wordpress/android/models/Note.java index 95acbdfc7e9f..ec8de45de9ea 100644 --- a/src/org/wordpress/android/models/Note.java +++ b/src/org/wordpress/android/models/Note.java @@ -3,14 +3,29 @@ */ package org.wordpress.android.models; +import android.text.Html; + import org.json.JSONObject; +import org.json.JSONException; public class Note { + private JSONObject mNoteJSON; /** * Create a note using JSON from REST API */ public Note(JSONObject noteJSON){ - + mNoteJSON = noteJSON; + } + + public String toString(){ + String label = ""; + try { + JSONObject subjectJSON = mNoteJSON.getJSONObject("subject"); + label = Html.fromHtml(subjectJSON.getString("text").trim().toString()).toString(); + } catch (JSONException e) { + label = ""; + } + return label; } } \ No newline at end of file diff --git a/src/org/wordpress/android/ui/notifications/NotificationsActivity.java b/src/org/wordpress/android/ui/notifications/NotificationsActivity.java index d24ffc68fcec..3d76aecedfc0 100644 --- a/src/org/wordpress/android/ui/notifications/NotificationsActivity.java +++ b/src/org/wordpress/android/ui/notifications/NotificationsActivity.java @@ -1,7 +1,14 @@ package org.wordpress.android.ui.notifications; +import java.util.List; +import java.util.ArrayList; + import android.os.Bundle; import android.util.Log; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.widget.ArrayAdapter; import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.view.Menu; @@ -10,12 +17,26 @@ import org.wordpress.android.R; import org.wordpress.android.ui.WPActionBarActivity; +import org.wordpress.android.models.Blog; +import org.wordpress.android.models.Note; +import static org.wordpress.android.WordPress.*; + +import com.wordpress.rest.OauthTokenResponseHandler; +import com.wordpress.rest.OauthToken; +import com.loopj.android.http.JsonHttpResponseHandler; + +import org.json.JSONObject; +import org.json.JSONArray; +import org.json.JSONException; + public class NotificationsActivity extends WPActionBarActivity { public final String TAG="WPNotifications"; + + private NotificationsListFragment mNotesList; + @Override public void onCreate(Bundle savedInstanceState){ - Log.d(TAG, "Launching notifications activity"); super.onCreate(savedInstanceState); createMenuDrawer(R.layout.notifications); @@ -23,5 +44,62 @@ public void onCreate(Bundle savedInstanceState){ actionBar.setDisplayShowTitleEnabled(true); setTitle(getString(R.string.notifications)); + FragmentManager fm = getSupportFragmentManager(); + mNotesList = (NotificationsListFragment) fm.findFragmentById(R.id.notes_list); + + Blog blog = getCurrentBlog(); + + // ok it's time to request notifications + // TODO: access token should be stored in preferences, not fetched each time + restClient.requestAccessToken(blog.getUsername(), blog.getPassword(), new OauthTokenResponseHandler(){ + @Override + public void onStart(){ + Log.d(TAG, "Requesting token"); + } + @Override + public void onSuccess(OauthToken token){ + refreshNotes(); + } + @Override + public void onFailure(Throwable e, JSONObject response){ + Log.e(TAG, String.format("Failed: %s", response), e); + } + @Override + public void onFinish(){ + Log.d(TAG, "Done requesting token"); + } + }); + Log.d(TAG, "Created"); + } + + public void refreshNotes(){ + restClient.getNotifications(new JsonHttpResponseHandler(){ + @Override + public void onSuccess(int responseCode, JSONObject response) { + try { + JSONArray notesJSON = response.getJSONArray("notes"); + final List notesList = new ArrayList(notesJSON.length()); + for (int i=0; i notes){ + // create a new ListAdapter and set it on the fragment + ArrayAdapter adapter = new ArrayAdapter(this, R.layout.note_list_item, R.id.note_label, notes); + mNotesList.setListAdapter(adapter); } } \ No newline at end of file diff --git a/src/org/wordpress/android/ui/notifications/NotificationsListFragment.java b/src/org/wordpress/android/ui/notifications/NotificationsListFragment.java new file mode 100644 index 000000000000..1d9cbb25e666 --- /dev/null +++ b/src/org/wordpress/android/ui/notifications/NotificationsListFragment.java @@ -0,0 +1,11 @@ +package org.wordpress.android.ui.notifications; + +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.app.ListFragment; + + +public class NotificationsListFragment extends ListFragment { + + +} \ No newline at end of file diff --git a/src/org/wordpress/android/util/WPRestClient.java b/src/org/wordpress/android/util/WPRestClient.java index 2d900b68ffd2..07881da22d36 100644 --- a/src/org/wordpress/android/util/WPRestClient.java +++ b/src/org/wordpress/android/util/WPRestClient.java @@ -10,6 +10,9 @@ import android.content.SharedPreferences; +import com.loopj.android.http.AsyncHttpResponseHandler; +import com.loopj.android.http.RequestParams; + import java.util.Properties; import org.json.JSONObject; @@ -37,28 +40,51 @@ public WPRestClient(Properties config, SharedPreferences prefs){ /** * Authenticate the user using WordPress.com Oauth */ - public void requestAccessToken(String username, String password, OauthTokenResponseHandler handler){ + public void requestAccessToken(String username, String password, final OauthTokenResponseHandler handler){ mOauth.requestAccessToken(username, password, new OauthTokenResponseHandler() { @Override - public void onStart(){} + public void onStart(){ + handler.onStart(); + } /** * Save the token to a preference */ @Override public void onSuccess(OauthToken token){ - + mRestClient.setAccessToken(token.toString()); + handler.onSuccess(token); } @Override - public void onFailure(Throwable e, JSONObject respose){ - + public void onFailure(Throwable e, JSONObject response){ + handler.onFailure(e, response); } @Override public void onFinish(){ - + handler.onFinish(); } }); } + /** + * Get notifications + */ + public void getNotifications(AsyncHttpResponseHandler handler){ + RequestParams params = new RequestParams(); + params.put("number", "20"); + get("notifications", params, handler); + } + /** + * Make GET request + */ + public void get(String path, AsyncHttpResponseHandler handler){ + get(path, null, handler); + } + /** + * Make GET request with params + */ + public void get(String path, RequestParams params, AsyncHttpResponseHandler handler){ + mRestClient.get(path, params, handler); + } } \ No newline at end of file From e8061715812689a5c5e6ec77f403c09f709f53f9 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Thu, 11 Apr 2013 13:37:29 -0700 Subject: [PATCH 008/172] Added method to query note json by keypath --- src/org/wordpress/android/models/Note.java | 130 +++++++++++++++++++-- 1 file changed, 123 insertions(+), 7 deletions(-) diff --git a/src/org/wordpress/android/models/Note.java b/src/org/wordpress/android/models/Note.java index ec8de45de9ea..3602a9c9ed08 100644 --- a/src/org/wordpress/android/models/Note.java +++ b/src/org/wordpress/android/models/Note.java @@ -4,28 +4,144 @@ package org.wordpress.android.models; import android.text.Html; +import android.util.Log; import org.json.JSONObject; +import org.json.JSONArray; import org.json.JSONException; public class Note { + protected static final String TAG="NoteModel"; + public static final String UNKNOWN_TYPE="unknown"; + public static final String COMMENT_TYPE="comment"; + public static final String LIKE_TYPE="like"; + + private static String QUERY_SEPERATOR="."; + private static String QUERY_ARRAY_INDEX_START="["; + private static String QUERY_ARRAY_INDEX_END="]"; + private static String QUERY_ARRAY_FIRST="first"; + private static String QUERY_ARRAY_LAST="last"; + private JSONObject mNoteJSON; /** * Create a note using JSON from REST API */ public Note(JSONObject noteJSON){ mNoteJSON = noteJSON; + Log.d(TAG, String.format("Built note of type: %s", getType())); } public String toString(){ - String label = ""; - try { - JSONObject subjectJSON = mNoteJSON.getJSONObject("subject"); - label = Html.fromHtml(subjectJSON.getString("text").trim().toString()).toString(); - } catch (JSONException e) { - label = ""; - } + String labelText = queryJSON("subject.text", ""); + String label = Html.fromHtml(labelText.trim()).toString(); return label; } + public JSONObject toJSONObject(){ + return mNoteJSON; + } + + public String getType(){ + return queryJSON("type", UNKNOWN_TYPE); + } + public Boolean isType(String type){ + return getType().equals(type); + } + public Boolean isCommentType(){ + return isType(COMMENT_TYPE); + } + public String getIconURL(){ + return (String) queryJSON("subject.icon", ""); + } + protected Object queryJSON(String query){ + Object defaultObject = ""; + return queryJSON(query, defaultObject); + } + /** + * Rudimentary system for pulling an item out of a JSON object hierarchy + */ + public U queryJSON(String query, U defaultObject){ + int offset = 0; + JSONObject queryJSON = mNoteJSON; + do { + // find the next . or [ + int next_seperator = query.indexOf(QUERY_SEPERATOR, offset); + boolean has_next_seperator = next_seperator > -1; + int start_array = query.indexOf(QUERY_ARRAY_INDEX_START, offset); + // pull out the key up to the seperator + if (next_seperator == -1 && start_array == -1) { + try { + return (U) queryJSON.get(query.substring(offset)); + } catch (JSONException e) { + Log.e(TAG, String.format("Failed to query %s", query), e); + return defaultObject; + } + } + String key; + if (start_array == -1 || (next_seperator > -1 && start_array > next_seperator )) { + try { + queryJSON = queryJSON.getJSONObject(query.substring(offset, next_seperator)); + } catch (JSONException e) { + Log.e(TAG, String.format("Failed to query key %s", query), e); + return defaultObject; + } + offset = next_seperator+1; + continue; + } + if (next_seperator == -1 || start_array == -1) { + key = query.substring(offset, Math.max(next_seperator, start_array)); + } else { + key = query.substring(offset, Math.min(next_seperator, start_array)); + } + if (start_array > -1 && (start_array < next_seperator || !has_next_seperator)) { + // time to pull off arrays + try { + JSONArray arrayJSON = queryJSON.getJSONArray(key); + do { + int end_array = query.indexOf(QUERY_ARRAY_INDEX_END, start_array); + if (end_array <= start_array) break; + offset = end_array; + String index = query.substring(start_array+1, end_array); + int i; + if (index.equals(QUERY_ARRAY_FIRST)) { + i = 0; + } else if (index.equals(QUERY_ARRAY_LAST)) { + i = -1; + } else { + i = Integer.parseInt(index); + } + if (i < 0) + i = arrayJSON.length() + i; + start_array = query.indexOf(QUERY_ARRAY_INDEX_START, end_array); + // no more arrays and no seperator, end of query, return object at index + // e.g. key[0][0] + if (start_array == -1 && !has_next_seperator) { + return (U) arrayJSON.get(i); + } + // no more arrays but there's a seperator, we must have a JSONObject + if (start_array == -1 && has_next_seperator) { + queryJSON = arrayJSON.getJSONObject(i); + break; + } + // theres more query but this is the last array in this section + // eg key[0][0][0].something[0] + arrayJSON = arrayJSON.getJSONArray(i); + // the next item is an array, so pull the array off and continue + } while(start_array < next_seperator); + offset = next_seperator + 1; + continue; + } catch (JSONException e) { + Log.e(TAG, String.format("Failed to query array %s", query), e); + return defaultObject; + } + } + Log.d(TAG, String.format("We need to remove the key from %s", query.substring(offset, next_seperator))); + key = query.substring(offset, next_seperator); + + if(true) return defaultObject; + + } while(offset > 0); + return defaultObject; + } + } \ No newline at end of file From dcc1f07a3ba3de273fb894f686543beda258bbc4 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Thu, 11 Apr 2013 13:38:17 -0700 Subject: [PATCH 009/172] Configuring note row layout --- res/layout/note_list_item.xml | 29 +++++++++++++++---- .../notifications/NotificationsActivity.java | 27 ++++++++++++++++- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/res/layout/note_list_item.xml b/res/layout/note_list_item.xml index a9e3fd0e7e1c..b169c47845b0 100644 --- a/res/layout/note_list_item.xml +++ b/res/layout/note_list_item.xml @@ -1,15 +1,34 @@ - + - \ No newline at end of file + android:layout_height="wrap_content" + android:layout_toRightOf="@+id/note_icon" /> + + \ No newline at end of file diff --git a/src/org/wordpress/android/ui/notifications/NotificationsActivity.java b/src/org/wordpress/android/ui/notifications/NotificationsActivity.java index 3d76aecedfc0..cb7fef54030a 100644 --- a/src/org/wordpress/android/ui/notifications/NotificationsActivity.java +++ b/src/org/wordpress/android/ui/notifications/NotificationsActivity.java @@ -8,7 +8,12 @@ import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; +import android.text.Html; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListAdapter; import android.widget.ArrayAdapter; +import android.widget.TextView; import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.view.Menu; @@ -99,7 +104,27 @@ public void run(){ public void displayNotes(List notes){ // create a new ListAdapter and set it on the fragment - ArrayAdapter adapter = new ArrayAdapter(this, R.layout.note_list_item, R.id.note_label, notes); + ListAdapter adapter = new NotesAdapter(notes); mNotesList.setListAdapter(adapter); } + + private class NotesAdapter extends ArrayAdapter { + NotesAdapter(List notes){ + super(NotificationsActivity.this, R.layout.note_list_item, R.id.note_label, notes); + } + @Override + public View getView(int position, View convertView, ViewGroup parent){ + View view = super.getView(position, convertView, parent); + Note note = getItem(position); + Log.d(TAG, String.format("Display note %s", note.toJSONObject())); + TextView detailText = (TextView) view.findViewById(R.id.note_detail); + if (note.isCommentType()) { + detailText.setText(Html.fromHtml(note.queryJSON("body.items[last].html", "Couldn't find note body")).toString().trim()); + detailText.setVisibility(View.VISIBLE); + } else { + detailText.setVisibility(View.GONE); + } + return view; + } + } } \ No newline at end of file From 31cce527bb70059221ee94019b7f4aaa2254b41c Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Thu, 11 Apr 2013 16:29:17 -0700 Subject: [PATCH 010/172] Dispaying gravatars in notification list --- res/layout/note_list_item.xml | 1 - .../ui/notifications/NotificationsActivity.java | 13 ++++++++----- .../NotificationsListFragment.java | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/res/layout/note_list_item.xml b/res/layout/note_list_item.xml index b169c47845b0..d59f3cd808fb 100644 --- a/res/layout/note_list_item.xml +++ b/res/layout/note_list_item.xml @@ -12,7 +12,6 @@ android:id="@+id/note_icon" android:layout_width="48dp" android:layout_height="48dp" - android:background="#FF0000" android:layout_alignParentTop="true" android:layout_marginRight="8dp" /> { @Override public View getView(int position, View convertView, ViewGroup parent){ View view = super.getView(position, convertView, parent); - Note note = getItem(position); - Log.d(TAG, String.format("Display note %s", note.toJSONObject())); + final Note note = getItem(position); TextView detailText = (TextView) view.findViewById(R.id.note_detail); if (note.isCommentType()) { detailText.setText(Html.fromHtml(note.queryJSON("body.items[last].html", "Couldn't find note body")).toString().trim()); @@ -124,7 +124,10 @@ public View getView(int position, View convertView, ViewGroup parent){ } else { detailText.setVisibility(View.GONE); } + final ImageView iconView = (ImageView) view.findViewById(R.id.note_icon); + iconView.setImageResource(R.drawable.placeholder); + iconView.setTag(note.getIconURL()); return view; } } -} \ No newline at end of file +} diff --git a/src/org/wordpress/android/ui/notifications/NotificationsListFragment.java b/src/org/wordpress/android/ui/notifications/NotificationsListFragment.java index 1d9cbb25e666..7adf624ae9b3 100644 --- a/src/org/wordpress/android/ui/notifications/NotificationsListFragment.java +++ b/src/org/wordpress/android/ui/notifications/NotificationsListFragment.java @@ -3,9 +3,26 @@ import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.app.ListFragment; +import android.widget.ListAdapter; +import org.wordpress.android.R; + +import com.commonsware.cwac.cache.SimpleWebImageCache; +import com.commonsware.cwac.thumbnail.ThumbnailAdapter; +import com.commonsware.cwac.thumbnail.ThumbnailBus; +import com.commonsware.cwac.thumbnail.ThumbnailMessage; public class NotificationsListFragment extends ListFragment { + private static final int[] IMAGE_IDS={ R.id.note_icon }; + @Override + public void setListAdapter(ListAdapter adapter){ + // Wrap the adapter in the thumbnail adapter + ThumbnailBus bus = new ThumbnailBus(); + SimpleWebImageCache cache; + cache = new SimpleWebImageCache(null, null, 101, bus); + ThumbnailAdapter thumbAdapter = new ThumbnailAdapter( getActivity(), adapter, cache, IMAGE_IDS); + super.setListAdapter(thumbAdapter); + } } \ No newline at end of file From efccc0b6b7ef2305b0e9a98eb7c0c1fae5d09f8a Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Thu, 11 Apr 2013 18:45:27 -0700 Subject: [PATCH 011/172] Adding global WP.com account. Refs #496 * Credentials are stored in Preferences and uses the existing encryption methods for the password. * Incremented db version and added migration script that attempt to migrate the WP.com credentials from the first matching WP.com blog. * Added basic GCM setup. App ID needs to be added to the config.properties in res/raw as gcm.id * Added WordPress.com section to preferences, and added capability for !AddAccountActivity to be used as a WordPress.com authenticator of sorts. --- AndroidManifest.xml | 23 +++++ libs/gcm.jar | Bin 0 -> 13662 bytes res/layout/add_account.xml | 8 -- res/values/strings.xml | 18 ++-- res/xml/preferences.xml | 5 + src/org/wordpress/android/WordPress.java | 18 ++++ src/org/wordpress/android/WordPressDB.java | 42 ++++++++- .../android/ui/WPActionBarActivity.java | 11 +-- .../ui/accounts/AddAccountActivity.java | 23 +++-- .../android/ui/posts/PostsActivity.java | 20 ++++ .../android/ui/prefs/PreferencesActivity.java | 89 ++++++++++++++++++ 11 files changed, 225 insertions(+), 32 deletions(-) create mode 100644 libs/gcm.jar diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 88d02bb254f9..6d55b59f4861 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -24,6 +24,16 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libs/gcm.jar b/libs/gcm.jar new file mode 100644 index 0000000000000000000000000000000000000000..ac109a830ebe95e89e1456e23328212ad70576dc GIT binary patch literal 13662 zcmaib1yo#Hwk__#U4uj6?h@Qxf(NG{xVyWjaCZ&v9yCz61cJM}JN(?=-LK#4-~Ilo zI#r{_Saa^P*BSeaQ*-U9A`cCN1Ob5n0m1e4Srp>G1tJ6tgrcOzCni}XDV8@d1cb`p zQxPCI|D=BSV%}Q$C;joyf&J(BuT;fPO0rUt>Ke?7QnvARNPVo>lJ{coo3Z>SccIcy0u)UwF0-i05L+Si=*b(ox7c>F2&i;Y#q8qI@$~uJ}g6yO@k)8sq#s%M!VZv=iATf*0=KM6|_oJ{HuA zVT;XHRm8=RU9)+E`MMDu2e*!`yKR9e6!tNx83{o~|0V!^59lQPR_DJagtoH?{g25( z{NKs_w=(>x0tAGqy)Das&OrWqhNZo|F{_}MH zb~WfsZ|I)TARsJ0LO`(nXOOhGqL{I>xvZUwxt)u;xsw~v)STJ$tFg0lwhpq7`o62k z&y?>rk5E!yf)x;gE-seI(MZD}>JvVF6rn&SqW!Q%VMG+@Q&RvR&kdB#nEJ*on^~gx zMX|$TbY-C(qWrQ?t9>ew`Lx{)xZt`v-?-RbrPCqZ%q-v0{O(LGU!SPR{&MZ!^Yd}y z{(a(D;AiJe<$jNZgw3M@wQu&DeullwHQtPmdWOC1b!zeryKl!g{zC!w7Y1NA!?=PWl?4Pn{ zg#8pLR>OB_bXcnNMDUs!{AQ5G#ZMc1;kDTKso7qm)xg8n>B0hj9ZvYhn%aJ|%B}uwyvog_QSre$d!<(x-rf>` z=Izef78Kq{sjH{YA+_{0Gm^$u9|??`PKW*s(ic8b9V7N@_fJ0$RV;Xujo*K$-z%cFtT5Tn}USn=! zK^&RMKyfe5Sgw7mTsxG*kss)-Osx&rTGYE798$W@%O5L-cMN7dR9rZNQh9r8x^iu4&W^F297kF+Q$hnT0Boj7yZIhv zweB#|`nOr1+27SaZySY^wqY9OEiG^ogzg&C=eMSMfFUVC>7W&cKuyUbc;V`_T}p(tmCY41EJ`o8F% zF`~+JDuVk9>o5%{3W-zhpoOhjsB;(^PwOHt8tw``s;hx89l0B4AUIo|V8L!`1`kQO zkEf7rZ&XS+ z^sD0wpC(*_=Pb~dGjLRk`qt&@p=iKhz6?(ZQo)mJ2Ll)>v799N1uzTIZif`fQ%x<6 zTy~19PKOf4$;0;{+h9@`l#y_d2D-^(6>X!QskQ0B3Aw248Xm4PE6QXD1N&x9@<9r}mg6aT2MZn_@@BAZisZ?83uc0$%md&x5%!fzLW`?^ zYJf<*)fN%s2gcpvgV*z~3_2%!F_Q0R2};aisRBLp$)>k_flO8VU4bK@4_P_C6MnLW z8ZVg==9CGRj3{v0(rC^<1b|gP&4%pfb3F?-?%gaCb^)y#xsK_p$lv2c$<~S-@=BL` z#Q}m&pLa2Y%Vs2>u=^gl?w?-*{W>SDCpaj9ttDVKFHAywa9HQCmGItporx}0%f^N{ zfXsr|O^70kRG z{?z3Dt-(hQc;*!gBCl{f2dzV3p>pnVSoj*$_q!MiLM2F1~(kiNjyUr9E+=OxltoJ`_bmQS| zx?g*1JTdx8?PB@l%Hc?AfG_mp0} zz8EI2NuO&!Li#0?=d*aJkbwtwg9aglv^;F?cKW`<){d#8z1xX`HJ%_<9;tdJd%yTb zkp=zWicC|vQBa-hB~y6}M$r-Oi?48zD;NqNNJ@3?nv?Hq2rvJrT`0_qnOo+Ty$E~qcKz<($v6gz?q^O8%+rfT{=m1Zre>Yv{djR;q)nD)t0SAaNZ)GVJMy*5OD!dZ z8ctC~E=flnN*d8DP{-DW9sgs5W+B2smM-4h7jUM`fb*o3VYobmONyY8+pgNk(P6+x znb)%zTxCU&OLa^V_Ht|sFIC)1B4<;6S&H0#fuC9kpED4OdOTTdi2tWQN=uL{&MxG~ zhJvPVQ{Pf*CiA|QD}d&)D0W>KHM1%v-GuWxcE;rhbvqL##o<7p{1=Vov9A_o=#m1y zKkK|X$uz4qDfbwhciNQAFW=531gUq$nL1$I=Xr74Y`sks>RHfIp>4Qpgj*fBI zklEwI1H8pw>&6zsrZCZiCyJc!x_7H!L(?@ka_ag<-qJswgA}?Nu-7<9(CLT`Dncrl z>ifPoJ#VEgghaa5xZ_s=b`@=WmdUPorXQFJeBmBr{&|*ip0`M1cC0= zgBj=d-keGtGd(3Z5^4(CwnKap+7fpY4JrBZxHV1AHAp0?u7d?xxR@GwIW`+w0KkSc zGV@j_nTQh9+)YNe)eu-QLZeA&LE1Ye1(1#}tC;4p52|>C8W*v_5Vo#k78i&(C%GZN znPa5)O(X5Uhpc^+iBp9CjZoE42Mp3zq;5Sam78H?Zqfl0;~fg28V-Lfo*TRDASrZ0 z#`R#b`uaw}#K@Xu!}K9B+vO{1bDCHEj%SJH4cqbj4Az}_o&;Z}slPJnS#z`ohex2b zLG%#NkfWq()WgD_`DgjuJY~$~uWH@KUqQO;?h{%s zNbt;M#eJCfrjkb+f(#S&^*j{!*BPLwBwB@{ONCRDNG z?79L{qf60cc5und!a%tel$hzGzOAZyD+a^wFs+WdJW{2)`o~Pzv>EzDj1K!&g;Ujh z;`vbFcR=Ea>5^WzXXJgWYaHDJ-J^EJ1Y=Gmy`eHBEWJ>$ZBLA92eeVk_KXgK`7$sm zA8UuC@Mvhy@yM!$XEfBeujW|yB%^nHv6|D!jUjc&%GtX(w9iu!TM1bbr~9lz{tPT{ zX?$&v^vS0)a&70>gCMCjMq76sf=p~gSul#JDl(;!b%(mSrPmv@xJtEc!s%*g?B2uv^aNkXl#TC^*nB+iiv?U@H^5#Gc%u z2Ssolw!ogU+7F)eQRw03{*dnpQb<{3;EXKrNptKi&9Ic`KyicS36|kTK1aF7f`?;a zR=G9SaNG4py*t)#NZ2bFI`0^A72@D7`0V4sj6U3e{RQIp?13f9&)!frxdTfL`gVN7 ztW)8@wq0EuQ4MLMJP}cQTG&G!-Ut#NK1s{zhRyFt3_-sGS;hCw;!ZjTo#-WG*JSqD zwC&p5CN`(MCM>%XY9n!NKchDeBcqpumPsH-3sxaQ1!8@#LsUL*F^U?(YYF!cwJ ztzZ|_7+4BiJJYxuGJW)=Aq`DN3NhSuLxx2@`A3kl>b6i;rglZ%UBbD@}=dOPYd+dX3K)p|*SJbC!8F+WW80W;x+>Z_f0!Rn;9cNPFMhzdO(I zo?JS+Xvdmn-JKehV*i$)LZXiltA3qvmQiMK=#qhlGdN~ZY3!OYI9v+AXIB!3R@^-U zUm?ycXDe14%g0kX^jAb7@_#NAENjc&#;$bCDil{%*Tjkw=KU1j5J!YpVtVz0JT%=` z=zysu>m$UsQFZ+rdYq~Bv@^=>${Oj45UCgK&I$U16Y^=W2=NBd;m36enso(4LuIsH zQnGzFuPC1B+0Jy~56@jc^bT?E`rLhPoF9$U&vpQ7Bd)|dI4gs(EB#C>qb*JmJPv6s zPCe#0Xj8=e2t;p`2VGETL$5bKf9wZHmLA9-(BBZQHObe;%i$6|u~P(tDy;|#`prMj zst=QO2OX%8<9@r3wFSjvc$w&#w;oG*gg;CP8p^Zik~T+B=S0u~ZY&>9D%ZV!N9f`u zIOcFCQ|`CQ&|s(WD!XSFcHpTwWzC$6D2M0mID!?X60aLCzu(3PTX#)e^^E8R48x;c z&Wi0sjskgDJI#O_HXe)Are8X*vTy9R*8fG;p#v-M3H^~g7XBzg{~sa2$==w^)Y#cY z&D_)+=w|NpHz6TeGg@s~3R?&nZcz;JOO9Q}Jf|%*z8+9MBLG;&7S_&2(;GKC70$2| zOPN4TF`9G5@E9d*L0po32!A8~M3B9fY_1TKogQOxced8$ebyFl@Or(T>ks{aGLEk0 z$P>cis4N9@AN(0q%WNv401~qsen3JS_^Lj%AQQ?O$3y`9Iy(GCy01FMJ|X#%i4@$J zifp1RMfz-VDT5MkPMn7>q1uNPwrOIWN}eS!pOIm0t)(GS34JuWJ9?V{3h=|2s0))gAUTs2e84+xP_wpbPgN5j$OiVk6KC`f$grcv5<8} zsk=*tybo2ijM#VSDABU*uy)dH0L0m9%vBhwyU%*-#Z_s#RGqmAVpS#BJ~D*;8r0tp zvD0I2(dS~?VX?acOU+}+_NrH87b{jNO;C5)PM{M+NEj35gc}5xggIk z+Mw?ST`2UD+tEFstA-u=!Q=0F0d2WZ8YtX`n89_VnioQT2^WPyxhh&09AHdNSm`O8 zl>vB601|JGd>OzakX`(QM2@9b#UTa@u_TC)5f-uR6rs?*i26v=L{(QhBmn*2?{;XQ#52Rn$`JQklnX);*(aN71 zm0@oz&;Y{xS^sE4W5kHZ4qyD0UtMWmEmVvzcWG4RIFZs$v$LdahETg>kOi4*{Oh|C zb@iNLP@AnPZ?rEF^TE<7U%v!=$tGb*MuY5N21?6w?O}hs8r$@*GcpqAYzoW zwT~P0BkvyZQD^idK=&t!8ayzD|KgNFeMZ|@#VJ#p%fq}A@kNEIJ4viHQMq$P<8hWN z*!R$H{ASee@bts?U!Kb0q1j=-w9KoJcPBb}j9=SA1m9e}sPR*98+?)G0vnExwE8R3 zRs?u>ehBruf}bcAMW!qCiy)D2o+GB>tr5_tO0&w#!Oi@LQ}jmf#wnLUyQx2W{UkdSHk?0>hyFjdvDpV#11XgBX}XWEwx_ znw}hbR0DPfQ@s}R9MWd@8hGuT#&E6@^Le*(3`9Fk{l;vl!X<=F_S0UQXpJ*-K#R@2us0ECLM)`bWP5Cj2$d0J z9J>~A6sVIe{S$-jQ}vdu(R^ksz0!gz32wutvhFEcXGymu(&74O>T8nV-_$7uKDpi( zMlWdp&{+Q_YPv?K_5?yhKzxRWfS~_hMNM&gJ7*VTI~V7_-GgG2kDX?vuzya5C6x7& zZ(^RQShd3HwoM64&BS)PX_$@3)R#UMsh*H%$1r(Pr2~yhd{Hcx((k;ZjJ>)? zeP`OTACr!5}4(N~$dD0Tc6iZDK80ewQH2-2H^qoAIm@7Er zA8)~gg=_h4VAnYn!O`wFH^=U7aCX)|=zh<&WbSfVCadm2YL5_$B878FxdoR~Yt#3u zZ0Nv(ezPjYoX~s-x3Odu0W3<0NZj0*fy1J&I$Jhzph1sUef&{Bcv_48GjAZ`a+i4W z0%q7qm8T9(Au@?wlcWk(%7vtD$ra~Q;B?!}xVsOpA`_<yqBOA7M4Z<{TRLl7wT=zuWD)CSjf^4I?3A+Jvm%_QmJ7PUX zZl@)S_-SwhU7SCJ6+11hsQ^C5kdT66B&>l??DJq<3gh~>mA!1 zU!XD*rSg^P5Y3D$#v6!vTk(2U8)Y#u#M`e><2k~63ii@?$Wcx9s)NMwQpja%=UESP z%ZimQq#8G$BpBfwE2P2|ao;U_=(Tzlnqgnc`ss{nIARxv?Jy@V?R!+_PIe#Wg}u){ zal81HeaSO?*C&oZ$fu^Pp=w=th;VQ+wk|B@tec~Rd<^x@ni}M&T+;f}fwRvmZd>E7 zkTJFVgpHqmmNU?)5IZFsg~AFTo^Ebz1oy~7xdOIOMx|OJQ5ZZ}_sZGX{FnxKEA?#6 zH%>B-_>LzY!7%TLwO6E~TvAiWjQN$MgQDzY*y6|4@6aDq-Cgi8N-S|AZhJi?q~{0b zoduE22kcc-0Ja%X`5QXW&EpLsB!7hN@(ZR5X(an#dj=nPAMH)=jc26u05ZB4^q0E6+yK|m$_-^4zYrt& zL8R?RJ}PBVlu2+f&%j$=@hgwZR!!tC-UIU+X{6tv!Hbj;u2wOVucXoT$U$c0F32D9 zf3D-7Y#$J&|FkmtIsbp_Sk2rL=!s()#@6`C!4}hrvl9r^(q10XQht*9?ADf5 z-JuHC7{tQ_1l0U^?1jr3(QmY zqLgf}@o3QJnVD(7mAQMk3RtJOzmEt!cfI-}zWq`ZkPM`_zVh3=fW%{NDS!Dy-eItd zR_+tt`BV!g!;nYge^lZL4Y`zlWNz~r8wuAC{(iN}{?rP-=Y7NitRI*7Z7xsVm;3ZM zc#G~TM@7JE=2TyaKfbVv6ga6)a7A>X2vd`ZR9#^tlW9Jd#gKwVx3|VjuWksE!$Yq; zgeNyJ+G4=Fh{?*suMJ|?d_1{3>HvR3g{L5}k;_%;iVK6k&XAGnqinv9T&s`fZq07D z`713;j@IKbz@HNDNu|vVTDG(2aL2y<#g>Nz6G4!Y`5`XCQ)~CU;zg+Ln2$#Y#vej? zIqSujR%DXXKf0#eSlU@zS}5#vP6# z_|Nl*cE1*l^7U?DzNi5e`f25{!chNo_pQR&_=nj27A`IV=j#?X?%T#~$$l*|sZ)|} z8l&)G%K}SFz9a@6Ui?0K-Y}w&UfMOk9rxJ55zY= zN)_Xb)7$la<}8;6xyCxJ=j!*k(1HrtaG`$P>^W%Ilboy<{_KGArfByN!9|g9gWEDD z&|@KZpTwlEsxwpn%1U^3igpA&;=l+ldMs3xM-%F8wEOcX_pSX-^dd?$cMUUQi;!g! zjM&T7=%L0 zS;)ljADmc@y-VpJO(KWJJqtfpD zx^k@H&Qcl*+Z0IZEi-{IG4HF|i4HXnKBB%Wj(Xy0M`HDb;s>~I zUPg?lo}OKs5#*-5R0lxz?r3m&ai)|-*m0O#ue9zoW2BeXAeAw)g;ms(S$Eop@8Ss} zA!=-ra+A0rD}k+RnZqeK0{3uRUS%Y?x2>cfj<%wJmyTO2buw0#fM4x}Pu(}7$h2y$ z)8gu>8LOgSSPS5{wqxn#6gV$dW|{uznap@n_$&F(9%pAieuyrG2nu0Kp*minU7T>_ zqyv=C%bY;C1s260&{-NK1#Vj*`yW9>b;~kdpDleqlV8LTCL(d7C#aR2lObfbyM@9+ zaGVNHIOHy*CT4goXYH|TQcc;9%PVksA`P}gZ^5FkesSK^=zB?bvee>Wsuq8+=#09( zqrmmO3w#~L)_ssf-p|1ZQfwD&9IKl8UYM1qsN7TGN+=o@O$OXJ_Mf@&}TGa zkHw5l5fH_|yP6^C^xefbL21#xt8C*Pqz$pmu+hHDY?FNprrH>-*89riSkA zU)>gGfi~#u;UfP?lF%Z5ho%v5aX!3{MWa*#_6h!t027VSqJV802ss1Xo zd?I^QH>9aQ?h>~s;q?rUC3{Y~N+jb|7jn5Wb#~8vEbcYXF|UUqbzTiGw)VM3dwb!* zwe1D*hhfEK?juWI6xSQW`v+V2PHh{nn)xe3TcZN)?Vf9qwE36#50tY=@{=kG${vMv zMeg=rWbSoaua#(SKD;3ds$|M;HN}p$+%!J$3)E##J>bZN5dRS%a}PR^(68AAEl z81i0{oRYT)V|qxCQR&=fdLQfXst{0yw{kbGRC-p~vb*P* zltQ_@uv*oz6-QKw3A?C*^%Y0@MI8YEb&!E&CB`ky?0J1CMJ8QI#R%Q_d^K(dx4&g- z+xpCb^o?oBU}^Tu!S(sT(05*ModxXFt@$J$NoN48>8d^d%n94(q#(4vp<;T80({4b zS%z8}HXlxW$(V|UVtVqm)rkNYGi2S&rr0vgVjx;29&-mca~4aaSJGc6so~pS zB~JS&>f~jGl1zn2&Y+(Yoxn3Z-=V)Lv=qu?4#r0+MG3o@34{>jNOCwp#2qJ+(2Gvn2;Hoz$p8gI$x7i)z=`spc$sH9cLs{sFmCZ@V~# zZ%3novskx$I!U)!X`5Z@KrVr_YhG%VXa!mMBjwGI>1*vQIcPfe`#quJr7*twf9(D>RH<4G{c-XKy5nrqqGbUl7AO$0f(JAry&YJJlXR_@4wDZ19)DUdT2ZzcEY9qm~8TE>nnI8oWQegzxpPEnGh#xE}cxSsa%TF3tp=NKYx~p^CjHIJ` z)RnIJr#rwzm6$FrU|{6n;L7dKsRV&bb7#F$dpQm@_>`@vm;h3r!^;-8N8OGe-ieQM zvvbq)CO&@_NzPE%o&{fq1TuThg!Lx~)@knBU+XL8Wr7P{)4#X_=|KzbJyZd>e{9zJ4ZQmI)|52nVMr-C!+!>)faES#*bOiz*Tmkd*t$Muc9Mg(uVX-D@o zSz3x;qo%QXf8!?hYHYND9X)Gmz20bouSPEP0J)Mk$@dsBoVQ>|4Jsm!0X`V7WU+KDhht3ouG zSkG(^ENY`3>7W+oss9?tF5_Z=6?-PwX{@tXp~Tv(bP*T#kp7@2owIdbycjPggtgu` z*A*yHS$9ONy3LLIJEgLg2HMo8DryLlleSEQp0f5JAlV85^-}NW_l&Pw>Lnaa%5~1U zeRv?G1ZqCk2t!Qs;Bq#il&_J6LIJVv5~6&Xo!r?36ttFka)I`fKzyODx~ zrmxU-bU9{=gokC06)rm?#4#2Vym-n$x-DinZ(BM}e_jhfd)5Uj)AM^3`c*HTmjPzZu^1PUd&iqBAr}edN z%1yPzI75yWip>RVgPgRC)^V~3_2~_-J?uYu-xNG)iLBk^jlbbo%8g0Y8z_voxQXo5 zw8b28tFsDW99jNGHxs~QB3{p#X7@@Ur6_;`np9$s3qk~aI24Lr2kc9LylEI-i1NZW2Nv-P5K)}G+s1I8qFKC8nury*c~6)^vU~+tLXt*hxf&vy?qU&?GLR&Y`A{D#i_k2Ta01 zKM)L1YW(sIs`#X;o~!*$@0`&wN55+CyW6 zMviI7snd^$16^?OF?z!BCs)b?JIGx{4Mm>dC#|XVI0CXf<^X0XvI6pUXbxcocSx6e zV`HCK)aeG7G(TncSt1v-{b}R*17YTp=S6umCs?7eeTWjwC!*Tg3SbU~7d&rM`#(k@ZIy>NIm`s*g>Imq$akla(`t-M9`__W9Y+Hhcp)racrDsKT^9WKuK9(A zPPe1-iMI+1JBc@}yt|`>s%1(*;TS_e;!Ci^7ZgTri*QLzINVH_4{6@apzod6>{u{_ zSH*#{U)ba_Lw%zKxxi|_)oN18eh$Oa&2?1B=@-sX5qv2(c+=WE!<@4P$=gaL!>{o> zVeh3w{Faz?6hgWe3~RdO9S_RfrxalN#H|~KGKX@1Dr(q9XmZ{c^NcZ6h_G~x(2tA| z0@H+d)=oCZ(tFFSQYB62A5^;<0U;tUB1yNY!0`2o>eH9)LrbaOP8emW7+_+nix>Z#%lk=*uM%z_qC-jOs0 z7MM&a>3?4~LJ3xJ<+Q=M2-+s|UET3E?Z-^P_Rj^nXQ$2lGKhR>EEFi*U%a5~ZD_l& zqJC+@%+W=gZ0x06t$o4#oC7YO0#rR0r<}D7kN9(Tc4BgFO%wmV26S00It%PY(sFAG z%UV-A5zP!w804I7yKK_eRk2$>IxhV>`Tbef^NJKu4G2l;V31Gk?>r;IrpnW<__9|B z^u@!U_Jb9EB^eJ`#}$1t8P*GY=J#5i`h^^6I9IxWAvE|S?*QQ3bwm5a!Rb70G?Q#2 zbQXl0iZO`MZsmji{sHR^nx_lMx}ab(iXnek1C%l-!e8GLmckQ}9n7K5k*5+&#z&nk z{(OgL$&1(TH~`qKi!Q0lv~Uzob_i7L)0YndNWZ0o+(Q$aOf{o0d$G6H_HVNP$ZrFO zh->2VYG}oV_Ub#(Fc+yY7Yv@{6PmTdPa?qPYa(2^@@_&}|18BgvJFG^%`Hv1vW?$8Tho@I+i!Z zYCv8n1C|pIsq21(xjnyyJP2>vNh3tFGfk_IG^91EKwloQy0B7}qk0?11-_<8yb}or z7`xe^cyVa08*O%n=1~LHxt`}QW&&mZZr-S+U8wa+^jl5k(qb4b6mE^QzB8+WVsEVF zq6n(|t8qR_V-N_Ipodz4u_!?0kr#HzIGqQ|Jkv@z*G7B^N-HL8g1U#`^GXl2^<(CD zq0i{D5Wp!8PPu2NvSM(k!$MSoPJJy|^lLn(%= zE2qlCr5J%4WzT7+FSm570ZXwKy&RvpG{l*I`~V zi%dw1oIN7uuY~7bUN2T4kM=5Oek2;WfNV_SqR0ODt%okSh8 zggvdZwAVhuzt0XyZxc4c^_5Pv=7<}S1M3pRrT{(!X}P^<*JFZ@sZu@rBHxbR|MUeQ zp|GL-)bfC4Y0D`~&*${U?6`{)(bNhCiSG1^sVsl>a{XUk;YP zvi}i(nLPd$e+`ZR`47v-KgRrjZ6SY|KK_XS{2lRM7Lfl8{L2LLPoVn$1pdyG)P?&$K=ijUIk4gS@ zj(^er`;8?3O8>9j;D6G`;r?&*|I!=&SK5DVPW_Wsi|~J={g+miiaadbUr?k!uO%o5 K2s`$_e*GU@T3ng{ literal 0 HcmV?d00001 diff --git a/res/layout/add_account.xml b/res/layout/add_account.xml index aef17ed0b2f8..79fee1a4c38a 100644 --- a/res/layout/add_account.xml +++ b/res/layout/add_account.xml @@ -87,14 +87,6 @@ android:shadowDy="1" android:shadowRadius="1" android:text="@string/log_in" /> - -