Commit 788603d1 authored by Thomas's avatar Thomas
Browse files

- Fix #607 detect unreachable gaps in home timeline (when server no longer has older statuses)

parent da65bc5c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -167,6 +167,7 @@ public class Status implements Serializable, Cloneable {

    public transient boolean isFetchMore = false;
    public transient boolean isFetching = false;
    public transient boolean isUnreachableGap = false;

    public transient PositionFetchMore positionFetchMore = PositionFetchMore.BOTTOM;

+89 −68
Original line number Diff line number Diff line
@@ -2637,11 +2637,26 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
            holder.bindingReport.checkbox.setOnClickListener(v -> status.isChecked = !status.isChecked);
        }

        if (status.isFetchMore && fetchMoreCallBack != null) {
            if (!autofetch) {
        if ((status.isFetchMore || status.isUnreachableGap) && fetchMoreCallBack != null) {
            if (status.isUnreachableGap) {
                DrawerFetchMoreBinding drawerFetchMoreBinding = DrawerFetchMoreBinding.inflate(LayoutInflater.from(context));
                drawerFetchMoreBinding.fetchMoreContainer.setVisibility(View.GONE);
                drawerFetchMoreBinding.unreachableGapMessage.setVisibility(View.VISIBLE);
                if (status.positionFetchMore == Status.PositionFetchMore.BOTTOM) {
                    holder.binding.fetchMoreContainerBottom.setVisibility(View.GONE);
                    holder.binding.fetchMoreContainerTop.setVisibility(View.VISIBLE);
                    holder.binding.fetchMoreContainerTop.removeAllViews();
                    holder.binding.fetchMoreContainerTop.addView(drawerFetchMoreBinding.getRoot());
                } else {
                    holder.binding.fetchMoreContainerBottom.setVisibility(View.VISIBLE);
                    holder.binding.fetchMoreContainerTop.setVisibility(View.GONE);
                    holder.binding.fetchMoreContainerBottom.removeAllViews();
                    holder.binding.fetchMoreContainerBottom.addView(drawerFetchMoreBinding.getRoot());
                }
            } else if (!autofetch) {
                DrawerFetchMoreBinding drawerFetchMoreBinding = DrawerFetchMoreBinding.inflate(LayoutInflater.from(context));
                LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                drawerFetchMoreBinding.fetchMoreContainer.setLayoutParams(lp);
                drawerFetchMoreBinding.getRoot().setLayoutParams(lp);
                if (status.positionFetchMore == Status.PositionFetchMore.BOTTOM) {
                    holder.binding.fetchMoreContainerBottom.setVisibility(View.GONE);
                    holder.binding.fetchMoreContainerTop.setVisibility(View.VISIBLE);
@@ -3908,7 +3923,11 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
                holder.bindingFilteredHide.cardviewContainer.setCardElevation(Helper.convertDpToPixel(5, context));
                holder.bindingFilteredHide.dividerCard.setVisibility(View.GONE);
            }
            if (status.isFetchMore && fetchMoreCallBack != null) {
            if ((status.isFetchMore || status.isUnreachableGap) && fetchMoreCallBack != null) {
                if (status.isUnreachableGap) {
                    holder.bindingFilteredHide.layoutFetchMore.fetchMoreContainer.setVisibility(View.GONE);
                    holder.bindingFilteredHide.layoutFetchMore.unreachableGapMessage.setVisibility(View.VISIBLE);
                } else {
                    boolean autofetch = sharedpreferences.getBoolean(context.getString(R.string.SET_AUTO_FETCH_MISSING_MESSAGES), false);
                    if (!autofetch) {
                        holder.bindingFilteredHide.layoutFetchMore.fetchMoreContainer.setVisibility(View.VISIBLE);
@@ -3927,7 +3946,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
                            }
                        });
                        holder.bindingFilteredHide.layoutFetchMore.fetchMoreMax.setOnClickListener(v -> {
                        //We hide the button
                            status.isFetchMore = false;
                            status.isFetching = true;
                            notifyItemChanged(holder.getBindingAdapterPosition());
@@ -3938,7 +3956,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
                                fromId = statusList.get(holder.getBindingAdapterPosition() - 1).id;
                            }
                            fetchMoreCallBack.onClickMaxId(fromId, status);

                        });
                    } else {
                        status.isFetchMore = false;
@@ -3958,7 +3975,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
                        }
                        fetchMoreCallBack.autoFetch(minId, maxId, status);
                    }

                }
            } else {
                holder.bindingFilteredHide.layoutFetchMore.fetchMoreContainer.setVisibility(View.GONE);
            }
@@ -3970,7 +3987,11 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
                notifyItemChanged(position);
            });

            if (status.isFetchMore && fetchMoreCallBack != null) {
            if ((status.isFetchMore || status.isUnreachableGap) && fetchMoreCallBack != null) {
                if (status.isUnreachableGap) {
                    holder.bindingFiltered.layoutFetchMore.fetchMoreContainer.setVisibility(View.GONE);
                    holder.bindingFiltered.layoutFetchMore.unreachableGapMessage.setVisibility(View.VISIBLE);
                } else {
                    holder.bindingFiltered.layoutFetchMore.fetchMoreContainer.setVisibility(View.VISIBLE);
                    holder.bindingFiltered.layoutFetchMore.fetchMoreMin.setOnClickListener(v -> {
                        status.isFetchMore = false;
@@ -3987,7 +4008,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
                        }
                    });
                    holder.bindingFiltered.layoutFetchMore.fetchMoreMax.setOnClickListener(v -> {
                    //We hide the button
                        status.isFetchMore = false;
                        status.isFetching = true;
                        String fromId;
@@ -3999,6 +4019,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
                        fetchMoreCallBack.onClickMaxId(fromId, status);
                        notifyItemChanged(holder.getBindingAdapterPosition());
                    });
                }
            } else {
                holder.bindingFiltered.layoutFetchMore.fetchMoreContainer.setVisibility(View.GONE);
            }
+28 −0
Original line number Diff line number Diff line
@@ -1127,6 +1127,34 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.

    private void getLiveStatus(DIRECTION direction, boolean fetchingMissing, TimelinesVM.TimelineParams timelineParams, boolean canScroll, Status fetchStatus) {

        // For HOME timeline fetch more: check if the gap can be filled by the server
        if (fetchingMissing && timelineType == Timeline.TimeLineEnum.HOME
                && (direction == DIRECTION.TOP || direction == DIRECTION.BOTTOM)
                && fetchStatus != null && timelineStatuses != null && !timelineStatuses.isEmpty()) {
            String newestCachedId = timelineStatuses.get(0).id;
            timelinesVM.getOldestHomeStatus(BaseMainActivity.currentInstance, BaseMainActivity.currentToken)
                    .observe(getViewLifecycleOwner(), oldestStatus -> {
                        if (oldestStatus != null && Helper.compareTo(oldestStatus.id, newestCachedId) > 0) {
                            // Server's oldest available status is newer than the most recent cached one - gap cannot be filled
                            fetchStatus.isUnreachableGap = true;
                            fetchStatus.isFetching = false;
                            int position = timelineStatuses.indexOf(fetchStatus);
                            if (position >= 0 && statusAdapter != null) {
                                statusAdapter.notifyItemChanged(position);
                            }
                        } else {
                            // Gap is reachable, proceed with fetching
                            getLiveStatusInternal(direction, fetchingMissing, timelineParams, canScroll, fetchStatus);
                        }
                    });
            return;
        }

        getLiveStatusInternal(direction, fetchingMissing, timelineParams, canScroll, fetchStatus);
    }

    private void getLiveStatusInternal(DIRECTION direction, boolean fetchingMissing, TimelinesVM.TimelineParams timelineParams, boolean canScroll, Status fetchStatus) {

        if (direction == null) {
            timelinesVM.getTimeline(timelineStatuses, timelineParams)
                    .observe(getViewLifecycleOwner(), statuses -> {
+24 −0
Original line number Diff line number Diff line
@@ -701,6 +701,30 @@ public class TimelinesVM extends AndroidViewModel {
        return peertubeVideoMutableLiveData;
    }

    /**
     * Get the oldest status available in the home timeline (using min_id=0&limit=1)
     */
    public LiveData<Status> getOldestHomeStatus(@NonNull String instance, String token) {
        statusMutableLiveData = new MutableLiveData<>();
        MastodonTimelinesService mastodonTimelinesService = init(instance);
        new Thread(() -> {
            Status oldest = null;
            try {
                Call<List<Status>> homeCall = mastodonTimelinesService.getHome(token, null, null, "0", 1, null);
                Response<List<Status>> response = homeCall.execute();
                if (response.isSuccessful() && response.body() != null && !response.body().isEmpty()) {
                    oldest = response.body().get(0);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalOldest = oldest;
            mainHandler.post(() -> statusMutableLiveData.setValue(finalOldest));
        }).start();
        return statusMutableLiveData;
    }

    public LiveData<Statuses> getTimeline(List<Status> timelineStatuses, TimelineParams timelineParams) {
        statusesMutableLiveData = new MutableLiveData<>();
        MastodonTimelinesService mastodonTimelinesService = init(timelineParams.instance);
+62 −35
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/fetch_more_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:orientation="horizontal"
    android:paddingHorizontal="6dp">
    android:orientation="vertical"
    android:paddingVertical="4dp">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:id="@+id/fetch_more_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:paddingHorizontal="12dp"
        android:paddingVertical="4dp">

        <com.google.android.material.button.MaterialButton
            android:id="@+id/fetch_more_max"
            style="@style/Widget.Material3.Button.OutlinedButton.Icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
            android:layout_width="44dp"
            android:layout_height="44dp"
            android:contentDescription="@string/fetch_more_messages"
            android:insetTop="0dp"
            android:insetBottom="0dp"
            android:padding="0dp"
            app:icon="@drawable/ic_baseline_keyboard_double_arrow_down_24"
            app:iconGravity="textStart"
@@ -25,21 +34,24 @@
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginHorizontal="12dp"
            android:layout_weight="1"
            android:gravity="center"
            android:text="@string/fetch_more_messages"
            android:textAlignment="center"
            android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
            android:textColor="?colorPrimary"
        android:textSize="18sp"
            android:textSize="16sp"
            app:textAllCaps="false" />

        <com.google.android.material.button.MaterialButton
            android:id="@+id/fetch_more_min"
            style="@style/Widget.Material3.Button.OutlinedButton.Icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
            android:layout_width="44dp"
            android:layout_height="44dp"
            android:contentDescription="@string/fetch_more_messages"
            android:insetTop="0dp"
            android:insetBottom="0dp"
            android:padding="0dp"
            app:icon="@drawable/ic_baseline_keyboard_double_arrow_up_24"
            app:iconGravity="textStart"
@@ -47,3 +59,18 @@
            app:strokeColor="?colorPrimary" />

    </androidx.appcompat.widget.LinearLayoutCompat>

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/unreachable_gap_message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:paddingHorizontal="16dp"
        android:paddingVertical="12dp"
        android:text="@string/unreachable_gap"
        android:textAlignment="center"
        android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
        android:textColor="?colorOnSurfaceVariant"
        android:visibility="gone" />

</LinearLayout>
Loading