option to require confirmation for range-marks (#965)

This commit is contained in:
dosiecki 2016-11-30 14:07:28 -08:00
parent 0205ec247f
commit 7d2dffc576
15 changed files with 154 additions and 146 deletions

View file

@ -239,6 +239,14 @@
<item>Mark all read</item>
<item>Cancel</item>
</string-array>
<string-array name="mark_older_read_options">
<item>Mark older stories read</item>
<item>Cancel</item>
</string-array>
<string-array name="mark_newer_read_options">
<item>Mark newer stories read</item>
<item>Cancel</item>
</string-array>
<string name="settings_auto_open_first_unread">Auto-Open First Story</string>
<string name="settings_auto_open_first_unread_sum">Automatically open first unread story from story list</string>
@ -311,6 +319,8 @@
</string-array>
<string name="confirm_mark_all_read_value">FOLDER_ONLY</string>
<string name="settings_confirm_mark_range_read">Confirm Mark Older/Newer Read</string>
<string name="settings_ltr_gesture_action">Rightward Swipe on Story Title</string>
<string name="gest_action_none">No Action</string>
<string name="gest_action_markread">Mark Story Read</string>

View file

@ -51,6 +51,10 @@
android:entries="@array/confirm_mark_all_read_entries"
android:entryValues="@array/confirm_mark_all_read_values"
android:defaultValue="@string/confirm_mark_all_read_value" />
<CheckBoxPreference
android:defaultValue="false"
android:key="pref_confirm_mark_range_read"
android:title="@string/settings_confirm_mark_range_read" />
<CheckBoxPreference
android:defaultValue="false"
android:key="pref_auto_open_first_unread"

View file

@ -31,11 +31,6 @@ public class GlobalSharedStoriesItemsList extends ItemsList {
}
}
@Override
public void markItemListAsRead() {
; // This activity has no mark-as-read action
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();

View file

@ -19,8 +19,6 @@ import butterknife.Bind;
import com.newsblur.R;
import com.newsblur.fragment.DefaultFeedViewDialogFragment;
import com.newsblur.fragment.ItemListFragment;
import com.newsblur.fragment.MarkAllReadDialogFragment;
import com.newsblur.fragment.MarkAllReadDialogFragment.MarkAllReadDialogListener;
import com.newsblur.fragment.ReadFilterDialogFragment;
import com.newsblur.fragment.StoryOrderDialogFragment;
import com.newsblur.fragment.TextSizeDialogFragment;
@ -39,7 +37,7 @@ import com.newsblur.util.StoryOrder;
import com.newsblur.util.StoryOrderChangedListener;
import com.newsblur.util.UIUtils;
public abstract class ItemsList extends NbActivity implements StoryOrderChangedListener, ReadFilterChangedListener, DefaultFeedViewChangedListener, MarkAllReadDialogListener, OnSeekBarChangeListener {
public abstract class ItemsList extends NbActivity implements StoryOrderChangedListener, ReadFilterChangedListener, DefaultFeedViewChangedListener, OnSeekBarChangeListener {
public static final String EXTRA_FEED_SET = "feed_set";
@ -134,35 +132,13 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
NBSyncService.addRecountCandidates(fs);
}
public void markItemListAsRead() {
MarkAllReadConfirmation confirmation = PrefsUtils.getMarkAllReadConfirmation(this);
if (confirmation.feedSetRequiresConfirmation(fs)) {
MarkAllReadDialogFragment dialog = MarkAllReadDialogFragment.newInstance(fs);
dialog.show(fragmentManager, "dialog");
} else {
onMarkAllRead(fs);
}
}
@Override
public void onMarkAllRead(FeedSet feedSet) {
if (itemListFragment != null) {
// since v6.0 of Android, the ListView in the fragment likes to crash if the underlying
// dataset changes rapidly as happens when marking-all-read and when the fragment is
// stopping. do a manual hard-stop of the loaders in the fragment before we finish
itemListFragment.stopLoader();
}
FeedUtils.markFeedsRead(fs, null, null, this);
finish();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
} else if (item.getItemId() == R.id.menu_mark_all_as_read) {
markItemListAsRead();
FeedUtils.markRead(this, fs, null, null, R.array.mark_all_read_options, true);
return true;
} else if (item.getItemId() == R.id.menu_story_order) {
StoryOrder currentValue = getStoryOrder();
@ -298,6 +274,12 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
@Override
public void finish() {
if (itemListFragment != null) {
// since v6.0 of Android, the ListView in the fragment likes to crash if the underlying
// dataset changes rapidly as happens when marking-all-read and when the fragment is
// stopping. do a manual hard-stop of the loaders in the fragment before we finish
itemListFragment.stopLoader();
}
super.finish();
/*
* Animate out the list by sliding it to the right and the Main activity in from

View file

@ -30,7 +30,6 @@ import com.newsblur.fragment.FeedIntelligenceSelectorFragment;
import com.newsblur.fragment.FolderListFragment;
import com.newsblur.fragment.LoginAsDialogFragment;
import com.newsblur.fragment.LogoutDialogFragment;
import com.newsblur.fragment.MarkAllReadDialogFragment.MarkAllReadDialogListener;
import com.newsblur.fragment.TextSizeDialogFragment;
import com.newsblur.service.BootReceiver;
import com.newsblur.service.NBSyncService;
@ -42,7 +41,7 @@ import com.newsblur.util.StateFilter;
import com.newsblur.util.UIUtils;
import com.newsblur.view.StateToggleButton.StateChangedListener;
public class Main extends NbActivity implements StateChangedListener, SwipeRefreshLayout.OnRefreshListener, AbsListView.OnScrollListener, PopupMenu.OnMenuItemClickListener, MarkAllReadDialogListener, OnSeekBarChangeListener {
public class Main extends NbActivity implements StateChangedListener, SwipeRefreshLayout.OnRefreshListener, AbsListView.OnScrollListener, PopupMenu.OnMenuItemClickListener, OnSeekBarChangeListener {
private FolderListFragment folderFeedList;
private FragmentManager fragmentManager;
@ -291,11 +290,6 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
}
}
@Override
public void onMarkAllRead(FeedSet feedSet) {
FeedUtils.markFeedsRead(feedSet, null, null, this);
}
// NB: this callback is for the text size slider
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

View file

@ -31,11 +31,6 @@ public class ReadStoriesItemsList extends ItemsList {
}
}
@Override
public void markItemListAsRead() {
; // This activity has no mark-as-read action
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();

View file

@ -35,11 +35,6 @@ public class SavedStoriesItemsList extends ItemsList {
}
}
@Override
public void markItemListAsRead() {
; // This activity has no mark-as-read action
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();

View file

@ -46,7 +46,6 @@ import com.newsblur.domain.SocialFeed;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.MarkAllReadConfirmation;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.StateFilter;
@ -314,13 +313,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
}
private void markFeedsAsRead(FeedSet fs) {
MarkAllReadConfirmation confirmation = PrefsUtils.getMarkAllReadConfirmation(getActivity());
if (confirmation.feedSetRequiresConfirmation(fs)) {
MarkAllReadDialogFragment dialog = MarkAllReadDialogFragment.newInstance(fs);
dialog.show(getFragmentManager(), "dialog");
} else {
FeedUtils.markFeedsRead(fs, null, null, getActivity());
}
FeedUtils.markRead(getActivity(), fs, null, null, R.array.mark_all_read_options, false);
}
public void changeState(StateFilter state) {

View file

@ -348,11 +348,11 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis
return true;
case R.id.menu_mark_older_stories_as_read:
FeedUtils.markFeedsRead(getFeedSet(), story.timestamp, null, activity);
FeedUtils.markRead(activity, getFeedSet(), story.timestamp, null, R.array.mark_older_read_options, false);
return true;
case R.id.menu_mark_newer_stories_as_read:
FeedUtils.markFeedsRead(getFeedSet(), null, story.timestamp, activity);
FeedUtils.markRead(activity, getFeedSet(), null, story.timestamp, R.array.mark_newer_read_options, false);
return true;
case R.id.menu_send_story:

View file

@ -1,55 +0,0 @@
package com.newsblur.fragment;
import com.newsblur.R;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.app.DialogFragment;
public class MarkAllReadDialogFragment extends DialogFragment {
private static final String FEED_SET = "feed_set";
public interface MarkAllReadDialogListener {
void onMarkAllRead(FeedSet feedSet);
}
public static MarkAllReadDialogFragment newInstance(FeedSet feedSet) {
MarkAllReadDialogFragment fragment = new MarkAllReadDialogFragment();
Bundle args = new Bundle();
args.putSerializable(FEED_SET, feedSet);
fragment.setArguments(args);
return fragment;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
final FeedSet feedSet = (FeedSet)getArguments().getSerializable(FEED_SET);
String title = null;
if (feedSet.isAllNormal()) {
title = getResources().getString(R.string.all_stories);
} else if (feedSet.isFolder()) {
title = feedSet.getFolderName();
} else if (feedSet.isSingleSocial()) {
title = FeedUtils.getSocialFeed(feedSet.getSingleSocialFeed().getKey()).feedTitle;
} else {
title = FeedUtils.getFeed(feedSet.getSingleFeed()).title;
}
final MarkAllReadDialogListener listener = (MarkAllReadDialogListener) getActivity();
builder.setTitle(title)
.setItems(R.array.mark_all_read_options, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
listener.onMarkAllRead(feedSet);
}
}
});
return builder.create();
}
}

View file

@ -0,0 +1,52 @@
package com.newsblur.fragment;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.ReadingAction;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.app.DialogFragment;
public class ReadingActionConfirmationFragment extends DialogFragment {
private static final String READING_ACTION = "reading_action";
private static final String DIALOG_TITLE = "dialog_title";
private static final String DIALOG_CHOICES_RID = "dialog_choices_rid";
private static final String FINISH_AFTER = "finish_after";
public static ReadingActionConfirmationFragment newInstance(ReadingAction ra, CharSequence title, int choicesId, boolean finishAfter) {
ReadingActionConfirmationFragment fragment = new ReadingActionConfirmationFragment();
Bundle args = new Bundle();
args.putSerializable(READING_ACTION, ra);
args.putCharSequence(DIALOG_TITLE, title);
args.putInt(DIALOG_CHOICES_RID, choicesId);
args.putBoolean(FINISH_AFTER, finishAfter);
fragment.setArguments(args);
return fragment;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
final ReadingAction ra = (ReadingAction)getArguments().getSerializable(READING_ACTION);
CharSequence title = getArguments().getCharSequence(DIALOG_TITLE);
int choicesId = getArguments().getInt(DIALOG_CHOICES_RID);
final boolean finishAfter = getArguments().getBoolean(FINISH_AFTER);
builder.setTitle(title);
builder.setItems(choicesId, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
FeedUtils.doAction(ra, getActivity());
if (finishAfter) {
getActivity().finish();
}
}
}
});
return builder.create();
}
}

View file

@ -4,6 +4,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
@ -19,6 +20,7 @@ import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.domain.SocialFeed;
import com.newsblur.domain.Story;
import com.newsblur.fragment.ReadingActionConfirmationFragment;
import com.newsblur.network.APIManager;
import com.newsblur.network.domain.NewsBlurResponse;
import com.newsblur.service.NBSyncService;
@ -156,41 +158,70 @@ public class FeedUtils {
NBSyncService.addRecountCandidates(impactedFeeds);
}
public static void markFeedsRead(final FeedSet fs, final Long olderThan, final Long newerThan, final Context context) {
/**
* Marks some or all of the stories in a FeedSet as read for an activity, handling confirmation dialogues as necessary.
*/
public static void markRead(Activity activity, FeedSet fs, Long olderThan, Long newerThan, int choicesRid, boolean finishAfter) {
ReadingAction ra = null;
if (fs.isAllNormal() && (olderThan != null || newerThan != null)) {
// the mark-all-read API doesn't support range bounding, so we need to pass each and every
// feed ID to the API instead.
FeedSet newFeedSet = FeedSet.folder("all", dbHelper.getAllActiveFeeds());
ra = ReadingAction.markFeedRead(newFeedSet, olderThan, newerThan);
} else {
if (fs.getSingleFeed() != null) {
if (!fs.isMuted()) {
ra = ReadingAction.markFeedRead(fs, olderThan, newerThan);
}
} else if (fs.isFolder()) {
Set<String> feedIds = fs.getMultipleFeeds();
Set<String> allActiveFeedIds = dbHelper.getAllActiveFeeds();
Set<String> activeFeedIds = new HashSet<String>();
activeFeedIds.addAll(feedIds);
activeFeedIds.retainAll(allActiveFeedIds);
FeedSet filteredFs = FeedSet.folder(fs.getFolderName(), activeFeedIds);
ra = ReadingAction.markFeedRead(filteredFs, olderThan, newerThan);
} else {
ra = ReadingAction.markFeedRead(fs, olderThan, newerThan);
}
}
boolean doImmediate = true;
if ((olderThan != null) || (newerThan != null)) {
// if this is a range mark, check that option
if (PrefsUtils.isConfirmMarkRangeRead(activity)) doImmediate = false;
} else {
// if this is an all mark, check that option
MarkAllReadConfirmation confirmation = PrefsUtils.getMarkAllReadConfirmation(activity);
if (confirmation.feedSetRequiresConfirmation(fs)) doImmediate = false;
}
if (doImmediate) {
doAction(ra, activity);
if (finishAfter) {
activity.finish();
}
} else {
String title = null;
if (fs.isAllNormal()) {
title = activity.getResources().getString(R.string.all_stories);
} else if (fs.isFolder()) {
title = fs.getFolderName();
} else if (fs.isSingleSocial()) {
title = FeedUtils.getSocialFeed(fs.getSingleSocialFeed().getKey()).feedTitle;
} else {
title = FeedUtils.getFeed(fs.getSingleFeed()).title;
}
ReadingActionConfirmationFragment dialog = ReadingActionConfirmationFragment.newInstance(ra, title, choicesRid, finishAfter);
dialog.show(activity.getFragmentManager(), "dialog");
}
}
public static void doAction(final ReadingAction ra, final Context context) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... arg) {
// Only active feed IDs should be passed to the API call.
ReadingAction ra = null;
if (fs.isAllNormal() && (olderThan != null || newerThan != null)) {
// the mark-all-read API doesn't support range bounding, so we need to pass each and every
// feed ID to the API instead.
FeedSet newFeedSet = FeedSet.folder("all", dbHelper.getAllActiveFeeds());
ra = ReadingAction.markFeedRead(newFeedSet, olderThan, newerThan);
} else {
if (fs.getSingleFeed() != null) {
if (!fs.isMuted()) {
ra = ReadingAction.markFeedRead(fs, olderThan, newerThan);
}
} else if (fs.isFolder()) {
Set<String> feedIds = fs.getMultipleFeeds();
Set<String> allActiveFeedIds = dbHelper.getAllActiveFeeds();
Set<String> activeFeedIds = new HashSet<String>();
activeFeedIds.addAll(feedIds);
activeFeedIds.retainAll(allActiveFeedIds);
FeedSet filteredFs = FeedSet.folder(fs.getFolderName(), activeFeedIds);
ra = ReadingAction.markFeedRead(filteredFs, olderThan, newerThan);
} else {
ra = ReadingAction.markFeedRead(fs, olderThan, newerThan);
}
}
if (ra != null) {
dbHelper.enqueueAction(ra);
ra.doLocal(dbHelper);
}
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA | NbActivity.UPDATE_STORY);
dbHelper.enqueueAction(ra);
int impact = ra.doLocal(dbHelper);
NbActivity.updateAllActivities(impact);
triggerSync(context);
return null;
}

View file

@ -73,6 +73,7 @@ public class PrefConstants {
public static final String VOLUME_KEY_NAVIGATION = "volume_key_navigation";
public static final String MARK_ALL_READ_CONFIRMATION = "pref_confirm_mark_all_read";
public static final String MARK_RANGE_READ_CONFIRMATION = "pref_confirm_mark_range_read";
public static final String LTR_GESTURE_ACTION = "ltr_gesture_action";
public static final String RTL_GESTURE_ACTION = "rtl_gesture_action";

View file

@ -607,6 +607,11 @@ public class PrefsUtils {
return MarkAllReadConfirmation.valueOf(prefs.getString(PrefConstants.MARK_ALL_READ_CONFIRMATION, MarkAllReadConfirmation.FOLDER_ONLY.toString()));
}
public static boolean isConfirmMarkRangeRead(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
return prefs.getBoolean(PrefConstants.MARK_RANGE_READ_CONFIRMATION, false);
}
public static GestureAction getLeftToRightGestureAction(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
return GestureAction.valueOf(prefs.getString(PrefConstants.LTR_GESTURE_ACTION, GestureAction.GEST_ACTION_MARKREAD.toString()));

View file

@ -3,6 +3,8 @@ package com.newsblur.util;
import android.content.ContentValues;
import android.database.Cursor;
import java.io.Serializable;
import com.newsblur.activity.NbActivity;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.database.DatabaseConstants;
@ -11,7 +13,10 @@ import com.newsblur.network.APIManager;
import java.util.Set;
public class ReadingAction {
@SuppressWarnings("serial")
public class ReadingAction implements Serializable {
private static final long serialVersionUID = 0L;
private enum ActionType {
MARK_READ,
@ -338,6 +343,7 @@ public class ReadingAction {
dbHelper.updateLocalFeedCounts(feedSet);
}
impact |= NbActivity.UPDATE_METADATA;
impact |= NbActivity.UPDATE_STORY;
break;
case MARK_UNREAD: