Several changes in one commit (bad me)

- Speed up main feed list refresh by not downloading feed icons and using
   nested feed list (rather than flat)

 - Update icons on main feed list in background now that they are no
   longer downloaded from sync

 - Cache gson builder as to not take penalty to rebuild it for each parse

 - Perform bulk insert of records into Sqlite DB during main feed list sync

 - Add initial support for shared feed button off story page (still needs a bit of work)

 - Add 'mark item read' and 'mark previous items read' functionality to
   the feed story list view
This commit is contained in:
Lance Johnson 2013-03-28 22:43:24 -05:00
parent dc0507f43f
commit 60a828a914
26 changed files with 453 additions and 130 deletions

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#1e78c1"
android:startColor="#42aaff" />
<corners
android:bottomLeftRadius="5dp"
android:bottomRightRadius="5dp"
android:topLeftRadius="5dp"
android:topRightRadius="5dp" />
</shape>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#2379bf"
android:startColor="#1e78c1" />
<corners
android:bottomLeftRadius="5dp"
android:bottomRightRadius="5dp"
android:topLeftRadius="5dp"
android:topRightRadius="5dp" />
</shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_pressed="true" android:drawable="@drawable/savebutton_background_pressed" />
<item android:drawable="@drawable/savebutton_background_default" />
</selector>

View file

@ -13,6 +13,19 @@
android:padding="10dp"
android:text="@string/share_this" />
<Button
android:id="@+id/save_story_button"
style="@style/saveButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:layout_marginTop="12dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:background="@drawable/selector_savebutton_background"
android:padding="10dp"
android:text="@string/save_this" />
<RelativeLayout
android:id="@+id/reading_shared_container"
android:layout_width="match_parent"

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/menu_mark_story_as_read"
android:title="@string/menu_mark_story_as_read" />
<item android:id="@+id/menu_mark_previous_stories_as_read"
android:title="@string/menu_mark_previous_stories_as_read" />
<!-- TODO: Share/Unshare, Save/Unsave, mark unread -->
</menu>

View file

@ -59,6 +59,8 @@
<string name="error_sharing">There was an problem sharing this story.</string>
<string name="share_newsblur">Share \"%s\" to your Blurblog?</string>
<string name="save_this">Save this story</string>
<string name="reply_to">Reply to \"%s\"</string>
<string name="alert_dialog_ok">Okay</string>
@ -93,6 +95,8 @@
<string name="menu_sharenewsblur">Share this story</string>
<string name="menu_textsize">Adjust text size</string>
<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="toast_marked_folder_as_read">Folder marked as read</string>
<string name="toast_marked_feed_as_read">Feed marked as read</string>

View file

@ -35,6 +35,18 @@
<item name="android:padding">10dp</item>
</style>
<style name="saveButton" parent="@android:style/Widget.Button">
<item name="android:background">@drawable/selector_sharebutton_background</item>
<item name="android:textColor">@color/button_text_selector</item>
<item name="android:textStyle">normal</item>
<item name="android:shadowDx">0</item>
<item name="android:shadowDy">0</item>
<item name="android:shadowRadius">5</item>
<item name="android:shadowColor">#003366</item>
<item name="android:textSize">16sp</item>
<item name="android:padding">10dp</item>
</style>
<style name="button" parent="@android:style/Widget.Button">
<item name="android:background">@drawable/selector_button_background</item>

View file

