From 2d24e50ff24991a383442bfb8286da407d5d660c Mon Sep 17 00:00:00 2001 From: Angelo Suzuki <1063155+tinsukE@users.noreply.github.com> Date: Fri, 1 Sep 2023 21:48:10 +0200 Subject: [PATCH] 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 --- .../android/GlobalUserPreferences.java | 17 ++ .../fragments/HomeTimelineFragment.java | 148 ++++++++++++------ .../settings/SettingsBehaviorFragment.java | 25 ++- ...ent_arrow_maximize_vertical_24_regular.xml | 3 + mastodon/src/main/res/values/strings_sk.xml | 3 + 5 files changed, 151 insertions(+), 45 deletions(-) create mode 100644 mastodon/src/main/res/drawable/ic_fluent_arrow_maximize_vertical_24_regular.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java index 200499599..f83eb76fb 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java +++ b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java @@ -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; + } + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java index d511740fe..9a70f75e4 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java @@ -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 result){ @@ -204,52 +220,96 @@ public class HomeTimelineFragment extends StatusListFragment { AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false); } }else{ - Set 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 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 targetList=displayItems.subList(gapPos, gapPos+1); - targetList.clear(); - List 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 targetList=displayItems.subList(gapPos, gapPos+1); + targetList.clear(); + List 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 targetList=displayItems.subList(gapPos, gapPos+1); + if(indexOfGapInResponse 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 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); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsBehaviorFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsBehaviorFragment.java index 919f6fbe6..2c28bd37c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsBehaviorFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsBehaviorFragment.java @@ -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 implements HasAccountID{ - private ListItem languageItem; + private ListItem languageItem, loadMissingPostsItem; private CheckableListItem altTextItem, playGifsItem, customTabsItem, confirmUnfollowItem, confirmBoostItem, confirmDeleteItem; private MastodonLanguage postLanguage; private ComposeLanguageAlertViewController.SelectedOption newPostLanguage; @@ -45,6 +46,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment impleme List> 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 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){ diff --git a/mastodon/src/main/res/drawable/ic_fluent_arrow_maximize_vertical_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_arrow_maximize_vertical_24_regular.xml new file mode 100644 index 000000000..405c1eaba --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_arrow_maximize_vertical_24_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/values/strings_sk.xml b/mastodon/src/main/res/values/strings_sk.xml index 950285895..41d7659f1 100644 --- a/mastodon/src/main/res/values/strings_sk.xml +++ b/mastodon/src/main/res/values/strings_sk.xml @@ -378,4 +378,7 @@ post posts + Load missing posts behavior + Newest first (downwards) + Oldest first (upwards) \ No newline at end of file