diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Status.java b/app/src/main/java/app/fedilab/android/client/entities/api/Status.java index 80910bcb..473f95b7 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Status.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Status.java @@ -95,6 +95,7 @@ public class Status implements Serializable, Cloneable { public boolean isExpended = false; public boolean isTruncated = true; public boolean isFetchMore = false; + public boolean isFetchMoreHidden = false; public boolean isMediaDisplayed = false; public boolean isMediaObfuscated = true; public boolean isChecked = false; diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java b/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java index 11c2b00d..4023bc1d 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java +++ b/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java @@ -23,6 +23,7 @@ import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; @@ -284,22 +285,24 @@ public class StatusCache { * @return Statuses * @throws DBException - throws a db exception */ - public Statuses geStatuses(CacheEnum type, String instance, String user_id, String max_id, String min_id) throws DBException { + public Statuses geStatuses(CacheEnum type, String instance, String user_id, String max_id, String min_id, String since_id) throws DBException { if (db == null) { throw new DBException("db is null. Wrong initialization."); } + String order = " DESC"; String selection = Sqlite.COL_INSTANCE + "='" + instance + "' AND " + Sqlite.COL_USER_ID + "= '" + user_id + "'"; String limit = String.valueOf(MastodonHelper.statusesPerCall(context)); - if (max_id == null && min_id != null) { + if (min_id != null) { selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + min_id + "'"; - } else if (max_id != null && min_id == null) { - selection += "AND " + Sqlite.COL_STATUS_ID + " < '" + max_id + "'"; + order = " ASC"; } else if (max_id != null) { - selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + min_id + "' AND " + Sqlite.COL_STATUS_ID + " < '" + max_id + "'"; + selection += "AND " + Sqlite.COL_STATUS_ID + " < '" + max_id + "'"; + } else if (since_id != null) { + selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + since_id + "'"; limit = null; } try { - Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, null, null, Sqlite.COL_STATUS_ID + " DESC", limit); + Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, null, null, Sqlite.COL_STATUS_ID + order, limit); return createStatusReply(cursorToListOfStatuses(c)); } catch (Exception e) { e.printStackTrace(); @@ -373,6 +376,11 @@ public class StatusCache { statuses.statuses = statusList; Pagination pagination = new Pagination(); if (statusList != null && statusList.size() > 0) { + //Status list is inverted, it happens for min_id due to ASC ordering + if (statusList.get(0).id.compareTo(statusList.get(statusList.size() - 1).id) < 0) { + Collections.reverse(statusList); + statuses.statuses = statusList; + } pagination.max_id = statusList.get(0).id; pagination.min_id = statusList.get(statusList.size() - 1).id; } diff --git a/app/src/main/java/app/fedilab/android/helper/PinnedTimelineHelper.java b/app/src/main/java/app/fedilab/android/helper/PinnedTimelineHelper.java index e36cb86b..f693096c 100644 --- a/app/src/main/java/app/fedilab/android/helper/PinnedTimelineHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/PinnedTimelineHelper.java @@ -43,6 +43,7 @@ import java.util.List; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; +import app.fedilab.android.activities.MainActivity; import app.fedilab.android.client.entities.api.MastodonList; import app.fedilab.android.client.entities.app.BottomMenu; import app.fedilab.android.client.entities.app.Pinned; @@ -146,7 +147,8 @@ public class PinnedTimelineHelper { //Pinned tab position will start after BOTTOM_TIMELINE_COUNT (ie 5) activityMainBinding.tabLayout.removeAllTabs(); //Small hack to hide first tabs (they represent the item of the bottom menu) - for (int i = 0; i < BOTTOM_TIMELINE_COUNT; i++) { + int toRemove = itemToRemoveInBottomMenu(activity); + for (int i = 0; i < BOTTOM_TIMELINE_COUNT - toRemove; i++) { activityMainBinding.tabLayout.addTab(activityMainBinding.tabLayout.newTab()); ((ViewGroup) activityMainBinding.tabLayout.getChildAt(0)).getChildAt(i).setVisibility(View.GONE); } @@ -203,7 +205,7 @@ public class PinnedTimelineHelper { activityMainBinding.viewPager.clearOnPageChangeListeners(); activityMainBinding.tabLayout.clearOnTabSelectedListeners(); - FedilabPageAdapter fedilabPageAdapter = new FedilabPageAdapter(activity.getSupportFragmentManager(), pinned, bottomMenu); + FedilabPageAdapter fedilabPageAdapter = new FedilabPageAdapter(activity, activity.getSupportFragmentManager(), pinned, bottomMenu); activityMainBinding.viewPager.setAdapter(fedilabPageAdapter); activityMainBinding.viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(activityMainBinding.tabLayout)); activityMainBinding.viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @@ -254,6 +256,25 @@ public class PinnedTimelineHelper { } + public static int itemToRemoveInBottomMenu(Context context) { + //Small hack to hide first tabs (they represent the item of the bottom menu) + BottomMenu bottomMenuDb; + int toRemove = 0; + try { + //If some menu items have been hidden we should not create tab for them + bottomMenuDb = new BottomMenu(context).getAllBottomMenu(MainActivity.accountWeakReference.get()); + if (bottomMenuDb != null && bottomMenuDb.bottom_menu != null) { + for (BottomMenu.MenuItem menuItem : bottomMenuDb.bottom_menu) { + if (!menuItem.visible) { + toRemove++; + } + } + } + } catch (DBException e) { + e.printStackTrace(); + } + return toRemove; + } /** * Manage long clicks on Tag timelines diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java index 6c33b91f..099f0d70 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java @@ -1675,6 +1675,9 @@ public class StatusAdapter extends RecyclerView.Adapter } else if (viewType == STATUS_ART) { //Art statuses DrawerStatusArtBinding itemBinding = DrawerStatusArtBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); return new StatusViewHolder(itemBinding); + } else if (viewType == STATUS_FETCH_MORE) { //Fetch more button + DrawerFetchMoreBinding itemBinding = DrawerFetchMoreBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new StatusViewHolder(itemBinding); } else { //Classic statuses if (!minified) { DrawerStatusBinding itemBinding = DrawerStatusBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); @@ -1764,7 +1767,19 @@ public class StatusAdapter extends RecyclerView.Adapter }); } else if (viewHolder.getItemViewType() == STATUS_FETCH_MORE) { StatusViewHolder holder = (StatusViewHolder) viewHolder; - holder.bindingFetchMore.fetchMore.setOnClickListener(v -> fetchMoreCallBack.onClick(status.id)); + if (status.isFetchMoreHidden) { + holder.bindingFetchMore.fetchMore.setVisibility(View.GONE); + } else { + holder.bindingFetchMore.fetchMore.setVisibility(View.VISIBLE); + } + holder.bindingFetchMore.fetchMore.setOnClickListener(v -> { + //We hide the button + status.isFetchMoreHidden = true; + notifyItemChanged(position); + if (position + 1 < statusList.size()) { + fetchMoreCallBack.onClick(statusList.get(position + 1).id); + } + }); } } diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java index 0942b620..e263f3d3 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java @@ -75,6 +75,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. private String max_id, min_id, min_id_fetch_more; private StatusAdapter statusAdapter; private Timeline.TimeLineEnum timelineType; + private static final int STATUS_PRESENT = -1; //Handle actions that can be done in other fragments private final BroadcastReceiver receive_action = new BroadcastReceiver() { @Override @@ -179,6 +180,15 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } } + private static final int STATUS_AT_THE_BOTTOM = -2; + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + } + + private ArrayList idOfAddedStatuses; + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -201,6 +211,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. minified = getArguments().getBoolean(Helper.ARG_MINIFIED, false); statusReport = (Status) getArguments().getSerializable(Helper.ARG_STATUS_REPORT); } + idOfAddedStatuses = new ArrayList<>(); if (tagTimeline != null) { ident = "@T@" + tagTimeline.name; if (tagTimeline.isART) { @@ -234,16 +245,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. markers = new ArrayList<>(); max_id = statusReport != null ? statusReport.id : null; flagLoading = false; - router(null, false); + router(null); return binding.getRoot(); } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - } - /** * Intialize the common view for statuses on different timelines * @@ -259,9 +265,8 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. binding.swipeContainer.setRefreshing(false); binding.swipeContainer.setOnRefreshListener(() -> { binding.swipeContainer.setRefreshing(true); - max_id = null; flagLoading = false; - router(null, false); + route(DIRECTION.REFRESH, true); }); if (statuses == null || statuses.statuses == null || statuses.statuses.size() == 0) { @@ -289,6 +294,9 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. statuses.statuses = mediaStatuses; } } + for (Status status : statuses.statuses) { + idOfAddedStatuses.add(status.id); + } flagLoading = statuses.pagination.max_id == null; binding.recyclerView.setVisibility(View.VISIBLE); if (statusAdapter != null && this.statuses != null) { @@ -308,12 +316,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. if (max_id == null || (statuses.pagination.max_id != null && statuses.pagination.max_id.compareTo(max_id) < 0)) { max_id = statuses.pagination.max_id; } - if (min_id == null || (statuses.pagination.max_id != null && statuses.pagination.min_id.compareTo(min_id) > 0)) { + if (min_id == null || (statuses.pagination.min_id != null && statuses.pagination.min_id.compareTo(min_id) > 0)) { min_id = statuses.pagination.min_id; } - statusAdapter = new StatusAdapter(this.statuses, timelineType, minified); - + statusAdapter.fetchMoreCallBack = this; if (statusReport != null) { scrollToTop(); } @@ -340,7 +347,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. if (!flagLoading) { flagLoading = true; binding.loadingNextElements.setVisibility(View.VISIBLE); - router(DIRECTION.BOTTOM, false); + router(DIRECTION.BOTTOM); } } else { binding.loadingNextElements.setVisibility(View.GONE); @@ -349,7 +356,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. if (!flagLoading) { flagLoading = true; binding.loadingNextElements.setVisibility(View.VISIBLE); - router(DIRECTION.TOP, false); + router(DIRECTION.TOP); } } } @@ -357,7 +364,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } } - /** * Update view and pagination when scrolling down * @@ -367,10 +373,10 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. if (binding == null) { return; } + binding.swipeContainer.setRefreshing(false); binding.loadingNextElements.setVisibility(View.GONE); flagLoading = false; if (statuses != null && fetched_statuses != null && fetched_statuses.statuses != null && fetched_statuses.statuses.size() > 0) { - flagLoading = fetched_statuses.pagination.max_id == null; if (timelineType == Timeline.TimeLineEnum.ART) { //We have to split media in different statuses @@ -386,20 +392,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } fetched_statuses.statuses = mediaStatuses; } - //There are some statuses present in the timeline - int startId = statuses.size(); - if (direction == DIRECTION.TOP) { - statuses.addAll(0, fetched_statuses.statuses); - statusAdapter.notifyItemRangeInserted(0, fetched_statuses.statuses.size()); - //Maybe a better solution but max_id excludes fetched id, so when fetching with min_id we have to scroll top of one status to get it. - if (fetched_statuses.statuses.size() > 0) { - binding.recyclerView.scrollToPosition(fetched_statuses.statuses.size() - 1); - } - } else { - statuses.addAll(fetched_statuses.statuses); - statusAdapter.notifyItemRangeInserted(startId, fetched_statuses.statuses.size()); - } - if (max_id == null || (fetched_statuses.pagination.max_id != null && fetched_statuses.pagination.max_id.compareTo(max_id) < 0)) { + //Update the timeline with new statuses + updateStatusListWith(fetched_statuses.statuses, fetchingMissing); + if (fetched_statuses.pagination.max_id == null) { + flagLoading = true; + } else if (max_id == null || fetched_statuses.pagination.max_id.compareTo(max_id) < 0) { max_id = fetched_statuses.pagination.max_id; } if (min_id == null || (fetched_statuses.pagination.min_id != null && fetched_statuses.pagination.min_id.compareTo(min_id) > 0)) { @@ -410,6 +407,47 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } } + private void updateStatusListWith(List statusListReceived, boolean fetchingMissing) { + if (statusListReceived != null && statusListReceived.size() > 0) { + int insertedPosition = STATUS_PRESENT; + for (Status statusReceived : statusListReceived) { + insertedPosition = insertStatus(statusReceived); + } + //If there were no overlap for top status + if (fetchingMissing && insertedPosition != STATUS_PRESENT && insertedPosition != STATUS_AT_THE_BOTTOM && this.statuses.size() > insertedPosition) { + Status statusFetchMore = new Status(); + statusFetchMore.isFetchMore = true; + int insertAt = insertedPosition + 1; + this.statuses.add(insertAt, statusFetchMore); + statusAdapter.notifyItemInserted(insertAt); + } + } + } + + private int insertStatus(Status statusReceived) { + if (idOfAddedStatuses.contains(statusReceived.id)) { + return STATUS_PRESENT; + } + int position = 0; + for (Status statusAlreadyPresent : this.statuses) { + if (statusAlreadyPresent.created_at != null && statusReceived.created_at != null && statusReceived.created_at.after(statusAlreadyPresent.created_at)) { + idOfAddedStatuses.add(statusReceived.id); + this.statuses.add(position, statusReceived); + statusAdapter.notifyItemInserted(position); + break; + } + position++; + } + //Statuses added at the bottom, we flag them by position = -2 for not dealing with them and fetch more + if (position == this.statuses.size()) { + idOfAddedStatuses.add(statusReceived.id); + this.statuses.add(position, statusReceived); + statusAdapter.notifyItemInserted(position); + return STATUS_AT_THE_BOTTOM; + } + return position; + } + @Override public void onPause() { if (mLayoutManager != null) { @@ -460,18 +498,18 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } } - private void router(DIRECTION direction, boolean fetchingMissing) { + private void router(DIRECTION direction) { if (networkAvailable == BaseMainActivity.status.UNKNOWN) { new Thread(() -> { if (networkAvailable == BaseMainActivity.status.UNKNOWN) { networkAvailable = Helper.isConnectedToInternet(requireActivity(), BaseMainActivity.currentInstance); } Handler mainHandler = new Handler(Looper.getMainLooper()); - Runnable myRunnable = () -> route(direction, fetchingMissing); + Runnable myRunnable = () -> route(direction, false); mainHandler.post(myRunnable); }).start(); } else { - route(direction, fetchingMissing); + route(direction, false); } } @@ -486,7 +524,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. if (binding == null) { return; } - if (!fetchingMissing && !binding.swipeContainer.isRefreshing() && direction == null && quickLoad != null && quickLoad.statuses != null && quickLoad.statuses.size() > 0) { + if (direction != DIRECTION.REFRESH && !fetchingMissing && !binding.swipeContainer.isRefreshing() && direction == null && quickLoad != null && quickLoad.statuses != null && quickLoad.statuses.size() > 0) { Statuses statuses = new Statuses(); statuses.statuses = quickLoad.statuses; statuses.pagination = new Pagination(); @@ -515,6 +553,9 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else if (direction == DIRECTION.TOP) { timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, true, false, false, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing)); + } else if (direction == DIRECTION.REFRESH) { + timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) + .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.REFRESH, fetchingMissing)); } } else if (timelineType == Timeline.TimeLineEnum.PUBLIC) { //PUBLIC TIMELINE if (direction == null) { @@ -526,6 +567,9 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else if (direction == DIRECTION.TOP) { timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, false, true, false, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing)); + } else if (direction == DIRECTION.REFRESH) { + timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, false, true, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) + .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.REFRESH, fetchingMissing)); } } else if (timelineType == Timeline.TimeLineEnum.REMOTE) { //REMOTE TIMELINE if (direction == null) { @@ -537,6 +581,9 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else if (direction == DIRECTION.TOP) { timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing)); + } else if (direction == DIRECTION.REFRESH) { + timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) + .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.REFRESH, fetchingMissing)); } } else if (timelineType == Timeline.TimeLineEnum.LIST) { //LIST TIMELINE if (direction == null) { @@ -548,6 +595,9 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else if (direction == DIRECTION.TOP) { timelinesVM.getList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, list_id, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing)); + } else if (direction == DIRECTION.REFRESH) { + timelinesVM.getList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, list_id, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) + .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.REFRESH, fetchingMissing)); } } else if (timelineType == Timeline.TimeLineEnum.TAG || timelineType == Timeline.TimeLineEnum.ART) { //TAG TIMELINE if (tagTimeline == null) { @@ -563,6 +613,9 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else if (direction == DIRECTION.TOP) { timelinesVM.getHashTag(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, tagTimeline.name, false, tagTimeline.isART, tagTimeline.all, tagTimeline.any, tagTimeline.none, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing)); + } else if (direction == DIRECTION.REFRESH) { + timelinesVM.getHashTag(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, tagTimeline.name, false, tagTimeline.isART, tagTimeline.all, tagTimeline.any, tagTimeline.none, null, null, null, MastodonHelper.statusesPerCall(requireActivity())) + .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.REFRESH, fetchingMissing)); } } else if (timelineType == Timeline.TimeLineEnum.ACCOUNT_TIMELINE) { //PROFILE TIMELINES if (direction == null) { @@ -585,7 +638,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } } else if (direction == DIRECTION.BOTTOM) { accountsVM.getAccountStatuses(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountTimeline.id, max_id, null, null, exclude_replies, exclude_reblogs, media_only, false, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, fetchingMissing)); + .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false)); } else { flagLoading = false; } @@ -613,7 +666,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); } else if (direction == DIRECTION.BOTTOM) { accountsVM.getFavourites(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, String.valueOf(MastodonHelper.statusesPerCall(requireActivity())), null, max_id) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, fetchingMissing)); + .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false)); } else { flagLoading = false; } @@ -623,7 +676,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); } else if (direction == DIRECTION.BOTTOM) { accountsVM.getBookmarks(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, String.valueOf(MastodonHelper.statusesPerCall(requireActivity())), max_id, null, null) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, fetchingMissing)); + .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false)); } else { flagLoading = false; } @@ -667,16 +720,16 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else { max_id = null; } - timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()), false) + timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, false, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()), false) .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); }); } else { - timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()), false) + timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, false, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()), false) .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); } } else { - timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, null, null) + timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, null, null, null) .observe(getViewLifecycleOwner(), cachedStatus -> { if (cachedStatus != null && cachedStatus.statuses != null) { initializeStatusesCommonView(cachedStatus); @@ -687,35 +740,38 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else if (direction == DIRECTION.BOTTOM) { if (networkAvailable == BaseMainActivity.status.CONNECTED) { //We first if we get results from cache - timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, max_id, null) + timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, max_id, null, null) .observe(getViewLifecycleOwner(), statusesBottomCache -> { if (statusesBottomCache != null && statusesBottomCache.statuses != null && statusesBottomCache.statuses.size() > 0) { dealWithPagination(statusesBottomCache, DIRECTION.BOTTOM, fetchingMissing); } else { // If not, we fetch remotely - timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()), false) + timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, fetchingMissing, max_id, null, null, MastodonHelper.statusesPerCall(requireActivity()), false) .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, fetchingMissing)); } }); } else { - timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, max_id, null) + timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, max_id, null, null) .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, fetchingMissing)); } } else if (direction == DIRECTION.TOP) { if (networkAvailable == BaseMainActivity.status.CONNECTED) { - timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, null, fetchingMissing ? min_id_fetch_more : min_id) + timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, null, fetchingMissing ? min_id_fetch_more : min_id, null) .observe(getViewLifecycleOwner(), statusesTopCache -> { if (statusesTopCache != null && statusesTopCache.statuses != null && statusesTopCache.statuses.size() > 0) { dealWithPagination(statusesTopCache, DIRECTION.TOP, fetchingMissing); } else { - timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity()), false) + timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, fetchingMissing, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity()), false) .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.TOP, fetchingMissing)); } }); } else { - timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, null, fetchingMissing ? min_id_fetch_more : min_id) + timelinesVM.getHomeCache(BaseMainActivity.currentInstance, BaseMainActivity.currentUserID, null, fetchingMissing ? min_id_fetch_more : min_id, null) .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.TOP, fetchingMissing)); } + } else if (direction == DIRECTION.REFRESH) { + timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, fetchingMissing, null, null, null, MastodonHelper.statusesPerCall(requireActivity()), false) + .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.REFRESH, fetchingMissing)); } } @@ -738,6 +794,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. public enum DIRECTION { TOP, - BOTTOM + BOTTOM, + REFRESH } } \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java b/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java index d9716bc8..91e77441 100644 --- a/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java @@ -14,6 +14,7 @@ package app.fedilab.android.ui.pageadapter; * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ +import android.content.Context; import android.os.Bundle; import android.view.ViewGroup; @@ -27,6 +28,7 @@ import app.fedilab.android.client.entities.app.Pinned; import app.fedilab.android.client.entities.app.PinnedTimeline; import app.fedilab.android.client.entities.app.Timeline; import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.PinnedTimelineHelper; import app.fedilab.android.ui.fragment.timeline.FragmentMastodonConversation; import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline; import app.fedilab.android.ui.fragment.timeline.FragmentNotificationContainer; @@ -37,11 +39,15 @@ public class FedilabPageAdapter extends FragmentStatePagerAdapter { private final Pinned pinned; private final BottomMenu bottomMenu; private Fragment mCurrentFragment; + private final Context context; + private final int toRemove; - public FedilabPageAdapter(FragmentManager fm, Pinned pinned, BottomMenu bottomMenu) { + public FedilabPageAdapter(Context context, FragmentManager fm, Pinned pinned, BottomMenu bottomMenu) { super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); this.pinned = pinned; this.bottomMenu = bottomMenu; + this.context = context; + toRemove = PinnedTimelineHelper.itemToRemoveInBottomMenu(context); } public Fragment getCurrentFragment() { @@ -68,9 +74,10 @@ public class FedilabPageAdapter extends FragmentStatePagerAdapter { FragmentMastodonTimeline fragment = new FragmentMastodonTimeline(); Bundle bundle = new Bundle(); //Position 3 is for notifications - if (position < 5) { + if (position < BOTTOM_TIMELINE_COUNT - toRemove) { if (bottomMenu != null) { BottomMenu.ItemMenuType type = BottomMenu.getType(bottomMenu, position); + if (type == null) { return fragment; } @@ -91,7 +98,7 @@ public class FedilabPageAdapter extends FragmentStatePagerAdapter { } } else { - int pinnedPosition = position - BOTTOM_TIMELINE_COUNT; //Real position has an offset. + int pinnedPosition = position - (BOTTOM_TIMELINE_COUNT - toRemove); //Real position has an offset. PinnedTimeline pinnedTimeline = pinned.pinnedTimelines.get(pinnedPosition); bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, pinnedTimeline.type); @@ -112,10 +119,11 @@ public class FedilabPageAdapter extends FragmentStatePagerAdapter { @Override public int getCount() { + if (pinned != null && pinned.pinnedTimelines != null) { - return pinned.pinnedTimelines.size() + BOTTOM_TIMELINE_COUNT; + return pinned.pinnedTimelines.size() + BOTTOM_TIMELINE_COUNT - toRemove; } else { - return BOTTOM_TIMELINE_COUNT; + return BOTTOM_TIMELINE_COUNT - toRemove; } } } diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java index e1aa9d99..6f63ea14 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java @@ -193,6 +193,7 @@ public class TimelinesVM extends AndroidViewModel { * @return {@link LiveData} containing a {@link Statuses} */ public LiveData getHome(@NonNull String instance, String token, + boolean fetchingMissing, String maxId, String sinceId, String minId, @@ -211,18 +212,20 @@ public class TimelinesVM extends AndroidViewModel { List filteredStatuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), notFilteredStatuses, TimelineHelper.FilterTimeLineType.HOME); statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), filteredStatuses); statuses.pagination = MastodonHelper.getPagination(homeTlResponse.headers()); - for (Status status : statuses.statuses) { - StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); - StatusCache statusCache = new StatusCache(); - statusCache.instance = instance; - statusCache.user_id = BaseMainActivity.currentUserID; - statusCache.status = status; - statusCache.type = StatusCache.CacheEnum.HOME; - statusCache.status_id = status.id; - try { - statusCacheDAO.insertOrUpdate(statusCache); - } catch (DBException e) { - e.printStackTrace(); + if (!fetchingMissing) { + for (Status status : statuses.statuses) { + StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); + StatusCache statusCache = new StatusCache(); + statusCache.instance = instance; + statusCache.user_id = BaseMainActivity.currentUserID; + statusCache.status = status; + statusCache.type = StatusCache.CacheEnum.HOME; + statusCache.status_id = status.id; + try { + statusCacheDAO.insertOrUpdate(statusCache); + } catch (DBException e) { + e.printStackTrace(); + } } } } @@ -249,13 +252,14 @@ public class TimelinesVM extends AndroidViewModel { */ public LiveData getHomeCache(@NonNull String instance, String user_id, String maxId, - String minId) { + String minId, + String sinceId) { statusesMutableLiveData = new MutableLiveData<>(); new Thread(() -> { StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); Statuses statuses = null; try { - statuses = statusCacheDAO.geStatuses(StatusCache.CacheEnum.HOME, instance, user_id, maxId, minId); + statuses = statusCacheDAO.geStatuses(StatusCache.CacheEnum.HOME, instance, user_id, maxId, minId, sinceId); if (statuses != null) { statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statuses.statuses); if (statuses.statuses != null && statuses.statuses.size() > 0) { diff --git a/app/src/main/res/layout/drawer_fetch_more.xml b/app/src/main/res/layout/drawer_fetch_more.xml index 5a28df3f..70736b25 100644 --- a/app/src/main/res/layout/drawer_fetch_more.xml +++ b/app/src/main/res/layout/drawer_fetch_more.xml @@ -1,12 +1,9 @@ \ No newline at end of file + android:text="@string/fetch_more_messages" /> \ No newline at end of file