@ -69,12 +69,11 @@ public class FeedItemsList extends ItemsList {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (!super.onOptionsItemSelected(item)) {
switch (item.getItemId()) {
case R.id.menu_delete_feed:
deleteFeed();
return true;
default:
return false;
if (item.getItemId() == R.id.menu_delete_feed) {
deleteFeed();
return true;
} else {
return false;
}
} else {
return true;

View file

@ -62,14 +62,12 @@ public abstract class ItemsList extends SherlockFragmentActivity implements Sync
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.menu_mark_all_as_read:
markItemListAsRead();
return true;
if (item.getItemId() == android.R.id.home) {
finish();
return true;
} else if (item.getItemId() == R.id.menu_mark_all_as_read) {
markItemListAsRead();
return true;
}
return false;

View file

@ -90,19 +90,18 @@ public class Main extends SherlockFragmentActivity implements StateChangedListen
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_profile:
if (item.getItemId() == R.id.menu_profile) {
Intent profileIntent = new Intent(this, Profile.class);
startActivity(profileIntent);
return true;
case R.id.menu_refresh:
} else if (item.getItemId() == R.id.menu_refresh) {
triggerRecount();
return true;
case R.id.menu_add_feed:
} else if (item.getItemId() == R.id.menu_add_feed) {
Intent intent = new Intent(this, SearchForFeeds.class);
startActivityForResult(intent, 0);
return true;
case R.id.menu_logout:
} else if (item.getItemId() == R.id.menu_logout) {
DialogFragment newFragment = new LogoutDialogFragment();
newFragment.show(getSupportFragmentManager(), "dialog");
}

View file

@ -39,6 +39,7 @@ import com.newsblur.fragment.SyncUpdateFragment;
import com.newsblur.fragment.TextSizeDialogFragment;
import com.newsblur.network.APIManager;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
@ -119,25 +120,24 @@ public abstract class Reading extends SherlockFragmentActivity implements OnPage
Story story = readingAdapter.getStory(currentItem);
UserDetails user = PrefsUtils.getUserDetails(this);
switch (item.getItemId()) {
case android.R.id.home:
if (item.getItemId() == android.R.id.home) {
finish();
return true;
case R.id.menu_reading_original:
} else if (item.getItemId() == R.id.menu_reading_original) {
if (story != null) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(story.permalink));
startActivity(i);
}
return true;
case R.id.menu_reading_sharenewsblur:
} else if (item.getItemId() == R.id.menu_reading_sharenewsblur) {
if (story != null) {
ReadingItemFragment currentFragment = (ReadingItemFragment) readingAdapter.instantiateItem(pager, currentItem);
DialogFragment newFragment = ShareDialogFragment.newInstance(currentFragment, story, currentFragment.previouslySavedShareText);
newFragment.show(getSupportFragmentManager(), "dialog");
}
return true;
case R.id.menu_shared:
} else if (item.getItemId() == R.id.menu_shared) {
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("text/plain");
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
@ -146,35 +146,15 @@ public abstract class Reading extends SherlockFragmentActivity implements OnPage
intent.putExtra(Intent.EXTRA_TEXT, String.format(shareString, new Object[] { story.title, story.permalink }));
startActivity(Intent.createChooser(intent, "Share using"));
return true;
case R.id.menu_textsize:
} else if (item.getItemId() == R.id.menu_textsize) {
float currentValue = getSharedPreferences(PrefConstants.PREFERENCES, 0).getFloat(PrefConstants.PREFERENCE_TEXT_SIZE, 0.5f);
TextSizeDialogFragment textSize = TextSizeDialogFragment.newInstance(currentValue);
textSize.show(getSupportFragmentManager(), TEXT_SIZE);
return true;
case R.id.menu_reading_save:
if (story != null) {
final String feedId = story.feedId;
final String storyId = story.id;
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... arg) {
return apiManager.markStoryAsStarred(feedId, storyId);
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
Toast.makeText(Reading.this, R.string.toast_story_saved, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(Reading.this, R.string.toast_story_save_error, Toast.LENGTH_LONG).show();
}
}
}.execute();
} else {
Log.w(this.getClass().getName(), "Couldn't save story, no selection found.");
}
return true;
default:
} else if (item.getItemId() == R.id.menu_reading_save) {
FeedUtils.saveStory(story, Reading.this, apiManager);
return true;
} else {
return super.onOptionsItemSelected(item);
}
}

View file

@ -83,16 +83,12 @@ public class SearchForFeeds extends SherlockFragmentActivity implements LoaderCa
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_search:
if (item.getItemId() == R.id.menu_search) {
onSearchRequested();
return true;
case android.R.id.home:
} else if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}

View file

