mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-20 13:25:48 +00:00
New preference to load next feed on mark read (#15)
New preference to load next feed on mark read. When the option is enabled, a session data source is created to calculate the next feed or folder to load.
This commit is contained in:
parent
b88906404c
commit
48dad79bab
23 changed files with 557 additions and 78 deletions
|
@ -44,6 +44,8 @@ dependencies {
|
|||
implementation 'androidx.core:core-splashscreen:1.0.0'
|
||||
implementation "com.google.dagger:hilt-android:2.43.2"
|
||||
kapt "com.google.dagger:hilt-compiler:2.43.2"
|
||||
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -71,6 +73,9 @@ android {
|
|||
res.srcDirs = ['res']
|
||||
assets.srcDirs = ['assets']
|
||||
}
|
||||
test {
|
||||
java.srcDirs = ['test']
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
|
@ -579,6 +579,8 @@
|
|||
<item>DOWN_NEXT</item>
|
||||
</string-array>
|
||||
<string name="default_volume_key_navigation_value">OFF</string>
|
||||
<string name="settings_load_next_on_mark_read">Open next feed/folder after read</string>
|
||||
<string name="settings_load_next_on_mark_read_summary">Load the next feed/folder after marked as read</string>
|
||||
|
||||
<string name="settings_confirm_mark_all_read">Confirm mark all read on…</string>
|
||||
<string name="none">Neither</string>
|
||||
|
|
|
@ -148,6 +148,11 @@
|
|||
android:entries="@array/volume_key_navigation_entries"
|
||||
android:entryValues="@array/volume_key_navigation_values"
|
||||
android:defaultValue="@string/default_volume_key_navigation_value" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="load_next_on_mark_read"
|
||||
android:title="@string/settings_load_next_on_mark_read"
|
||||
android:summary="@string/settings_load_next_on_mark_read_summary" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
|
|
|
@ -3,6 +3,9 @@ package com.newsblur.activity;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
@ -12,17 +15,30 @@ import com.google.android.play.core.review.ReviewManager;
|
|||
import com.google.android.play.core.review.ReviewManagerFactory;
|
||||
import com.google.android.play.core.tasks.Task;
|
||||
import com.newsblur.R;
|
||||
import com.newsblur.di.IconLoader;
|
||||
import com.newsblur.domain.Feed;
|
||||
import com.newsblur.fragment.DeleteFeedFragment;
|
||||
import com.newsblur.fragment.FeedIntelTrainerFragment;
|
||||
import com.newsblur.fragment.RenameDialogFragment;
|
||||
import com.newsblur.util.FeedExt;
|
||||
import com.newsblur.util.FeedSet;
|
||||
import com.newsblur.util.ImageLoader;
|
||||
import com.newsblur.util.PrefsUtils;
|
||||
import com.newsblur.util.Session;
|
||||
import com.newsblur.util.SessionDataSource;
|
||||
import com.newsblur.util.UIUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class FeedItemsList extends ItemsList {
|
||||
|
||||
@Inject
|
||||
@IconLoader
|
||||
ImageLoader iconLoader;
|
||||
|
||||
public static final String EXTRA_FEED = "feed";
|
||||
public static final String EXTRA_FOLDER_NAME = "folderName";
|
||||
private Feed feed;
|
||||
|
@ -31,22 +47,21 @@ public class FeedItemsList extends ItemsList {
|
|||
private ReviewInfo reviewInfo;
|
||||
|
||||
public static void startActivity(Context context, FeedSet feedSet,
|
||||
Feed feed, String folderName) {
|
||||
Feed feed, String folderName,
|
||||
@Nullable SessionDataSource sessionDataSource) {
|
||||
Intent intent = new Intent(context, FeedItemsList.class);
|
||||
intent.putExtra(ItemsList.EXTRA_FEED_SET, feedSet);
|
||||
intent.putExtra(FeedItemsList.EXTRA_FEED, feed);
|
||||
intent.putExtra(FeedItemsList.EXTRA_FOLDER_NAME, folderName);
|
||||
intent.putExtra(ItemsList.EXTRA_FEED_SET, feedSet);
|
||||
intent.putExtra(ItemsList.EXTRA_SESSION_DATA, sessionDataSource);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
feed = (Feed) getIntent().getSerializableExtra(EXTRA_FEED);
|
||||
folderName = getIntent().getStringExtra(EXTRA_FOLDER_NAME);
|
||||
|
||||
super.onCreate(bundle);
|
||||
|
||||
UIUtils.setupToolbar(this, feed.faviconUrl, feed.title, iconLoader, false);
|
||||
setupFeedItems(getIntent());
|
||||
viewModel.getNextSession().observe(this, this::setupFeedItems);
|
||||
checkInAppReview();
|
||||
}
|
||||
|
||||
|
@ -138,6 +153,28 @@ public class FeedItemsList extends ItemsList {
|
|||
return "feed:" + feed.feedId;
|
||||
}
|
||||
|
||||
private void setupFeedItems(Session session) {
|
||||
Feed feed = session.getFeed();
|
||||
String folderName = session.getFolderName();
|
||||
if (feed != null && folderName != null) {
|
||||
setupFeedItems(feed, folderName);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupFeedItems(Intent intent) {
|
||||
Feed feed = (Feed) intent.getSerializableExtra(EXTRA_FEED);
|
||||
String folderName = intent.getStringExtra(EXTRA_FOLDER_NAME);
|
||||
setupFeedItems(feed, folderName);
|
||||
}
|
||||
|
||||
private void setupFeedItems(@NonNull Feed feed, @NonNull String folderName) {
|
||||
this.feed = feed;
|
||||
this.folderName = folderName;
|
||||
UIUtils.setupToolbar(this, feed.faviconUrl, feed.title, iconLoader, false);
|
||||
}
|
||||
|
||||
private void checkInAppReview() {
|
||||
if (!PrefsUtils.hasInAppReviewed(this)) {
|
||||
reviewManager = ReviewManagerFactory.create(this);
|
||||
|
|
|
@ -12,15 +12,19 @@ public class FolderItemsList extends ItemsList {
|
|||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
folderName = getIntent().getStringExtra(EXTRA_FOLDER_NAME);
|
||||
|
||||
super.onCreate(bundle);
|
||||
|
||||
UIUtils.setupToolbar(this, R.drawable.ic_folder_closed, folderName, false);
|
||||
setupFolder(getIntent().getStringExtra(EXTRA_FOLDER_NAME));
|
||||
viewModel.getNextSession().observe(this, session ->
|
||||
setupFolder(session.getFolderName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
String getSaveSearchFeedId() {
|
||||
return "river:" + folderName;
|
||||
}
|
||||
|
||||
private void setupFolder(String folderName) {
|
||||
this.folderName = folderName;
|
||||
UIUtils.setupToolbar(this, R.drawable.ic_folder_closed, folderName, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,12 @@ import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
|
|||
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
|
@ -19,23 +23,25 @@ import com.newsblur.database.BlurDatabaseHelper;
|
|||
import com.newsblur.databinding.ActivityItemslistBinding;
|
||||
import com.newsblur.delegate.ItemListContextMenuDelegate;
|
||||
import com.newsblur.delegate.ItemListContextMenuDelegateImpl;
|
||||
import com.newsblur.di.IconLoader;
|
||||
import com.newsblur.fragment.ItemSetFragment;
|
||||
import com.newsblur.service.NBSyncService;
|
||||
import com.newsblur.util.AppConstants;
|
||||
import com.newsblur.util.FeedSet;
|
||||
import com.newsblur.util.FeedUtils;
|
||||
import com.newsblur.util.ImageLoader;
|
||||
import com.newsblur.util.ReadingActionListener;
|
||||
import com.newsblur.util.PrefsUtils;
|
||||
import com.newsblur.util.Session;
|
||||
import com.newsblur.util.SessionDataSource;
|
||||
import com.newsblur.util.StateFilter;
|
||||
import com.newsblur.util.UIUtils;
|
||||
import com.newsblur.viewModel.ItemListViewModel;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public abstract class ItemsList extends NbActivity {
|
||||
public abstract class ItemsList extends NbActivity implements ReadingActionListener {
|
||||
|
||||
@Inject
|
||||
BlurDatabaseHelper dbHelper;
|
||||
|
@ -43,22 +49,22 @@ public abstract class ItemsList extends NbActivity {
|
|||
@Inject
|
||||
FeedUtils feedUtils;
|
||||
|
||||
@Inject
|
||||
@IconLoader
|
||||
ImageLoader iconLoader;
|
||||
|
||||
public static final String EXTRA_FEED_SET = "feed_set";
|
||||
public static final String EXTRA_STORY_HASH = "story_hash";
|
||||
public static final String EXTRA_WIDGET_STORY = "widget_story";
|
||||
public static final String EXTRA_VISIBLE_SEARCH = "visibleSearch";
|
||||
public static final String EXTRA_SESSION_DATA = "session_data";
|
||||
private static final String BUNDLE_ACTIVE_SEARCH_QUERY = "activeSearchQuery";
|
||||
private ActivityItemslistBinding binding;
|
||||
|
||||
protected ItemListContextMenuDelegate contextMenuDelegate;
|
||||
protected ItemSetFragment itemSetFragment;
|
||||
protected StateFilter intelState;
|
||||
protected ItemListViewModel viewModel;
|
||||
protected FeedSet fs;
|
||||
|
||||
private ItemSetFragment itemSetFragment;
|
||||
private ActivityItemslistBinding binding;
|
||||
private ItemListContextMenuDelegate contextMenuDelegate;
|
||||
@Nullable
|
||||
private SessionDataSource sessionDataSource;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
@ -66,8 +72,9 @@ public abstract class ItemsList extends NbActivity {
|
|||
overridePendingTransition(R.anim.slide_in_from_right, R.anim.slide_out_to_left);
|
||||
|
||||
contextMenuDelegate = new ItemListContextMenuDelegateImpl(this, feedUtils);
|
||||
viewModel = new ViewModelProvider(this).get(ItemListViewModel.class);
|
||||
fs = (FeedSet) getIntent().getSerializableExtra(EXTRA_FEED_SET);
|
||||
intelState = PrefsUtils.getStateFilter(this);
|
||||
sessionDataSource = (SessionDataSource) getIntent().getSerializableExtra(EXTRA_SESSION_DATA);
|
||||
|
||||
// this is not strictly necessary, since our first refresh with the fs will swap in
|
||||
// the correct session, but that can be delayed by sync backup, so we try here to
|
||||
|
@ -77,6 +84,7 @@ public abstract class ItemsList extends NbActivity {
|
|||
String hash = (String) getIntent().getSerializableExtra(EXTRA_STORY_HASH);
|
||||
UIUtils.startReadingActivity(fs, hash, this);
|
||||
} else if (PrefsUtils.isAutoOpenFirstUnread(this)) {
|
||||
StateFilter intelState = PrefsUtils.getStateFilter(this);
|
||||
if (dbHelper.getUnreadCount(fs, intelState) > 0) {
|
||||
UIUtils.startReadingActivity(fs, Reading.FIND_FIRST_UNREAD, this);
|
||||
}
|
||||
|
@ -188,6 +196,27 @@ public abstract class ItemsList extends NbActivity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadingActionCompleted() {
|
||||
if (sessionDataSource != null) {
|
||||
Session session = sessionDataSource.getNextSession();
|
||||
if (session != null) {
|
||||
// set the next session on the parent activity
|
||||
fs = session.getFeedSet();
|
||||
feedUtils.prepareReadingSession(fs, false);
|
||||
triggerSync();
|
||||
|
||||
// set the next session on the child activity
|
||||
viewModel.updateSession(session);
|
||||
|
||||
// update item set fragment
|
||||
itemSetFragment.resetEmptyState();
|
||||
itemSetFragment.hasUpdated();
|
||||
itemSetFragment.scrollToTop();
|
||||
} else finish();
|
||||
} else finish();
|
||||
}
|
||||
|
||||
private void updateStatusIndicators() {
|
||||
if (binding.itemlistSyncStatus != null) {
|
||||
String syncStatus = NBSyncService.getSyncStatusMessage(this, true);
|
||||
|
@ -266,4 +295,5 @@ public abstract class ItemsList extends NbActivity {
|
|||
}
|
||||
|
||||
abstract String getSaveSearchFeedId();
|
||||
|
||||
}
|
||||
|
|
|
@ -2,11 +2,22 @@ package com.newsblur.activity;
|
|||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.newsblur.di.IconLoader;
|
||||
import com.newsblur.domain.SocialFeed;
|
||||
import com.newsblur.util.ImageLoader;
|
||||
import com.newsblur.util.UIUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class SocialFeedItemsList extends ItemsList {
|
||||
|
||||
@Inject
|
||||
@IconLoader
|
||||
ImageLoader iconLoader;
|
||||
|
||||
public static final String EXTRA_SOCIAL_FEED = "social_feed";
|
||||
|
||||
private SocialFeed socialFeed;
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
package com.newsblur.database;
|
||||
|
||||
import static com.newsblur.util.AppConstants.ALL_SHARED_STORIES_GROUP_KEY;
|
||||
import static com.newsblur.util.AppConstants.ALL_STORIES_GROUP_KEY;
|
||||
import static com.newsblur.util.AppConstants.GLOBAL_SHARED_STORIES_GROUP_KEY;
|
||||
import static com.newsblur.util.AppConstants.INFREQUENT_SITE_STORIES_GROUP_KEY;
|
||||
import static com.newsblur.util.AppConstants.READ_STORIES_GROUP_KEY;
|
||||
import static com.newsblur.util.AppConstants.SAVED_SEARCHES_GROUP_KEY;
|
||||
import static com.newsblur.util.AppConstants.SAVED_STORIES_GROUP_KEY;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -35,8 +43,10 @@ import com.newsblur.domain.Folder;
|
|||
import com.newsblur.domain.SavedSearch;
|
||||
import com.newsblur.domain.StarredCount;
|
||||
import com.newsblur.domain.SocialFeed;
|
||||
import com.newsblur.util.Session;
|
||||
import com.newsblur.util.AppConstants;
|
||||
import com.newsblur.util.FeedListOrder;
|
||||
import com.newsblur.util.SessionDataSource;
|
||||
import com.newsblur.util.SpacingStyle;
|
||||
import com.newsblur.util.FeedSet;
|
||||
import com.newsblur.util.ImageLoader;
|
||||
|
@ -52,21 +62,6 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
|
|||
private enum GroupType { GLOBAL_SHARED_STORIES, ALL_SHARED_STORIES, INFREQUENT_STORIES, ALL_STORIES, FOLDER, READ_STORIES, SAVED_SEARCHES, SAVED_STORIES }
|
||||
private enum ChildType { SOCIAL_FEED, FEED, SAVED_BY_TAG, SAVED_SEARCH }
|
||||
|
||||
// The following keys are used to mark the position of the special meta-folders within
|
||||
// the folders array. Since the ExpandableListView doesn't handle collapsing of views
|
||||
// set to View.GONE, we have to totally remove any hidden groups from the group count
|
||||
// and adjust all folder indicies accordingly. Fake folders are created with these
|
||||
// very unlikely names and layout methods check against them before assuming a row is
|
||||
// a normal folder. All the string comparison is a small price to pay to avoid the
|
||||
// alternative of index-counting in a situation where some rows might be disabled.
|
||||
private static final String GLOBAL_SHARED_STORIES_GROUP_KEY = "GLOBAL_SHARED_STORIES_GROUP_KEY";
|
||||
private static final String ALL_SHARED_STORIES_GROUP_KEY = "ALL_SHARED_STORIES_GROUP_KEY";
|
||||
private static final String ALL_STORIES_GROUP_KEY = "ALL_STORIES_GROUP_KEY";
|
||||
private static final String INFREQUENT_SITE_STORIES_GROUP_KEY = "INFREQUENT_SITE_STORIES_GROUP_KEY";
|
||||
private static final String READ_STORIES_GROUP_KEY = "READ_STORIES_GROUP_KEY";
|
||||
private static final String SAVED_STORIES_GROUP_KEY = "SAVED_STORIES_GROUP_KEY";
|
||||
private static final String SAVED_SEARCHES_GROUP_KEY = "SAVED_SEARCHES_GROUP_KEY";
|
||||
|
||||
private final static float defaultTextSize_childName = 14;
|
||||
private final static float defaultTextSize_groupName = 13;
|
||||
private final static float defaultTextSize_count = 13;
|
||||
|
@ -982,4 +977,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
|
|||
this.spacingStyle = spacingStyle;
|
||||
}
|
||||
|
||||
public SessionDataSource buildSessionDataSource(Session activeSession) {
|
||||
return new SessionDataSource(activeSession, activeFolderNames, activeFolderChildren);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -423,11 +423,11 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
return true;
|
||||
|
||||
case R.id.menu_mark_older_stories_as_read:
|
||||
feedUtils.markRead(context, fs, story.timestamp, null, R.array.mark_older_read_options, false);
|
||||
feedUtils.markRead(context, fs, story.timestamp, null, R.array.mark_older_read_options);
|
||||
return true;
|
||||
|
||||
case R.id.menu_mark_newer_stories_as_read:
|
||||
feedUtils.markRead(context, fs, null, story.timestamp, R.array.mark_newer_read_options, false);
|
||||
feedUtils.markRead(context, fs, null, story.timestamp, R.array.mark_newer_read_options);
|
||||
return true;
|
||||
|
||||
case R.id.menu_send_story:
|
||||
|
@ -456,7 +456,7 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
case R.id.menu_go_to_feed:
|
||||
FeedSet fs = FeedSet.singleFeed(story.feedId);
|
||||
FeedItemsList.startActivity(context, fs,
|
||||
feedUtils.getFeed(story.feedId), null);
|
||||
feedUtils.getFeed(story.feedId), null, null);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
|
|
@ -27,7 +27,7 @@ interface ItemListContextMenuDelegate {
|
|||
open class ItemListContextMenuDelegateImpl(
|
||||
private val activity: ItemsList,
|
||||
private val feedUtils: FeedUtils,
|
||||
) : ItemListContextMenuDelegate {
|
||||
) : ItemListContextMenuDelegate, ReadingActionListener by activity {
|
||||
|
||||
override fun onCreateMenuOptions(menu: Menu, menuInflater: MenuInflater, fs: FeedSet): Boolean {
|
||||
menuInflater.inflate(R.menu.itemslist, menu)
|
||||
|
@ -171,7 +171,7 @@ open class ItemListContextMenuDelegateImpl(
|
|||
activity.finish()
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_mark_all_as_read) {
|
||||
feedUtils.markRead(activity, fs, null, null, R.array.mark_all_read_options, true)
|
||||
feedUtils.markRead(activity, fs, null, null, R.array.mark_all_read_options, this)
|
||||
return true
|
||||
} else if (item.itemId == R.id.menu_story_order_newest) {
|
||||
updateStoryOrder(fragment, fs, StoryOrder.NEWEST)
|
||||
|
|
|
@ -10,10 +10,6 @@ annotation class StoryFileCache
|
|||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class IconFileCache
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ThumbnailFileCache
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class IconLoader
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.List;
|
|||
import com.google.gson.annotations.SerializedName;
|
||||
import com.newsblur.database.DatabaseConstants;
|
||||
import com.newsblur.util.FeedListOrder;
|
||||
import com.newsblur.util.FeedUtils;
|
||||
|
||||
public class Feed implements Comparable<Feed>, Serializable {
|
||||
|
||||
|
@ -153,7 +154,7 @@ public class Feed implements Comparable<Feed>, Serializable {
|
|||
public boolean equals(Object o) {
|
||||
if (! (o instanceof Feed)) return false;
|
||||
Feed otherFeed = (Feed) o;
|
||||
return (TextUtils.equals(feedId, otherFeed.feedId));
|
||||
return (FeedUtils.textUtilsEquals(feedId, otherFeed.feedId));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -51,8 +51,10 @@ import com.newsblur.domain.Feed;
|
|||
import com.newsblur.domain.Folder;
|
||||
import com.newsblur.domain.SavedSearch;
|
||||
import com.newsblur.domain.SocialFeed;
|
||||
import com.newsblur.util.Session;
|
||||
import com.newsblur.util.AppConstants;
|
||||
import com.newsblur.util.FeedExt;
|
||||
import com.newsblur.util.SessionDataSource;
|
||||
import com.newsblur.util.SpacingStyle;
|
||||
import com.newsblur.util.FeedSet;
|
||||
import com.newsblur.util.FeedUtils;
|
||||
|
@ -392,7 +394,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
|
|||
}
|
||||
|
||||
private void markFeedsAsRead(FeedSet fs) {
|
||||
feedUtils.markRead(((NbActivity) getActivity()), fs, null, null, R.array.mark_all_read_options, false);
|
||||
feedUtils.markRead(((NbActivity) getActivity()), fs, null, null, R.array.mark_all_read_options);
|
||||
adapter.lastFeedViewedId = fs.getSingleFeed();
|
||||
adapter.lastFolderViewed = fs.getFolderName();
|
||||
}
|
||||
|
@ -437,7 +439,13 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
|
|||
|
||||
@Override
|
||||
public boolean onGroupClick(ExpandableListView list, View group, int groupPosition, long id) {
|
||||
Intent i = null;
|
||||
if (adapter.isRowSavedSearches(groupPosition)) {
|
||||
// group not clickable
|
||||
return true;
|
||||
}
|
||||
|
||||
FeedSet fs = adapter.getGroup(groupPosition);
|
||||
Intent i;
|
||||
if (adapter.isRowAllStories(groupPosition)) {
|
||||
if (currentState == StateFilter.SAVED) {
|
||||
// the existence of this row in saved mode is something of a framework artifact and may
|
||||
|
@ -456,17 +464,15 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
|
|||
i = new Intent(getActivity(), ReadStoriesItemsList.class);
|
||||
} else if (adapter.isRowSavedStories(groupPosition)) {
|
||||
i = new Intent(getActivity(), SavedStoriesItemsList.class);
|
||||
} else if (adapter.isRowSavedSearches(groupPosition)) {
|
||||
// group not clickable
|
||||
return true;
|
||||
} else {
|
||||
i = new Intent(getActivity(), FolderItemsList.class);
|
||||
String canonicalFolderName = adapter.getGroupFolderName(groupPosition);
|
||||
SessionDataSource sessionDataSource = getSessionData(fs, canonicalFolderName, null);
|
||||
i.putExtra(FolderItemsList.EXTRA_FOLDER_NAME, canonicalFolderName);
|
||||
i.putExtra(ItemsList.EXTRA_SESSION_DATA, sessionDataSource);
|
||||
adapter.lastFeedViewedId = null;
|
||||
adapter.lastFolderViewed = canonicalFolderName;
|
||||
}
|
||||
FeedSet fs = adapter.getGroup(groupPosition);
|
||||
i.putExtra(ItemsList.EXTRA_FEED_SET, fs);
|
||||
startActivity(i);
|
||||
|
||||
|
@ -541,7 +547,8 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
|
|||
|
||||
feedUtils.currentFolderName = folderName;
|
||||
}
|
||||
FeedItemsList.startActivity(getActivity(), fs, feed, folderName);
|
||||
SessionDataSource sessionDataSource = getSessionData(fs, folderName, feed);
|
||||
FeedItemsList.startActivity(getActivity(), fs, feed, folderName, sessionDataSource);
|
||||
adapter.lastFeedViewedId = feed.feedId;
|
||||
adapter.lastFolderViewed = null;
|
||||
}
|
||||
|
@ -619,4 +626,13 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
|
|||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private SessionDataSource getSessionData(FeedSet fs, String folderName, @Nullable Feed feed) {
|
||||
if (PrefsUtils.loadNextOnMarkRead(requireContext())) {
|
||||
Session activeSession = new Session(fs, folderName, feed);
|
||||
return adapter.buildSessionDataSource(activeSession);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
package com.newsblur.fragment;
|
||||
|
||||
import com.newsblur.util.FeedUtils;
|
||||
import com.newsblur.util.ReadingAction;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.newsblur.util.FeedUtils;
|
||||
import com.newsblur.util.ReadingAction;
|
||||
import com.newsblur.util.ReadingActionListener;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
@ -24,29 +27,30 @@ public class ReadingActionConfirmationFragment extends DialogFragment {
|
|||
private static final String DIALOG_TITLE = "dialog_title";
|
||||
private static final String DIALOG_MESSAGE = "dialog_message";
|
||||
private static final String DIALOG_CHOICES_RID = "dialog_choices_rid";
|
||||
private static final String FINISH_AFTER = "finish_after";
|
||||
private static final String ACTION_CALLBACK = "action_callback";
|
||||
|
||||
public static ReadingActionConfirmationFragment newInstance(ReadingAction ra, CharSequence title, CharSequence message, int choicesId, boolean finishAfter) {
|
||||
public static ReadingActionConfirmationFragment newInstance(ReadingAction ra, CharSequence title, CharSequence message, int choicesId, @Nullable ReadingActionListener callback) {
|
||||
ReadingActionConfirmationFragment fragment = new ReadingActionConfirmationFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putSerializable(READING_ACTION, ra);
|
||||
args.putCharSequence(DIALOG_TITLE, title);
|
||||
args.putCharSequence(DIALOG_MESSAGE, message);
|
||||
args.putInt(DIALOG_CHOICES_RID, choicesId);
|
||||
args.putBoolean(FINISH_AFTER, finishAfter);
|
||||
args.putSerializable(ACTION_CALLBACK, callback);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
|
||||
|
||||
final ReadingAction ra = (ReadingAction)getArguments().getSerializable(READING_ACTION);
|
||||
final ReadingAction ra = (ReadingAction) getArguments().getSerializable(READING_ACTION);
|
||||
CharSequence title = getArguments().getCharSequence(DIALOG_TITLE);
|
||||
CharSequence message = getArguments().getCharSequence(DIALOG_MESSAGE);
|
||||
int choicesId = getArguments().getInt(DIALOG_CHOICES_RID);
|
||||
final boolean finishAfter = getArguments().getBoolean(FINISH_AFTER);
|
||||
@Nullable ReadingActionListener callback = (ReadingActionListener) getArguments().getSerializable(ACTION_CALLBACK);
|
||||
|
||||
builder.setTitle(title);
|
||||
// NB: setting a message will override the display of the buttons, making the dialogue a no-op
|
||||
|
@ -54,9 +58,9 @@ public class ReadingActionConfirmationFragment extends DialogFragment {
|
|||
builder.setItems(choicesId, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == 0) {
|
||||
feedUtils.doAction(ra, getActivity());
|
||||
if (finishAfter) {
|
||||
getActivity().finish();
|
||||
feedUtils.doAction(ra, requireContext());
|
||||
if (callback != null) {
|
||||
callback.onReadingActionCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -419,7 +419,7 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
|
|||
true
|
||||
}
|
||||
R.id.menu_go_to_feed -> {
|
||||
FeedItemsList.startActivity(context, fs, dbHelper.getFeed(story!!.feedId), null)
|
||||
FeedItemsList.startActivity(context, fs, dbHelper.getFeed(story!!.feedId), null, null)
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
|
|
|
@ -95,4 +95,19 @@ public class AppConstants {
|
|||
// Free standard account sites limit
|
||||
public final static int FREE_ACCOUNT_SITE_LIMIT = 64;
|
||||
|
||||
// The following keys are used to mark the position of the special meta-folders within
|
||||
// the folders array. Since the ExpandableListView doesn't handle collapsing of views
|
||||
// set to View.GONE, we have to totally remove any hidden groups from the group count
|
||||
// and adjust all folder indicies accordingly. Fake folders are created with these
|
||||
// very unlikely names and layout methods check against them before assuming a row is
|
||||
// a normal folder. All the string comparison is a small price to pay to avoid the
|
||||
// alternative of index-counting in a situation where some rows might be disabled.
|
||||
public static final String GLOBAL_SHARED_STORIES_GROUP_KEY = "GLOBAL_SHARED_STORIES_GROUP_KEY";
|
||||
public static final String ALL_SHARED_STORIES_GROUP_KEY = "ALL_SHARED_STORIES_GROUP_KEY";
|
||||
public static final String ALL_STORIES_GROUP_KEY = "ALL_STORIES_GROUP_KEY";
|
||||
public static final String INFREQUENT_SITE_STORIES_GROUP_KEY = "INFREQUENT_SITE_STORIES_GROUP_KEY";
|
||||
public static final String READ_STORIES_GROUP_KEY = "READ_STORIES_GROUP_KEY";
|
||||
public static final String SAVED_STORIES_GROUP_KEY = "SAVED_STORIES_GROUP_KEY";
|
||||
public static final String SAVED_SEARCHES_GROUP_KEY = "SAVED_SEARCHES_GROUP_KEY";
|
||||
|
||||
}
|
||||
|
|
|
@ -330,8 +330,8 @@ public class FeedSet implements Serializable {
|
|||
if (!( o instanceof FeedSet)) return false;
|
||||
FeedSet s = (FeedSet) o;
|
||||
|
||||
if ( !TextUtils.equals(searchQuery, s.searchQuery)) return false;
|
||||
if ( !TextUtils.equals(folderName, s.folderName)) return false;
|
||||
if ( !FeedUtils.textUtilsEquals(searchQuery, s.searchQuery)) return false;
|
||||
if ( !FeedUtils.textUtilsEquals(folderName, s.folderName)) return false;
|
||||
if ( isFilterSaved != s.isFilterSaved ) return false;
|
||||
if ( (feeds != null) && (s.feeds != null) && s.feeds.equals(feeds) ) return true;
|
||||
if ( (socialFeeds != null) && (s.socialFeeds != null) && s.socialFeeds.equals(socialFeeds) ) return true;
|
||||
|
|
|
@ -210,10 +210,13 @@ class FeedUtils(
|
|||
triggerSync(context)
|
||||
}
|
||||
|
||||
fun markRead(activity: NbActivity, fs: FeedSet, olderThan: Long?, newerThan: Long?, choicesRid: Int) =
|
||||
markRead(activity, fs, olderThan, newerThan, choicesRid, null)
|
||||
|
||||
/**
|
||||
* Marks some or all of the stories in a FeedSet as read for an activity, handling confirmation dialogues as necessary.
|
||||
*/
|
||||
fun markRead(activity: NbActivity, fs: FeedSet, olderThan: Long?, newerThan: Long?, choicesRid: Int, finishAfter: Boolean) {
|
||||
fun markRead(activity: NbActivity, fs: FeedSet, olderThan: Long?, newerThan: Long?, choicesRid: Int, callback: ReadingActionListener?) {
|
||||
val ra: ReadingAction = 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.
|
||||
|
@ -252,9 +255,7 @@ class FeedUtils(
|
|||
}
|
||||
if (doImmediate) {
|
||||
doAction(ra, activity)
|
||||
if (finishAfter) {
|
||||
activity.finish()
|
||||
}
|
||||
callback?.onReadingActionCompleted()
|
||||
} else {
|
||||
val title: String? = when {
|
||||
fs.isAllNormal -> {
|
||||
|
@ -270,7 +271,7 @@ class FeedUtils(
|
|||
dbHelper.getFeed(fs.singleFeed)?.title ?: ""
|
||||
}
|
||||
}
|
||||
val dialog = ReadingActionConfirmationFragment.newInstance(ra, title, optionalOverrideMessage, choicesRid, finishAfter)
|
||||
val dialog = ReadingActionConfirmationFragment.newInstance(ra, title, optionalOverrideMessage, choicesRid, callback)
|
||||
dialog.show(activity.supportFragmentManager, "dialog")
|
||||
}
|
||||
}
|
||||
|
@ -508,5 +509,23 @@ class FeedUtils(
|
|||
val parts = TextUtils.split(storyHash, ":")
|
||||
return if (parts.size != 2) null else parts[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy of TextUtils.equals because of Java for unit tests
|
||||
*/
|
||||
@JvmStatic
|
||||
fun textUtilsEquals(a: CharSequence?, b: CharSequence?): Boolean {
|
||||
if (a === b) return true
|
||||
return if (a != null && b != null && a.length == b.length) {
|
||||
if (a is String && b is String) {
|
||||
a == b
|
||||
} else {
|
||||
for (i in a.indices) {
|
||||
if (a[i] != b[i]) return false
|
||||
}
|
||||
true
|
||||
}
|
||||
} else false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,4 +123,5 @@ public class PrefConstants {
|
|||
public static final String FEED_CHOOSER_FOLDER_VIEW = "feed_chooser_folder_view";
|
||||
public static final String WIDGET_BACKGROUND = "widget_background";
|
||||
public static final String IN_APP_REVIEW = "in_app_review";
|
||||
public static final String LOAD_NEXT_ON_MARK_READ = "load_next_on_mark_read";
|
||||
}
|
||||
|
|
|
@ -1064,4 +1064,9 @@ public class PrefsUtils {
|
|||
SharedPreferences preferences = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
|
||||
return MarkStoryReadBehavior.valueOf(preferences.getString(PrefConstants.STORY_MARK_READ_BEHAVIOR, MarkStoryReadBehavior.IMMEDIATELY.name()));
|
||||
}
|
||||
|
||||
public static boolean loadNextOnMarkRead(Context context) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
|
||||
return prefs.getBoolean(PrefConstants.LOAD_NEXT_ON_MARK_READ, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
package com.newsblur.util
|
||||
|
||||
import com.newsblur.domain.Feed
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* @return Set of folder keys that don't support
|
||||
* mark all read action
|
||||
*/
|
||||
private val invalidMarkAllReadFolderKeys by lazy {
|
||||
setOf(
|
||||
AppConstants.GLOBAL_SHARED_STORIES_GROUP_KEY,
|
||||
AppConstants.ALL_SHARED_STORIES_GROUP_KEY,
|
||||
AppConstants.INFREQUENT_SITE_STORIES_GROUP_KEY,
|
||||
AppConstants.READ_STORIES_GROUP_KEY,
|
||||
AppConstants.SAVED_STORIES_GROUP_KEY,
|
||||
AppConstants.SAVED_SEARCHES_GROUP_KEY,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* As of writing this function, the zipping of the two sources
|
||||
* is valid as the "activeFolderNames" and "activeFolderChildren"
|
||||
* can be mapped by their folder name.
|
||||
* @return Map of folder names to their feed list.
|
||||
*/
|
||||
private fun List<String>.zipFolderFeed(foldersChildren: List<List<Feed>>): Map<String, List<Feed>> {
|
||||
val first = this.iterator()
|
||||
val second = foldersChildren.iterator()
|
||||
return buildMap {
|
||||
while (first.hasNext() && second.hasNext()) {
|
||||
this[first.next()] = second.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Feed.toFeedSet() = FeedSet.singleFeed(this.feedId).apply {
|
||||
isMuted = !this@toFeedSet.active
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the user's current reading session data source
|
||||
* as constructed and filtered by the home list adapter
|
||||
* based on settings and preferences.
|
||||
*/
|
||||
class SessionDataSource private constructor(
|
||||
private val folders: List<String>,
|
||||
private val foldersChildrenMap: Map<String, List<Feed>>
|
||||
) : Serializable {
|
||||
|
||||
private lateinit var session: Session
|
||||
|
||||
constructor(
|
||||
activeSession: Session,
|
||||
folders: List<String>,
|
||||
foldersChildren: List<List<Feed>>,
|
||||
) : this(
|
||||
folders = folders.filterNot { invalidMarkAllReadFolderKeys.contains(it) },
|
||||
foldersChildrenMap = folders.zipFolderFeed(foldersChildren)
|
||||
.filterNot { invalidMarkAllReadFolderKeys.contains(it.key) },
|
||||
) {
|
||||
this.session = activeSession
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The next feed within a folder or null if the folder
|
||||
* is showing the last feed.
|
||||
*/
|
||||
private fun getNextFolderFeed(feed: Feed, folderName: String): Feed? {
|
||||
val cleanFolderName =
|
||||
// ROOT FOLDER maps to ALL_STORIES_GROUP_KEY
|
||||
if (folderName == AppConstants.ROOT_FOLDER)
|
||||
AppConstants.ALL_STORIES_GROUP_KEY
|
||||
else folderName
|
||||
val folderFeeds = foldersChildrenMap[cleanFolderName]
|
||||
return folderFeeds?.let { feeds ->
|
||||
val feedIndex = feeds.indexOf(feed)
|
||||
if (feedIndex == -1) return null // invalid feed
|
||||
|
||||
val nextFeedIndex = when (feedIndex) {
|
||||
feeds.size - 1 -> null // null feed if EOL
|
||||
in feeds.indices -> feedIndex + 1 // next feed
|
||||
else -> null // no valid feed found
|
||||
}
|
||||
|
||||
nextFeedIndex?.let { feeds[it] }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The next non empty folder and its feeds based on the given folder name.
|
||||
* If the next folder doesn't have feeds, it will call itself with the new folder name
|
||||
* until it finds a non empty folder or it will get to the end of the folder list.
|
||||
*/
|
||||
private fun getNextNonEmptyFolder(folderName: String): Pair<String, List<Feed>>? = with(folders.indexOf(folderName)) {
|
||||
val nextIndex = if (this == folders.size - 1) {
|
||||
0 // first folder if EOL
|
||||
} else if (this in folders.indices) {
|
||||
this + 1 // next folder
|
||||
} else this // no folder found
|
||||
|
||||
val nextFolderName = if (nextIndex in folders.indices) {
|
||||
folders[nextIndex]
|
||||
} else null
|
||||
|
||||
if (nextFolderName == null || nextFolderName == folderName)
|
||||
return null
|
||||
|
||||
val feeds = foldersChildrenMap[nextFolderName]
|
||||
if (feeds == null || feeds.isEmpty())
|
||||
// try and get the next non empty folder name
|
||||
getNextNonEmptyFolder(nextFolderName)
|
||||
else nextFolderName to feeds
|
||||
}
|
||||
|
||||
fun getNextSession(): Session? = if (session.feedSet.isFolder) {
|
||||
val folderName = session.feedSet.folderName
|
||||
getNextNonEmptyFolder(folderName)?.let { (nextFolderName, nextFolderFeeds) ->
|
||||
val nextFeedSet = FeedSet.folder(nextFolderName, nextFolderFeeds.map { it.feedId }.toSet())
|
||||
Session(feedSet = nextFeedSet, folderName = nextFolderName).also { nextSession ->
|
||||
session = nextSession
|
||||
}
|
||||
}
|
||||
} else if (session.feed != null && session.folderName != null) {
|
||||
val nextFeed = getNextFolderFeed(feed = session.feed!!, folderName = session.folderName!!)
|
||||
nextFeed?.let {
|
||||
Session(feedSet = it.toFeedSet(), session.folderName, it).also { nextSession ->
|
||||
session = nextSession
|
||||
}
|
||||
}
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the user's current reading session.
|
||||
*
|
||||
* When reading a folder, [folderName] and [feed] will be null.
|
||||
*
|
||||
* When reading a feed, [folderName] and [feed] will be non null.
|
||||
*/
|
||||
data class Session(
|
||||
val feedSet: FeedSet,
|
||||
val folderName: String? = null,
|
||||
val feed: Feed? = null,
|
||||
) : Serializable
|
||||
|
||||
interface ReadingActionListener : Serializable {
|
||||
fun onReadingActionCompleted()
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.newsblur.viewModel
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.newsblur.util.Session
|
||||
|
||||
class ItemListViewModel : ViewModel() {
|
||||
|
||||
private val _nextSession = MutableLiveData<Session>()
|
||||
val nextSession: LiveData<Session> = _nextSession
|
||||
|
||||
fun updateSession(session: Session) {
|
||||
_nextSession.value = session
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package com.newsblur
|
||||
|
||||
import com.newsblur.domain.Feed
|
||||
import com.newsblur.util.FeedSet
|
||||
import com.newsblur.util.Session
|
||||
import com.newsblur.util.SessionDataSource
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class SessionDataSourceTest {
|
||||
|
||||
private val folders = listOf(
|
||||
"F1",
|
||||
"F2",
|
||||
"F3",
|
||||
"F4",
|
||||
"F5",
|
||||
)
|
||||
|
||||
private val folderChildren = listOf(
|
||||
emptyList(),
|
||||
listOf(
|
||||
createFeed("20"),
|
||||
createFeed("21"),
|
||||
createFeed("22"),
|
||||
),
|
||||
listOf(
|
||||
createFeed("30"),
|
||||
),
|
||||
emptyList(),
|
||||
listOf(
|
||||
createFeed("50"),
|
||||
createFeed("51"),
|
||||
)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `session constructor test`() {
|
||||
val feedSet = FeedSet.singleFeed("1")
|
||||
val session = Session(feedSet)
|
||||
Assert.assertEquals(feedSet, session.feedSet)
|
||||
Assert.assertNull(session.feed)
|
||||
Assert.assertNull(session.folderName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `session full constructor test`() {
|
||||
val feedSet = FeedSet.singleFeed("10")
|
||||
val feed = createFeed("10")
|
||||
val session = Session(feedSet, "folderName", feed)
|
||||
Assert.assertEquals(feedSet, session.feedSet)
|
||||
Assert.assertEquals("folderName", session.folderName)
|
||||
Assert.assertEquals(feed, session.feed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `next session for unknown feedId`() {
|
||||
val session = Session(FeedSet.singleFeed("123"))
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
Assert.assertNull(sessionDs.getNextSession())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `next session for empty folder`() {
|
||||
val feedSet = FeedSet.singleFeed("123")
|
||||
val feed = createFeed("123")
|
||||
val session = Session(feedSet, "F1", feed)
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
|
||||
Assert.assertNull(sessionDs.getNextSession())
|
||||
}
|
||||
|
||||
/**
|
||||
* Expected to return the next [Session] containing feed id 11
|
||||
* as the second feed in folder F2 after feed id 10
|
||||
*/
|
||||
@Test
|
||||
fun `next session for F2 feedSet`() {
|
||||
val feedSet = FeedSet.singleFeed("20")
|
||||
val feed = createFeed("20")
|
||||
val session = Session(feedSet, "F2", feed)
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
|
||||
sessionDs.getNextSession()?.let {
|
||||
Assert.assertEquals("F2", it.folderName)
|
||||
Assert.assertEquals("21", it.feed?.feedId)
|
||||
with(it.feedSet) {
|
||||
Assert.assertNotNull(this)
|
||||
Assert.assertTrue(it.feedSet.flatFeedIds.size == 1)
|
||||
Assert.assertEquals("21", it.feedSet.flatFeedIds.first())
|
||||
}
|
||||
} ?: Assert.fail("Next session was null")
|
||||
}
|
||||
|
||||
/**
|
||||
* Expected to return a null [Session] because feed id 12
|
||||
* is the last feed id in folder F2
|
||||
*/
|
||||
@Test
|
||||
fun `next session for end of F2 feedSet`() {
|
||||
val feedSet = FeedSet.singleFeed("22")
|
||||
val feed = createFeed("22")
|
||||
val session = Session(feedSet, "F2", feed)
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
|
||||
Assert.assertNull(sessionDs.getNextSession())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `next session for F2 feedSetFolder`() {
|
||||
val feedSet = FeedSet.folder("F2", setOf("21"))
|
||||
val feed = createFeed("21")
|
||||
val session = Session(feedSet, "F2", feed)
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
|
||||
sessionDs.getNextSession()?.let {
|
||||
Assert.assertNull(it.feed)
|
||||
Assert.assertEquals("F3", it.folderName)
|
||||
Assert.assertEquals("F3", it.feedSet.folderName)
|
||||
Assert.assertEquals("30", it.feedSet.flatFeedIds.firstOrNull())
|
||||
} ?: Assert.fail("Next session is null for F2 feedSetFolder")
|
||||
}
|
||||
|
||||
/**
|
||||
* Expected to return folder "F5" because folder "F3"
|
||||
* doesn't have any feeds
|
||||
*/
|
||||
@Test
|
||||
fun `next session for F3 feedSetFolder`() {
|
||||
val feedSet = FeedSet.folder("F3", setOf("30"))
|
||||
val feed = createFeed("30")
|
||||
val session = Session(feedSet, "F3", feed)
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
|
||||
sessionDs.getNextSession()?.let {
|
||||
Assert.assertNull(it.feed)
|
||||
Assert.assertEquals("F5", it.folderName)
|
||||
Assert.assertEquals("F5", it.feedSet.folderName)
|
||||
Assert.assertEquals("50", it.feedSet.flatFeedIds.firstOrNull())
|
||||
} ?: Assert.fail("Next session is null for F5 feedSetFolder")
|
||||
}
|
||||
|
||||
/**
|
||||
* Expected to return session for F1 feedSetFolder
|
||||
*/
|
||||
@Test
|
||||
fun `next session for F5 feedSetFolder`() {
|
||||
val feedSet = FeedSet.folder("F5", setOf("50"))
|
||||
val feed = createFeed("50")
|
||||
val session = Session(feedSet, "F5", feed)
|
||||
val sessionDs = SessionDataSource(session, folders, folderChildren)
|
||||
|
||||
sessionDs.getNextSession()?.let {
|
||||
Assert.assertNull(it.feed)
|
||||
Assert.assertEquals("F2", it.folderName)
|
||||
Assert.assertEquals("F2", it.feedSet.folderName)
|
||||
Assert.assertEquals(setOf("20", "21", "22"), it.feedSet.flatFeedIds)
|
||||
} ?: Assert.fail("Next session is null for F5 feedSetFolder")
|
||||
}
|
||||
|
||||
private fun createFeed(id: String) = Feed().apply {
|
||||
feedId = id
|
||||
title = "Feed #$id"
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue