Recently-read stories view. (#561)

This commit is contained in:
dosiecki 2015-05-03 22:20:38 -07:00
parent b6c20eccb6
commit 555ec171d9
20 changed files with 391 additions and 30 deletions

View file

@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.newsblur"
android:versionCode="90"
android:versionName="4.2.3b4" >
android:versionName="4.2.4b6" >
<uses-sdk
android:minSdkVersion="11"
@ -73,6 +73,9 @@
<activity
android:name=".activity.AllStoriesItemsList" />
<activity
android:name=".activity.ReadStoriesItemsList" />
<activity
android:name=".activity.SavedStoriesItemsList" />
@ -94,6 +97,9 @@
<activity
android:name=".activity.AllStoriesReading"/>
<activity
android:name=".activity.ReadStoriesReading"/>
<activity
android:name=".activity.SavedStoriesReading"/>

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="?selectorFolderBackground" >
<ImageView
android:id="@+id/row_read_icon"
android:layout_width="13dp"
android:layout_height="13dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="13dp"
android:layout_marginRight="13dp"
android:contentDescription="@string/description_row_folder_icon"
android:src="@drawable/g_icn_unread" />
<TextView
android:id="@+id/row_read_stories_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/row_read_icon"
android:paddingBottom="9dp"
android:paddingTop="9dp"
android:text="@string/read_stories_row_title"
style="?selectorRowFeedName"
android:textSize="13dp"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentTop="true"
style="?folderBorderTop" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentBottom="true"
style="?folderBorderBottom" />
</RelativeLayout>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/menu_default_view"
android:title="@string/menu_default_view"
android:showAsAction="never" />
</menu>

View file

@ -41,7 +41,9 @@
<string name="all_stories_row_title">ALL STORIES</string>
<string name="all_shared_stories">ALL SHARED STORIES</string>
<string name="global_shared_stories">GLOBAL SHARED STORIES</string>
<string name="read_stories_row_title">READ STORIES</string>
<string name="saved_stories_row_title">SAVED STORIES</string>
<string name="read_stories_title">Read Stories</string>
<string name="saved_stories_title">Saved Stories</string>
<string name="now">now</string>

View file

@ -0,0 +1,92 @@
package com.newsblur.activity;
import android.os.Bundle;
import android.app.FragmentTransaction;
import android.view.Menu;
import android.view.MenuInflater;
import android.widget.Toast;
import com.newsblur.R;
import com.newsblur.fragment.ReadStoriesItemListFragment;
import com.newsblur.fragment.FeedItemListFragment;
import com.newsblur.util.DefaultFeedView;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.ReadFilter;
import com.newsblur.util.StoryOrder;
import com.newsblur.util.UIUtils;
public class ReadStoriesItemsList extends ItemsList {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
UIUtils.setCustomActionBar(this, R.drawable.g_icn_unread, getResources().getString(R.string.read_stories_title));
itemListFragment = (ReadStoriesItemListFragment) fragmentManager.findFragmentByTag(ReadStoriesItemListFragment.class.getName());
if (itemListFragment == null) {
itemListFragment = ReadStoriesItemListFragment.newInstance(getDefaultFeedView());
itemListFragment.setRetainInstance(true);
FragmentTransaction listTransaction = fragmentManager.beginTransaction();
listTransaction.add(R.id.activity_itemlist_container, itemListFragment, ReadStoriesItemListFragment.class.getName());
listTransaction.commit();
}
}
@Override
protected FeedSet createFeedSet() {
return FeedSet.allRead();
}
@Override
public void markItemListAsRead() {
; // This activity has no mark-as-read action
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.readstories_itemslist, menu);
return true;
}
@Override
protected DefaultFeedView getDefaultFeedView() {
return PrefsUtils.getDefaultFeedViewForFolder(this, PrefConstants.READ_STORIES_FOLDER_NAME);
}
@Override
public void defaultFeedViewChanged(DefaultFeedView value) {
PrefsUtils.setDefaultFeedViewForFolder(this, PrefConstants.READ_STORIES_FOLDER_NAME, value);
if (itemListFragment != null) {
itemListFragment.setDefaultFeedView(value);
}
}
@Override
public StoryOrder getStoryOrder() {
// dummy method. read stories don't have an order option
return StoryOrder.NEWEST;
}
@Override
public void updateStoryOrderPreference(StoryOrder newValue) {
// dummy method. read stories don't have an order option
}
@Override
protected void updateReadFilterPreference(ReadFilter newValue) {
// dummy method. read stories don't have an order option
}
@Override
protected ReadFilter getReadFilter() {
// dummy method. read stories always show reads and unreads
return ReadFilter.ALL;
}
}