@ -1,5 +1,8 @@
package com.newsblur.database;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Color;
@ -82,4 +85,20 @@ public class FeedItemsAdapter extends SimpleCursorAdapter {
return v;
}
public Story getStory(int position) {
cursor.moveToPosition(position);
return Story.fromCursor(cursor);
}
public ArrayList<Story> getPreviousStories(int position) {
ArrayList<Story> stories = new ArrayList<Story>();
cursor.moveToPosition(0);
for(int i=0;i<=position && position < cursor.getCount();i++) {
Story story = Story.fromCursor(cursor);
stories.add(story);
cursor.moveToNext();
}
return stories;
}
}

View file

@ -1,7 +1,6 @@
package com.newsblur.database;
import com.newsblur.util.AppConstants;
import android.R.string;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
@ -11,6 +10,8 @@ import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import com.newsblur.util.AppConstants;
public class FeedProvider extends ContentProvider {
private static final String TAG = "FeedProvider";
@ -146,6 +147,41 @@ public class FeedProvider extends ContentProvider {
return null;
}
@Override
public int bulkInsert(Uri uri, ContentValues[] valuesArray) {
int count = 0;
final SQLiteDatabase db = databaseHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case ALL_FOLDERS:
db.beginTransaction();
try {
for(ContentValues values: valuesArray) {
db.insertWithOnConflict(DatabaseConstants.FEED_FOLDER_MAP_TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
count++;
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
break;
case ALL_SOCIAL_FEEDS:
db.beginTransaction();
try {
for(ContentValues values: valuesArray) {
db.insertWithOnConflict(DatabaseConstants.SOCIALFEED_TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
count++;
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
break;
default:
count = super.bulkInsert(uri, valuesArray);
}
return count;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = databaseHelper.getWritableDatabase();

View file

@ -1,6 +1,9 @@
package com.newsblur.fragment;
import java.util.ArrayList;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
@ -9,9 +12,13 @@ import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v4.widget.SimpleCursorAdapter;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
@ -27,16 +34,18 @@ import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedItemsAdapter;
import com.newsblur.database.FeedProvider;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Story;
import com.newsblur.network.MarkStoryAsReadTask;
import com.newsblur.util.NetworkUtils;
import com.newsblur.view.FeedItemViewBinder;
public class FeedItemListFragment extends ItemListFragment implements LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, OnScrollListener {
public class FeedItemListFragment extends ItemListFragment implements LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, OnScrollListener, OnCreateContextMenuListener {
private static final String TAG = "itemListFragment";
public static final String FRAGMENT_TAG = "itemListFragment";
private ContentResolver contentResolver;
private String feedId;
private SimpleCursorAdapter adapter;
private FeedItemsAdapter adapter;
private Uri storiesUri;
private int currentState;
private int currentPage = 1;
@ -95,6 +104,7 @@ public class FeedItemListFragment extends ItemListFragment implements LoaderMana
adapter.setViewBinder(new FeedItemViewBinder(getActivity()));
itemList.setAdapter(adapter);
itemList.setOnItemClickListener(this);
itemList.setOnCreateContextMenuListener(this);
return v;
}
@ -142,7 +152,11 @@ public class FeedItemListFragment extends ItemListFragment implements LoaderMana
public void changeState(int state) {
currentState = state;
final String selection = FeedProvider.getStorySelectionFromState(state);
refreshStories();
}
private void refreshStories() {
final String selection = FeedProvider.getStorySelectionFromState(currentState);
Cursor cursor = contentResolver.query(storiesUri, null, selection, null, DatabaseConstants.STORY_DATE + " DESC");
adapter.swapCursor(cursor);
}
@ -160,5 +174,57 @@ public class FeedItemListFragment extends ItemListFragment implements LoaderMana
public void onScrollStateChanged(AbsListView view, int scrollState) { }
@Override
public boolean onContextItemSelected(MenuItem item) {
final AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
if (item.getItemId() == R.id.menu_mark_story_as_read) {
final Story story = adapter.getStory(menuInfo.position);
ArrayList<String> storyIdsToMarkRead = new ArrayList<String>();
storyIdsToMarkRead.add(story.id);
new MarkStoryAsReadTask(getActivity(), storyIdsToMarkRead, feedId) {
@Override
protected void onPostExecute(Void result) {
// TODO this isn't sufficient. We also need to update counts
ContentValues values = new ContentValues();
values.put(DatabaseConstants.STORY_READ, true);
contentResolver.update(FeedProvider.STORY_URI.buildUpon().appendPath(story.id).build(), values, null, null);
refreshStories();
}
}.execute();
} else if (item.getItemId() == R.id.menu_mark_previous_stories_as_read) {
ArrayList<Story> previousStories = adapter.getPreviousStories(menuInfo.position);
final ArrayList<String> storyIdsToMarkRead = new ArrayList<String>();
for(Story story: previousStories) {
if(story.read == 0) {
storyIdsToMarkRead.add(story.id);
}
}
new MarkStoryAsReadTask(getActivity(), storyIdsToMarkRead, feedId) {
@Override
protected void onPostExecute(Void result) {
// TODO this isn't sufficient. We also need to update counts
ContentValues values = new ContentValues();
values.put(DatabaseConstants.STORY_READ, true);
for(String storyId: storyIdsToMarkRead) {
contentResolver.update(FeedProvider.STORY_URI.buildUpon().appendPath(storyId).build(), values, null, null);
}
refreshStories();
}
}.execute();
}
return super.onContextItemSelected(item);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.context_story, menu);
}
}

View file

@ -27,6 +27,7 @@ import com.newsblur.activity.AllStoriesItemsList;
import com.newsblur.activity.FeedItemsList;
import com.newsblur.activity.ItemsList;
import com.newsblur.activity.Main;
import com.newsblur.activity.NewsBlurApplication;
import com.newsblur.activity.SocialFeedItemsList;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
@ -36,6 +37,7 @@ import com.newsblur.network.MarkFeedAsReadTask;
import com.newsblur.network.MarkFolderAsReadTask;
import com.newsblur.network.MarkSocialFeedAsReadTask;
import com.newsblur.util.AppConstants;
import com.newsblur.util.ImageLoader;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.UIUtils;
import com.newsblur.view.FolderTreeViewBinder;
@ -64,7 +66,8 @@ public class FolderListFragment extends Fragment implements OnGroupClickListener
Cursor countCursor = resolver.query(FeedProvider.FEED_COUNT_URI, null, DatabaseConstants.SOCIAL_INTELLIGENCE_SOME, null, null);
Cursor sharedCountCursor = resolver.query(FeedProvider.SOCIALCOUNT_URI, null, DatabaseConstants.SOCIAL_INTELLIGENCE_SOME, null, null);
groupViewBinder = new FolderTreeViewBinder();
ImageLoader imageLoader = ((NewsBlurApplication) getActivity().getApplicationContext()).getImageLoader();
groupViewBinder = new FolderTreeViewBinder(imageLoader);
blogViewBinder = new SocialFeedViewBinder(getActivity());
leftBound = UIUtils.convertDPsToPixels(getActivity(), 20);
@ -72,7 +75,7 @@ public class FolderListFragment extends Fragment implements OnGroupClickListener
final String[] groupFrom = new String[] { DatabaseConstants.FOLDER_NAME, DatabaseConstants.SUM_POS, DatabaseConstants.SUM_NEUT };
final int[] groupTo = new int[] { R.id.row_foldername, R.id.row_foldersumpos, R.id.row_foldersumneu };
final String[] childFrom = new String[] { DatabaseConstants.FEED_TITLE, DatabaseConstants.FEED_FAVICON, DatabaseConstants.FEED_NEUTRAL_COUNT, DatabaseConstants.FEED_POSITIVE_COUNT };
final String[] childFrom = new String[] { DatabaseConstants.FEED_TITLE, DatabaseConstants.FEED_FAVICON_URL, DatabaseConstants.FEED_NEUTRAL_COUNT, DatabaseConstants.FEED_POSITIVE_COUNT };
final int[] childTo = new int[] { R.id.row_feedname, R.id.row_feedfavicon, R.id.row_feedneutral, R.id.row_feedpositive };
final String[] blogFrom = new String[] { DatabaseConstants.SOCIAL_FEED_TITLE, DatabaseConstants.SOCIAL_FEED_ICON, DatabaseConstants.SOCIAL_FEED_NEUTRAL_COUNT, DatabaseConstants.SOCIAL_FEED_POSITIVE_COUNT };
final int[] blogTo = new int[] { R.id.row_socialfeed_name, R.id.row_socialfeed_icon, R.id.row_socialsumneu, R.id.row_socialsumpos };
@ -152,9 +155,7 @@ public class FolderListFragment extends Fragment implements OnGroupClickListener
@Override
public boolean onContextItemSelected(MenuItem item) {
final ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) item.getMenuInfo();
switch (item.getItemId()) {
case R.id.menu_mark_feed_as_read:
if (item.getItemId() == R.id.menu_mark_feed_as_read) {
new MarkFeedAsReadTask(getActivity(), apiManager) {
@Override
protected void onPostExecute(Boolean result) {
@ -172,13 +173,11 @@ public class FolderListFragment extends Fragment implements OnGroupClickListener
}
}.execute(Long.toString(info.id));
return true;
case R.id.menu_delete_feed:
} else if (item.getItemId() == R.id.menu_delete_feed) {
Toast.makeText(getActivity(), "Deleted feed", Toast.LENGTH_SHORT).show();
((Main) getActivity()).deleteFeed(info.id, null);
return true;
case R.id.menu_mark_folder_as_read:
} else if (item.getItemId() == R.id.menu_mark_folder_as_read) {
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
if (folderAdapter.isExpandable(groupPosition)) {
final Cursor folderCursor = ((MixedExpandableListAdapter) list.getExpandableListAdapter()).getGroup(groupPosition);

View file

@ -98,13 +98,10 @@ public class LoginRegisterFragment extends Fragment implements OnClickListener {
@Override
public void onClick(View viewClicked) {
switch (viewClicked.getId()) {
case R.id.login_button:
if (viewClicked.getId() == R.id.login_button) {
logIn();
break;
case R.id.registration_button:
} else if (viewClicked.getId() == R.id.registration_button) {
signUp();
break;
}
}

View file

@ -179,13 +179,10 @@ public class ProfileDetailsFragment extends Fragment implements OnClickListener
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.profile_follow_button:
new FollowTask().execute();
break;
case R.id.profile_unfollow_button:
new UnfollowTask().execute();
break;
if (v.getId() == R.id.profile_follow_button) {
new FollowTask().execute();
} else if (v.getId() == R.id.profile_unfollow_button) {
new UnfollowTask().execute();
}
}

View file

@ -25,12 +25,14 @@ import android.widget.TextView;
import com.newsblur.R;
import com.newsblur.activity.NewsBlurApplication;
import com.newsblur.activity.Reading;
import com.newsblur.domain.Classifier;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserDetails;
import com.newsblur.network.APIManager;
import com.newsblur.network.SetupCommentSectionTask;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.ImageLoader;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.PrefsUtils;
@ -119,6 +121,7 @@ public class ReadingItemFragment extends Fragment implements ClassifierDialogFra
setupWebview(web);
setupItemMetadata();
setupShareButton();
setupSaveButton();
if (story.sharedUserIds.length > 0 || story.commentCount > 0 ) {
view.findViewById(R.id.reading_share_bar).setVisibility(View.VISIBLE);
@ -129,6 +132,18 @@ public class ReadingItemFragment extends Fragment implements ClassifierDialogFra
return view;
}
private void setupSaveButton() {
Button saveButton = (Button) view.findViewById(R.id.save_story_button);
saveButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
FeedUtils.saveStory(story, getActivity(), apiManager);
}
});
}
private void setupShareButton() {
Button shareButton = (Button) view.findViewById(R.id.share_story_button);

View file

@ -6,8 +6,7 @@ public class APIConstants {
// they are not.
public static final String URL_LOGIN = "http://newsblur.com/api/login";
public static final String URL_FEEDS = "http://newsblur.com/reader/feeds/?include_favicons=true&flat=true";
public static final String URL_FEEDS_NO_UPDATE = "http://newsblur.com/reader/feeds/?include_favicons=true&flat=true&update_counts=false";
public static final String URL_FEEDS = "http://newsblur.com/reader/feeds/";
public static final String URL_USER_PROFILE = "http://newsblur.com/social/profile";
public static final String URL_MY_PROFILE = "http://newsblur.com/social/load_user_profile";
public static final String URL_FOLLOW = "http://newsblur.com/social/follow";

View file

@ -48,15 +48,21 @@ public class APIManager {
private static final String TAG = "APIManager";
private Context context;
private Gson gson;
private static Gson gson;
private ContentResolver contentResolver;
public APIManager(final Context context) {
this.context = context;
final GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Date.class, new DateStringTypeAdapter());
contentResolver = context.getContentResolver();
gson = builder.create();
initGsonIfNeededBuilder();
}
private static synchronized void initGsonIfNeededBuilder() {
if(gson == null) {
final GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Date.class, new DateStringTypeAdapter());
gson = builder.create();
}
}
public LoginResponse login(final String username, final String password) {
@ -445,10 +451,9 @@ public class APIManager {
public boolean getFolderFeedMapping(boolean doUpdateCounts) {
final APIClient client = new APIClient(context);
final APIResponse response = client.get(doUpdateCounts ? APIConstants.URL_FEEDS : APIConstants.URL_FEEDS_NO_UPDATE);
final FeedFolderResponse feedUpdate = gson.fromJson(response.responseString, FeedFolderResponse.class);
final APIResponse response = client.get(APIConstants.URL_FEEDS);
final FeedFolderResponse feedUpdate = new FeedFolderResponse(response.responseString, gson);
if (response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
if (feedUpdate.folders.size() == 0) {
@ -457,11 +462,15 @@ public class APIManager {
HashMap<String, Feed> existingFeeds = getExistingFeeds();
List<ContentValues> feedValues = new ArrayList<ContentValues>();
for (String newFeedId : feedUpdate.feeds.keySet()) {
if (existingFeeds.get(newFeedId) == null || !feedUpdate.feeds.get(newFeedId).equals(existingFeeds.get(newFeedId))) {
contentResolver.insert(FeedProvider.FEEDS_URI, feedUpdate.feeds.get(newFeedId).getValues());
feedValues.add(feedUpdate.feeds.get(newFeedId).getValues());
}
}
if(feedValues.size() > 0) {
contentResolver.bulkInsert(FeedProvider.FEEDS_URI, feedValues.toArray(new ContentValues[feedValues.size()]));
}
for (String olderFeedId : existingFeeds.keySet()) {
if (feedUpdate.feeds.get(olderFeedId) == null) {
@ -470,10 +479,13 @@ public class APIManager {
}
}
List<ContentValues> socialFeedValues = new ArrayList<ContentValues>();
for (final SocialFeed feed : feedUpdate.socialFeeds) {
contentResolver.insert(FeedProvider.SOCIAL_FEEDS_URI, feed.getValues());
socialFeedValues.add(feed.getValues());
}
if(socialFeedValues.size() > 0) {
contentResolver.bulkInsert(FeedProvider.SOCIAL_FEEDS_URI, socialFeedValues.toArray(new ContentValues[socialFeedValues.size()]));
}
Cursor folderCursor = contentResolver.query(FeedProvider.FOLDERS_URI, null, null, null, null);
folderCursor.moveToFirst();

View file

@ -16,6 +16,10 @@ public class MarkStoryAsReadTask extends AsyncTask<Void, Void, Void> {
private final String feedId;
private final ArrayList<String> storyIds;
public MarkStoryAsReadTask(final Context context, final ArrayList<String> storyIds, final String feedId) {
this(context, null, storyIds, feedId);
}
public MarkStoryAsReadTask(final Context context, final SyncUpdateFragment fragment, final ArrayList<String> storyIds, final String feedId) {
this.context = context;
this.receiver = fragment;
@ -26,7 +30,9 @@ public class MarkStoryAsReadTask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... stories) {
final Intent intent = new Intent(Intent.ACTION_SYNC, null, context, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, receiver.receiver);
if(receiver != null) {
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, receiver.receiver);
}
intent.putExtra(SyncService.SYNCSERVICE_TASK, SyncService.EXTRA_TASK_MARK_STORY_READ);
intent.putExtra(SyncService.EXTRA_TASK_FEED_ID, feedId);
intent.putStringArrayListExtra(SyncService.EXTRA_TASK_STORY_ID, storyIds);

View file

@ -1,8 +1,18 @@
package com.newsblur.network.domain;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.annotations.SerializedName;
import com.newsblur.domain.Feed;
import com.newsblur.domain.SocialFeed;
@ -21,5 +31,88 @@ public class FeedFolderResponse {
@SerializedName("social_feeds")
public SocialFeed[] socialFeeds;
public FeedFolderResponse() {
}
public FeedFolderResponse(String json, Gson gson) {
// This is a mess but I don't see a way to parse the mixed content in
// folders w/o going to low level Gson API
JsonParser parser = new JsonParser();
JsonObject asJsonObject = parser.parse(json).getAsJsonObject();
JsonArray jsonFoldersArray = (JsonArray) asJsonObject.get("folders");
ArrayList<String> nestedFolderList = new ArrayList<String>();
folders = new HashMap<String, List<Long>>();
parseFeedArray(nestedFolderList, folders, null, jsonFoldersArray);
JsonElement starredCountElement = asJsonObject.get("starred_count");
if(starredCountElement != null) {
starredCount = gson.fromJson(starredCountElement, int.class);
}
JsonObject feedsElement = (JsonObject) asJsonObject.get("feeds");
if(feedsElement != null) {
feeds = new HashMap<String, Feed>();
Set<Entry<String, JsonElement>> entrySet = feedsElement.entrySet();
Iterator<Entry<String, JsonElement>> iterator = entrySet.iterator();
while(iterator.hasNext()) {
Entry<String, JsonElement> feedElement = iterator.next();
Feed feed = gson.fromJson(feedElement.getValue(), Feed.class);
feeds.put(feedElement.getKey(), feed);
}
}
JsonArray socialFeedsArray = (JsonArray) asJsonObject.get("social_feeds");
if(socialFeedsArray != null) {
List<SocialFeed> socialFeedsList = new ArrayList<SocialFeed>();
for(int i=0;i<socialFeedsArray.size();i++) {
JsonElement jsonElement = socialFeedsArray.get(i);
SocialFeed socialFeed = gson.fromJson(jsonElement, SocialFeed.class);
socialFeedsList.add(socialFeed);
}
socialFeeds = socialFeedsList.toArray(new SocialFeed[socialFeedsArray.size()]);
}
}
private void parseFeed(JsonObject asJsonObject2, List<String> parentFeedNames, Map<String, List<Long>> folders) {
Set<Entry<String, JsonElement>> entrySet = asJsonObject2.entrySet();
Iterator<Entry<String, JsonElement>> iterator = entrySet.iterator();
while(iterator.hasNext()) {
Entry<String, JsonElement> next = iterator.next();
String key = next.getKey();
JsonArray value = (JsonArray) next.getValue();
parseFeedArray(parentFeedNames, folders, key, value);
}
}
private void parseFeedArray(List<String> nestedFolderList,
Map<String, List<Long>> folders, String name, JsonArray arrayValue) {
String fullFolderName = getFolderName(name, nestedFolderList);
ArrayList<Long> feedIds = new ArrayList<Long>();
for(int k=0;k<arrayValue.size();k++) {
JsonElement jsonElement = arrayValue.get(k);
if(jsonElement.isJsonPrimitive()) {
feedIds.add(jsonElement.getAsLong());
} else {
List<String> nestedFolerListCopy = new ArrayList<String>(nestedFolderList);
if(name != null) {
nestedFolerListCopy.add(name);
}
parseFeed((JsonObject) jsonElement, nestedFolerListCopy, folders);
}
}
folders.put(fullFolderName, feedIds);
}
private String getFolderName(String key, List<String> parentFeedNames) {
StringBuilder builder = new StringBuilder();
for(String parentFolder: parentFeedNames) {
builder.append(parentFolder);
builder.append(" - ");
}
if(key != null) {
builder.append(key);
}
return builder.toString();
}
}

View file

@ -0,0 +1,36 @@
package com.newsblur.util;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;
import com.newsblur.R;
import com.newsblur.domain.Story;
import com.newsblur.network.APIManager;
public class FeedUtils {
public static void saveStory(final Story story, final Context context, final APIManager apiManager) {
if (story != null) {
final String feedId = story.feedId;
final String storyId = story.id;
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... arg) {
return apiManager.markStoryAsStarred(feedId, storyId);
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
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();
}
}
}.execute();
} else {
Log.w(FeedUtils.class.getName(), "Couldn't save story, no selection found.");
}
}
}

View file

@ -7,7 +7,6 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v4.widget.SimpleCursorAdapter.ViewBinder;
import android.text.TextUtils;
import android.util.Base64;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
@ -17,24 +16,28 @@ import com.newsblur.R;
import com.newsblur.activity.FolderItemsList;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.util.AppConstants;
import com.newsblur.util.ImageLoader;
public class FolderTreeViewBinder implements ViewBinder {
private int currentState = AppConstants.STATE_SOME;
private int READING_RETURNED = 0x02;
private final ImageLoader imageLoader;
public FolderTreeViewBinder(ImageLoader imageLoader) {
this.imageLoader = imageLoader;
}
@Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
if (TextUtils.equals(cursor.getColumnName(columnIndex), DatabaseConstants.FEED_FAVICON)) {
Bitmap bitmap = null;
if (cursor.getBlob(columnIndex) != null) {
final byte[] data = Base64.decode(cursor.getBlob(columnIndex), Base64.DEFAULT);
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
if (TextUtils.equals(cursor.getColumnName(columnIndex), DatabaseConstants.FEED_FAVICON_URL)) {
if (cursor.getString(columnIndex) != null) {
String imageUrl = cursor.getString(columnIndex);
imageLoader.displayImage(imageUrl, (ImageView)view);
} else {
Bitmap bitmap = BitmapFactory.decodeResource(view.getContext().getResources(), R.drawable.world);
((ImageView) view).setImageBitmap(bitmap);
}
if (bitmap == null) {
bitmap = BitmapFactory.decodeResource(view.getContext().getResources(), R.drawable.world);
}
((ImageView) view).setImageBitmap(bitmap);
return true;
} else if (TextUtils.equals(cursor.getColumnName(columnIndex), DatabaseConstants.FEED_POSITIVE_COUNT) || TextUtils.equals(cursor.getColumnName(columnIndex), DatabaseConstants.SUM_POS)) {
int feedPositive = cursor.getInt(columnIndex);

View file

@ -64,25 +64,21 @@ public class StateToggleButton extends LinearLayout implements OnClickListener {
}
public void setState(final int state) {
switch (state) {
case R.id.toggle_all:
allButton.setEnabled(false);
someButton.setEnabled(true);
focusButton.setEnabled(true);
CURRENT_STATE = AppConstants.STATE_ALL;
break;
case R.id.toggle_some:
allButton.setEnabled(true);
someButton.setEnabled(false);
focusButton.setEnabled(true);
CURRENT_STATE = AppConstants.STATE_SOME;
break;
case R.id.toggle_focus:
allButton.setEnabled(true);
someButton.setEnabled(true);
focusButton.setEnabled(false);
CURRENT_STATE = AppConstants.STATE_BEST;
break;
if (state == R.id.toggle_all) {
allButton.setEnabled(false);
someButton.setEnabled(true);
focusButton.setEnabled(true);
CURRENT_STATE = AppConstants.STATE_ALL;
} else if (state == R.id.toggle_some) {
allButton.setEnabled(true);
someButton.setEnabled(false);
focusButton.setEnabled(true);
CURRENT_STATE = AppConstants.STATE_SOME;
} else if (state == R.id.toggle_focus) {
allButton.setEnabled(true);
someButton.setEnabled(true);
focusButton.setEnabled(false);
CURRENT_STATE = AppConstants.STATE_BEST;
}
}