Merge remote-tracking branch 'megalodon_main/main'
# Conflicts: # mastodon/build.gradle # mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java # mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java # metadata/en-US/full_description.txt # metadata/ja-JP/title.txt
This commit is contained in:
@@ -121,7 +121,7 @@ public class CacheController{
|
||||
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
if(!clear)
|
||||
db.delete("home_timeline", "`id` NOT IN (SELECT `id` FROM `home_timeline` ORDER BY `time` DESC LIMIT ?)", new String[]{"100"});
|
||||
db.delete("home_timeline", "`id` NOT IN (SELECT `id` FROM `home_timeline` ORDER BY `time` DESC LIMIT ?)", new String[]{"1000"});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -273,6 +273,28 @@ public class CacheController{
|
||||
|
||||
public void deleteStatus(String id){
|
||||
runOnDbThread((db)->{
|
||||
String gapId=null;
|
||||
int gapFlags=0;
|
||||
// select to-be-removed and newer row
|
||||
try(Cursor cursor=db.query("home_timeline", new String[]{"id", "flags"}, "`time`>=(SELECT `time` FROM `home_timeline` WHERE `id`=?)", new String[]{id}, null, null, "`time` ASC", "2")){
|
||||
boolean hadGapAfter=false;
|
||||
// always either one or two iterations (only one if there's no newer post)
|
||||
while(cursor.moveToNext()){
|
||||
String currentId=cursor.getString(0);
|
||||
int currentFlags=cursor.getInt(1);
|
||||
if(currentId.equals(id)){
|
||||
hadGapAfter=((currentFlags & POST_FLAG_GAP_AFTER)!=0);
|
||||
}else if(hadGapAfter){
|
||||
gapFlags=currentFlags|POST_FLAG_GAP_AFTER;
|
||||
gapId=currentId;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(gapId!=null){
|
||||
ContentValues values=new ContentValues();
|
||||
values.put("flags", gapFlags);
|
||||
db.update("home_timeline", values, "`id`=?", new String[]{gapId});
|
||||
}
|
||||
db.delete("home_timeline", "`id`=?", new String[]{id});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,14 +8,10 @@ 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.api.requests.markers.SaveMarkers;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
import org.joinmastodon.android.model.FilterContext;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
@@ -27,11 +23,9 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
@@ -57,7 +51,6 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
String maxID=this.maxID;
|
||||
AccountSessionManager.getInstance()
|
||||
.getAccount(accountID).getCacheController()
|
||||
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
|
||||
@@ -65,8 +58,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
|
||||
if(getActivity()==null) return;
|
||||
boolean empty=result.items.isEmpty();
|
||||
if(result.isFromCache()) refreshCachedData(result, maxID);
|
||||
HomeTimelineFragment.this.maxID=result.maxID;
|
||||
maxID=result.maxID;
|
||||
AccountSessionManager.get(accountID).filterStatuses(result.items, getFilterContext());
|
||||
onDataLoaded(result.items, !empty);
|
||||
if(result.isFromCache() && GlobalUserPreferences.loadNewPosts)
|
||||
@@ -75,53 +67,6 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
});
|
||||
}
|
||||
|
||||
private void handleRefreshedData(List<Status> result, List<Status> cachedList){
|
||||
Map<String, Status> refreshed=result.stream().collect(Collectors.toMap(Status::getID, Function.identity()));
|
||||
for(Status cached : cachedList){
|
||||
if(refreshed.containsKey(cached.id)){
|
||||
Status updated=refreshed.get(cached.id);
|
||||
if(updated.editedAt!=null && cached.editedAt!=null && updated.editedAt.isAfter(cached.editedAt))
|
||||
E.post(new StatusUpdatedEvent(updated));
|
||||
else
|
||||
E.post(new StatusCountersUpdatedEvent(updated.getContentStatus()));
|
||||
}else{
|
||||
removeStatus(cached);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshCachedData(CacheablePaginatedResponse<List<Status>> result, String maxID){
|
||||
List<Status> cachedList=new ArrayList<>(result.items);
|
||||
if(cachedList.isEmpty()) return;
|
||||
if(maxID==null){
|
||||
// fetch top status manually so we can use its id as the max_id to fetch the rest
|
||||
Status firstFromCache=cachedList.get(0);
|
||||
maxID=firstFromCache.id;
|
||||
cachedList.remove(0);
|
||||
new GetStatusByID(maxID).setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
handleRefreshedData(
|
||||
Collections.singletonList(result),
|
||||
Collections.singletonList(firstFromCache)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse ignored){}
|
||||
}).exec(accountID);
|
||||
}
|
||||
new GetHomeTimeline(maxID, null, cachedList.size(), null, getSession().getLocalPreferences().timelineReplyVisibility).setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
handleRefreshedData(result, cachedList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse ignored){}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
@@ -267,6 +212,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
||||
}
|
||||
}else{
|
||||
// TODO: refactor this code. it's too long. incomprehensible, even
|
||||
if(downwards) {
|
||||
Set<String> idsBelowGap=new HashSet<>();
|
||||
boolean belowGap=false;
|
||||
@@ -387,7 +333,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
dataLoading=false;
|
||||
}
|
||||
if(parent!=null) parent.hideNewPostsButton();
|
||||
loadNewPosts();
|
||||
super.onRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
@@ -209,10 +210,12 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||
if(firstIndex==-1) return false;
|
||||
int lastIndex=firstIndex;
|
||||
while(lastIndex<displayItems.size()){
|
||||
if(!displayItems.get(lastIndex).parentID.equals(parentID)) break;
|
||||
StatusDisplayItem item=displayItems.get(lastIndex);
|
||||
if(!item.parentID.equals(parentID) || item instanceof GapStatusDisplayItem) break;
|
||||
lastIndex++;
|
||||
}
|
||||
int count=lastIndex-firstIndex;
|
||||
if(count<1) return false;
|
||||
displayItems.subList(firstIndex, lastIndex).clear();
|
||||
adapter.notifyItemRangeRemoved(firstIndex, count);
|
||||
return true;
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.drawables.SawtoothTearDrawable;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
@@ -63,10 +64,14 @@ public class GapStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
top.setClickable(!item.loading);
|
||||
bottom.setClickable(!item.loading);
|
||||
StatusDisplayItem next=getNextVisibleDisplayItem().orElse(null);
|
||||
Instant dateBelow=next instanceof HeaderStatusDisplayItem h ? h.status.createdAt
|
||||
: next instanceof ReblogOrReplyLineStatusDisplayItem l ? l.status.createdAt
|
||||
: null;
|
||||
Status next=!(item.parentFragment instanceof StatusListFragment) ? null : getNextVisibleDisplayItem(i->{
|
||||
Status s=((StatusListFragment) item.parentFragment).getStatusByID(i.parentID);
|
||||
return s!=null && !s.fromStatusCreated;
|
||||
})
|
||||
.map(i->((StatusListFragment) item.parentFragment).getStatusByID(i.parentID))
|
||||
.orElse(null);
|
||||
bottom.setVisibility(next==null ? View.GONE : View.VISIBLE);
|
||||
Instant dateBelow=next!=null ? next.createdAt : null;
|
||||
String text=dateBelow!=null && item.status.createdAt!=null && dateBelow.isBefore(item.status.createdAt)
|
||||
? UiUtils.formatPeriodBetween(item.parentFragment.getContext(), dateBelow, item.status.createdAt)
|
||||
: null;
|
||||
|
||||
@@ -382,7 +382,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
public MediaAttachmentViewController getViewController(int index){
|
||||
return controllers.get(index);
|
||||
return index<controllers.size() ? controllers.get(index) : null;
|
||||
}
|
||||
|
||||
public void setClipChildren(boolean clip){
|
||||
|
||||
@@ -44,9 +44,11 @@ import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
@@ -313,10 +315,7 @@ public abstract class StatusDisplayItem{
|
||||
footer=new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID);
|
||||
footer.hideCounts=hideCounts;
|
||||
items.add(footer);
|
||||
if(status.hasGapAfter!=null && !(fragment instanceof ThreadFragment))
|
||||
items.add(new GapStatusDisplayItem(parentID, fragment, status));
|
||||
}
|
||||
int i=1;
|
||||
boolean inset=(flags & FLAG_INSET)!=0;
|
||||
// add inset dummy so last content item doesn't clip out of inset bounds
|
||||
if((inset || footer==null) && (flags & FLAG_CHECKABLE)==0){
|
||||
@@ -326,6 +325,10 @@ public abstract class StatusDisplayItem{
|
||||
// !contentItems.isEmpty() && contentItems
|
||||
// .get(contentItems.size() - 1) instanceof MediaGridStatusDisplayItem));
|
||||
}
|
||||
GapStatusDisplayItem gap=null;
|
||||
if((flags & FLAG_NO_FOOTER)==0 && status.hasGapAfter!=null && !(fragment instanceof ThreadFragment))
|
||||
items.add(gap=new GapStatusDisplayItem(parentID, fragment, status));
|
||||
int i=1;
|
||||
for(StatusDisplayItem item:items){
|
||||
item.inset=inset;
|
||||
item.index=i++;
|
||||
@@ -341,8 +344,13 @@ public abstract class StatusDisplayItem{
|
||||
if(!new StatusFilterPredicate(accountID, filterContext, FilterAction.HIDE).test(status))
|
||||
return new ArrayList<StatusDisplayItem>() ;
|
||||
|
||||
return applyingFilter==null ? items :
|
||||
new ArrayList<>(List.of(new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items, applyingFilter)));
|
||||
List<StatusDisplayItem> nonGapItems=gap!=null ? items.subList(0, items.size()-1) : items;
|
||||
WarningFilteredStatusDisplayItem warning=applyingFilter==null ? null :
|
||||
new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, nonGapItems, applyingFilter);
|
||||
return applyingFilter==null ? items : new ArrayList<>(gap!=null
|
||||
? List.of(warning, gap)
|
||||
: Collections.singletonList(warning)
|
||||
);
|
||||
}
|
||||
|
||||
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List<StatusDisplayItem> items, Status status){
|
||||
@@ -400,12 +408,14 @@ public abstract class StatusDisplayItem{
|
||||
}
|
||||
|
||||
public Optional<StatusDisplayItem> getNextVisibleDisplayItem(){
|
||||
return getNextVisibleDisplayItem(null);
|
||||
}
|
||||
public Optional<StatusDisplayItem> getNextVisibleDisplayItem(Predicate<StatusDisplayItem> predicate){
|
||||
Optional<StatusDisplayItem> next=getNextDisplayItem();
|
||||
for(int offset=1; next.isPresent(); next=getDisplayItemOffset(++offset)){
|
||||
if(!next.map(n->
|
||||
(n instanceof EmojiReactionsStatusDisplayItem e && e.isHidden()) ||
|
||||
(n instanceof DummyStatusDisplayItem)
|
||||
).orElse(false)) return next;
|
||||
boolean isHidden=next.map(n->(n instanceof EmojiReactionsStatusDisplayItem e && e.isHidden())
|
||||
|| (n instanceof DummyStatusDisplayItem)).orElse(false);
|
||||
if(!isHidden && (predicate==null || predicate.test(next.get()))) return next;
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user