Loading app/src/main/java/app/fedilab/android/mastodon/client/entities/api/Status.java +1 −0 Original line number Diff line number Diff line Loading @@ -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; Loading app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java +89 −68 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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); Loading @@ -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()); Loading @@ -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; Loading @@ -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); } Loading @@ -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; Loading @@ -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; Loading @@ -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); } Loading app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java +28 −0 Original line number Diff line number Diff line Loading @@ -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 -> { Loading app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/TimelinesVM.java +24 −0 Original line number Diff line number Diff line Loading @@ -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); Loading app/src/main/res/layouts/mastodon/layout/drawer_fetch_more.xml +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" Loading @@ -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" Loading @@ -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
app/src/main/java/app/fedilab/android/mastodon/client/entities/api/Status.java +1 −0 Original line number Diff line number Diff line Loading @@ -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; Loading
app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java +89 −68 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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); Loading @@ -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()); Loading @@ -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; Loading @@ -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); } Loading @@ -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; Loading @@ -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; Loading @@ -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); } Loading
app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java +28 −0 Original line number Diff line number Diff line Loading @@ -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 -> { Loading
app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/TimelinesVM.java +24 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
app/src/main/res/layouts/mastodon/layout/drawer_fetch_more.xml +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" Loading @@ -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" Loading @@ -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>