mirror of
				https://codeberg.org/tom79/Fedilab.git
				synced 2025-10-20 11:20:16 +03:00 
			
		
		
		
	Fix issue #672 - Support pagination for search / trending
This commit is contained in:
		
							parent
							
								
									f38ae254c1
								
							
						
					
					
						commit
						e91a2f7984
					
				
					 9 changed files with 151 additions and 35 deletions
				
			
		|  | @ -19,4 +19,5 @@ public class Pagination { | |||
|     public String max_id; | ||||
|     public String min_id; | ||||
|     public String since_id; | ||||
|     public Integer offset; | ||||
| } | ||||
|  |  | |||
|  | @ -24,5 +24,6 @@ public class Results { | |||
|     public java.util.List<Status> statuses; | ||||
|     @SerializedName("hashtags") | ||||
|     public java.util.List<Tag> hashtags; | ||||
|     public Pagination pagination; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -80,6 +80,7 @@ public class MastodonHelper { | |||
| 
 | ||||
|     public static final int ACCOUNTS_PER_CALL = 40; | ||||
|     public static final int STATUSES_PER_CALL = 40; | ||||
|     public static final int SEARCH_PER_CALL = 20; | ||||
|     public static final int NOTIFICATIONS_PER_CALL = 30; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -56,6 +56,7 @@ public class FragmentMastodonAccount extends Fragment { | |||
|     private boolean flagLoading; | ||||
|     private List<Account> accounts; | ||||
|     private String max_id; | ||||
|     private Integer offset; | ||||
|     private AccountAdapter accountAdapter; | ||||
|     private String search; | ||||
|     private Account accountTimeline; | ||||
|  | @ -84,6 +85,7 @@ public class FragmentMastodonAccount extends Fragment { | |||
|         binding.recyclerView.setVisibility(View.GONE); | ||||
|         accountsVM = new ViewModelProvider(FragmentMastodonAccount.this).get(viewModelKey, AccountsVM.class); | ||||
|         max_id = null; | ||||
|         offset = 0; | ||||
|         router(true); | ||||
|     } | ||||
| 
 | ||||
|  | @ -109,7 +111,8 @@ public class FragmentMastodonAccount extends Fragment { | |||
|             } | ||||
|         } else if (search != null) { | ||||
|             SearchVM searchVM = new ViewModelProvider(FragmentMastodonAccount.this).get(viewModelKey, SearchVM.class); | ||||
|             searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, search.trim(), null, "accounts", false, true, false, 0, null, null, MastodonHelper.STATUSES_PER_CALL) | ||||
|             if (firstLoad) { | ||||
|                 searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, search.trim(), null, "accounts", false, true, false, 0, null, null, MastodonHelper.SEARCH_PER_CALL) | ||||
|                         .observe(getViewLifecycleOwner(), results -> { | ||||
|                             if (results != null) { | ||||
|                                 Accounts accounts = new Accounts(); | ||||
|  | @ -121,6 +124,18 @@ public class FragmentMastodonAccount extends Fragment { | |||
|                                 Toasty.error(requireActivity(), getString(R.string.toast_error), Toasty.LENGTH_SHORT).show(); | ||||
|                             } | ||||
|                         }); | ||||
|             } else { | ||||
|                 searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, search.trim(), null, "accounts", false, true, false, offset, null, null, MastodonHelper.SEARCH_PER_CALL) | ||||
|                         .observe(getViewLifecycleOwner(), results -> { | ||||
|                             if (results != null) { | ||||
|                                 Accounts accounts = new Accounts(); | ||||
|                                 Pagination pagination = new Pagination(); | ||||
|                                 accounts.accounts = results.accounts; | ||||
|                                 accounts.pagination = pagination; | ||||
|                                 dealWithPagination(accounts); | ||||
|                             } | ||||
|                         }); | ||||
|             } | ||||
|         } else if (timelineType == Timeline.TimeLineEnum.MUTED_TIMELINE) { | ||||
|             if (firstLoad) { | ||||
|                 accountsVM.getMutes(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, String.valueOf(MastodonHelper.accountsPerCall(requireActivity())), null, null) | ||||
|  | @ -204,7 +219,11 @@ public class FragmentMastodonAccount extends Fragment { | |||
| 
 | ||||
|         this.accounts = accounts.accounts; | ||||
|         accountAdapter = new AccountAdapter(this.accounts, timelineType == Timeline.TimeLineEnum.MUTED_TIMELINE_HOME); | ||||
|         if (search == null) { | ||||
|             flagLoading = accounts.pagination.max_id == null; | ||||
|         } else { | ||||
|             offset += MastodonHelper.SEARCH_PER_CALL; | ||||
|         } | ||||
|         LinearLayoutManager mLayoutManager = new LinearLayoutManager(requireActivity()); | ||||
|         binding.recyclerView.setLayoutManager(mLayoutManager); | ||||
|         binding.recyclerView.setAdapter(accountAdapter); | ||||
|  | @ -263,6 +282,9 @@ public class FragmentMastodonAccount extends Fragment { | |||
|             //Fetch the relationship | ||||
|             fetchRelationShip(fetched_accounts.accounts, position); | ||||
|             max_id = fetched_accounts.pagination.max_id; | ||||
|             if (search != null) { | ||||
|                 offset += MastodonHelper.SEARCH_PER_CALL; | ||||
|             } | ||||
|             accountAdapter.notifyItemRangeInserted(startId, fetched_accounts.accounts.size()); | ||||
|         } else { | ||||
|             flagLoading = true; | ||||
|  |  | |||
|  | @ -25,6 +25,7 @@ import androidx.annotation.Nullable; | |||
| import androidx.fragment.app.Fragment; | ||||
| import androidx.lifecycle.ViewModelProvider; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
|  | @ -49,6 +50,9 @@ public class FragmentMastodonTag extends Fragment { | |||
|     private TagAdapter tagAdapter; | ||||
|     private String search; | ||||
|     private Timeline.TimeLineEnum timelineType; | ||||
|     private Integer offset; | ||||
|     private boolean flagLoading; | ||||
|     private List<Tag> tagList; | ||||
| 
 | ||||
|     public View onCreateView(@NonNull LayoutInflater inflater, | ||||
|                              ViewGroup container, Bundle savedInstanceState) { | ||||
|  | @ -66,6 +70,10 @@ public class FragmentMastodonTag extends Fragment { | |||
|         super.onViewCreated(view, savedInstanceState); | ||||
|         binding.loader.setVisibility(View.VISIBLE); | ||||
|         binding.recyclerView.setVisibility(View.GONE); | ||||
|         offset = 0; | ||||
|         flagLoading = false; | ||||
|         binding.swipeContainer.setRefreshing(false); | ||||
|         binding.swipeContainer.setEnabled(false); | ||||
|         router(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -75,16 +83,24 @@ public class FragmentMastodonTag extends Fragment { | |||
|     private void router() { | ||||
|         if (search != null && timelineType == null) { | ||||
|             SearchVM searchVM = new ViewModelProvider(FragmentMastodonTag.this).get(SearchVM.class); | ||||
|             searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, search.trim(), null, "hashtags", false, true, false, 0, null, null, MastodonHelper.STATUSES_PER_CALL) | ||||
|             searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, search.trim(), null, "hashtags", false, true, false, offset, null, null, MastodonHelper.SEARCH_PER_CALL) | ||||
|                     .observe(getViewLifecycleOwner(), results -> { | ||||
|                         if (results != null && results.hashtags != null) { | ||||
|                         if (results != null && results.hashtags != null && offset == 0) { | ||||
|                             initializeTagCommonView(results.hashtags); | ||||
|                         } else if (results != null && results.hashtags != null) { | ||||
|                             dealWithPaginationTag(results.hashtags); | ||||
|                         } | ||||
|                     }); | ||||
|         } else if (timelineType == Timeline.TimeLineEnum.TREND_TAG) { | ||||
|             TimelinesVM timelinesVM = new ViewModelProvider(FragmentMastodonTag.this).get(TimelinesVM.class); | ||||
|             timelinesVM.getTagsTrends(BaseMainActivity.currentToken, BaseMainActivity.currentInstance) | ||||
|                     .observe(getViewLifecycleOwner(), this::initializeTagCommonView); | ||||
|             timelinesVM.getTagsTrends(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, offset, MastodonHelper.SEARCH_PER_CALL) | ||||
|                     .observe(getViewLifecycleOwner(), tags -> { | ||||
|                         if (tags != null && offset == 0) { | ||||
|                             initializeTagCommonView(tags); | ||||
|                         } else if (tags != null) { | ||||
|                             dealWithPaginationTag(tags); | ||||
|                         } | ||||
|                     }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -92,6 +108,24 @@ public class FragmentMastodonTag extends Fragment { | |||
|         binding.recyclerView.setAdapter(tagAdapter); | ||||
|     } | ||||
| 
 | ||||
|     private void dealWithPaginationTag(final List<Tag> tags) { | ||||
|         if (binding == null || !isAdded() || getActivity() == null) { | ||||
|             return; | ||||
|         } | ||||
|         if (tags == null || tags.size() == 0) { | ||||
|             flagLoading = true; | ||||
|             binding.loadingNextElements.setVisibility(View.GONE); | ||||
|             return; | ||||
|         } | ||||
|         offset += MastodonHelper.SEARCH_PER_CALL; | ||||
|         binding.swipeContainer.setRefreshing(false); | ||||
|         binding.loadingNextElements.setVisibility(View.GONE); | ||||
|         flagLoading = false; | ||||
|         int start = tagList.size(); | ||||
|         tagList.addAll(tags); | ||||
|         tagAdapter.notifyItemRangeInserted(start, tags.size()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Intialize the view for tags | ||||
|      * | ||||
|  | @ -101,6 +135,7 @@ public class FragmentMastodonTag extends Fragment { | |||
|         if (binding == null || !isAdded() || getActivity() == null) { | ||||
|             return; | ||||
|         } | ||||
|         tagList = new ArrayList<>(); | ||||
|         binding.loader.setVisibility(View.GONE); | ||||
|         binding.noAction.setVisibility(View.GONE); | ||||
|         binding.swipeContainer.setRefreshing(false); | ||||
|  | @ -130,12 +165,35 @@ public class FragmentMastodonTag extends Fragment { | |||
|                 tags.add(0, tag); | ||||
|             } | ||||
|         } | ||||
|         offset += MastodonHelper.SEARCH_PER_CALL; | ||||
|         binding.recyclerView.setVisibility(View.VISIBLE); | ||||
|         binding.noAction.setVisibility(View.GONE); | ||||
|         tagAdapter = new TagAdapter(tags); | ||||
|         tagList.addAll(tags); | ||||
|         tagAdapter = new TagAdapter(tagList); | ||||
|         LinearLayoutManager mLayoutManager = new LinearLayoutManager(requireActivity()); | ||||
|         binding.recyclerView.setLayoutManager(mLayoutManager); | ||||
|         binding.recyclerView.setAdapter(tagAdapter); | ||||
|         binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { | ||||
|             @Override | ||||
|             public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { | ||||
| 
 | ||||
|                 int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition(); | ||||
|                 if (dy > 0) { | ||||
|                     int visibleItemCount = mLayoutManager.getChildCount(); | ||||
|                     int totalItemCount = mLayoutManager.getItemCount(); | ||||
| 
 | ||||
|                     if (firstVisibleItem + visibleItemCount == totalItemCount) { | ||||
|                         if (!flagLoading) { | ||||
|                             flagLoading = true; | ||||
|                             binding.loadingNextElements.setVisibility(View.VISIBLE); | ||||
|                             router(); | ||||
|                         } | ||||
|                     } else { | ||||
|                         binding.loadingNextElements.setVisibility(View.GONE); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -80,6 +80,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. | |||
|     private String search, searchCache; | ||||
|     private Status statusReport; | ||||
|     private String max_id, min_id, min_id_fetch_more, max_id_fetch_more; | ||||
|     private Integer offset; | ||||
|     private StatusAdapter statusAdapter; | ||||
|     private Timeline.TimeLineEnum timelineType; | ||||
|     private List<Status> timelineStatuses; | ||||
|  | @ -188,6 +189,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. | |||
|             if (statusAdapter != null) { | ||||
|                 statusAdapter.notifyItemRangeRemoved(0, count); | ||||
|                 max_id = statusReport != null ? statusReport.id : null; | ||||
|                 offset = 0; | ||||
|                 SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); | ||||
|                 rememberPosition = sharedpreferences.getBoolean(getString(R.string.SET_REMEMBER_POSITION), true); | ||||
|                 //Inner marker are only for pinned timelines and main timelines, they have isViewInitialized set to false | ||||
|  | @ -281,6 +283,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. | |||
|         binding.loader.setVisibility(View.VISIBLE); | ||||
|         binding.recyclerView.setVisibility(View.GONE); | ||||
|         max_id = statusReport != null ? statusReport.id : null; | ||||
|         offset = 0; | ||||
|         SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); | ||||
|         rememberPosition = sharedpreferences.getBoolean(getString(R.string.SET_REMEMBER_POSITION), true); | ||||
|         //Inner marker are only for pinned timelines and main timelines, they have isViewInitialized set to false | ||||
|  | @ -428,7 +431,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. | |||
|             } | ||||
|             //Update the timeline with new statuses | ||||
|             int insertedStatus; | ||||
|             if (timelineType != Timeline.TimeLineEnum.TREND_MESSAGE_PUBLIC && timelineType != Timeline.TimeLineEnum.TREND_MESSAGE) { | ||||
|             if (timelineType != Timeline.TimeLineEnum.TREND_MESSAGE_PUBLIC && timelineType != Timeline.TimeLineEnum.TREND_MESSAGE && search == null) { | ||||
|                 insertedStatus = updateStatusListWith(fetched_statuses.statuses); | ||||
|             } else { //Trends cannot be ordered by id | ||||
|                 insertedStatus = fetched_statuses.statuses.size(); | ||||
|  | @ -455,6 +458,9 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. | |||
|                     min_id = fetched_statuses.pagination.min_id; | ||||
|                 } | ||||
|             } | ||||
|             if (search != null) { | ||||
|                 offset += MastodonHelper.SEARCH_PER_CALL; | ||||
|             } | ||||
|             int sizeBeforeFilter = 0; | ||||
|             int filteredMessage = 0; | ||||
|             int requestedMessages = MastodonHelper.statusesPerCall(requireActivity()); | ||||
|  | @ -557,6 +563,9 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. | |||
|         if (min_id == null || (statuses.pagination.min_id != null && Helper.compareTo(statuses.pagination.min_id, min_id) > 0)) { | ||||
|             min_id = statuses.pagination.min_id; | ||||
|         } | ||||
|         if (search != null) { | ||||
|             offset += MastodonHelper.SEARCH_PER_CALL; | ||||
|         } | ||||
|         statusAdapter = new StatusAdapter(timelineStatuses, timelineType, minified, canBeFederated, checkRemotely); | ||||
|         statusAdapter.fetchMoreCallBack = this; | ||||
|         if (statusReport != null) { | ||||
|  | @ -570,8 +579,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. | |||
|         mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); | ||||
|         binding.recyclerView.setLayoutManager(mLayoutManager); | ||||
|         binding.recyclerView.setAdapter(statusAdapter); | ||||
| 
 | ||||
|         if (searchCache == null && timelineType != Timeline.TimeLineEnum.TREND_MESSAGE) { | ||||
|         if (timelineType != Timeline.TimeLineEnum.TREND_MESSAGE) { | ||||
|             binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { | ||||
|                 @Override | ||||
|                 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { | ||||
|  | @ -1033,7 +1041,8 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. | |||
|             } | ||||
|         } else if (search != null) { | ||||
|             SearchVM searchVM = new ViewModelProvider(FragmentMastodonTimeline.this).get(viewModelKey, SearchVM.class); | ||||
|             searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, search.trim(), null, null, false, true, false, 0, null, null, MastodonHelper.STATUSES_PER_CALL) | ||||
|             if (direction == null) { | ||||
|                 searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, search.trim(), null, null, false, true, false, 0, null, null, MastodonHelper.SEARCH_PER_CALL) | ||||
|                         .observe(getViewLifecycleOwner(), results -> { | ||||
|                             if (results != null) { | ||||
|                                 Statuses statuses = new Statuses(); | ||||
|  | @ -1044,6 +1053,19 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. | |||
|                                 Toasty.error(requireActivity(), getString(R.string.toast_error), Toasty.LENGTH_LONG).show(); | ||||
|                             } | ||||
|                         }); | ||||
|             } else if (direction == DIRECTION.BOTTOM) { | ||||
|                 searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, search.trim(), null, null, false, true, false, offset, null, null, MastodonHelper.SEARCH_PER_CALL) | ||||
|                         .observe(getViewLifecycleOwner(), results -> { | ||||
|                             if (results != null) { | ||||
|                                 Statuses statuses = new Statuses(); | ||||
|                                 statuses.statuses = results.statuses; | ||||
|                                 statuses.pagination = new Pagination(); | ||||
|                                 dealWithPagination(statuses, direction, false); | ||||
|                             } | ||||
|                         }); | ||||
|             } else { | ||||
|                 flagLoading = false; | ||||
|             } | ||||
|         } else if (searchCache != null) { | ||||
|             SearchVM searchVM = new ViewModelProvider(FragmentMastodonTimeline.this).get(viewModelKey, SearchVM.class); | ||||
|             searchVM.searchCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, searchCache.trim()) | ||||
|  |  | |||
|  | @ -99,10 +99,15 @@ public class SearchVM extends AndroidViewModel { | |||
|         MastodonSearchService mastodonSearchService = init(instance); | ||||
|         resultsMutableLiveData = new MutableLiveData<>(); | ||||
|         new Thread(() -> { | ||||
|             int finalLimit = 40; | ||||
|             if (limit != null && limit < 40) { | ||||
|                 finalLimit = limit; | ||||
|             } | ||||
|             Call<Results> resultsCall = mastodonSearchService.search( | ||||
|                     token, q, account_id, type, exclude_unreviewed, | ||||
|                     resolve, following, offset, max_id, min_id, limit); | ||||
|                     resolve, following, offset, max_id, min_id, finalLimit); | ||||
|             Results results = null; | ||||
| 
 | ||||
|             if (resultsCall != null) { | ||||
|                 try { | ||||
|                     Response<Results> resultsResponse = resultsCall.execute(); | ||||
|  | @ -118,6 +123,7 @@ public class SearchVM extends AndroidViewModel { | |||
|                             if (results.hashtags == null) { | ||||
|                                 results.hashtags = new ArrayList<>(); | ||||
|                             } | ||||
|                             results.pagination.offset = finalLimit; | ||||
|                         } | ||||
|                     } | ||||
|                 } catch (Exception e) { | ||||
|  |  | |||
|  | @ -198,11 +198,11 @@ public class TimelinesVM extends AndroidViewModel { | |||
|         return statusesMutableLiveData; | ||||
|     } | ||||
| 
 | ||||
|     public LiveData<List<Tag>> getTagsTrends(String token, @NonNull String instance) { | ||||
|     public LiveData<List<Tag>> getTagsTrends(String token, @NonNull String instance, Integer offset, Integer limit) { | ||||
|         MastodonTimelinesService mastodonTimelinesService = init(instance); | ||||
|         tagListMutableLiveData = new MutableLiveData<>(); | ||||
|         new Thread(() -> { | ||||
|             Call<List<Tag>> publicTlCall = mastodonTimelinesService.getTagTrends(token); | ||||
|             Call<List<Tag>> publicTlCall = mastodonTimelinesService.getTagTrends(token, offset, limit); | ||||
|             List<Tag> tagList = null; | ||||
|             if (publicTlCall != null) { | ||||
|                 try { | ||||
|  |  | |||
|  | @ -0,0 +1,5 @@ | |||
| Fixed: | ||||
| - Long press on Nitter tabs | ||||
| - Open with another accounts | ||||
| - Chars size not respected for Android 5-6 | ||||
| - Wrong instance fetched for instances.social | ||||
		Loading…
	
		Reference in a new issue