Add a setting for "Load missing posts behavior"

This will make sure that items that are filtered out don't show up on the interface.
Fixes sk22#311
This commit is contained in:
Angelo Suzuki 2023-09-01 21:48:10 +02:00 committed by sk
parent 53369eb2d4
commit 2d24e50ff2
5 changed files with 151 additions and 45 deletions

View File

@ -25,6 +25,8 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import androidx.annotation.StringRes;
public class GlobalUserPreferences{
private static final String TAG="GlobalUserPreferences";
@ -32,6 +34,7 @@ public class GlobalUserPreferences{
public static boolean useCustomTabs;
public static boolean altTextReminders, confirmUnfollow, confirmBoost, confirmDeletePost;
public static ThemePreference theme;
public static LoadMissingPostsPreference loadMissingPosts;
// MEGALODON
public static boolean trueBlackTheme;
@ -119,6 +122,7 @@ public class GlobalUserPreferences{
displayPronounsInThreads=prefs.getBoolean("displayPronounsInThreads", true);
displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true);
overlayMedia=prefs.getBoolean("overlayMedia", false);
loadMissingPosts=LoadMissingPostsPreference.values()[prefs.getInt("loadMissingItems", 0)];
if (prefs.contains("prefixRepliesWithRe")) {
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
@ -177,6 +181,7 @@ public class GlobalUserPreferences{
.putBoolean("displayPronounsInThreads", displayPronounsInThreads)
.putBoolean("displayPronounsInUserListings", displayPronounsInUserListings)
.putBoolean("overlayMedia", overlayMedia)
.putInt("loadMissingItems", loadMissingPosts.ordinal())
.apply();
}
@ -271,4 +276,16 @@ public class GlobalUserPreferences{
ALWAYS,
TO_OTHERS
}
public enum LoadMissingPostsPreference{
NEWEST_FIRST(R.string.sk_load_missing_posts_newest_first), // Downwards, default
OLDEST_FIRST(R.string.sk_load_missing_posts_oldest_first); // Upwards
@StringRes
public int labelRes;
LoadMissingPostsPreference(@StringRes int labelRes){
this.labelRes=labelRes;
}
}
}

View File

@ -8,7 +8,9 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
@ -22,9 +24,12 @@ import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -179,12 +184,23 @@ public class HomeTimelineFragment extends StatusListFragment {
public void onGapClick(GapStatusDisplayItem.Holder item){
if(dataLoading)
return;
item.getItem().loading=true;
GapStatusDisplayItem gap=item.getItem();
gap.loading=true;
V.setVisibilityAnimated(item.progress, View.VISIBLE);
V.setVisibilityAnimated(item.text, View.GONE);
GapStatusDisplayItem gap=item.getItem();
dataLoading=true;
currentRequest=new GetHomeTimeline(item.getItemID(), null, 20, null, getLocalPrefs().timelineReplyVisibility)
GlobalUserPreferences.LoadMissingPostsPreference preference = GlobalUserPreferences.loadMissingPosts;
String maxID = null;
String minID = null;
if (preference==GlobalUserPreferences.LoadMissingPostsPreference.NEWEST_FIRST) {
maxID = item.getItemID();
} else {
int gapPos=displayItems.indexOf(gap);
StatusDisplayItem nextItem=displayItems.get(gapPos + 1);
minID=nextItem.parentID;
}
currentRequest=new GetHomeTimeline(maxID, minID, 20, null, getLocalPrefs().timelineReplyVisibility)
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Status> result){
@ -204,52 +220,96 @@ public class HomeTimelineFragment extends StatusListFragment {
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
}
}else{
Set<String> idsBelowGap=new HashSet<>();
boolean belowGap=false;
int gapPostIndex=0;
for(Status s:data){
if(belowGap){
idsBelowGap.add(s.id);
}else if(s.id.equals(gap.parentID)){
belowGap=true;
s.hasGapAfter=false;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
}else{
gapPostIndex++;
if(preference==GlobalUserPreferences.LoadMissingPostsPreference.NEWEST_FIRST) {
Set<String> idsBelowGap=new HashSet<>();
boolean belowGap=false;
int gapPostIndex=0;
for(Status s:data){
if(belowGap){
idsBelowGap.add(s.id);
}else if(s.id.equals(gap.parentID)){
belowGap=true;
s.hasGapAfter=false;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
}else{
gapPostIndex++;
}
}
}
int endIndex=0;
for(Status s:result){
endIndex++;
if(idsBelowGap.contains(s.id))
break;
}
if(endIndex==result.size()){
result.get(result.size()-1).hasGapAfter=true;
}else{
result=result.subList(0, endIndex);
}
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
targetList.clear();
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext());
for(Status s:result){
if(idsBelowGap.contains(s.id))
break;
if(typeFilterPredicate(s) && filterPredicate.test(s)){
int endIndex=0;
for(Status s:result){
endIndex++;
if(idsBelowGap.contains(s.id))
break;
}
if(endIndex==result.size()){
result.get(result.size()-1).hasGapAfter=true;
}else{
result=result.subList(0, endIndex);
}
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
targetList.clear();
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
for(Status s:result){
if(idsBelowGap.contains(s.id))
break;
targetList.addAll(buildDisplayItems(s));
insertedPosts.add(s);
}
if(targetList.isEmpty()){
// oops. We didn't add new posts, but at least we know there are none.
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
}else{
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
}
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
} else {
String aboveGapID = gap.parentID;
int gapPostIndex = 0;
for (;gapPostIndex<data.size();gapPostIndex++){
if (Objects.equals(aboveGapID, data.get(gapPostIndex).id)) {
break;
}
}
// find if there's an overlap between the new data and the current data
int indexOfGapInResponse = 0;
for (;indexOfGapInResponse<result.size();indexOfGapInResponse++){
if (Objects.equals(aboveGapID, result.get(indexOfGapInResponse).id)) {
break;
}
}
// there is an overlap between new and current data
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
if(indexOfGapInResponse<result.size()){
result=result.subList(indexOfGapInResponse+1,result.size());
Optional<Status> gapStatus=data.stream()
.filter(s->Objects.equals(s.id, gap.parentID))
.findFirst();
if (gapStatus.isPresent()) {
gapStatus.get().hasGapAfter=false;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false);
}
targetList.clear();
} else {
gap.loading=false;
}
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
for(Status s:result){
targetList.addAll(buildDisplayItems(s));
insertedPosts.add(s);
}
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, FilterContext.HOME);
if(targetList.isEmpty()){
// oops. We didn't add new posts, but at least we know there are none.
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
}else{
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
}
list.scrollToPosition(getMainAdapterOffset()+gapPos+targetList.size());
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
}
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, getFilterContext());
if(targetList.isEmpty()){
// oops. We didn't add new posts, but at least we know there are none.
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
}else{
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
}
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
}
}

View File

@ -20,9 +20,10 @@ import org.joinmastodon.android.utils.MastodonLanguage;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> implements HasAccountID{
private ListItem<Void> languageItem;
private ListItem<Void> languageItem, loadMissingPostsItem;
private CheckableListItem<Void> altTextItem, playGifsItem, customTabsItem, confirmUnfollowItem, confirmBoostItem, confirmDeleteItem;
private MastodonLanguage postLanguage;
private ComposeLanguageAlertViewController.SelectedOption newPostLanguage;
@ -45,6 +46,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
List<ListItem<Void>> items = new ArrayList<>(List.of(
languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(getContext()) : null, R.drawable.ic_fluent_local_language_24_regular, this::onDefaultLanguageClick),
loadMissingPostsItem=new ListItem<>(R.string.sk_settings_load_missing_posts, GlobalUserPreferences.loadMissingPosts.labelRes, R.drawable.ic_fluent_arrow_maximize_vertical_24_regular, this::onLoadMissingPostsClick),
altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_fluent_image_alt_text_24_regular, ()->toggleCheckableItem(altTextItem)),
playGifsItem=new CheckableListItem<>(R.string.settings_gif, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.playGifs, R.drawable.ic_fluent_gif_24_regular, ()->toggleCheckableItem(playGifsItem)),
overlayMediaItem=new CheckableListItem<>(R.string.sk_settings_continues_playback, R.string.sk_settings_continues_playback_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.overlayMedia, R.drawable.ic_fluent_play_circle_hint_24_regular, ()->toggleCheckableItem(overlayMediaItem)),
@ -127,6 +129,27 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
.show();
}
private void onLoadMissingPostsClick(){
GlobalUserPreferences.LoadMissingPostsPreference[] values=GlobalUserPreferences.LoadMissingPostsPreference.values();
int selected=GlobalUserPreferences.loadMissingPosts.ordinal();
int[] newSelected={selected};
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_load_missing_posts)
.setSingleChoiceItems(Stream.of(values).map(pref->getString(pref.labelRes)).toArray(String[]::new),
selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->{
GlobalUserPreferences.LoadMissingPostsPreference pref=values[newSelected[0]];
if(pref!=GlobalUserPreferences.loadMissingPosts){
GlobalUserPreferences.loadMissingPosts=pref;
GlobalUserPreferences.save();
loadMissingPostsItem.subtitleRes=pref.labelRes;
rebindItem(loadMissingPostsItem);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
}
private void onReplyVisibilityClick(){
AccountLocalPreferences lp=getLocalPrefs();
int selected=lp.timelineReplyVisibility==null ? 2 : switch(lp.timelineReplyVisibility){

View File

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M13.72 5.78c0.293 0.293 0.767 0.293 1.06 0 0.293-0.293 0.293-0.767 0-1.06l-2.5-2.5c-0.293-0.293-0.767-0.293-1.06 0l-2.5 2.5c-0.293 0.293-0.293 0.767 0 1.06 0.293 0.293 0.767 0.293 1.06 0L11 4.56v4.19c0 0.414 0.336 0.75 0.75 0.75s0.75-0.336 0.75-0.75V4.56l1.22 1.22zM4 11.75C4 11.336 4.336 11 4.75 11h14.5c0.414 0 0.75 0.336 0.75 0.75s-0.336 0.75-0.75 0.75H4.75C4.336 12.5 4 12.164 4 11.75zm8.5 3c0-0.414-0.336-0.75-0.75-0.75S11 14.336 11 14.75v4.69l-1.22-1.22c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.767 0 1.06l2.5 2.5c0.293 0.293 0.767 0.293 1.06 0l2.5-2.5c0.293-0.293 0.293-0.767 0-1.06-0.293-0.293-0.767-0.293-1.06 0l-1.22 1.22v-4.69z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@ -378,4 +378,7 @@
<item quantity="one">post</item>
<item quantity="other">posts</item>
</plurals>
<string name="sk_settings_load_missing_posts">Load missing posts behavior</string>
<string name="sk_load_missing_posts_newest_first">Newest first (downwards)</string>
<string name="sk_load_missing_posts_oldest_first">Oldest first (upwards)</string>
</resources>