package ml.docilealligator.infinityforreddit.post;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.paging.ListenableFuturePagingSource;
import androidx.paging.PagingState;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.concurrent.Executor;
import ml.docilealligator.infinityforreddit.readpost.ReadPostsListInterface;
import ml.docilealligator.infinityforreddit.thing.SortType;
import ml.docilealligator.infinityforreddit.account.Account;
import ml.docilealligator.infinityforreddit.apis.RedditAPI;
import ml.docilealligator.infinityforreddit.postfilter.PostFilter;
import ml.docilealligator.infinityforreddit.utils.APIUtils;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
import retrofit2.HttpException;
import retrofit2.Response;
import retrofit2.Retrofit;
public class PostPagingSource extends ListenableFuturePagingSource<String, Post> {
public static final int TYPE_FRONT_PAGE = 0;
public static final int TYPE_SUBREDDIT = 1;
public static final int TYPE_USER = 2;
public static final int TYPE_SEARCH = 3;
public static final int TYPE_MULTI_REDDIT = 4;
public static final int TYPE_ANONYMOUS_FRONT_PAGE = 5;
public static final int TYPE_ANONYMOUS_MULTIREDDIT = 6;
public static final String USER_WHERE_SUBMITTED = "submitted";
public static final String USER_WHERE_UPVOTED = "upvoted";
public static final String USER_WHERE_DOWNVOTED = "downvoted";
public static final String USER_WHERE_HIDDEN = "hidden";
public static final String USER_WHERE_SAVED = "saved";
private final Executor executor;
private final Retrofit retrofit;
private final String accessToken;
private final String accountName;
private final SharedPreferences sharedPreferences;
private final SharedPreferences postFeedScrolledPositionSharedPreferences;
private String subredditOrUserName;
private String query;
private String trendingSource;
private final int postType;
private final SortType sortType;
private final PostFilter postFilter;
private final ReadPostsListInterface readPostsList;
private String userWhere;
private String multiRedditPath;
private final LinkedHashSet<Post> postLinkedHashSet;
private String previousLastItem;
PostPagingSource(Executor executor, Retrofit retrofit, @Nullable String accessToken, @NonNull String accountName,
SharedPreferences sharedPreferences,
SharedPreferences postFeedScrolledPositionSharedPreferences, int postType,
SortType sortType, PostFilter postFilter, ReadPostsListInterface readPostsList) {
this.executor = executor;
this.retrofit = retrofit;
this.accessToken = accessToken;
this.accountName = accountName;
this.sharedPreferences = sharedPreferences;
this.postFeedScrolledPositionSharedPreferences = postFeedScrolledPositionSharedPreferences;
this.postType = postType;
this.sortType = sortType == null ? new SortType(SortType.Type.BEST) : sortType;
this.postFilter = postFilter;
this.readPostsList = readPostsList;
postLinkedHashSet = new LinkedHashSet<>();
}
// PostPagingSource.TYPE_SUBREDDIT || PostPagingSource.TYPE_ANONYMOUS_FRONT_PAGE || PostPagingSource.TYPE_ANONYMOUS_MULTIREDDIT:
PostPagingSource(Executor executor, Retrofit retrofit, @Nullable String accessToken, @NonNull String accountName,
SharedPreferences sharedPreferences, SharedPreferences postFeedScrolledPositionSharedPreferences,
String name, int postType, SortType sortType, PostFilter postFilter,
ReadPostsListInterface readPostsList) {
this.executor = executor;
this.retrofit = retrofit;
this.accessToken = accessToken;
this.accountName = accountName;
this.sharedPreferences = sharedPreferences;
this.postFeedScrolledPositionSharedPreferences = postFeedScrolledPositionSharedPreferences;
this.subredditOrUserName = name;
if (subredditOrUserName == null) {
subredditOrUserName = "popular";
}
this.postType = postType;
if (sortType == null) {
if ("popular".equals(name) || "all".equals(name)) {
this.sortType = new SortType(SortType.Type.HOT);
} else {
this.sortType = new SortType(SortType.Type.BEST);
}
} else {
this.sortType = sortType;
}
this.postFilter = postFilter;
this.readPostsList = readPostsList;
postLinkedHashSet = new LinkedHashSet<>();
}
// PostPagingSource.TYPE_MULTI_REDDIT
PostPagingSource(Executor executor, Retrofit retrofit, @Nullable String accessToken, @NonNull String accountName,
SharedPreferences sharedPreferences, SharedPreferences postFeedScrolledPositionSharedPreferences,
String path, String query, int postType, SortType sortType, PostFilter postFilter,
ReadPostsListInterface readPostsList) {
this.executor = executor;
this.retrofit = retrofit;
this.accessToken = accessToken;
this.accountName = accountName;
this.sharedPreferences = sharedPreferences;
this.postFeedScrolledPositionSharedPreferences = postFeedScrolledPositionSharedPreferences;
if (path.endsWith("/")) {
multiRedditPath = path.substring(0, path.length() - 1);
} else {
multiRedditPath = path;
}
this.query = query;
this.postType = postType;
if (sortType == null) {
this.sortType = new SortType(SortType.Type.HOT);
} else {
this.sortType = sortType;
}
this.postFilter = postFilter;
this.readPostsList = readPostsList;
postLinkedHashSet = new LinkedHashSet<>();
}
PostPagingSource(Executor executor, Retrofit retrofit, @Nullable String accessToken, @NonNull String accountName,
SharedPreferences sharedPreferences, SharedPreferences postFeedScrolledPositionSharedPreferences,
String subredditOrUserName, int postType, SortType sortType, PostFilter postFilter,
String where, ReadPostsListInterface readPostsList) {
this.executor = executor;
this.retrofit = retrofit;
this.accessToken = accessToken;
this.accountName = accountName;
this.sharedPreferences = sharedPreferences;
this.postFeedScrolledPositionSharedPreferences = postFeedScrolledPositionSharedPreferences;
this.subredditOrUserName = subredditOrUserName;
this.postType = postType;
this.sortType = sortType == null ? new SortType(SortType.Type.NEW) : sortType;
this.postFilter = postFilter;
userWhere = where;
this.readPostsList = readPostsList;
postLinkedHashSet = new LinkedHashSet<>();
}
PostPagingSource(Executor executor, Retrofit retrofit, @Nullable String accessToken, @NonNull String accountName,
SharedPreferences sharedPreferences, SharedPreferences postFeedScrolledPositionSharedPreferences,
String subredditOrUserName, String query, String trendingSource, int postType,
SortType sortType, PostFilter postFilter, ReadPostsListInterface readPostsList) {
this.executor = executor;
this.retrofit = retrofit;
this.accessToken = accessToken;
this.accountName = accountName;
this.sharedPreferences = sharedPreferences;
this.postFeedScrolledPositionSharedPreferences = postFeedScrolledPositionSharedPreferences;
this.subredditOrUserName = subredditOrUserName;
this.query = query;
this.trendingSource = trendingSource;
this.postType = postType;
this.sortType = sortType == null ? new SortType(SortType.Type.RELEVANCE) : sortType;
this.postFilter = postFilter;
postLinkedHashSet = new LinkedHashSet<>();
this.readPostsList = readPostsList;
}
@Nullable
@Override
public String getRefreshKey(@NonNull PagingState<String, Post> pagingState) {
return null;
}
@NonNull
@Override
public ListenableFuture<LoadResult<String, Post>> loadFuture(@NonNull LoadParams<String> loadParams) {
RedditAPI api = retrofit.create(RedditAPI.class);
switch (postType) {
case TYPE_FRONT_PAGE:
return loadHomePosts(loadParams, api);
case TYPE_SUBREDDIT:
return loadSubredditPosts(loadParams, api);
case TYPE_USER:
return loadUserPosts(loadParams, api);
case TYPE_SEARCH:
return loadSearchPosts(loadParams, api);
case TYPE_MULTI_REDDIT:
return loadMultiRedditPosts(loadParams, api);
default:
return loadAnonymousHomePosts(loadParams, api);
}
}
public LoadResult<String, Post> transformData(Response<String> response) {
if (response.isSuccessful()) {
String responseString = response.body();
LinkedHashSet<Post> newPosts = ParsePost.parsePostsSync(responseString, -1, postFilter, readPostsList);
String lastItem = ParsePost.getLastItem(responseString);
if (newPosts == null) {
return new LoadResult.Error<>(new Exception("Error parsing posts"));
} else {
int currentPostsSize = postLinkedHashSet.size();
if (lastItem != null && lastItem.equals(previousLastItem)) {
lastItem = null;
}
previousLastItem = lastItem;
postLinkedHashSet.addAll(newPosts);
if (currentPostsSize == postLinkedHashSet.size()) {
return new LoadResult.Page<>(new ArrayList<>(), null, lastItem);
} else {
return new LoadResult.Page<>(new ArrayList<>(postLinkedHashSet).subList(currentPostsSize, postLinkedHashSet.size()), null, lastItem);
}
}
} else {
return new LoadResult.Error<>(new Exception("Response failed"));
}
}
private ListenableFuture<LoadResult<String, Post>> loadHomePosts(@NonNull LoadParams<String> loadParams, RedditAPI api) {
ListenableFuture<Response<String>> bestPost;
String afterKey;
if (loadParams.getKey() == null) {
boolean savePostFeedScrolledPosition = sortType != null && sortType.getType() == SortType.Type.BEST && sharedPreferences.getBoolean(SharedPreferencesUtils.SAVE_FRONT_PAGE_SCROLLED_POSITION, false);
if (savePostFeedScrolledPosition) {
String accountNameForCache = accountName.equals(Account.ANONYMOUS_ACCOUNT) ? SharedPreferencesUtils.FRONT_PAGE_SCROLLED_POSITION_ANONYMOUS : accountName;
afterKey = postFeedScrolledPositionSharedPreferences.getString(accountNameForCache + SharedPreferencesUtils.FRONT_PAGE_SCROLLED_POSITION_FRONT_PAGE_BASE, null);
} else {
afterKey = null;
}
} else {
afterKey = loadParams.getKey();
}
bestPost = api.getBestPostsListenableFuture(sortType.getType(), sortType.getTime(), afterKey,
APIUtils.getOAuthHeader(accessToken));
ListenableFuture<LoadResult<String, Post>> pageFuture = Futures.transform(bestPost, this::transformData, executor);
ListenableFuture<LoadResult<String, Post>> partialLoadResultFuture =
Futures.catching(pageFuture, HttpException.class,
LoadResult.Error::new, executor);
return Futures.catching(partialLoadResultFuture,
IOException.class, LoadResult.Error::new, executor);
}
private ListenableFuture<LoadResult<String, Post>> loadSubredditPosts(@NonNull LoadParams<String> loadParams, RedditAPI api) {
ListenableFuture<Response<String>> subredditPost;
if (accountName.equals(Account.ANONYMOUS_ACCOUNT)) {
subredditPost = api.getSubredditBestPostsListenableFuture(subredditOrUserName, sortType.getType(), sortType.getTime(), loadParams.getKey());
} else {
subredditPost = api.getSubredditBestPostsOauthListenableFuture(subredditOrUserName, sortType.getType(),
sortType.getTime(), loadParams.getKey(), APIUtils.getOAuthHeader(accessToken));
}
ListenableFuture<LoadResult<String, Post>> pageFuture = Futures.transform(subredditPost, this::transformData, executor);
ListenableFuture<LoadResult<String, Post>> partialLoadResultFuture =
Futures.catching(pageFuture, HttpException.class,
LoadResult.Error::new, executor);
return Futures.catching(partialLoadResultFuture,
IOException.class, LoadResult.Error::new, executor);
}
private ListenableFuture<LoadResult<String, Post>> loadUserPosts(@NonNull LoadParams<String> loadParams, RedditAPI api) {
ListenableFuture<Response<String>> userPosts;
if (accountName.equals(Account.ANONYMOUS_ACCOUNT)) {
userPosts = api.getUserPostsListenableFuture(subredditOrUserName, loadParams.getKey(), sortType.getType(),
sortType.getTime());
} else {
userPosts = api.getUserPostsOauthListenableFuture(APIUtils.AUTHORIZATION_BASE + accessToken,
subredditOrUserName, userWhere, loadParams.getKey(), sortType.getType(), sortType.getTime());
}
ListenableFuture<LoadResult<String, Post>> pageFuture = Futures.transform(userPosts, this::transformData, executor);
ListenableFuture<LoadResult<String, Post>> partialLoadResultFuture =
Futures.catching(pageFuture, HttpException.class,
LoadResult.Error::new, executor);
return Futures.catching(partialLoadResultFuture,
IOException.class, LoadResult.Error::new, executor);
}
private ListenableFuture<LoadResult<String, Post>> loadSearchPosts(@NonNull LoadParams<String> loadParams, RedditAPI api) {
ListenableFuture<Response<String>> searchPosts;
if (subredditOrUserName == null) {
if (accountName.equals(Account.ANONYMOUS_ACCOUNT)) {
searchPosts = api.searchPostsListenableFuture(query, loadParams.getKey(), sortType.getType(), sortType.getTime(),
trendingSource);
} else {
searchPosts = api.searchPostsOauthListenableFuture(query, loadParams.getKey(), sortType.getType(),
sortType.getTime(), trendingSource, APIUtils.getOAuthHeader(accessToken));
}
} else {
if (accountName.equals(Account.ANONYMOUS_ACCOUNT)) {
searchPosts = api.searchPostsInSpecificSubredditListenableFuture(subredditOrUserName, query,
sortType.getType(), sortType.getTime(), loadParams.getKey());
} else {
searchPosts = api.searchPostsInSpecificSubredditOauthListenableFuture(subredditOrUserName, query,
sortType.getType(), sortType.getTime(), loadParams.getKey(),
APIUtils.getOAuthHeader(accessToken));
}
}
ListenableFuture<LoadResult<String, Post>> pageFuture = Futures.transform(searchPosts, this::transformData, executor);
ListenableFuture<LoadResult<String, Post>> partialLoadResultFuture =
Futures.catching(pageFuture, HttpException.class,
LoadResult.Error::new, executor);
return Futures.catching(partialLoadResultFuture,
IOException.class, LoadResult.Error::new, executor);
}
private ListenableFuture<LoadResult<String, Post>> loadMultiRedditPosts(@NonNull LoadParams<String> loadParams, RedditAPI api) {
ListenableFuture<Response<String>> multiRedditPosts;
if (accountName.equals(Account.ANONYMOUS_ACCOUNT)) {
if (query != null && !query.isEmpty()) {
multiRedditPosts = api.searchMultiRedditPostsListenableFuture(multiRedditPath, query, loadParams.getKey(),
sortType.getType(), sortType.getTime());
} else {
multiRedditPosts = api.getMultiRedditPostsListenableFuture(multiRedditPath, sortType.getType(), loadParams.getKey(), sortType.getTime());
}
} else {
if (query != null && !query.isEmpty()) {
multiRedditPosts = api.searchMultiRedditPostsOauthListenableFuture(multiRedditPath, query, loadParams.getKey(),
sortType.getType(), sortType.getTime(), APIUtils.getOAuthHeader(accessToken));
} else {
multiRedditPosts = api.getMultiRedditPostsOauthListenableFuture(multiRedditPath, sortType.getType(), loadParams.getKey(),
sortType.getTime(), APIUtils.getOAuthHeader(accessToken));
}
}
ListenableFuture<LoadResult<String, Post>> pageFuture = Futures.transform(multiRedditPosts, this::transformData, executor);
ListenableFuture<LoadResult<String, Post>> partialLoadResultFuture =
Futures.catching(pageFuture, HttpException.class,
LoadResult.Error::new, executor);
return Futures.catching(partialLoadResultFuture,
IOException.class, LoadResult.Error::new, executor);
}
private ListenableFuture<LoadResult<String, Post>> loadAnonymousHomePosts(@NonNull LoadParams<String> loadParams, RedditAPI api) {
ListenableFuture<Response<String>> anonymousHomePosts = api.getSubredditBestPostsListenableFuture(subredditOrUserName, sortType.getType(), sortType.getTime(), loadParams.getKey());
ListenableFuture<LoadResult<String, Post>> pageFuture = Futures.transform(anonymousHomePosts, this::transformData, executor);
ListenableFuture<LoadResult<String, Post>> partialLoadResultFuture =
Futures.catching(pageFuture, HttpException.class,
LoadResult.Error::new, executor);
return Futures.catching(partialLoadResultFuture,
IOException.class, LoadResult.Error::new, executor);
}
}