Loading app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java +51 −92 Original line number Diff line number Diff line Loading @@ -65,8 +65,6 @@ import es.dmoral.toasty.Toasty; public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.FetchMoreCallBack { private static final int STATUS_PRESENT = -1; private static final int STATUS_AT_THE_BOTTOM = -2; private FragmentPaginationBinding binding; private TimelinesVM timelinesVM; private AccountsVM accountsVM; Loading Loading @@ -133,7 +131,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. private String viewModelKey, remoteInstance; private PinnedTimeline pinnedTimeline; private String ident; private String instance, user_id; private String slug; private boolean canBeFederated; Loading Loading @@ -186,8 +184,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. ViewGroup container, Bundle savedInstanceState) { timelineType = Timeline.TimeLineEnum.HOME; instance = BaseMainActivity.currentInstance; user_id = BaseMainActivity.currentUserID; canBeFederated = true; if (getArguments() != null) { timelineType = (Timeline.TimeLineEnum) getArguments().get(Helper.ARG_TIMELINE_TYPE); Loading Loading @@ -233,6 +229,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else { ident = null; } slug = timelineType.getValue() + (ident != null ? "|" + ident : ""); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); //Retrieve the max_id to keep position LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(receive_action, new IntentFilter(Helper.RECEIVE_STATUS_ACTION)); binding = FragmentPaginationBinding.inflate(inflater, container, false); binding.getRoot().setBackgroundColor(ThemeHelper.getBackgroundColor(requireActivity())); Loading @@ -249,22 +250,22 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. binding.loader.setVisibility(View.VISIBLE); binding.recyclerView.setVisibility(View.GONE); max_id = statusReport != null ? statusReport.id : null; if (max_id == null) { max_id = sharedpreferences.getString(getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, null); } flagLoading = false; router(null); return binding.getRoot(); } private void initializeStatusesCommonView(final Statuses statuses) { initializeStatusesCommonView(statuses, -1); } /** * Intialize the common view for statuses on different timelines * * @param statuses {@link Statuses} */ private void initializeStatusesCommonView(final Statuses statuses, int position) { private void initializeStatusesCommonView(final Statuses statuses) { flagLoading = false; if (binding == null || !isAdded() || getActivity() == null) { return; Loading Loading @@ -337,9 +338,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. binding.recyclerView.setLayoutManager(mLayoutManager); binding.recyclerView.setAdapter(statusAdapter); if (position != -1 && position < this.statuses.size()) { binding.recyclerView.scrollToPosition(position); } if (searchCache == null && timelineType != Timeline.TimeLineEnum.TREND_MESSAGE) { binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { Loading Loading @@ -406,7 +404,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. fetched_statuses.statuses = mediaStatuses; } //Update the timeline with new statuses updateStatusListWith(direction, fetched_statuses.statuses, fetchingMissing); updateStatusListWith(fetched_statuses.statuses); if (!fetchingMissing) { if (fetched_statuses.pagination.max_id == null) { flagLoading = true; Loading @@ -429,86 +427,37 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. * Update the timeline with received statuses * * @param statusListReceived - List<Status> Statuses received * @param fetchingMissing - boolean if the call concerns fetching messages (ie: refresh of from fetch more button) */ private void updateStatusListWith(DIRECTION direction, List<Status> statusListReceived, boolean fetchingMissing) { int numberInserted = 0; int lastInsertedPosition = 0; int initialInsertedPosition = STATUS_PRESENT; private void updateStatusListWith(List<Status> statusListReceived) { if (statusListReceived != null && statusListReceived.size() > 0) { int insertedPosition = STATUS_PRESENT; for (Status statusReceived : statusListReceived) { insertedPosition = insertStatus(statusReceived); if (insertedPosition != STATUS_PRESENT && insertedPosition != STATUS_AT_THE_BOTTOM) { numberInserted++; //Find the first position of insertion, the initial id is set to STATUS_PRESENT if (initialInsertedPosition == STATUS_PRESENT) { initialInsertedPosition = insertedPosition; } //If next statuses have a lower id, there are inserted before (normally, that should not happen) if (insertedPosition < initialInsertedPosition) { initialInsertedPosition = lastInsertedPosition; } } } lastInsertedPosition = initialInsertedPosition + numberInserted; //lastInsertedPosition contains the position of the last inserted status //If there were no overlap for top status if (fetchingMissing && insertedPosition != STATUS_PRESENT && insertedPosition != STATUS_AT_THE_BOTTOM && this.statuses.size() > insertedPosition && numberInserted == MastodonHelper.statusesPerCall(requireActivity())) { Status statusFetchMore = new Status(); statusFetchMore.isFetchMore = true; statusFetchMore.id = Helper.generateString(); int insertAt; if (direction == DIRECTION.REFRESH || direction == DIRECTION.BOTTOM || direction == DIRECTION.SCROLL_TOP) { insertAt = lastInsertedPosition; } else { insertAt = initialInsertedPosition; } this.statuses.add(insertAt, statusFetchMore); statusAdapter.notifyItemInserted(insertAt); if (direction == DIRECTION.TOP && lastInsertedPosition + 1 < statuses.size()) { binding.recyclerView.scrollToPosition(lastInsertedPosition + 1); } } } } /** * Insert a status if not yet in the timeline and returns its position of insertion * * @param statusReceived - Status coming from the api/db * @return int >= 0 | STATUS_PRESENT = -1 | STATUS_AT_THE_BOTTOM = -2 */ private int insertStatus(Status statusReceived) { int position = 0; if (this.statuses != null) { if (this.statuses.contains(statusReceived)) { return STATUS_PRESENT; } //First we refresh statuses statusAdapter.notifyItemRangeChanged(0, this.statuses.size()); //We loop through messages already in the timeline for (Status statusAlreadyPresent : this.statuses) { //We compare the date of each status and we only add status having a date greater than the another, it is inserted at this position //We compare the id of each status and we only add status having an id greater than the another, it is inserted at this position //Pinned messages are ignored because their date can be older if (statusReceived.id.compareTo(statusAlreadyPresent.id) > 0 && !statusAlreadyPresent.pinned) { if (statusReceived.id.compareTo(statusAlreadyPresent.id) > 0) { //We add the status to a list of id - thus we know it is already in the timeline if (!this.statuses.contains(statusReceived) && !statusReceived.pinned && timelineType != Timeline.TimeLineEnum.ACCOUNT_TIMELINE) { 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()) { //Statuses added at the bottom if (position == this.statuses.size() && !this.statuses.contains(statusReceived)) { //We add the status to a list of id - thus we know it is already in the timeline this.statuses.add(position, statusReceived); statusAdapter.notifyItemInserted(position); return STATUS_AT_THE_BOTTOM; } } return position; } } } @Override Loading Loading @@ -536,7 +485,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. Status status = statuses.get(position); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); SharedPreferences.Editor editor = sharedpreferences.edit(); editor.putString(getString(R.string.SET_HOME_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance, status.id); editor.putString(getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, status.id); editor.apply(); timelinesVM.addMarker(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, status.id, null); Loading Loading @@ -581,9 +530,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else if (direction == DIRECTION.BOTTOM) { timelineParams.maxId = fetchingMissing ? max_id_fetch_more : max_id; timelineParams.minId = null; } else { } else if (direction == DIRECTION.TOP) { timelineParams.minId = fetchingMissing ? min_id_fetch_more : min_id; timelineParams.maxId = null; } else { timelineParams.maxId = max_id; } timelineParams.fetchingMissing = fetchingMissing; Loading @@ -610,6 +561,10 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. timelineParams.any = tagTimeline.any; timelineParams.hashtagTrim = tagTimeline.name; break; case REMOTE: timelineParams.instance = remoteInstance; timelineParams.token = null; break; } SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); boolean useCache = sharedpreferences.getBoolean(getString(R.string.SET_USE_CACHE), true); Loading Loading @@ -643,6 +598,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else if (direction == DIRECTION.TOP) { timelinesVM.getTimelineCache(timelineParams) .observe(getViewLifecycleOwner(), statusesCachedTop -> { if (statusesCachedTop == null || statusesCachedTop.statuses == null || statusesCachedTop.statuses.size() == 0) { getLiveStatus(DIRECTION.TOP, fetchingMissing, timelineParams); } else { Loading @@ -653,11 +609,15 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { timelinesVM.getTimelineCache(timelineParams) .observe(getViewLifecycleOwner(), statusesRefresh -> { if (statusesRefresh == null || statusesRefresh.statuses == null || statusesRefresh.statuses.size() == 0) { getLiveStatus(direction, fetchingMissing, timelineParams); } else { if (statusAdapter != null) { dealWithPagination(statusesRefresh, direction, true); } else { initializeStatusesCommonView(statusesRefresh); } } }); } } Loading @@ -671,7 +631,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, fetchingMissing)); } else if (direction == DIRECTION.TOP) { timelinesVM.getTimeline(timelineParams) .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing)); .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.TOP, fetchingMissing)); } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { timelinesVM.getTimeline(timelineParams) .observe(getViewLifecycleOwner(), statusesRefresh -> { Loading Loading @@ -748,7 +708,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } //PEERTUBE TIMELINES else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.PEERTUBE) { if (direction == null) { timelinesVM.getPeertube(remoteInstance, null, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); } else if (direction == DIRECTION.BOTTOM) { Loading Loading
app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java +51 −92 Original line number Diff line number Diff line Loading @@ -65,8 +65,6 @@ import es.dmoral.toasty.Toasty; public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.FetchMoreCallBack { private static final int STATUS_PRESENT = -1; private static final int STATUS_AT_THE_BOTTOM = -2; private FragmentPaginationBinding binding; private TimelinesVM timelinesVM; private AccountsVM accountsVM; Loading Loading @@ -133,7 +131,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. private String viewModelKey, remoteInstance; private PinnedTimeline pinnedTimeline; private String ident; private String instance, user_id; private String slug; private boolean canBeFederated; Loading Loading @@ -186,8 +184,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. ViewGroup container, Bundle savedInstanceState) { timelineType = Timeline.TimeLineEnum.HOME; instance = BaseMainActivity.currentInstance; user_id = BaseMainActivity.currentUserID; canBeFederated = true; if (getArguments() != null) { timelineType = (Timeline.TimeLineEnum) getArguments().get(Helper.ARG_TIMELINE_TYPE); Loading Loading @@ -233,6 +229,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else { ident = null; } slug = timelineType.getValue() + (ident != null ? "|" + ident : ""); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); //Retrieve the max_id to keep position LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(receive_action, new IntentFilter(Helper.RECEIVE_STATUS_ACTION)); binding = FragmentPaginationBinding.inflate(inflater, container, false); binding.getRoot().setBackgroundColor(ThemeHelper.getBackgroundColor(requireActivity())); Loading @@ -249,22 +250,22 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. binding.loader.setVisibility(View.VISIBLE); binding.recyclerView.setVisibility(View.GONE); max_id = statusReport != null ? statusReport.id : null; if (max_id == null) { max_id = sharedpreferences.getString(getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, null); } flagLoading = false; router(null); return binding.getRoot(); } private void initializeStatusesCommonView(final Statuses statuses) { initializeStatusesCommonView(statuses, -1); } /** * Intialize the common view for statuses on different timelines * * @param statuses {@link Statuses} */ private void initializeStatusesCommonView(final Statuses statuses, int position) { private void initializeStatusesCommonView(final Statuses statuses) { flagLoading = false; if (binding == null || !isAdded() || getActivity() == null) { return; Loading Loading @@ -337,9 +338,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. binding.recyclerView.setLayoutManager(mLayoutManager); binding.recyclerView.setAdapter(statusAdapter); if (position != -1 && position < this.statuses.size()) { binding.recyclerView.scrollToPosition(position); } if (searchCache == null && timelineType != Timeline.TimeLineEnum.TREND_MESSAGE) { binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { Loading Loading @@ -406,7 +404,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. fetched_statuses.statuses = mediaStatuses; } //Update the timeline with new statuses updateStatusListWith(direction, fetched_statuses.statuses, fetchingMissing); updateStatusListWith(fetched_statuses.statuses); if (!fetchingMissing) { if (fetched_statuses.pagination.max_id == null) { flagLoading = true; Loading @@ -429,86 +427,37 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. * Update the timeline with received statuses * * @param statusListReceived - List<Status> Statuses received * @param fetchingMissing - boolean if the call concerns fetching messages (ie: refresh of from fetch more button) */ private void updateStatusListWith(DIRECTION direction, List<Status> statusListReceived, boolean fetchingMissing) { int numberInserted = 0; int lastInsertedPosition = 0; int initialInsertedPosition = STATUS_PRESENT; private void updateStatusListWith(List<Status> statusListReceived) { if (statusListReceived != null && statusListReceived.size() > 0) { int insertedPosition = STATUS_PRESENT; for (Status statusReceived : statusListReceived) { insertedPosition = insertStatus(statusReceived); if (insertedPosition != STATUS_PRESENT && insertedPosition != STATUS_AT_THE_BOTTOM) { numberInserted++; //Find the first position of insertion, the initial id is set to STATUS_PRESENT if (initialInsertedPosition == STATUS_PRESENT) { initialInsertedPosition = insertedPosition; } //If next statuses have a lower id, there are inserted before (normally, that should not happen) if (insertedPosition < initialInsertedPosition) { initialInsertedPosition = lastInsertedPosition; } } } lastInsertedPosition = initialInsertedPosition + numberInserted; //lastInsertedPosition contains the position of the last inserted status //If there were no overlap for top status if (fetchingMissing && insertedPosition != STATUS_PRESENT && insertedPosition != STATUS_AT_THE_BOTTOM && this.statuses.size() > insertedPosition && numberInserted == MastodonHelper.statusesPerCall(requireActivity())) { Status statusFetchMore = new Status(); statusFetchMore.isFetchMore = true; statusFetchMore.id = Helper.generateString(); int insertAt; if (direction == DIRECTION.REFRESH || direction == DIRECTION.BOTTOM || direction == DIRECTION.SCROLL_TOP) { insertAt = lastInsertedPosition; } else { insertAt = initialInsertedPosition; } this.statuses.add(insertAt, statusFetchMore); statusAdapter.notifyItemInserted(insertAt); if (direction == DIRECTION.TOP && lastInsertedPosition + 1 < statuses.size()) { binding.recyclerView.scrollToPosition(lastInsertedPosition + 1); } } } } /** * Insert a status if not yet in the timeline and returns its position of insertion * * @param statusReceived - Status coming from the api/db * @return int >= 0 | STATUS_PRESENT = -1 | STATUS_AT_THE_BOTTOM = -2 */ private int insertStatus(Status statusReceived) { int position = 0; if (this.statuses != null) { if (this.statuses.contains(statusReceived)) { return STATUS_PRESENT; } //First we refresh statuses statusAdapter.notifyItemRangeChanged(0, this.statuses.size()); //We loop through messages already in the timeline for (Status statusAlreadyPresent : this.statuses) { //We compare the date of each status and we only add status having a date greater than the another, it is inserted at this position //We compare the id of each status and we only add status having an id greater than the another, it is inserted at this position //Pinned messages are ignored because their date can be older if (statusReceived.id.compareTo(statusAlreadyPresent.id) > 0 && !statusAlreadyPresent.pinned) { if (statusReceived.id.compareTo(statusAlreadyPresent.id) > 0) { //We add the status to a list of id - thus we know it is already in the timeline if (!this.statuses.contains(statusReceived) && !statusReceived.pinned && timelineType != Timeline.TimeLineEnum.ACCOUNT_TIMELINE) { 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()) { //Statuses added at the bottom if (position == this.statuses.size() && !this.statuses.contains(statusReceived)) { //We add the status to a list of id - thus we know it is already in the timeline this.statuses.add(position, statusReceived); statusAdapter.notifyItemInserted(position); return STATUS_AT_THE_BOTTOM; } } return position; } } } @Override Loading Loading @@ -536,7 +485,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. Status status = statuses.get(position); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); SharedPreferences.Editor editor = sharedpreferences.edit(); editor.putString(getString(R.string.SET_HOME_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance, status.id); editor.putString(getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, status.id); editor.apply(); timelinesVM.addMarker(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, status.id, null); Loading Loading @@ -581,9 +530,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else if (direction == DIRECTION.BOTTOM) { timelineParams.maxId = fetchingMissing ? max_id_fetch_more : max_id; timelineParams.minId = null; } else { } else if (direction == DIRECTION.TOP) { timelineParams.minId = fetchingMissing ? min_id_fetch_more : min_id; timelineParams.maxId = null; } else { timelineParams.maxId = max_id; } timelineParams.fetchingMissing = fetchingMissing; Loading @@ -610,6 +561,10 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. timelineParams.any = tagTimeline.any; timelineParams.hashtagTrim = tagTimeline.name; break; case REMOTE: timelineParams.instance = remoteInstance; timelineParams.token = null; break; } SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); boolean useCache = sharedpreferences.getBoolean(getString(R.string.SET_USE_CACHE), true); Loading Loading @@ -643,6 +598,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else if (direction == DIRECTION.TOP) { timelinesVM.getTimelineCache(timelineParams) .observe(getViewLifecycleOwner(), statusesCachedTop -> { if (statusesCachedTop == null || statusesCachedTop.statuses == null || statusesCachedTop.statuses.size() == 0) { getLiveStatus(DIRECTION.TOP, fetchingMissing, timelineParams); } else { Loading @@ -653,11 +609,15 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { timelinesVM.getTimelineCache(timelineParams) .observe(getViewLifecycleOwner(), statusesRefresh -> { if (statusesRefresh == null || statusesRefresh.statuses == null || statusesRefresh.statuses.size() == 0) { getLiveStatus(direction, fetchingMissing, timelineParams); } else { if (statusAdapter != null) { dealWithPagination(statusesRefresh, direction, true); } else { initializeStatusesCommonView(statusesRefresh); } } }); } } Loading @@ -671,7 +631,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, fetchingMissing)); } else if (direction == DIRECTION.TOP) { timelinesVM.getTimeline(timelineParams) .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing)); .observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.TOP, fetchingMissing)); } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { timelinesVM.getTimeline(timelineParams) .observe(getViewLifecycleOwner(), statusesRefresh -> { Loading Loading @@ -748,7 +708,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } //PEERTUBE TIMELINES else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.PEERTUBE) { if (direction == null) { timelinesVM.getPeertube(remoteInstance, null, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); } else if (direction == DIRECTION.BOTTOM) { Loading