Merge pull request #335 from ojiikun/master

Android: Mark Unread, Better API Error Handling
This commit is contained in:
Samuel Clay 2013-07-02 15:46:10 -07:00
commit b94fb83a77
24 changed files with 455 additions and 440 deletions

View file

@ -1,4 +1,5 @@
build.xml
local.properties
libs/ActionBarSherlock/build.xml
*.java.swp
*.xml.swp

View file

@ -27,4 +27,9 @@
android:showAsAction="never"
android:title="@string/menu_save_story"/>
<item
android:id="@+id/menu_reading_markunread"
android:showAsAction="never"
android:title="@string/menu_mark_unread"/>
</menu>

View file

@ -2,6 +2,11 @@
<string name="newsblur">NewsBlur</string>
<string name="error_unset_message">An unknown error occurred</string>
<string name="error_http_connection">There was a problem connecting to NewsBlur</string>
<string name="error_read_connection">There was a problem communicating with NewsBlur</string>
<string name="error_offline">There was a problem connecting to the network</string>
<string name="login_username_hint">username</string>
<string name="login_password_hint">password</string>
<string name="login_registration_email_hint">email address</string>
@ -88,6 +93,7 @@
<string name="menu_save_story">Save this story</string>
<string name="menu_mark_previous_stories_as_read">Mark previous as read</string>
<string name="menu_mark_story_as_read">Mark as read</string>
<string name="menu_mark_unread">Mark as unread</string>
<string name="toast_marked_folder_as_read">Folder marked as read</string>
<string name="toast_marked_feed_as_read">Feed marked as read</string>
@ -97,6 +103,9 @@
<string name="toast_story_saved">Story saved</string>
<string name="toast_story_save_error">Error marking story as saved.</string>
<string name="toast_story_unread">Story marked as unread</string>
<string name="toast_story_unread_error">Error marking story as unread</string>
<string name="logout_warning">Are you sure you want to log out?</string>

View file