View file

@ -0,0 +1,29 @@
package com.newsblur.activity;
import android.database.Cursor;
import android.os.Bundle;
import android.content.Loader;
import com.newsblur.R;
import com.newsblur.database.MixedFeedsReadingAdapter;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.UIUtils;
public class ReadStoriesReading extends Reading {
@Override
protected void onCreate(Bundle savedInstanceBundle) {
super.onCreate(savedInstanceBundle);
UIUtils.setCustomActionBar(this, R.drawable.g_icn_unread, getResources().getString(R.string.read_stories_title));
readingAdapter = new MixedFeedsReadingAdapter(getFragmentManager(), defaultFeedView, null);
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
super.onLoadFinished(loader, cursor);
}
}

View file

@ -32,6 +32,7 @@ import com.newsblur.util.StateFilter;
import com.newsblur.util.StoryOrder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
@ -343,6 +344,12 @@ public class BlurDatabaseHelper {
return folder;
}
public void touchStory(String hash) {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.STORY_LAST_READ_DATE, (new Date()).getTime());
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.STORY_TABLE, values, DatabaseConstants.STORY_HASH + " = ?", new String[]{hash});}
}
public void markStoryHashesRead(List<String> hashes) {
// NOTE: attempting to wrap these updates in a transaction for speed makes them silently fail
for (String hash : hashes) {
@ -783,6 +790,15 @@ public class BlurDatabaseHelper {
DatabaseConstants.appendStorySelectionGroupOrder(q, readFilter, order, stateFilter, DatabaseConstants.STORY_TABLE + "." + DatabaseConstants.STORY_ID);
return rawQuery(q.toString(), null, cancellationSignal);
} else if (fs.isAllRead()) {
StringBuilder q = new StringBuilder(DatabaseConstants.MULTIFEED_STORIES_QUERY_BASE);
q.append(" FROM " + DatabaseConstants.STORY_TABLE);
q.append(DatabaseConstants.JOIN_FEEDS_ON_STORIES);
q.append(" WHERE (" + DatabaseConstants.STORY_LAST_READ_DATE + " > 0)");
q.append(" ORDER BY " + DatabaseConstants.READ_STORY_ORDER);
return rawQuery(q.toString(), null, cancellationSignal);
} else if (fs.isAllSaved()) {
StringBuilder q = new StringBuilder(DatabaseConstants.MULTIFEED_STORIES_QUERY_BASE);

View file

@ -97,6 +97,7 @@ public class DatabaseConstants {
public static final String STORY_HASH = "story_hash";
public static final String STORY_ACTIVE = "active";
public static final String STORY_IMAGE_URLS = "image_urls";
public static final String STORY_LAST_READ_DATE = "last_read_date";
public static final String STORY_TEXT_TABLE = "storytext";
public static final String STORY_TEXT_STORY_HASH = "story_hash";
@ -230,7 +231,8 @@ public class DatabaseConstants {
STORY_STARRED_DATE + INTEGER + ", " +
STORY_TITLE + TEXT + ", " +
STORY_ACTIVE + INTEGER + " DEFAULT 0, " +
STORY_IMAGE_URLS + TEXT +
STORY_IMAGE_URLS + TEXT + ", " +
STORY_LAST_READ_DATE + INTEGER +
")";
static final String STORY_TEXT_SQL = "CREATE TABLE " + STORY_TEXT_TABLE + " (" +
@ -305,7 +307,8 @@ public class DatabaseConstants {
STORY_AUTHORS, STORY_COMMENT_COUNT, STORY_SHORT_CONTENT, STORY_TIMESTAMP, STORY_SHARED_DATE, STORY_SHORTDATE, STORY_LONGDATE,
STORY_TABLE + "." + STORY_FEED_ID, STORY_TABLE + "." + STORY_ID, STORY_INTELLIGENCE_AUTHORS, STORY_INTELLIGENCE_FEED, STORY_INTELLIGENCE_TAGS,
STORY_INTELLIGENCE_TITLE, STORY_PERMALINK, STORY_READ, STORY_STARRED, STORY_STARRED_DATE, STORY_SHARE_COUNT, STORY_TAGS, STORY_TITLE,
STORY_SOCIAL_USER_ID, STORY_SOURCE_USER_ID, STORY_SHARED_USER_IDS, STORY_FRIEND_USER_IDS, STORY_PUBLIC_USER_IDS, STORY_SUM_TOTAL, STORY_HASH
STORY_SOCIAL_USER_ID, STORY_SOURCE_USER_ID, STORY_SHARED_USER_IDS, STORY_FRIEND_USER_IDS, STORY_PUBLIC_USER_IDS, STORY_SUM_TOTAL, STORY_HASH,
STORY_LAST_READ_DATE
};
public static final String MULTIFEED_STORIES_QUERY_BASE =
@ -322,6 +325,7 @@ public class DatabaseConstants {
" INNER JOIN " + SOCIALFEED_TABLE + " ON " + SOCIALFEED_TABLE + "." + SOCIAL_FEED_ID + " = " + SOCIALFEED_STORY_MAP_TABLE + "." + SOCIALFEED_STORY_USER_ID;
public static final String STARRED_STORY_ORDER = STORY_STARRED_DATE + " DESC";
public static final String READ_STORY_ORDER = STORY_LAST_READ_DATE + " DESC";
/**
* Appends to the given story query any and all selection statements that are required to satisfy the specified

View file

@ -47,7 +47,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
public static final int GLOBAL_SHARED_STORIES_GROUP_POSITION = 0;
public static final int ALL_SHARED_STORIES_GROUP_POSITION = 1;
private enum GroupType { GLOBAL_SHARED_STORIES, ALL_SHARED_STORIES, ALL_STORIES, FOLDER, SAVED_STORIES }
private enum GroupType { GLOBAL_SHARED_STORIES, ALL_SHARED_STORIES, ALL_STORIES, FOLDER, READ_STORIES, SAVED_STORIES }
private enum ChildType { SOCIAL_FEED, FEED }
private Cursor socialFeedCursor;
@ -141,6 +141,10 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
} else if (isFolderRoot(groupPosition)) {
v = inflater.inflate(R.layout.row_all_stories, null, false);
bindCountViews(v, totalNeutCount, totalPosCount, true);
} else if (isRowReadStories(groupPosition)) {
if (convertView == null) {
v = inflater.inflate(R.layout.row_read_stories, null, false);
}
} else if (isRowSavedStories(groupPosition)) {
if (convertView == null) {
v = inflater.inflate(R.layout.row_saved_stories, null, false);
@ -255,7 +259,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
// in addition to the real folders returned by the /reader/feeds API, there are virtual folders
// for global shared stories, social feeds and saved stories
if (activeFolderNames == null) return 0;
return (activeFolderNames.size() + 3);
return (activeFolderNames.size() + 4);
}
@Override
@ -266,8 +270,10 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
return Long.MAX_VALUE;
} else if (groupPosition == ALL_SHARED_STORIES_GROUP_POSITION) {
return Long.MAX_VALUE-1;
} else if (isRowSavedStories(groupPosition)) {
} else if (isRowReadStories(groupPosition)) {
return Long.MAX_VALUE-2;
} else if (isRowSavedStories(groupPosition)) {
return Long.MAX_VALUE-3;
} else {
return activeFolderNames.get(convertGroupPositionToActiveFolderIndex(groupPosition)).hashCode();
}
@ -278,7 +284,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
if (groupPosition == ALL_SHARED_STORIES_GROUP_POSITION) {
if (socialFeedCursor == null) return 0;
return socialFeedCursor.getCount();
} else if (isRowSavedStories(groupPosition) || groupPosition == GLOBAL_SHARED_STORIES_GROUP_POSITION) {
} else if (isRowReadStories(groupPosition) || isRowSavedStories(groupPosition) || groupPosition == GLOBAL_SHARED_STORIES_GROUP_POSITION) {
return 0; // these rows never have children
} else {
return activeFolderChildren.get(convertGroupPositionToActiveFolderIndex(groupPosition)).size();
@ -307,6 +313,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
return "[ALL_SHARED_STORIES]";
} else if (groupPosition == GLOBAL_SHARED_STORIES_GROUP_POSITION) {
return "[GLOBAL_SHARED_STORIES]";
} else if (isRowReadStories(groupPosition)) {
return "[READ_STORIES]";
} else if (isRowSavedStories(groupPosition)) {
return "[SAVED_STORIES]";
} else {
@ -323,13 +331,22 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
return ( getGroupUniqueName(groupPosition).equals(AppConstants.ROOT_FOLDER) );
}
/**
* Determines if the row at the specified position is the special "read" folder. This
* row doesn't actually correspond to a row in the DB, much like the social row, but
* it is located at the bottom of the set rather than the top.
*/
public boolean isRowReadStories(int groupPosition) {
return ( groupPosition == (activeFolderNames.size() + 2) );
}
/**
* Determines if the row at the specified position is the special "saved" folder. This
* row doesn't actually correspond to a row in the DB, much like the social row, but
* it is located at the bottom of the set rather than the top.
*/
public boolean isRowSavedStories(int groupPosition) {
return ( groupPosition > (activeFolderNames.size() + 1) );
return ( groupPosition == (activeFolderNames.size() + 3) );
}
public void setSocialFeedCursor(Cursor cursor) {
@ -533,6 +550,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
return GroupType.ALL_SHARED_STORIES.ordinal();
} else if (isFolderRoot(groupPosition)) {
return GroupType.ALL_STORIES.ordinal();
} else if (isRowReadStories(groupPosition)) {
return GroupType.READ_STORIES.ordinal();
} else if (isRowSavedStories(groupPosition)) {
return GroupType.SAVED_STORIES.ordinal();
} else {

View file

@ -97,6 +97,9 @@ public class Story implements Serializable {
@SerializedName("image_urls")
public String[] imageUrls;
// not yet vended by the API, but tracked locally and fudged (see SyncService) for remote stories
public long lastReadTimestamp = 0L;
public ContentValues getValues() {
final ContentValues values = new ContentValues();
values.put(DatabaseConstants.STORY_ID, id);
@ -126,6 +129,7 @@ public class Story implements Serializable {
values.put(DatabaseConstants.STORY_FEED_ID, feedId);
values.put(DatabaseConstants.STORY_HASH, storyHash);
values.put(DatabaseConstants.STORY_IMAGE_URLS, TextUtils.join(",", imageUrls));
values.put(DatabaseConstants.STORY_LAST_READ_DATE, lastReadTimestamp);
return values;
}
@ -160,6 +164,7 @@ public class Story implements Serializable {
story.feedId = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_FEED_ID));
story.id = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_ID));
story.storyHash = cursor.getString(cursor.getColumnIndex(DatabaseConstants.STORY_HASH));
story.lastReadTimestamp = cursor.getLong(cursor.getColumnIndex(DatabaseConstants.STORY_LAST_READ_DATE));
return story;
}

View file

@ -26,6 +26,7 @@ import com.newsblur.R;
import com.newsblur.activity.AllStoriesItemsList;
import com.newsblur.activity.FeedItemsList;
import com.newsblur.activity.ItemsList;
import com.newsblur.activity.ReadStoriesItemsList;
import com.newsblur.activity.SavedStoriesItemsList;
import com.newsblur.activity.SocialFeedItemsList;
import com.newsblur.database.DatabaseConstants;
@ -192,9 +193,9 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
switch(type) {
case ExpandableListView.PACKED_POSITION_TYPE_GROUP:
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
if (! adapter.isRowSavedStories(groupPosition) ) {
inflater.inflate(R.menu.context_folder, menu);
}
if (adapter.isRowSavedStories(groupPosition)) break;
if (adapter.isRowReadStories(groupPosition)) break;
inflater.inflate(R.menu.context_folder, menu);
break;
case ExpandableListView.PACKED_POSITION_TYPE_CHILD:
@ -255,6 +256,10 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
i.putExtra(ItemsList.EXTRA_STATE, currentState);
startActivity(i);
return true;
} else if (adapter.isRowReadStories(groupPosition)) {
Intent i = new Intent(getActivity(), ReadStoriesItemsList.class);
startActivity(i);
return true;
} else if (adapter.isRowSavedStories(groupPosition)) {
Intent i = new Intent(getActivity(), SavedStoriesItemsList.class);
startActivity(i);
@ -279,6 +284,7 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
// these shouldn't ever be collapsible
if (adapter.isFolderRoot(groupPosition)) return;
if (adapter.isRowSavedStories(groupPosition)) return;
if (adapter.isRowReadStories(groupPosition)) return;
String flatGroupName = adapter.getGroupUniqueName(groupPosition);
// save the expanded preference, since the widget likes to forget it
@ -295,6 +301,7 @@ public class FolderListFragment extends NbFragment implements OnGroupClickListen
// these shouldn't ever be collapsible
if (adapter.isFolderRoot(groupPosition)) return;
if (adapter.isRowSavedStories(groupPosition)) return;
if (adapter.isRowReadStories(groupPosition)) return;
String flatGroupName = adapter.getGroupUniqueName(groupPosition);
// save the collapsed preference, since the widget likes to forget it

View file

@ -0,0 +1,67 @@
package com.newsblur.fragment;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View;
import android.widget.AdapterView;
import com.newsblur.R;
import com.newsblur.activity.Reading;
import com.newsblur.activity.ReadStoriesReading;
import com.newsblur.activity.FeedReading;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.MultipleFeedItemsAdapter;
import com.newsblur.util.DefaultFeedView;
import com.newsblur.util.StoryOrder;
import com.newsblur.view.SocialItemViewBinder;
public class ReadStoriesItemListFragment extends ItemListFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public static ItemListFragment newInstance(DefaultFeedView defaultFeedView) {
ItemListFragment fragment = new ReadStoriesItemListFragment();
Bundle args = new Bundle();
args.putSerializable("defaultFeedView", defaultFeedView);
fragment.setArguments(args);
return fragment;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if ((adapter == null) && (cursor != null)) {
String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.SUM_STORY_TOTAL, DatabaseConstants.FEED_TITLE };
int[] groupTo = new int[] { R.id.row_item_title, R.id.row_item_content, R.id.row_item_author, R.id.row_item_date, R.id.row_item_sidebar, R.id.row_item_feedtitle };
adapter = new MultipleFeedItemsAdapter(getActivity(), R.layout.row_socialitem, cursor, groupFrom, groupTo);
adapter.setViewBinder(new SocialItemViewBinder(getActivity(), true));
itemList.setAdapter(adapter);
}
super.onLoadFinished(loader, cursor);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (getActivity().isFinishing()) return;
Intent i = new Intent(getActivity(), ReadStoriesReading.class);
i.putExtra(Reading.EXTRA_FEEDSET, getFeedSet());
i.putExtra(FeedReading.EXTRA_POSITION, position);
i.putExtra(Reading.EXTRA_DEFAULT_FEED_VIEW, defaultFeedView);
startActivity(i);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.removeItem(R.id.menu_mark_newer_stories_as_read);
menu.removeItem(R.id.menu_mark_older_stories_as_read);
}
}

View file

@ -46,6 +46,7 @@ public class APIConstants {
public static final String URL_CLASSIFIER_SAVE = NEWSBLUR_URL + "/classifier/save";
public static final String URL_STORY_TEXT = NEWSBLUR_URL + "/rss_feeds/original_text";
public static final String URL_UNREAD_HASHES = NEWSBLUR_URL + "/reader/unread_story_hashes";
public static final String URL_READ_STORIES = NEWSBLUR_URL + "/reader/read_stories";
public static final String PARAMETER_FEEDS = "f";
public static final String PARAMETER_H = "h";

View file

@ -307,6 +307,8 @@ public class APIManager {
values.put(APIConstants.PARAMETER_INCLUDE_HIDDEN, APIConstants.VALUE_TRUE);
} else if (fs.isAllSocial()) {
uri = Uri.parse(APIConstants.URL_SHARED_RIVER_STORIES);
} else if (fs.isAllRead()) {
uri = Uri.parse(APIConstants.URL_READ_STORIES);
} else if (fs.isAllSaved()) {
uri = Uri.parse(APIConstants.URL_STARRED_STORIES);
} else if (fs.isGlobalShared()) {
@ -316,10 +318,12 @@ public class APIManager {
throw new IllegalStateException("Asked to get stories for FeedSet of unknown type.");
}
// request params common to all stories
// request params common to most story sets
values.put(APIConstants.PARAMETER_PAGE_NUMBER, Integer.toString(pageNumber));
values.put(APIConstants.PARAMETER_ORDER, order.getParameterValue());
values.put(APIConstants.PARAMETER_READ_FILTER, filter.getParameterValue());
if ( !(fs.isAllRead() || fs.isAllSaved())) {
values.put(APIConstants.PARAMETER_ORDER, order.getParameterValue());
values.put(APIConstants.PARAMETER_READ_FILTER, filter.getParameterValue());
}
APIResponse response = get(uri.toString(), values);
return (StoriesResponse) response.getResponse(gson, StoriesResponse.class);

View file

@ -40,6 +40,7 @@ import com.newsblur.util.StateFilter;
import com.newsblur.util.StoryOrder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -547,6 +548,7 @@ public class NBSyncService extends Service {
if (!FeedPagesSeen.containsKey(fs)) {
FeedPagesSeen.put(fs, 0);
FeedStoriesSeen.put(fs, 0);
workaroundReadStoryTimestamp = (new Date()).getTime();
}
int pageNumber = FeedPagesSeen.get(fs);
int totalStoriesSeen = FeedStoriesSeen.get(fs);
@ -585,7 +587,7 @@ public class NBSyncService extends Service {
if ((pageNumber == 1) && (apiResponse.stories.length > 0)) {
ModeCutoff = apiResponse.stories[0].timestamp;
}
insertStories(apiResponse);
insertStories(apiResponse, fs);
NbActivity.updateAllActivities(true);
if (apiResponse.stories.length == 0) {
@ -619,6 +621,27 @@ public class NBSyncService extends Service {
return true;
}
private long workaroundReadStoryTimestamp;
private void insertStories(StoriesResponse apiResponse, FeedSet fs) {
if (fs.isAllRead()) {
// Ugly Hack Warning: the API doesn't vend the sortation key necessary to display
// stories when in the "read stories" view. It does, however, return them in the
// correct order, so we can fudge a fake last-read-stamp so they will show up.
// Stories read locally with have the correct stamp and show up fine. When local
// and remote stories are integrated, the remote hack will override the ordering
// so they get put into the correct sequence recorded by the API (the authority).
for (Story story : apiResponse.stories) {
// this fake TS was set when we fetched the first page. have it decrease as
// we page through, so they append to the list as if most-recent-first.
workaroundReadStoryTimestamp --;
story.lastReadTimestamp = workaroundReadStoryTimestamp;
}
}
dbHelper.insertStories(apiResponse, ActMode, ModeCutoff);
}
void insertStories(StoriesResponse apiResponse) {
dbHelper.insertStories(apiResponse, ActMode, ModeCutoff);
}

View file

@ -27,6 +27,7 @@ public class FeedSet implements Serializable {
private Map<String,String> socialFeeds;
private boolean isAllNormal;
private boolean isAllSocial;
private boolean isAllRead;
private boolean isAllSaved;
private boolean isGlobalShared;
@ -36,9 +37,9 @@ public class FeedSet implements Serializable {
* Construct a new set of feeds. Only one of the arguments may be non-null or true. Specify an empty
* set to request all of a given type.
*/
private FeedSet(Set<String> feeds, Map<String,String> socialFeeds, boolean allSaved, boolean globalShared) {
private FeedSet(Set<String> feeds, Map<String,String> socialFeeds, boolean allSaved, boolean globalShared, boolean allRead) {
if ( booleanCardinality( (feeds != null), (socialFeeds != null), allSaved, globalShared ) > 1 ) {
if ( booleanCardinality( (feeds != null), (socialFeeds != null), allRead, allSaved, globalShared ) > 1 ) {
throw new IllegalArgumentException("at most one type of feed may be specified");
}
@ -64,6 +65,11 @@ public class FeedSet implements Serializable {
}
}
if (allRead) {
isAllRead = true;
return;
}
if (allSaved) {
isAllSaved = true;
return;
@ -83,7 +89,7 @@ public class FeedSet implements Serializable {
public static FeedSet singleFeed(String feedId) {
Set<String> feedIds = new HashSet<String>(1);
feedIds.add(feedId);
return new FeedSet(feedIds, null, false, false);
return new FeedSet(feedIds, null, false, false, false);
}
/**
@ -92,7 +98,7 @@ public class FeedSet implements Serializable {
public static FeedSet singleSocialFeed(String userId, String username) {
Map<String,String> socialFeedIds = new HashMap<String,String>(1);
socialFeedIds.put(userId, username);
return new FeedSet(null, socialFeedIds, false, false);
return new FeedSet(null, socialFeedIds, false, false, false);
}
/**
@ -104,42 +110,49 @@ public class FeedSet implements Serializable {
for (String id : userIds) {
socialFeedIds.put(id, "");
}
return new FeedSet(null, socialFeedIds, false, false);
return new FeedSet(null, socialFeedIds, false, false, false);
}
/**
* Convenience constructor for all (non-social) feeds.
*/
public static FeedSet allFeeds() {
return new FeedSet(Collections.EMPTY_SET, null, false, false);
return new FeedSet(Collections.EMPTY_SET, null, false, false, false);
}
/**
* Convenience constructor for read stories meta-feed.
*/
public static FeedSet allRead() {
return new FeedSet(null, null, false, false, true);
}
/**
* Convenience constructor for saved stories feed.
*/
public static FeedSet allSaved() {
return new FeedSet(null, null, true, false);
return new FeedSet(null, null, true, false, false);
}
/**
* Convenience constructor for global shared stories feed.
*/
public static FeedSet globalShared() {
return new FeedSet(null, null, false, true);
return new FeedSet(null, null, false, true, false);
}
/**
* Convenience constructor for all shared/social feeds.
*/
public static FeedSet allSocialFeeds() {
return new FeedSet(null, Collections.EMPTY_MAP, false, false);
return new FeedSet(null, Collections.EMPTY_MAP, false, false, false);
}
/**
* Convenience constructor for a folder.
*/
public static FeedSet folder(String folderName, Set<String> feedIds) {
FeedSet fs = new FeedSet(feedIds, null, false, false);
FeedSet fs = new FeedSet(feedIds, null, false, false, false);
fs.setFolderName(folderName);
return fs;
}
@ -181,6 +194,10 @@ public class FeedSet implements Serializable {
return this.isAllSocial;
}
public boolean isAllRead() {
return this.isAllRead;
}
public boolean isAllSaved() {
return this.isAllSaved;
}
@ -217,6 +234,8 @@ public class FeedSet implements Serializable {
return result;
}
// TODO: get rid of this compat serialisation hack when we switch to an object store!
private static final String COM_SER_NUL = "NUL";
public String toCompactSerial() {
@ -240,16 +259,18 @@ public class FeedSet implements Serializable {
s.append(isAllSaved);
s.append("|");
s.append(isGlobalShared);
s.append("|");
s.append(isAllRead);
return s.toString();
}
public static FeedSet fromCompactSerial(String s) {
String[] fields = TextUtils.split(s, "\\|");
if ((fields.length != 5) || (!fields[0].equals("FS"))) throw new IllegalArgumentException("invalid compact form");
if ((fields.length != 6) || (!fields[0].equals("FS"))) throw new IllegalArgumentException("invalid compact form");
if (! fields[1].equals(COM_SER_NUL)) {
HashSet<String> feeds = new HashSet<String>();
for (String id : TextUtils.split(fields[1], ",")) feeds.add(id);
return new FeedSet(feeds, null, false, false);
return new FeedSet(feeds, null, false, false, false);
}
if (! fields[2].equals(COM_SER_NUL)) {
HashMap<String,String> socialFeeds = new HashMap<String,String>();
@ -258,13 +279,16 @@ public class FeedSet implements Serializable {
if (kv.length != 2) throw new IllegalArgumentException("invalid compact form");
socialFeeds.put(kv[0], kv[1]);
}
return new FeedSet(null, socialFeeds, false, false);
return new FeedSet(null, socialFeeds, false, false, false);
}
if (fields[3].equals(Boolean.TRUE.toString())) {
return new FeedSet(null, null, true, false);
return new FeedSet(null, null, true, false, false);
}
if (fields[4].equals(Boolean.TRUE.toString())) {
return new FeedSet(null, null, false, true);
return new FeedSet(null, null, false, true, false);
}
if (fields[5].equals(Boolean.TRUE.toString())) {
return new FeedSet(null, null, false, false, true);
}
throw new IllegalArgumentException("invalid compact form");
}
@ -285,6 +309,7 @@ public class FeedSet implements Serializable {
if ( (socialFeeds != null) && (s.socialFeeds != null) && s.socialFeeds.equals(socialFeeds) ) return true;
if ( isAllNormal && s.isAllNormal ) return true;
if ( isAllSocial && s.isAllSocial ) return true;
if ( isAllRead && s.isAllRead ) return true;
if ( isAllSaved && s.isAllSaved ) return true;
if ( isGlobalShared && s.isGlobalShared ) return true;
}
@ -297,6 +322,7 @@ public class FeedSet implements Serializable {
if (isAllSocial) return 12;
if (isAllSaved) return 13;
if (isGlobalShared) return 14;
if (isAllRead) return 15;
int result = 17;
if (feeds != null) result = 31 * result + feeds.hashCode();

View file

@ -128,6 +128,7 @@ public class FeedUtils {
}
private static void setStoryReadState(Story story, Context context, boolean read) {
dbHelper.touchStory(story.storyHash);
if (story.read == read) { return; }
// update the local object to show as read before DB is touched

View file

@ -43,6 +43,7 @@ public class PrefConstants {
public static final String FEED_DEFAULT_FEED_VIEW_PREFIX = "feed_default_feed_view_";
public static final String FOLDER_DEFAULT_FEED_VIEW_PREFIX = "folder_default_feed_view_";
public static final String READ_STORIES_FOLDER_NAME = "read_stories";
public static final String SAVED_STORIES_FOLDER_NAME = "saved_stories";
public static final String READING_ENTER_IMMERSIVE_SINGLE_TAP = "immersive_enter_single_tap";

View file

@ -383,6 +383,11 @@ public class PrefsUtils {
throw new IllegalArgumentException( "requests for multiple social feeds not supported" );
}
if (fs.isAllRead()) {
// dummy value, not really used
return StoryOrder.NEWEST;
}
if (fs.isAllSaved()) {
return getStoryOrderForFolder(context, PrefConstants.SAVED_STORIES_FOLDER_NAME);
}
@ -415,6 +420,11 @@ public class PrefsUtils {
throw new IllegalArgumentException( "requests for multiple social feeds not supported" );
}
if (fs.isAllRead()) {
// dummy value, not really used
return ReadFilter.ALL;
}
if (fs.isAllSaved()) {
return getReadFilterForFolder(context, PrefConstants.SAVED_STORIES_FOLDER_NAME);
}