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:
LucasGGamerM
2023-10-28 10:46:33 -03:00
38 changed files with 891 additions and 646 deletions

View File

@@ -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});
});
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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){

View File

@@ -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();
}