@ -143,6 +143,9 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
} else if (item.getItemId() == R.id.menu_reading_save) {
FeedUtils.saveStory(story, Reading.this, apiManager);
return true;
} else if (item.getItemId() == R.id.menu_reading_markunread) {
this.markStoryUnread(story);
return true;
} else {
return super.onOptionsItemSelected(item);
}
@ -216,6 +219,17 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
}
}
private void markStoryUnread(Story story) {
// first, ensure the story isn't queued up to be marked as read
this.storiesToMarkAsRead.remove(story);
// next, call the API to un-mark it as read, just in case we missed the batch
// operation, or it was read long before now.
FeedUtils.markStoryUnread(story, Reading.this, this.apiManager);
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
getSharedPreferences(PrefConstants.PREFERENCES, 0).edit().putFloat(PrefConstants.PREFERENCE_TEXT_SIZE, (float) progress / AppConstants.FONT_SIZE_INCREMENT_FACTOR).commit();

View file

@ -19,7 +19,7 @@ import com.newsblur.R;
import com.newsblur.activity.Login;
import com.newsblur.activity.Main;
import com.newsblur.network.APIManager;
import com.newsblur.network.domain.LoginResponse;
import com.newsblur.network.domain.NewsBlurResponse;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
@ -68,7 +68,7 @@ public class LoginProgressFragment extends Fragment {
return v;
}
private class LoginTask extends AsyncTask<String, Void, LoginResponse> {
private class LoginTask extends AsyncTask<Void, Void, NewsBlurResponse> {
@Override
protected void onPreExecute() {
@ -77,15 +77,15 @@ public class LoginProgressFragment extends Fragment {
}
@Override
protected LoginResponse doInBackground(String... params) {
LoginResponse response = apiManager.login(username, password);
protected NewsBlurResponse doInBackground(Void... params) {
NewsBlurResponse response = apiManager.login(username, password);
apiManager.updateUserProfile();
return response;
}
@Override
protected void onPostExecute(LoginResponse result) {
if (result.authenticated) {
protected void onPostExecute(NewsBlurResponse result) {
if (!result.isError()) {
final Animation a = AnimationUtils.loadAnimation(getActivity(), R.anim.text_down);
updateStatus.setText(R.string.login_logged_in);
loggingInProgress.setVisibility(View.GONE);
@ -103,11 +103,7 @@ public class LoginProgressFragment extends Fragment {
getActivity().startActivity(startMain);
} else {
if (result.errors != null && result.errors.message != null) {
Toast.makeText(getActivity(), result.errors.message[0], Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getActivity(), getResources().getString(R.string.login_message_error), Toast.LENGTH_LONG).show();
}
Toast.makeText(getActivity(), result.getErrorMessage(), Toast.LENGTH_LONG).show();
startActivity(new Intent(getActivity(), Login.class));
}
}

View file

@ -20,12 +20,11 @@ import com.newsblur.activity.AddSites;
import com.newsblur.activity.Login;
import com.newsblur.activity.LoginProgress;
import com.newsblur.network.APIManager;
import com.newsblur.network.domain.LoginResponse;
import com.newsblur.network.domain.RegisterResponse;
public class RegisterProgressFragment extends Fragment {
private APIManager apiManager;
private String TAG = "LoginProgress";
private String username;
private String password;
@ -85,52 +84,33 @@ public class RegisterProgressFragment extends Fragment {
return v;
}
private class RegisterTask extends AsyncTask<Void, Void, Boolean> {
private LoginResponse response;
private class RegisterTask extends AsyncTask<Void, Void, RegisterResponse> {
@Override
protected Boolean doInBackground(Void... params) {
try {
// We include this wait simply as a small UX convenience. Otherwise the user could be met with a disconcerting flicker when attempting to register and failing.
Thread.sleep(700);
} catch (InterruptedException e) {
Log.e(TAG, "Error sleeping during login.");
}
response = apiManager.signup(username, password, email);
return response.code != -1 && response.authenticated;
protected RegisterResponse doInBackground(Void... params) {
return apiManager.signup(username, password, email);
}
@Override
protected void onPostExecute(Boolean hasFolders) {
if (response.code != -1 && response.authenticated) {
if (!hasFolders.booleanValue()) {
switcher.showNext();
} else {
Intent i = new Intent(getActivity(), LoginProgress.class);
i.putExtra("username", username);
i.putExtra("password", password);
startActivity(i);
}
protected void onPostExecute(RegisterResponse response) {
if (response.authenticated) {
switcher.showNext();
} else {
Toast.makeText(getActivity(), extractErrorMessage(response), Toast.LENGTH_LONG).show();
startActivity(new Intent(getActivity(), Login.class));
}
}
private String extractErrorMessage(LoginResponse response) {
String errorMessage = null;
if(response.errors != null) {
if(response.errors.email != null && response.errors.email.length > 0) {
errorMessage = response.errors.email[0];
} else if(response.errors.username != null && response.errors.username.length > 0) {
errorMessage = response.errors.username[0];
} else if(response.errors.message != null && response.errors.message.length > 0) {
errorMessage = response.errors.message[0];
}
}
private String extractErrorMessage(RegisterResponse response) {
// TODO: do we ever see these mysterious email/username messages in practice?
String errorMessage = null;
if(response.email != null && response.email.length > 0) {
errorMessage = response.email[0];
} else if(response.username != null && response.username.length > 0) {
errorMessage = response.username[0];
}
if(errorMessage == null) {
errorMessage = getResources().getString(R.string.login_message_error);
errorMessage = response.getErrorMessage(getResources().getString(R.string.login_message_error));
}
return errorMessage;
}

View file

@ -1,183 +0,0 @@
package com.newsblur.network;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Scanner;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.Log;
import com.newsblur.domain.ValueMultimap;
import com.newsblur.util.NetworkUtils;
import com.newsblur.util.PrefConstants;
public class APIClient {
private Context context;
public APIClient(final Context context) {
this.context = context;
}
public APIResponse get(final String urlString) {
HttpURLConnection connection = null;
if (!NetworkUtils.isOnline(context)) {
APIResponse response = new APIResponse();
response.isOffline = true;
return response;
}
try {
final URL url = new URL(urlString);
Log.d(this.getClass().getName(), "API GET " + url );
connection = (HttpURLConnection) url.openConnection();
final SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
final String cookie = preferences.getString(PrefConstants.PREF_COOKIE, null);
if (cookie != null) {
connection.setRequestProperty("Cookie", cookie);
}
return extractResponse(url, connection);
} catch (IOException e) {
Log.e(this.getClass().getName(), "Error opening GET connection to " + urlString, e.getCause());
return new APIResponse();
} finally {
connection.disconnect();
}
}
public APIResponse get(final String urlString, final ContentValues values) {
List<String> parameters = new ArrayList<String>();
for (Entry<String, Object> entry : values.valueSet()) {
StringBuilder builder = new StringBuilder();
builder.append((String) entry.getKey());
builder.append("=");
builder.append(URLEncoder.encode((String) entry.getValue()));
parameters.add(builder.toString());
}
return this.get(urlString + "?" + TextUtils.join("&", parameters));
}
public APIResponse get(final String urlString, final ValueMultimap valueMap) {
return this.get(urlString + "?" + valueMap.getParameterString());
}
private APIResponse extractResponse(final URL url, HttpURLConnection connection) throws IOException {
StringBuilder builder = new StringBuilder();
final Scanner scanner = new Scanner(connection.getInputStream());
while (scanner.hasNextLine()) {
builder.append(scanner.nextLine());
}
final APIResponse response = new APIResponse();
response.responseString = builder.toString();
response.responseCode = connection.getResponseCode();
response.cookie = connection.getHeaderField("Set-Cookie");
response.hasRedirected = !TextUtils.equals(url.getHost(), connection.getURL().getHost());
return response;
}
public APIResponse post(final String urlString, final ContentValues values) {
HttpURLConnection connection = null;
if (!NetworkUtils.isOnline(context)) {
APIResponse response = new APIResponse();
response.isOffline = true;
return response;
}
List<String> parameters = new ArrayList<String>();
for (Entry<String, Object> entry : values.valueSet()) {
final StringBuilder builder = new StringBuilder();
builder.append((String) entry.getKey());
builder.append("=");
try {
builder.append(URLEncoder.encode((String) entry.getValue(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
Log.e(this.getClass().getName(), e.getLocalizedMessage());
return new APIResponse();
}
parameters.add(builder.toString());
}
final String parameterString = TextUtils.join("&", parameters);
try {
final URL url = new URL(urlString);
Log.d(this.getClass().getName(), "API POST " + url );
connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setFixedLengthStreamingMode(parameterString.getBytes().length);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
final SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
final String cookie = preferences.getString(PrefConstants.PREF_COOKIE, null);
if (cookie != null) {
connection.setRequestProperty("Cookie", cookie);
}
final PrintWriter printWriter = new PrintWriter(connection.getOutputStream());
printWriter.print(parameterString);
printWriter.close();
return extractResponse(url, connection);
} catch (IOException e) {
Log.e(this.getClass().getName(), "Error opening POST connection to " + urlString + ": " + e.getCause(), e.getCause());
return new APIResponse();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
public APIResponse post(final String urlString, final ValueMultimap valueMap) {
return post(urlString, valueMap, true);
}
public APIResponse post(final String urlString, final ValueMultimap valueMap, boolean jsonIfy) {
HttpURLConnection connection = null;
if (!NetworkUtils.isOnline(context)) {
APIResponse response = new APIResponse();
response.isOffline = true;
return response;
}
try {
final URL url = new URL(urlString);
Log.d(this.getClass().getName(), "API POST " + url );
connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
String parameterString = jsonIfy ? valueMap.getJsonString() : valueMap.getParameterString();
connection.setFixedLengthStreamingMode(parameterString.getBytes().length);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
final SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
final String cookie = preferences.getString(PrefConstants.PREF_COOKIE, null);
if (cookie != null) {
connection.setRequestProperty("Cookie", cookie);
}
final PrintWriter printWriter = new PrintWriter(connection.getOutputStream());
printWriter.print(parameterString);
printWriter.close();
return extractResponse(url, connection);
} catch (IOException e) {
Log.e(this.getClass().getName(), "Error opening POST connection to " + urlString + ": " + e.getCause(), e.getCause());
return new APIResponse();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}

View file

@ -27,6 +27,7 @@ public class APIConstants {
public static final String URL_MARK_SOCIALSTORY_AS_READ = "http://newsblur.com/reader/mark_social_stories_as_read/";
public static final String URL_SHARE_STORY = "http://newsblur.com/social/share_story";
public static final String URL_MARK_STORY_AS_STARRED = "http://newsblur.com/reader/mark_story_as_starred/";
public static final String URL_MARK_STORY_AS_UNREAD = "http://newsblur.com/reader/mark_story_as_unread/";
public static final String URL_STARRED_STORIES = "http://newsblur.com/reader/starred_stories";
public static final String URL_FEED_AUTOCOMPLETE = "http://newsblur.com/rss_feeds/feed_autocomplete";

View file

@ -1,5 +1,10 @@
package com.newsblur.network;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
@ -8,11 +13,10 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import org.apache.http.HttpStatus;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.text.TextUtils;
@ -40,13 +44,15 @@ import com.newsblur.domain.ValueMultimap;
import com.newsblur.network.domain.CategoriesResponse;
import com.newsblur.network.domain.FeedFolderResponse;
import com.newsblur.network.domain.FeedRefreshResponse;
import com.newsblur.network.domain.LoginResponse;
import com.newsblur.network.domain.Message;
import com.newsblur.network.domain.NewsBlurResponse;
import com.newsblur.network.domain.ProfileResponse;
import com.newsblur.network.domain.RegisterResponse;
import com.newsblur.network.domain.SocialFeedResponse;
import com.newsblur.network.domain.StoriesResponse;
import com.newsblur.serialization.BooleanTypeAdapter;
import com.newsblur.serialization.DateStringTypeAdapter;
import com.newsblur.util.NetworkUtils;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.ReadFilter;
import com.newsblur.util.StoryOrder;
@ -67,47 +73,41 @@ public class APIManager {
.create();
}
public LoginResponse login(final String username, final String password) {
final APIClient client = new APIClient(context);
public NewsBlurResponse login(final String username, final String password) {
final ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_USERNAME, username);
values.put(APIConstants.PARAMETER_PASSWORD, password);
final APIResponse response = client.post(APIConstants.URL_LOGIN, values);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
LoginResponse loginResponse = gson.fromJson(response.responseString, LoginResponse.class);
PrefsUtils.saveLogin(context, username, response.cookie);
return loginResponse;
} else {
return new LoginResponse();
}
}
final APIResponse response = post(APIConstants.URL_LOGIN, values);
NewsBlurResponse loginResponse = response.getResponse(gson);
if (!response.isError()) {
PrefsUtils.saveLogin(context, username, response.getCookie());
}
return loginResponse;
}
public boolean setAutoFollow(boolean autofollow) {
final APIClient client = new APIClient(context);
ContentValues values = new ContentValues();
values.put("autofollow_friends", autofollow ? "true" : "false");
final APIResponse response = client.post(APIConstants.URL_AUTOFOLLOW_PREF, values);
return (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected);
final APIResponse response = post(APIConstants.URL_AUTOFOLLOW_PREF, values);
return (!response.isError());
}
public boolean addCategories(ArrayList<String> categories) {
final APIClient client = new APIClient(context);
final ValueMultimap values = new ValueMultimap();
for (String category : categories) {
values.put(APIConstants.PARAMETER_CATEGORY, URLEncoder.encode(category));
}
final APIResponse response = client.post(APIConstants.URL_ADD_CATEGORIES, values, false);
return (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected);
final APIResponse response = post(APIConstants.URL_ADD_CATEGORIES, values, false);
return (!response.isError());
}
public boolean markFeedAsRead(final String[] feedIds) {
final APIClient client = new APIClient(context);
final ValueMultimap values = new ValueMultimap();
for (String feedId : feedIds) {
values.put(APIConstants.PARAMETER_FEEDID, feedId);
}
final APIResponse response = client.post(APIConstants.URL_MARK_FEED_AS_READ, values, false);
if (!response.isOffline && response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
final APIResponse response = post(APIConstants.URL_MARK_FEED_AS_READ, values, false);
if (!response.isError()) {
return true;
} else {
return false;
@ -115,11 +115,10 @@ public class APIManager {
}
public boolean markAllAsRead() {
final APIClient client = new APIClient(context);
final ValueMultimap values = new ValueMultimap();
values.put(APIConstants.PARAMETER_DAYS, "0");
final APIResponse response = client.post(APIConstants.URL_MARK_ALL_AS_READ, values, false);
if (!response.isOffline && response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
final APIResponse response = post(APIConstants.URL_MARK_ALL_AS_READ, values, false);
if (!response.isError()) {
return true;
} else {
return false;
@ -127,68 +126,65 @@ public class APIManager {
}
public boolean markSocialStoryAsRead(final String updateJson) {
final APIClient client = new APIClient(context);
final ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_MARKSOCIAL_JSON, updateJson);
final APIResponse response = client.post(APIConstants.URL_MARK_SOCIALSTORY_AS_READ, values);
if (!response.isOffline && response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
final APIResponse response = post(APIConstants.URL_MARK_SOCIALSTORY_AS_READ, values);
if (!response.isError()) {
return true;
} else {
return false;
}
}
public boolean markStoryAsStarred(final String feedId, final String storyId) {
final APIClient client = new APIClient(context);
public NewsBlurResponse markStoryAsStarred(final String feedId, final String storyId) {
final ValueMultimap values = new ValueMultimap();
values.put(APIConstants.PARAMETER_FEEDID, feedId);
values.put(APIConstants.PARAMETER_STORYID, storyId);
final APIResponse response = client.post(APIConstants.URL_MARK_STORY_AS_STARRED, values, false);
if (!response.isOffline && response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
return true;
} else {
return false;
}
final APIResponse response = post(APIConstants.URL_MARK_STORY_AS_STARRED, values, false);
return response.getResponse(gson, NewsBlurResponse.class);
}
public NewsBlurResponse markStoryAsUnread( String feedId, String storyId ) {
final ValueMultimap values = new ValueMultimap();
values.put(APIConstants.PARAMETER_FEEDID, feedId);
values.put(APIConstants.PARAMETER_STORYID, storyId);
final APIResponse response = post(APIConstants.URL_MARK_STORY_AS_UNREAD, values, false);
return response.getResponse(gson, NewsBlurResponse.class);
}
public CategoriesResponse getCategories() {
final APIClient client = new APIClient(context);
final APIResponse response = client.get(APIConstants.URL_CATEGORIES);
if (!response.isOffline && response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
return gson.fromJson(response.responseString, CategoriesResponse.class);
final APIResponse response = get(APIConstants.URL_CATEGORIES);
if (!response.isError()) {
CategoriesResponse categoriesResponse = (CategoriesResponse) response.getResponse(gson, CategoriesResponse.class);
return categoriesResponse;
} else {
return null;
}
}
public LoginResponse signup(final String username, final String password, final String email) {
final APIClient client = new APIClient(context);
public RegisterResponse signup(final String username, final String password, final String email) {
final ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_USERNAME, username);
values.put(APIConstants.PARAMETER_PASSWORD, password);
values.put(APIConstants.PARAMETER_EMAIL, email);
final APIResponse response = client.post(APIConstants.URL_SIGNUP, values);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
LoginResponse loginResponse = gson.fromJson(response.responseString, LoginResponse.class);
PrefsUtils.saveLogin(context, username, response.cookie);
final APIResponse response = post(APIConstants.URL_SIGNUP, values);
RegisterResponse registerResponse = ((RegisterResponse) response.getResponse(gson, RegisterResponse.class));
if (!response.isError()) {
PrefsUtils.saveLogin(context, username, response.getCookie());
CookieSyncManager.createInstance(context.getApplicationContext());
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setCookie(".newsblur.com", response.cookie);
cookieManager.setCookie(".newsblur.com", response.getCookie());
CookieSyncManager.getInstance().sync();
return loginResponse;
} else {
return new LoginResponse();
}
}
return registerResponse;
}
public ProfileResponse updateUserProfile() {
final APIClient client = new APIClient(context);
final APIResponse response = client.get(APIConstants.URL_MY_PROFILE);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
ProfileResponse profileResponse = gson.fromJson(response.responseString, ProfileResponse.class);
final APIResponse response = get(APIConstants.URL_MY_PROFILE);
if (!response.isError()) {
ProfileResponse profileResponse = (ProfileResponse) response.getResponse(gson, ProfileResponse.class);
PrefsUtils.saveUserDetails(context, profileResponse.user);
return profileResponse;
} else {
@ -197,7 +193,6 @@ public class APIManager {
}
public StoriesResponse getStoriesForFeed(String feedId, String pageNumber, StoryOrder order, ReadFilter filter) {
final APIClient client = new APIClient(context);
final ContentValues values = new ContentValues();
Uri feedUri = Uri.parse(APIConstants.URL_FEED_STORIES).buildUpon().appendPath(feedId).build();
values.put(APIConstants.PARAMETER_FEEDS, feedId);
@ -205,14 +200,14 @@ public class APIManager {
values.put(APIConstants.PARAMETER_ORDER, order.getParameterValue());
values.put(APIConstants.PARAMETER_READ_FILTER, filter.getParameterValue());
final APIResponse response = client.get(feedUri.toString(), values);
final APIResponse response = get(feedUri.toString(), values);
Uri storyUri = FeedProvider.FEED_STORIES_URI.buildUpon().appendPath(feedId).build();
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
if (!response.isError()) {
if (TextUtils.equals(pageNumber, "1")) {
contentResolver.delete(storyUri, null, null);
}
StoriesResponse storiesResponse = gson.fromJson(response.responseString, StoriesResponse.class);
StoriesResponse storiesResponse = (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
Uri classifierUri = FeedProvider.CLASSIFIER_URI.buildUpon().appendPath(feedId).build();
@ -238,7 +233,6 @@ public class APIManager {
}
public StoriesResponse getStoriesForFeeds(String[] feedIds, String pageNumber, StoryOrder order, ReadFilter filter) {
final APIClient client = new APIClient(context);
final ValueMultimap values = new ValueMultimap();
for (String feedId : feedIds) {
values.put(APIConstants.PARAMETER_FEEDS, feedId);
@ -248,10 +242,10 @@ public class APIManager {
}
values.put(APIConstants.PARAMETER_ORDER, order.getParameterValue());
values.put(APIConstants.PARAMETER_READ_FILTER, filter.getParameterValue());
final APIResponse response = client.get(APIConstants.URL_RIVER_STORIES, values);
final APIResponse response = get(APIConstants.URL_RIVER_STORIES, values);
StoriesResponse storiesResponse = gson.fromJson(response.responseString, StoriesResponse.class);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
StoriesResponse storiesResponse = (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
if (!response.isError()) {
if (TextUtils.equals(pageNumber,"1")) {
Uri storyUri = FeedProvider.ALL_STORIES_URI;
contentResolver.delete(storyUri, null, null);
@ -274,15 +268,14 @@ public class APIManager {
}
public StoriesResponse getStarredStories(String pageNumber) {
final APIClient client = new APIClient(context);
final ValueMultimap values = new ValueMultimap();
if (!TextUtils.isEmpty(pageNumber)) {
values.put(APIConstants.PARAMETER_PAGE_NUMBER, "" + pageNumber);
}
final APIResponse response = client.get(APIConstants.URL_STARRED_STORIES, values);
final APIResponse response = get(APIConstants.URL_STARRED_STORIES, values);
StoriesResponse storiesResponse = gson.fromJson(response.responseString, StoriesResponse.class);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
StoriesResponse storiesResponse = (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
if (!response.isError()) {
if (TextUtils.equals(pageNumber,"1")) {
contentResolver.delete(FeedProvider.STARRED_STORIES_URI, null, null);
}
@ -300,7 +293,6 @@ public class APIManager {
}
public SocialFeedResponse getSharedStoriesForFeeds(String[] feedIds, String pageNumber) {
final APIClient client = new APIClient(context);
final ValueMultimap values = new ValueMultimap();
for (String feedId : feedIds) {
values.put(APIConstants.PARAMETER_FEEDS, feedId);
@ -309,10 +301,9 @@ public class APIManager {
values.put(APIConstants.PARAMETER_PAGE_NUMBER, "" + pageNumber);
}
final APIResponse response = client.get(APIConstants.URL_SHARED_RIVER_STORIES, values);
SocialFeedResponse storiesResponse = gson.fromJson(response.responseString, SocialFeedResponse.class);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
final APIResponse response = get(APIConstants.URL_SHARED_RIVER_STORIES, values);
SocialFeedResponse storiesResponse = (SocialFeedResponse) response.getResponse(gson, SocialFeedResponse.class);
if (!response.isError()) {
// If we've successfully retrieved the latest stories for all shared feeds (the first page), delete all previous shared feeds
if (TextUtils.equals(pageNumber,"1")) {
@ -349,7 +340,6 @@ public class APIManager {
}
public SocialFeedResponse getStoriesForSocialFeed(String userId, String username, String pageNumber) {
final APIClient client = new APIClient(context);
final ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_USER_ID, userId);
values.put(APIConstants.PARAMETER_USERNAME, username);
@ -357,9 +347,9 @@ public class APIManager {
values.put(APIConstants.PARAMETER_PAGE_NUMBER, "" + pageNumber);
}
Uri feedUri = Uri.parse(APIConstants.URL_SOCIALFEED_STORIES).buildUpon().appendPath(userId).appendPath(username).build();
final APIResponse response = client.get(feedUri.toString(), values);
SocialFeedResponse socialFeedResponse = gson.fromJson(response.responseString, SocialFeedResponse.class);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
final APIResponse response = get(feedUri.toString(), values);
SocialFeedResponse socialFeedResponse = (SocialFeedResponse) response.getResponse(gson, SocialFeedResponse.class);
if (!response.isError()) {
Uri storySocialUri = FeedProvider.SOCIALFEED_STORIES_URI.buildUpon().appendPath(userId).build();
if (TextUtils.equals(pageNumber, "1")) {
@ -423,11 +413,10 @@ public class APIManager {
}
public boolean followUser(final String userId) {
final APIClient client = new APIClient(context);
final ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_USERID, userId);
final APIResponse response = client.post(APIConstants.URL_FOLLOW, values);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
final APIResponse response = post(APIConstants.URL_FOLLOW, values);
if (!response.isError()) {
return true;
} else {
return false;
@ -435,11 +424,10 @@ public class APIManager {
}
public boolean unfollowUser(final String userId) {
final APIClient client = new APIClient(context);
final ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_USERID, userId);
final APIResponse response = client.post(APIConstants.URL_UNFOLLOW, values);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
final APIResponse response = post(APIConstants.URL_UNFOLLOW, values);
if (!response.isError()) {
return true;
} else {
return false;
@ -447,7 +435,6 @@ public class APIManager {
}
public Boolean shareStory(final String storyId, final String feedId, final String comment, final String sourceUserId) {
final APIClient client = new APIClient(context);
final ContentValues values = new ContentValues();
if (!TextUtils.isEmpty(comment)) {
values.put(APIConstants.PARAMETER_SHARE_COMMENT, comment);
@ -458,8 +445,8 @@ public class APIManager {
values.put(APIConstants.PARAMETER_FEEDID, feedId);
values.put(APIConstants.PARAMETER_STORYID, storyId);
final APIResponse response = client.post(APIConstants.URL_SHARE_STORY, values);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
final APIResponse response = post(APIConstants.URL_SHARE_STORY, values);
if (!response.isError()) {
return true;
} else {
return false;
@ -476,17 +463,17 @@ public class APIManager {
*/
public boolean getFolderFeedMapping(boolean doUpdateCounts) {
final APIClient client = new APIClient(context);
final ContentValues params = new ContentValues();
params.put( APIConstants.PARAMETER_UPDATE_COUNTS, (doUpdateCounts ? "true" : "false") );
final APIResponse response = client.get(APIConstants.URL_FEEDS, params);
final APIResponse response = get(APIConstants.URL_FEEDS, params);
final FeedFolderResponse feedUpdate = new FeedFolderResponse(response.responseString, gson);
// note: this response is complex enough, we have to do a custom parse in the FFR
final FeedFolderResponse feedUpdate = new FeedFolderResponse(response.getResponseBody(), gson);
// there is a rare issue with feeds that have no folder. capture them for debug.
List<String> debugFeedIds = new ArrayList<String>();
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
if (!response.isError()) {
// if the response says we aren't logged in, clear the DB and prompt for login. We test this
// here, since this the first sync call we make on launch if we believe we are cookied.
@ -554,18 +541,6 @@ public class APIManager {
return true;
}
private HashMap<String, Feed> getExistingFeeds() {
Cursor feedCursor = contentResolver.query(FeedProvider.FEEDS_URI, null, null, null, null);
feedCursor.moveToFirst();
HashMap<String, Feed> existingFeeds = new HashMap<String, Feed>();
while (!feedCursor.isAfterLast()) {
existingFeeds.put(Feed.fromCursor(feedCursor).feedId, Feed.fromCursor(feedCursor));
feedCursor.moveToNext();
}
feedCursor.close();
return existingFeeds;
}
public boolean trainClassifier(String feedId, String key, int type, int action) {
String typeText = null;
String actionText = null;
@ -612,18 +587,16 @@ public class APIManager {
}
values.put(APIConstants.PARAMETER_FEEDID, feedId);
final APIClient client = new APIClient(context);
final APIResponse response = client.post(APIConstants.URL_CLASSIFIER_SAVE, values);
return (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected);
final APIResponse response = post(APIConstants.URL_CLASSIFIER_SAVE, values);
return (!response.isError());
}
public ProfileResponse getUser(String userId) {
final APIClient client = new APIClient(context);
final ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_USER_ID, userId);
final APIResponse response = client.get(APIConstants.URL_USER_PROFILE, values);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
ProfileResponse profileResponse = gson.fromJson(response.responseString, ProfileResponse.class);
final APIResponse response = get(APIConstants.URL_USER_PROFILE, values);
if (!response.isError()) {
ProfileResponse profileResponse = (ProfileResponse) response.getResponse(gson, ProfileResponse.class);
return profileResponse;
} else {
return null;
@ -631,10 +604,9 @@ public class APIManager {
}
public void refreshFeedCounts() {
final APIClient client = new APIClient(context);
final APIResponse response = client.get(APIConstants.URL_FEED_COUNTS);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
final FeedRefreshResponse feedCountUpdate = gson.fromJson(response.responseString, FeedRefreshResponse.class);
final APIResponse response = get(APIConstants.URL_FEED_COUNTS);
if (!response.isError()) {
final FeedRefreshResponse feedCountUpdate = (FeedRefreshResponse) response.getResponse(gson, FeedRefreshResponse.class);
for (String feedId : feedCountUpdate.feedCounts.keySet()) {
Uri feedUri = FeedProvider.FEEDS_URI.buildUpon().appendPath(feedId).build();
if (feedCountUpdate.feedCounts.get(feedId) != null) {
@ -653,40 +625,36 @@ public class APIManager {
}
public boolean favouriteComment(String storyId, String commentId, String feedId) {
final APIClient client = new APIClient(context);
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_STORYID, storyId);
values.put(APIConstants.PARAMETER_STORY_FEEDID, feedId);
values.put(APIConstants.PARAMETER_COMMENT_USERID, commentId);
final APIResponse response = client.post(APIConstants.URL_LIKE_COMMENT, values);
return (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected);
final APIResponse response = post(APIConstants.URL_LIKE_COMMENT, values);
return (!response.isError());
}
public Boolean unFavouriteComment(String storyId, String commentId, String feedId) {
final APIClient client = new APIClient(context);
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_STORYID, storyId);
values.put(APIConstants.PARAMETER_STORY_FEEDID, feedId);
values.put(APIConstants.PARAMETER_COMMENT_USERID, commentId);
final APIResponse response = client.post(APIConstants.URL_UNLIKE_COMMENT, values);
return (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected);
final APIResponse response = post(APIConstants.URL_UNLIKE_COMMENT, values);
return (!response.isError());
}
public boolean replyToComment(String storyId, String storyFeedId, String commentUserId, String reply) {
final APIClient client = new APIClient(context);
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_STORYID, storyId);
values.put(APIConstants.PARAMETER_STORY_FEEDID, storyFeedId);
values.put(APIConstants.PARAMETER_COMMENT_USERID, commentUserId);
values.put(APIConstants.PARAMETER_REPLY_TEXT, reply);
final APIResponse response = client.post(APIConstants.URL_REPLY_TO, values);
return (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected);
final APIResponse response = post(APIConstants.URL_REPLY_TO, values);
return (!response.isError());
}
public boolean markMultipleStoriesAsRead(ContentValues values) {
final APIClient client = new APIClient(context);
final APIResponse response = client.post(APIConstants.URL_MARK_FEED_STORIES_AS_READ, values);
if (!response.isOffline && response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
final APIResponse response = post(APIConstants.URL_MARK_FEED_STORIES_AS_READ, values);
if (!response.isError()) {
return true;
} else {
return false;
@ -694,65 +662,138 @@ public class APIManager {
}
public boolean addFeed(String feedUrl, String folderName) {
final APIClient client = new APIClient(context);
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_URL, feedUrl);
if (!TextUtils.isEmpty(folderName)) {
values.put(APIConstants.PARAMETER_FOLDER, folderName);
}
final APIResponse response = client.post(APIConstants.URL_ADD_FEED, values);
return (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected);
final APIResponse response = post(APIConstants.URL_ADD_FEED, values);
return (!response.isError());
}
public FeedResult[] searchForFeed(String searchTerm) throws ServerErrorException {
final APIClient client = new APIClient(context);
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_FEED_SEARCH_TERM, searchTerm);
final APIResponse response = client.get(APIConstants.URL_FEED_AUTOCOMPLETE, values);
final APIResponse response = get(APIConstants.URL_FEED_AUTOCOMPLETE, values);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
return fromJson(response.responseString, FeedResult[].class);
if (!response.isError()) {
return gson.fromJson(response.getResponseBody(), FeedResult[].class);
} else {
return null;
}
}
public boolean deleteFeed(long feedId, String folderName) {
final APIClient client = new APIClient(context);
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_FEEDID, Long.toString(feedId));
if (!TextUtils.isEmpty(folderName)) {
values.put(APIConstants.PARAMETER_IN_FOLDER, folderName);
}
final APIResponse response = client.post(APIConstants.URL_DELETE_FEED, values);
return (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected);
final APIResponse response = post(APIConstants.URL_DELETE_FEED, values);
return (!response.isError());
}
private <T> T fromJson(String json, Class<T> classOfT) throws ServerErrorException {
if(isServerMessage(json)) {
Message errorMessage = gson.fromJson(json, Message.class);
throw new ServerErrorException(errorMessage.message);
/* HTTP METHODS */
private APIResponse get(final String urlString) {
HttpURLConnection connection = null;
if (!NetworkUtils.isOnline(context)) {
return new APIResponse(context);
}
return gson.fromJson(json, classOfT);
}
private boolean isServerMessage(String json) {
// TODO find a better way to identify these failed responses
boolean isServerMessage = false;
JsonParser parser = new JsonParser();
JsonElement jsonElement = parser.parse(json);
if(jsonElement.isJsonObject()) {
JsonObject asJsonObject = jsonElement.getAsJsonObject();
if(asJsonObject.has("code")) {
JsonElement codeItem = asJsonObject.get("code");
int code = codeItem.getAsInt();
if(code == -1)
isServerMessage = true;
try {
final URL url = new URL(urlString);
Log.d(this.getClass().getName(), "API GET " + url );
connection = (HttpURLConnection) url.openConnection();
final SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
final String cookie = preferences.getString(PrefConstants.PREF_COOKIE, null);
if (cookie != null) {
connection.setRequestProperty("Cookie", cookie);
}
}
return isServerMessage;
return new APIResponse(context, url, connection);
} catch (IOException e) {
Log.e(this.getClass().getName(), "Error opening GET connection to " + urlString, e.getCause());
return new APIResponse(context);
}
}
private APIResponse get(final String urlString, final ContentValues values) {
List<String> parameters = new ArrayList<String>();
for (Entry<String, Object> entry : values.valueSet()) {
StringBuilder builder = new StringBuilder();
builder.append((String) entry.getKey());
builder.append("=");
builder.append(URLEncoder.encode((String) entry.getValue()));
parameters.add(builder.toString());
}
return this.get(urlString + "?" + TextUtils.join("&", parameters));
}
private APIResponse get(final String urlString, final ValueMultimap valueMap) {
return this.get(urlString + "?" + valueMap.getParameterString());
}
private APIResponse post(String urlString, String postBodyString) {
HttpURLConnection connection = null;
if (!NetworkUtils.isOnline(context)) {
return new APIResponse(context);
}
try {
final URL url = new URL(urlString);
Log.d(this.getClass().getName(), "API POST " + url );
connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setFixedLengthStreamingMode(postBodyString.getBytes().length);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
final SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
final String cookie = preferences.getString(PrefConstants.PREF_COOKIE, null);
if (cookie != null) {
connection.setRequestProperty("Cookie", cookie);
}
final PrintWriter printWriter = new PrintWriter(connection.getOutputStream());
printWriter.print(postBodyString);
printWriter.close();
return new APIResponse(context, url, connection);
} catch (IOException e) {
Log.e(this.getClass().getName(), "Error opening POST connection to " + urlString + ": " + e.getCause(), e.getCause());
return new APIResponse(context);
}
}
private APIResponse post(final String urlString, final ContentValues values) {
List<String> parameters = new ArrayList<String>();
for (Entry<String, Object> entry : values.valueSet()) {
final StringBuilder builder = new StringBuilder();
builder.append((String) entry.getKey());
builder.append("=");
try {
builder.append(URLEncoder.encode((String) entry.getValue(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
Log.e(this.getClass().getName(), e.getLocalizedMessage());
return new APIResponse(context);
}
parameters.add(builder.toString());
}
final String parameterString = TextUtils.join("&", parameters);
return this.post(urlString, parameterString);
}
private APIResponse post(final String urlString, final ValueMultimap valueMap) {
return post(urlString, valueMap, true);
}
private APIResponse post(final String urlString, final ValueMultimap valueMap, boolean jsonIfy) {
String parameterString = jsonIfy ? valueMap.getJsonString() : valueMap.getParameterString();
return this.post(urlString, parameterString);
}
/**
* Convenience method to call contentResolver.bulkInsert using a list rather than an array.
*/

View file

@ -1,11 +1,131 @@
package com.newsblur.network;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Scanner;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import com.google.gson.Gson;
import org.apache.http.HttpStatus;
import com.newsblur.R;
import com.newsblur.network.domain.NewsBlurResponse;
/**
* A JSON-encoded response from the API servers. This class encodes the possible outcomes of
* an attempted API call, including total failure, online failures, and successful responses.
* In the latter case, the GSON reader used to look for errors is left open so that the expected
* response can be read. Instances of this class should be closed after use.
*/
public class APIResponse {
public String responseString = "";
public int responseCode = -1;
public boolean hasRedirected;
public boolean isOffline;
public String cookie;
private Context context;
private boolean isError;
private String errorMessage;
private String cookie;
private String responseBody;
/**
* Construct an online response. Will test the response for errors and extract all the
* info we might need.
*/
public APIResponse(Context context, URL originalUrl, HttpURLConnection connection) {
this.errorMessage = context.getResources().getString(R.string.error_unset_message);
try {
if (connection.getResponseCode() != HttpStatus.SC_OK) {
Log.e(this.getClass().getName(), "API returned error code " + connection.getResponseCode() + " calling " + originalUrl);
this.isError = true;
this.errorMessage = context.getResources().getString(R.string.error_http_connection);
return;
}
if (!TextUtils.equals(originalUrl.getHost(), connection.getURL().getHost())) {
// TODO: the existing code rejects redirects as errors. Is this correct?
Log.e(this.getClass().getName(), "API redirected calling " + originalUrl);
this.isError = true;
this.errorMessage = context.getResources().getString(R.string.error_http_connection);
return;
}
} catch (IOException ioe) {
Log.e(this.getClass().getName(), "Error (" + ioe.getMessage() + ") calling " + originalUrl, ioe);
this.isError = true;
this.errorMessage = context.getResources().getString(R.string.error_read_connection);
return;
}
this.cookie = connection.getHeaderField("Set-Cookie");
try {
StringBuilder builder = new StringBuilder();
Scanner scanner = new Scanner(connection.getInputStream(), "UTF-8");
while (scanner.hasNextLine()) { builder.append(scanner.nextLine()); }
this.responseBody = builder.toString();
} catch (Exception e) {
Log.e(this.getClass().getName(), e.getClass().getName() + " (" + e.getMessage() + ") reading " + originalUrl, e);
this.isError = true;
this.errorMessage = context.getResources().getString(R.string.error_read_connection);
return;
}
// Log.d(this.getClass().getName(), "received API response: \n" + this.responseBody);
try {
connection.disconnect();
} catch (Exception e) {
Log.e(this.getClass().getName(), e.getClass().getName() + " caught closing connection: " + e.getMessage(), e);
}
}
/**
* Construct and empty/offline response. Signals that the call was not made.
*/
public APIResponse(Context context) {
this.isError = true;
this.errorMessage = context.getResources().getString(R.string.error_offline);
}
public boolean isError() {
return this.isError;
}
/**
* Get the response object from this call. A specific subclass of NewsBlurResponse
* may be used for calls that return data, or the parent class may be used if no
* return data are expected.
*/
@SuppressWarnings("unchecked")
public <T extends NewsBlurResponse> T getResponse(Gson gson, Class<T> classOfT) {
if (this.isError) {
// if we encountered an error, make a generic response type and populate
// it's message field
NewsBlurResponse response = new NewsBlurResponse();
response.message = this.errorMessage;
return ((T) response);
} else {
// otherwise, parse the response as the expected class and defer error detection
// to the NewsBlurResponse parent class
return gson.fromJson(this.responseBody, classOfT);
}
}
public NewsBlurResponse getResponse(Gson gson) {
return getResponse(gson, NewsBlurResponse.class);
}
public String getResponseBody() {
return this.responseBody;
}
public String getCookie() {
return this.cookie;
}
}

View file

@ -6,7 +6,7 @@ import com.google.gson.annotations.SerializedName;
import com.newsblur.domain.Category;
import com.newsblur.domain.Feed;
public class CategoriesResponse {
public class CategoriesResponse extends NewsBlurResponse {
@SerializedName("feeds")
public HashMap<String, Feed> feeds;

View file

@ -15,6 +15,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.annotations.SerializedName;
import com.google.gson.stream.JsonReader;
import com.newsblur.domain.Feed;
import com.newsblur.domain.SocialFeed;
import com.newsblur.util.AppConstants;
@ -37,6 +38,7 @@ public class FeedFolderResponse {
public FeedFolderResponse(String json, Gson gson) {
// TODO: is there really any good reason the default GSON parser doesn't work here?
JsonParser parser = new JsonParser();
JsonObject asJsonObject = parser.parse(json).getAsJsonObject();

View file

@ -7,7 +7,7 @@ import android.content.ContentValues;
import com.google.gson.annotations.SerializedName;
import com.newsblur.database.DatabaseConstants;
public class FeedRefreshResponse {
public class FeedRefreshResponse extends NewsBlurResponse {
@SerializedName("feeds")

View file

@ -1,13 +0,0 @@
package com.newsblur.network.domain;
public class LoginResponse {
// {"code": -1, "authenticated": false, "errors": {"__all__": ["That username is not registered. Create an account with it instead."]}, "result": "ok"}
// {"code": -1, "authenticated": false, "errors": {"username": ["Please enter a username."]}, "result": "ok"}
public boolean authenticated;
public int code;
public LoginErrors errors;
public String result;
}

View file

@ -1,11 +0,0 @@
package com.newsblur.network.domain;
public class Message {
// {"message": "Overloaded, no autocomplete results.", "code": -1, "authenticated": true, "result": "ok"}
public String message;
public int code;
public boolean authenticated;
public String result;
}

View file

@ -0,0 +1,35 @@
package com.newsblur.network.domain;
/**
* A generic response to an API call that only encapsuates success versus failure.
*/
public class NewsBlurResponse {
public boolean authenticated;
public int code;
public String message;
public ResponseErrors errors;
public String result;
public boolean isError() {
if (message != null) return true;
if ((errors != null) && (errors.message.length > 0) && (errors.message[0] != null)) return true;
return false;
}
/**
* Gets the error message returned by the API, or defaultMessage if none was found.
*/
public String getErrorMessage(String defaultMessage) {
if (message != null) return message;
if ((errors != null) && (errors.message.length > 0) && (errors.message[0] != null)) return errors.message[0];
return defaultMessage;
}
/**
* Gets the error message returned by the API, or a simple numeric error code if non was found.
*/
public String getErrorMessage() {
return getErrorMessage(Integer.toString(code));
}
}

View file

@ -3,7 +3,7 @@ package com.newsblur.network.domain;
import com.google.gson.annotations.SerializedName;
import com.newsblur.domain.UserDetails;
public class ProfileResponse {
public class ProfileResponse extends NewsBlurResponse {
@SerializedName("user_profile")
public UserDetails user;

View file

@ -0,0 +1,8 @@
package com.newsblur.network.domain;
public class RegisterResponse extends NewsBlurResponse {
public String[] email;
public String[] username;
}

View file

@ -2,7 +2,7 @@ package com.newsblur.network.domain;
import com.google.gson.annotations.SerializedName;
public class LoginErrors {
public class ResponseErrors {
@SerializedName("__all__")
public String[] message;

View file

@ -1,16 +1,12 @@
package com.newsblur.network.domain;
import java.io.Serializable;
import com.google.gson.annotations.SerializedName;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserProfile;
public class SocialFeedResponse implements Serializable {
public class SocialFeedResponse extends NewsBlurResponse {
private static final long serialVersionUID = 1L;
@SerializedName("stories")
public Story[] stories;
@ -20,6 +16,5 @@ public class SocialFeedResponse implements Serializable {
@SerializedName("user_profiles")
public UserProfile[] userProfiles;
public boolean authenticated;
}
}

View file

@ -5,7 +5,7 @@ import com.newsblur.domain.Classifier;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserProfile;
public class StoriesResponse {
public class StoriesResponse extends NewsBlurResponse {
@SerializedName("stories")
public Story[] stories;
@ -15,6 +15,4 @@ public class StoriesResponse {
public Classifier classifiers;
public boolean authenticated;
}

View file

@ -18,7 +18,6 @@ import com.newsblur.R;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
import com.newsblur.domain.ValueMultimap;
import com.newsblur.network.APIClient;
import com.newsblur.network.APIConstants;
import com.newsblur.network.APIManager;
import com.newsblur.network.domain.SocialFeedResponse;
@ -69,7 +68,6 @@ public class SyncService extends IntentService {
public static final int EXTRA_TASK_MULTISOCIALFEED_UPDATE = 40;
public static final int EXTRA_TASK_STARRED_STORIES_UPDATE = 42;
public APIClient apiClient;
private APIManager apiManager;
private ContentResolver contentResolver;
public static final String SYNCSERVICE_TASK = "syncservice_task";

View file

@ -25,6 +25,7 @@ import com.newsblur.database.FeedProvider;
import com.newsblur.domain.Story;
import com.newsblur.domain.ValueMultimap;
import com.newsblur.network.APIManager;
import com.newsblur.network.domain.NewsBlurResponse;
import com.newsblur.service.SyncService;
public class FeedUtils {
@ -35,17 +36,17 @@ public class FeedUtils {
if (story != null) {
final String feedId = story.feedId;
final String storyId = story.id;
new AsyncTask<Void, Void, Boolean>() {
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
protected Boolean doInBackground(Void... arg) {
protected NewsBlurResponse doInBackground(Void... arg) {
return apiManager.markStoryAsStarred(feedId, storyId);
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
protected void onPostExecute(NewsBlurResponse result) {
if (!result.isError()) {
Toast.makeText(context, R.string.toast_story_saved, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, R.string.toast_story_save_error, Toast.LENGTH_LONG).show();
Toast.makeText(context, result.getErrorMessage(), Toast.LENGTH_LONG).show();
}
}
}.execute();
@ -54,6 +55,25 @@ public class FeedUtils {
}
}
public static void markStoryUnread( final Story story, final Context context, final APIManager apiManager) {
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
protected NewsBlurResponse doInBackground(Void... arg) {
return apiManager.markStoryAsUnread(story.feedId, story.id);
}
@Override
protected void onPostExecute(NewsBlurResponse result) {
if (!result.isError()) {
Toast.makeText(context, R.string.toast_story_unread, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, result.getErrorMessage(), Toast.LENGTH_LONG).show();
}
}
}.execute();
}
/**
* This utility method is a fast-returning way to mark as read a batch of stories in both
* the local DB and on the server.

View file

@ -1 +0,0 @@
../clients/android