mirror of
https://codeberg.org/Bazsalanszky/Infinity-For-Lemmy.git
synced 2025-10-29 16:08:07 +01:00
Multicommunity Working?
This commit improves the existing PoC multicommunity implementation with a "less hacked together" one
This commit is contained in:
@@ -114,7 +114,7 @@ public class CreateMultiRedditActivity extends BaseActivity {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
mAccessToken = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN, null);
|
||||
mAccountName = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCOUNT_NAME, "-");
|
||||
mAccountName = mCurrentAccountSharedPreferences.getString(SharedPreferencesUtils.ACCOUNT_QUALIFIED_NAME, "-");
|
||||
|
||||
visibilityLinearLayout.setVisibility(View.GONE);
|
||||
if (mAccessToken == null) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eu.toldi.infinityforlemmy.post;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.paging.ListenableFuturePagingSource;
|
||||
@@ -15,8 +14,10 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -26,6 +27,7 @@ import eu.toldi.infinityforlemmy.SortType;
|
||||
import eu.toldi.infinityforlemmy.apis.LemmyAPI;
|
||||
import eu.toldi.infinityforlemmy.post.enrich.PostEnricher;
|
||||
import eu.toldi.infinityforlemmy.postfilter.PostFilter;
|
||||
import eu.toldi.infinityforlemmy.utils.MultiCommunityUtils;
|
||||
import eu.toldi.infinityforlemmy.utils.SharedPreferencesUtils;
|
||||
import retrofit2.HttpException;
|
||||
import retrofit2.Response;
|
||||
@@ -226,7 +228,7 @@ public class PostPagingSource extends ListenableFuturePagingSource<Integer, Post
|
||||
if (savePostFeedScrolledPosition) {
|
||||
String accountNameForCache = accountName == null ? SharedPreferencesUtils.FRONT_PAGE_SCROLLED_POSITION_ANONYMOUS : accountName;
|
||||
// TODO: Fix this. Save the page number?
|
||||
page = null ; // postFeedScrolledPositionSharedPreferences.getString(accountNameForCache + SharedPreferencesUtils.FRONT_PAGE_SCROLLED_POSITION_FRONT_PAGE_BASE, null);
|
||||
page = null; // postFeedScrolledPositionSharedPreferences.getString(accountNameForCache + SharedPreferencesUtils.FRONT_PAGE_SCROLLED_POSITION_FRONT_PAGE_BASE, null);
|
||||
} else {
|
||||
page = null;
|
||||
}
|
||||
@@ -294,46 +296,79 @@ public class PostPagingSource extends ListenableFuturePagingSource<Integer, Post
|
||||
IOException.class, LoadResult.Error::new, executor);
|
||||
}
|
||||
|
||||
Map<String, List<Post>> undisplayedPosts = new HashMap<>();
|
||||
|
||||
private ListenableFuture<LoadResult<Integer, Post>> loadMultipleSubredditPosts(@NonNull LoadParams<Integer> loadParams, LemmyAPI api, List<String> communities) {
|
||||
List<ListenableFuture<LoadResult<Integer, Post>>> futures = new ArrayList<>();
|
||||
List<Post> combinedPostsFromCache = new ArrayList<>();
|
||||
|
||||
// Check and use undisplayed posts first
|
||||
for (String community : communities) {
|
||||
ListenableFuture<Response<String>> subredditPost;
|
||||
|
||||
subredditPost = api.getPostsListenableFuture(null, sortType.getType().value, loadParams.getKey(), 25, null, community, false, accessToken);
|
||||
|
||||
ListenableFuture<LoadResult<Integer, Post>> communityFuture = Futures.transform(subredditPost,
|
||||
this::transformData, executor);
|
||||
|
||||
ListenableFuture<LoadResult<Integer, Post>> partialLoadResultFuture =
|
||||
Futures.catching(communityFuture, HttpException.class,
|
||||
LoadResult.Error::new, executor);
|
||||
|
||||
futures.add(Futures.catching(partialLoadResultFuture,
|
||||
IOException.class, LoadResult.Error::new, executor));
|
||||
futures.add(fetchPostsFromCommunity(api, loadParams, community));
|
||||
}
|
||||
|
||||
// Combine and sort posts from cache and fetched
|
||||
return Futures.transform(Futures.successfulAsList(futures),
|
||||
results -> {
|
||||
List<Post> combinedPosts = new ArrayList<>();
|
||||
for (LoadResult<Integer, Post> result : results) {
|
||||
if (result instanceof LoadResult.Page) {
|
||||
combinedPosts.addAll(((LoadResult.Page<Integer, Post>) result).getData());
|
||||
} else if (result instanceof LoadResult.Error) {
|
||||
// Handle or propagate the error if needed
|
||||
return result;
|
||||
combinedPostsFromCache.addAll(((LoadResult.Page<Integer, Post>) result).getData());
|
||||
}
|
||||
}
|
||||
switch (sortType.getType()) {
|
||||
default:
|
||||
case NEW:
|
||||
MultiCommunityUtils.INSTANCE.sortByNewest(combinedPostsFromCache);
|
||||
case TOP_ALL:
|
||||
case TOP_YEAR:
|
||||
case TOP_NINE_MONTHS:
|
||||
case TOP_SIX_MONTHS:
|
||||
case TOP_THREE_MONTHS:
|
||||
case TOP_MONTH:
|
||||
case TOP_WEEK:
|
||||
case TOP_DAY:
|
||||
case TOP_TWELVE_HOURS:
|
||||
case TOP_SIX_HOURS:
|
||||
case TOP_HOUR:
|
||||
MultiCommunityUtils.INSTANCE.sortByScore(combinedPostsFromCache);
|
||||
|
||||
}
|
||||
|
||||
List<Post> result = MultiCommunityUtils.INSTANCE.takeFirstN(combinedPostsFromCache, 25);
|
||||
// Gather undisplayed posts
|
||||
for (int i = result.size(); i < combinedPostsFromCache.size(); i++) {
|
||||
Post post = combinedPostsFromCache.get(i);
|
||||
if (undisplayedPosts.containsKey(post.getCommunityInfo().getQualifiedName())) {
|
||||
undisplayedPosts.get(post.getCommunityInfo().getQualifiedName()).add(post);
|
||||
} else {
|
||||
undisplayedPosts.put(post.getCommunityInfo().getQualifiedName(), new ArrayList<>(Arrays.asList(post)));
|
||||
}
|
||||
}
|
||||
|
||||
if (sortType.getType().equals(SortType.Type.NEW)) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
combinedPosts.sort((o1, o2) -> Long.compare(o2.getPostTimeMillis(), o1.getPostTimeMillis()));
|
||||
}
|
||||
}
|
||||
return new LoadResult.Page<>(combinedPosts, null, null);
|
||||
Integer prevKey = result.size() > 0 ? (loadParams.getKey() != null) ? loadParams.getKey() : 1 : null;
|
||||
Integer nextKey = (prevKey != null) ? prevKey + 1 : null;
|
||||
|
||||
return new LoadResult.Page<>(result, prevKey, nextKey);
|
||||
}, executor);
|
||||
}
|
||||
|
||||
private ListenableFuture<LoadResult<Integer, Post>> fetchPostsFromCommunity(LemmyAPI api, LoadParams<Integer> loadParams, String community) {
|
||||
|
||||
ListenableFuture<Response<String>> subredditPost;
|
||||
|
||||
subredditPost = api.getPostsListenableFuture(null, sortType.getType().value, loadParams.getKey(), 25, null, community, false, accessToken);
|
||||
|
||||
ListenableFuture<LoadResult<Integer, Post>> communityFuture = Futures.transform(subredditPost,
|
||||
this::transformData, executor);
|
||||
|
||||
ListenableFuture<LoadResult<Integer, Post>> partialLoadResultFuture =
|
||||
Futures.catching(communityFuture, 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, LemmyAPI api) {
|
||||
|
||||
@@ -17,12 +17,16 @@ import androidx.paging.PagingConfig;
|
||||
import androidx.paging.PagingData;
|
||||
import androidx.paging.PagingDataTransforms;
|
||||
import androidx.paging.PagingLiveData;
|
||||
import androidx.paging.PagingSource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import eu.toldi.infinityforlemmy.RetrofitHolder;
|
||||
import eu.toldi.infinityforlemmy.SortType;
|
||||
import eu.toldi.infinityforlemmy.apis.LemmyAPI;
|
||||
import eu.toldi.infinityforlemmy.multicommunity.MulticommunityPagingSource;
|
||||
import eu.toldi.infinityforlemmy.post.enrich.PostEnricher;
|
||||
import eu.toldi.infinityforlemmy.postfilter.PostFilter;
|
||||
import eu.toldi.infinityforlemmy.utils.SharedPreferencesUtils;
|
||||
@@ -230,8 +234,8 @@ public class PostViewModel extends ViewModel {
|
||||
currentlyReadPostIdsLiveData.setValue(true);
|
||||
}
|
||||
|
||||
public PostPagingSource returnPagingSoruce() {
|
||||
PostPagingSource paging3PagingSource;
|
||||
public PagingSource returnPagingSoruce() {
|
||||
PagingSource paging3PagingSource;
|
||||
switch (postType) {
|
||||
case PostPagingSource.TYPE_FRONT_PAGE:
|
||||
paging3PagingSource = new PostPagingSource(executor, retrofit, accessToken, accountName,
|
||||
@@ -239,12 +243,15 @@ public class PostViewModel extends ViewModel {
|
||||
postFilter, readPostList, name, postEnricher);
|
||||
break;
|
||||
case PostPagingSource.TYPE_SUBREDDIT:
|
||||
case PostPagingSource.TYPE_MULTI_REDDIT:
|
||||
case PostPagingSource.TYPE_ANONYMOUS_FRONT_PAGE:
|
||||
paging3PagingSource = new PostPagingSource(executor, retrofit, accessToken, accountName,
|
||||
sharedPreferences, postFeedScrolledPositionSharedPreferences, name, postType,
|
||||
sortType, postFilter, readPostList, postEnricher);
|
||||
break;
|
||||
case PostPagingSource.TYPE_MULTI_REDDIT:
|
||||
case PostPagingSource.TYPE_ANONYMOUS_FRONT_PAGE:
|
||||
paging3PagingSource = new MulticommunityPagingSource(retrofit.getRetrofit().create(LemmyAPI.class), List.of(name.split(Pattern.quote(","))), accessToken,
|
||||
sortType, executor, postFilter, readPostList, postEnricher);
|
||||
break;
|
||||
case PostPagingSource.TYPE_SEARCH:
|
||||
paging3PagingSource = new PostPagingSource(executor, retrofit, accessToken, accountName,
|
||||
sharedPreferences, postFeedScrolledPositionSharedPreferences, name, query, trendingSource,
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package eu.toldi.infinityforlemmy.multicommunity
|
||||
|
||||
import androidx.paging.ListenableFuturePagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.google.common.base.Function
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import eu.toldi.infinityforlemmy.SortType
|
||||
import eu.toldi.infinityforlemmy.apis.LemmyAPI
|
||||
import eu.toldi.infinityforlemmy.post.ParsePost
|
||||
import eu.toldi.infinityforlemmy.post.Post
|
||||
import eu.toldi.infinityforlemmy.post.enrich.PostEnricher
|
||||
import eu.toldi.infinityforlemmy.postfilter.PostFilter
|
||||
import eu.toldi.infinityforlemmy.utils.MultiCommunityUtils.sortByNewest
|
||||
import eu.toldi.infinityforlemmy.utils.MultiCommunityUtils.sortByScore
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
|
||||
class MulticommunityPagingSource(
|
||||
private val api: LemmyAPI,
|
||||
private val communities: List<String>,
|
||||
private val accessToken: String,
|
||||
private val sortType: SortType,
|
||||
private val executor: Executor,
|
||||
private val postFilter: PostFilter,
|
||||
private val readPostList: List<String>, private val postEnricher: PostEnricher
|
||||
) : ListenableFuturePagingSource<Map<String, Int>, Post>() {
|
||||
|
||||
private val postLinkedHashSet: LinkedHashSet<Post> = LinkedHashSet()
|
||||
private val undisplayedPosts = mutableMapOf<String, MutableList<Post>>()
|
||||
|
||||
override fun loadFuture(loadParams: LoadParams<Map<String, Int>>): ListenableFuture<LoadResult<Map<String, Int>, Post>> {
|
||||
val currentPageMap = loadParams.key ?: communities.associateWith { 1 }
|
||||
|
||||
// Initialize a list to hold futures of post loading results
|
||||
val futuresList = mutableListOf<ListenableFuture<LoadResult<Int, Post>>>()
|
||||
val combinedPosts = mutableListOf<Post>()
|
||||
val wasCached = mutableMapOf<String, Boolean>()
|
||||
// Loop through each community and fetch posts
|
||||
for ((community, pageNumber) in currentPageMap) {
|
||||
|
||||
if (undisplayedPosts.containsKey(community) && undisplayedPosts[community]!!.size > 10) {
|
||||
val posts: List<Post> = undisplayedPosts[community] ?: listOf()
|
||||
combinedPosts.addAll(posts)
|
||||
undisplayedPosts[community]!!.clear() // Clear used posts
|
||||
wasCached[community] = true
|
||||
} else {
|
||||
val future = fetchPostsFromCommunity(api, pageNumber, community)
|
||||
futuresList.add(future)
|
||||
wasCached[community] = false
|
||||
}
|
||||
}
|
||||
|
||||
val combinedFuture: ListenableFuture<LoadResult<Map<String, Int>, Post>> =
|
||||
Futures.transform(Futures.successfulAsList(futuresList), { individualResults ->
|
||||
// Combine the results from individual communities
|
||||
|
||||
val nextPageMap = mutableMapOf<String, Int>()
|
||||
|
||||
for ((index, result) in individualResults.withIndex()) {
|
||||
if (result is LoadResult.Page<Int, Post>) {
|
||||
combinedPosts.addAll(result.data)
|
||||
val addition = if (wasCached[communities[index]] == true) 0 else 1
|
||||
nextPageMap[communities[index]] =
|
||||
result.nextKey ?: (currentPageMap[communities[index]]?.plus(addition))
|
||||
?: 1
|
||||
}
|
||||
// Handle other cases like LoadResult.Error
|
||||
}
|
||||
|
||||
val sorted = when (sortType.type) {
|
||||
SortType.Type.NEW -> {
|
||||
sortByNewest(combinedPosts)
|
||||
}
|
||||
|
||||
SortType.Type.TOP_ALL, SortType.Type.TOP_YEAR, SortType.Type.TOP_NINE_MONTHS, SortType.Type.TOP_SIX_MONTHS, SortType.Type.TOP_THREE_MONTHS, SortType.Type.TOP_MONTH, SortType.Type.TOP_WEEK, SortType.Type.TOP_DAY, SortType.Type.TOP_TWELVE_HOURS, SortType.Type.TOP_SIX_HOURS, SortType.Type.TOP_HOUR
|
||||
-> sortByScore(combinedPosts)
|
||||
|
||||
else -> {
|
||||
sortByNewest(combinedPosts)
|
||||
}
|
||||
}
|
||||
|
||||
val filteredPosts = sorted.take(25)
|
||||
|
||||
// Store undisplayed posts
|
||||
for (post in combinedPosts) {
|
||||
if (!filteredPosts.contains(post)) {
|
||||
if (undisplayedPosts.containsKey(post.subredditNamePrefixed)) {
|
||||
undisplayedPosts[post.subredditNamePrefixed]?.add(post)
|
||||
} else {
|
||||
undisplayedPosts[post.subredditNamePrefixed] = mutableListOf(post)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoadResult.Page(
|
||||
data = filteredPosts,
|
||||
prevKey = null, // Define prevKey logic if needed
|
||||
nextKey = nextPageMap
|
||||
)
|
||||
}, executor)
|
||||
val partialLoadResultFuture =
|
||||
Futures.catching<LoadResult<Map<String, Int>, Post>, HttpException>(
|
||||
combinedFuture,
|
||||
HttpException::class.java,
|
||||
Function<HttpException, LoadResult<Map<String, Int>, Post>> { throwable: HttpException ->
|
||||
LoadResult.Error(throwable)
|
||||
}, executor
|
||||
)
|
||||
|
||||
return partialLoadResultFuture
|
||||
|
||||
}
|
||||
|
||||
private fun fetchPostsFromCommunity(
|
||||
api: LemmyAPI,
|
||||
pageNumber: Int,
|
||||
community: String
|
||||
): ListenableFuture<LoadResult<Int, Post>> {
|
||||
val subredditPost: ListenableFuture<Response<String>>
|
||||
subredditPost = api.getPostsListenableFuture(
|
||||
null,
|
||||
sortType.getType().value,
|
||||
pageNumber,
|
||||
25,
|
||||
null,
|
||||
community,
|
||||
false,
|
||||
accessToken
|
||||
)
|
||||
val communityFuture: ListenableFuture<LoadResult<Int, Post>> = Futures.transform(
|
||||
subredditPost,
|
||||
{ response -> transformData(response, pageNumber) }, executor
|
||||
)
|
||||
|
||||
val partialLoadResultFuture: ListenableFuture<LoadResult<Int, Post>> =
|
||||
Futures.catching(
|
||||
communityFuture,
|
||||
HttpException::class.java,
|
||||
{ throwable -> LoadResult.Error<Int, Post>(throwable) },
|
||||
executor
|
||||
)
|
||||
|
||||
return Futures.catching(
|
||||
partialLoadResultFuture,
|
||||
IOException::class.java,
|
||||
Function<IOException, LoadResult<Int, Post>> { throwable ->
|
||||
LoadResult.Error<Int, Post>(throwable)
|
||||
}, executor
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun transformData(response: Response<String>, pageNumber: Int): LoadResult<Int, Post> {
|
||||
if (!response.isSuccessful) return LoadResult.Error(Exception("Response failed"))
|
||||
|
||||
val responseString =
|
||||
response.body() ?: return LoadResult.Error(Exception("Empty response body"))
|
||||
val newPosts =
|
||||
ParsePost.parsePostsSync(responseString, -1, postFilter, readPostList, postEnricher)
|
||||
?: return LoadResult.Error(Exception("Error parsing posts"))
|
||||
|
||||
// Filter out already linked posts
|
||||
val trulyNewPosts = newPosts.filterNot(postLinkedHashSet::contains)
|
||||
postLinkedHashSet.addAll(trulyNewPosts)
|
||||
|
||||
val nextKey = if (trulyNewPosts.isNotEmpty()) pageNumber + 1 else null
|
||||
return LoadResult.Page(trulyNewPosts, null, nextKey)
|
||||
}
|
||||
|
||||
|
||||
override fun getRefreshKey(pagingState: PagingState<Map<String, Int>, Post>): Map<String, Int>? {
|
||||
return null
|
||||
}
|
||||
|
||||
// Additional methods and logic...
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package eu.toldi.infinityforlemmy.utils
|
||||
|
||||
import eu.toldi.infinityforlemmy.post.Post
|
||||
|
||||
object MultiCommunityUtils {
|
||||
|
||||
fun takeFirstN(posts: List<Post>, take: Int = 25): List<Post> {
|
||||
return posts.take(take)
|
||||
}
|
||||
|
||||
fun sortByNewest(posts: List<Post>): List<Post> {
|
||||
return posts.sortedByDescending { it.postTimeMillis }
|
||||
}
|
||||
|
||||
fun sortByOldest(posts: List<Post>): List<Post> {
|
||||
return posts.sortedBy { it.postTimeMillis }
|
||||
}
|
||||
|
||||
fun sortByMostComments(posts: List<Post>): List<Post> {
|
||||
return posts.sortedByDescending { it.nComments }
|
||||
}
|
||||
|
||||
fun sortByScore(posts: List<Post>): List<Post> {
|
||||
return posts.sortedByDescending { it.score }
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user