Fix issue #672 - Support pagination for search / trending
This commit is contained in:
parent
f38ae254c1
commit
e91a2f7984
|
@ -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,18 +111,31 @@ 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)
|
||||
.observe(getViewLifecycleOwner(), results -> {
|
||||
if (results != null) {
|
||||
Accounts accounts = new Accounts();
|
||||
Pagination pagination = new Pagination();
|
||||
accounts.accounts = results.accounts;
|
||||
accounts.pagination = pagination;
|
||||
initializeAccountCommonView(accounts);
|
||||
} else {
|
||||
Toasty.error(requireActivity(), getString(R.string.toast_error), Toasty.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
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();
|
||||
Pagination pagination = new Pagination();
|
||||
accounts.accounts = results.accounts;
|
||||
accounts.pagination = pagination;
|
||||
initializeAccountCommonView(accounts);
|
||||
} else {
|
||||
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);
|
||||
flagLoading = accounts.pagination.max_id == null;
|
||||
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,17 +1041,31 @@ 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)
|
||||
.observe(getViewLifecycleOwner(), results -> {
|
||||
if (results != null) {
|
||||
Statuses statuses = new Statuses();
|
||||
statuses.statuses = results.statuses;
|
||||
statuses.pagination = new Pagination();
|
||||
initializeStatusesCommonView(statuses);
|
||||
} else {
|
||||
Toasty.error(requireActivity(), getString(R.string.toast_error), Toasty.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
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();
|
||||
statuses.statuses = results.statuses;
|
||||
statuses.pagination = new Pagination();
|
||||
initializeStatusesCommonView(statuses);
|
||||
} else {
|
||||
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 New Issue