feat(crash-status-display-item): makes the app not crash when creating the status display items

This is the first part of a 2 part patch, because crashes can still happpen when the item is being "binded", which makes it necessary to handle those errors as well, which we currently DON'T do.

Also the Error item is still needing to be better, so there is also that to work on
This commit is contained in:
LucasGGamerM
2024-05-19 10:20:15 -03:00
parent 44e3e5faaf
commit a082a3d325
2 changed files with 242 additions and 186 deletions

View File

@@ -0,0 +1,50 @@
package org.joinmastodon.android.ui.displayitems;
import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.ui.utils.UiUtils;
public class ErrorStatusDisplayItem extends StatusDisplayItem{
private final Exception exception;
public ErrorStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Exception exception) {
super(parentID, parentFragment);
this.exception=exception;
}
@Override
public Type getType() {
return Type.ERROR_ITEM;
}
public static class Holder extends StatusDisplayItem.Holder<ErrorStatusDisplayItem> {
private final TextView title, domain;
public Holder(Context context, ViewGroup parent) {
super(context, R.layout.display_item_file, parent);
title=findViewById(R.id.title);
domain=findViewById(R.id.domain);
findViewById(R.id.inner).setOnClickListener(this::onClick);
}
@Override
public void onBind(ErrorStatusDisplayItem item) {
title.setText(item.exception.getMessage());
// title.setEllipsize(item.attachment.description != null ? TextUtils.TruncateAt.END : TextUtils.TruncateAt.MIDDLE);
// domain.setText(url.getHost());
}
private void onClick(View v) {
// UiUtils.openURL(itemView.getContext(), item.parentFragment.getAccountID(), getUrl());
}
}
}

View File

@@ -139,6 +139,7 @@ public abstract class StatusDisplayItem{
case SPOILER, FILTER_SPOILER -> new SpoilerStatusDisplayItem.Holder(activity, parent, type); case SPOILER, FILTER_SPOILER -> new SpoilerStatusDisplayItem.Holder(activity, parent, type);
case SECTION_HEADER -> null; // new SectionHeaderStatusDisplayItem.Holder(activity, parent); case SECTION_HEADER -> null; // new SectionHeaderStatusDisplayItem.Holder(activity, parent);
case NOTIFICATION_HEADER -> new NotificationHeaderStatusDisplayItem.Holder(activity, parent); case NOTIFICATION_HEADER -> new NotificationHeaderStatusDisplayItem.Holder(activity, parent);
case ERROR_ITEM -> new ErrorStatusDisplayItem.Holder(activity, parent);
case DUMMY -> new DummyStatusDisplayItem.Holder(activity); case DUMMY -> new DummyStatusDisplayItem.Holder(activity);
}; };
} }
@@ -164,41 +165,42 @@ public abstract class StatusDisplayItem{
Status statusForContent=status.getContentStatus(); Status statusForContent=status.getContentStatus();
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
ScheduledStatus scheduledStatus = parentObject instanceof ScheduledStatus s ? s : null; try{
ScheduledStatus scheduledStatus=parentObject instanceof ScheduledStatus s ? s : null;
HeaderStatusDisplayItem header=null; HeaderStatusDisplayItem header=null;
boolean hideCounts=!AccountSessionManager.get(accountID).getLocalPreferences().showInteractionCounts; boolean hideCounts=!AccountSessionManager.get(accountID).getLocalPreferences().showInteractionCounts;
if((flags & FLAG_NO_HEADER)==0){ if((flags&FLAG_NO_HEADER)==0){
ReblogOrReplyLineStatusDisplayItem replyLine = null; ReblogOrReplyLineStatusDisplayItem replyLine=null;
boolean threadReply = statusForContent.inReplyToAccountId != null && boolean threadReply=statusForContent.inReplyToAccountId!=null &&
statusForContent.inReplyToAccountId.equals(statusForContent.account.id); statusForContent.inReplyToAccountId.equals(statusForContent.account.id);
if(statusForContent.inReplyToAccountId!=null && !(threadReply && fragment instanceof ThreadFragment)){ if(statusForContent.inReplyToAccountId!=null && !(threadReply && fragment instanceof ThreadFragment)){
Account account = knownAccounts.get(statusForContent.inReplyToAccountId); Account account=knownAccounts.get(statusForContent.inReplyToAccountId);
replyLine = buildReplyLine(fragment, status, accountID, parentObject, account, threadReply); replyLine=buildReplyLine(fragment, status, accountID, parentObject, account, threadReply);
} }
if(status.reblog!=null){ if(status.reblog!=null){
boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account); boolean isOwnPost=AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account);
statusForContent.rebloggedBy = status.account; statusForContent.rebloggedBy=status.account;
String text=fragment.getString(R.string.user_boosted, status.account.getDisplayName()); String text=fragment.getString(R.string.user_boosted, status.account.getDisplayName());
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20sp_filled, isOwnPost ? status.visibility : null, i->{ items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20sp_filled, isOwnPost ? status.visibility : null, i->{
args.putParcelable("profileAccount", Parcels.wrap(status.account)); args.putParcelable("profileAccount", Parcels.wrap(status.account));
Nav.go(fragment.getActivity(), ProfileFragment.class, args); Nav.go(fragment.getActivity(), ProfileFragment.class, args);
}, null, status, status.account)); }, null, status, status.account));
} else if (!(status.tags.isEmpty() || }else if(!(status.tags.isEmpty() ||
fragment instanceof HashtagTimelineFragment || fragment instanceof HashtagTimelineFragment ||
fragment instanceof ListTimelineFragment fragment instanceof ListTimelineFragment
) && fragment.getParentFragment() instanceof HomeTabFragment home) { ) && fragment.getParentFragment() instanceof HomeTabFragment home){
home.getHashtags().stream() home.getHashtags().stream()
.filter(followed -> status.tags.stream() .filter(followed->status.tags.stream()
.anyMatch(hashtag -> followed.name.equalsIgnoreCase(hashtag.name))) .anyMatch(hashtag->followed.name.equalsIgnoreCase(hashtag.name)))
.findAny() .findAny()
// post contains a hashtag the user is following // post contains a hashtag the user is following
.ifPresent(hashtag -> items.add(new ReblogOrReplyLineStatusDisplayItem( .ifPresent(hashtag->items.add(new ReblogOrReplyLineStatusDisplayItem(
parentID, fragment, hashtag.name, List.of(), parentID, fragment, hashtag.name, List.of(),
R.drawable.ic_fluent_number_symbol_20sp_filled, null, R.drawable.ic_fluent_number_symbol_20sp_filled, null,
i->UiUtils.openHashtagTimeline(fragment.getActivity(), accountID, hashtag), i->UiUtils.openHashtagTimeline(fragment.getActivity(), accountID, hashtag),
@@ -206,20 +208,20 @@ public abstract class StatusDisplayItem{
))); )));
} }
if (replyLine != null) { if(replyLine!=null){
Optional<ReblogOrReplyLineStatusDisplayItem> primaryLine = items.stream() Optional<ReblogOrReplyLineStatusDisplayItem> primaryLine=items.stream()
.filter(i -> i instanceof ReblogOrReplyLineStatusDisplayItem) .filter(i->i instanceof ReblogOrReplyLineStatusDisplayItem)
.map(ReblogOrReplyLineStatusDisplayItem.class::cast) .map(ReblogOrReplyLineStatusDisplayItem.class::cast)
.findFirst(); .findFirst();
if (primaryLine.isPresent()) { if(primaryLine.isPresent()){
primaryLine.get().extra = replyLine; primaryLine.get().extra=replyLine;
} else { }else{
items.add(replyLine); items.add(replyLine);
} }
} }
if((flags & FLAG_CHECKABLE)!=0) if((flags&FLAG_CHECKABLE)!=0)
items.add(header=new CheckableHeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null)); items.add(header=new CheckableHeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null));
else else
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, parentObject instanceof Notification n ? n : null, scheduledStatus)); items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, parentObject instanceof Notification n ? n : null, scheduledStatus));
@@ -227,15 +229,15 @@ public abstract class StatusDisplayItem{
LegacyFilter applyingFilter=null; LegacyFilter applyingFilter=null;
if(status.filtered!=null){ if(status.filtered!=null){
List<FilterResult> filters = status.filtered; List<FilterResult> filters=status.filtered;
// Only add client filters if there are no pre-existing status filter // Only add client filters if there are no pre-existing status filter
if(filters.isEmpty()) if(filters.isEmpty())
filters.addAll(AccountSessionManager.get(accountID).getClientSideFilters(status)); filters.addAll(AccountSessionManager.get(accountID).getClientSideFilters(status));
for(FilterResult filter:filters){ for(FilterResult filter : filters){
LegacyFilter f=filter.filter; LegacyFilter f=filter.filter;
if(f.isActive() && filterContext != null && f.context.contains(filterContext)){ if(f.isActive() && filterContext!=null && f.context.contains(filterContext)){
applyingFilter=f; applyingFilter=f;
break; break;
} }
@@ -244,10 +246,10 @@ public abstract class StatusDisplayItem{
ArrayList<StatusDisplayItem> contentItems; ArrayList<StatusDisplayItem> contentItems;
if(statusForContent.hasSpoiler()){ if(statusForContent.hasSpoiler()){
if (AccountSessionManager.get(accountID).getLocalPreferences().revealCWs) statusForContent.spoilerRevealed = true; if(AccountSessionManager.get(accountID).getLocalPreferences().revealCWs) statusForContent.spoilerRevealed=true;
SpoilerStatusDisplayItem spoilerItem=new SpoilerStatusDisplayItem(parentID, fragment, null, statusForContent, Type.SPOILER); SpoilerStatusDisplayItem spoilerItem=new SpoilerStatusDisplayItem(parentID, fragment, null, statusForContent, Type.SPOILER);
if((flags & FLAG_IS_FOR_QUOTE)!=0){ if((flags&FLAG_IS_FOR_QUOTE)!=0){
for(StatusDisplayItem item:spoilerItem.contentItems){ for(StatusDisplayItem item : spoilerItem.contentItems){
item.isForQuote=true; item.isForQuote=true;
} }
} }
@@ -257,9 +259,9 @@ public abstract class StatusDisplayItem{
contentItems=items; contentItems=items;
} }
if(statusForContent.quote!=null) { if(statusForContent.quote!=null){
int quoteInlineIndex=statusForContent.content.lastIndexOf("<span class=\"quote-inline\"><br/><br/>RE:"); int quoteInlineIndex=statusForContent.content.lastIndexOf("<span class=\"quote-inline\"><br/><br/>RE:");
if (quoteInlineIndex!=-1) if(quoteInlineIndex!=-1)
statusForContent.content=statusForContent.content.substring(0, quoteInlineIndex); statusForContent.content=statusForContent.content.substring(0, quoteInlineIndex);
} }
@@ -268,7 +270,7 @@ public abstract class StatusDisplayItem{
SpannableStringBuilder parsedText=HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID, fragment.getContext()); SpannableStringBuilder parsedText=HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID, fragment.getContext());
if(applyingFilter!=null) if(applyingFilter!=null)
HtmlParser.applyFilterHighlights(fragment.getActivity(), parsedText, status.filtered); HtmlParser.applyFilterHighlights(fragment.getActivity(), parsedText, status.filtered);
TextStatusDisplayItem text=new TextStatusDisplayItem(parentID, parsedText, fragment, statusForContent, (flags & FLAG_NO_TRANSLATE) != 0); TextStatusDisplayItem text=new TextStatusDisplayItem(parentID, parsedText, fragment, statusForContent, (flags&FLAG_NO_TRANSLATE)!=0);
contentItems.add(text); contentItems.add(text);
}else if(!hasSpoiler && header!=null){ }else if(!hasSpoiler && header!=null){
header.needBottomPadding=true; header.needBottomPadding=true;
@@ -277,28 +279,28 @@ public abstract class StatusDisplayItem{
} }
List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList()); List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList());
if(!imageAttachments.isEmpty() && (flags & FLAG_NO_MEDIA_PREVIEW)==0){ if(!imageAttachments.isEmpty() && (flags&FLAG_NO_MEDIA_PREVIEW)==0){
int color = UiUtils.getThemeColor(fragment.getContext(), R.attr.colorM3SurfaceVariant); int color=UiUtils.getThemeColor(fragment.getContext(), R.attr.colorM3SurfaceVariant);
for (Attachment att : imageAttachments) { for(Attachment att : imageAttachments){
if (att.blurhashPlaceholder == null) { if(att.blurhashPlaceholder==null){
att.blurhashPlaceholder = new ColorDrawable(color); att.blurhashPlaceholder=new ColorDrawable(color);
} }
} }
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments); PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments);
MediaGridStatusDisplayItem mediaGrid=new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent); MediaGridStatusDisplayItem mediaGrid=new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent);
if((flags & FLAG_MEDIA_FORCE_HIDDEN)!=0){ if((flags&FLAG_MEDIA_FORCE_HIDDEN)!=0){
mediaGrid.sensitiveTitle=fragment.getString(R.string.media_hidden); mediaGrid.sensitiveTitle=fragment.getString(R.string.media_hidden);
statusForContent.sensitiveRevealed=false; statusForContent.sensitiveRevealed=false;
statusForContent.sensitive=true; statusForContent.sensitive=true;
} else if(statusForContent.sensitive && AccountSessionManager.get(accountID).getLocalPreferences().revealCWs && !AccountSessionManager.get(accountID).getLocalPreferences().hideSensitiveMedia) }else if(statusForContent.sensitive && AccountSessionManager.get(accountID).getLocalPreferences().revealCWs && !AccountSessionManager.get(accountID).getLocalPreferences().hideSensitiveMedia)
statusForContent.sensitiveRevealed=true; statusForContent.sensitiveRevealed=true;
contentItems.add(mediaGrid); contentItems.add(mediaGrid);
} }
if((flags & FLAG_NO_MEDIA_PREVIEW)!=0){ if((flags&FLAG_NO_MEDIA_PREVIEW)!=0){
contentItems.add(new PreviewlessMediaGridStatusDisplayItem(parentID, fragment, null, imageAttachments, statusForContent)); contentItems.add(new PreviewlessMediaGridStatusDisplayItem(parentID, fragment, null, imageAttachments, statusForContent));
} }
for(Attachment att:statusForContent.mediaAttachments){ for(Attachment att : statusForContent.mediaAttachments){
if(att.type==Attachment.Type.AUDIO){ if(att.type==Attachment.Type.AUDIO){
contentItems.add(new AudioStatusDisplayItem(parentID, fragment, statusForContent, att)); contentItems.add(new AudioStatusDisplayItem(parentID, fragment, statusForContent, att));
} }
@@ -310,18 +312,18 @@ public abstract class StatusDisplayItem{
buildPollItems(parentID, fragment, statusForContent.poll, status, contentItems); buildPollItems(parentID, fragment, statusForContent.poll, status, contentItems);
} }
if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty() && statusForContent.quote==null && !statusForContent.card.isHashtagUrl(statusForContent.url)){ if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty() && statusForContent.quote==null && !statusForContent.card.isHashtagUrl(statusForContent.url)){
contentItems.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent, (flags & FLAG_NO_MEDIA_PREVIEW)==0)); contentItems.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent, (flags&FLAG_NO_MEDIA_PREVIEW)==0));
} }
if(statusForContent.quote!=null && !(parentObject instanceof Notification)){ if(statusForContent.quote!=null && !(parentObject instanceof Notification)){
if(!statusForContent.mediaAttachments.isEmpty() && statusForContent.poll==null) // add spacing if immediately preceded by attachment if(!statusForContent.mediaAttachments.isEmpty() && statusForContent.poll==null) // add spacing if immediately preceded by attachment
contentItems.add(new DummyStatusDisplayItem(parentID, fragment)); contentItems.add(new DummyStatusDisplayItem(parentID, fragment));
contentItems.addAll(buildItems(fragment, statusForContent.quote, accountID, parentObject, knownAccounts, filterContext, FLAG_NO_FOOTER | FLAG_INSET | FLAG_NO_EMOJI_REACTIONS | FLAG_IS_FOR_QUOTE)); contentItems.addAll(buildItems(fragment, statusForContent.quote, accountID, parentObject, knownAccounts, filterContext, FLAG_NO_FOOTER|FLAG_INSET|FLAG_NO_EMOJI_REACTIONS|FLAG_IS_FOR_QUOTE));
} }
if(contentItems!=items && statusForContent.spoilerRevealed){ if(contentItems!=items && statusForContent.spoilerRevealed){
items.addAll(contentItems); items.addAll(contentItems);
} }
AccountLocalPreferences lp=fragment.getLocalPrefs(); AccountLocalPreferences lp=fragment.getLocalPrefs();
if((flags & FLAG_NO_EMOJI_REACTIONS)==0 && !status.preview && lp.emojiReactionsEnabled && if((flags&FLAG_NO_EMOJI_REACTIONS)==0 && !status.preview && lp.emojiReactionsEnabled &&
(lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment) && (lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment) &&
statusForContent.reactions!=null){ statusForContent.reactions!=null){
boolean isMainStatus=fragment instanceof ThreadFragment t && t.getMainStatus().id.equals(statusForContent.id); boolean isMainStatus=fragment instanceof ThreadFragment t && t.getMainStatus().id.equals(statusForContent.id);
@@ -329,15 +331,15 @@ public abstract class StatusDisplayItem{
items.add(new EmojiReactionsStatusDisplayItem(parentID, fragment, statusForContent, accountID, !showAddButton, false)); items.add(new EmojiReactionsStatusDisplayItem(parentID, fragment, statusForContent, accountID, !showAddButton, false));
} }
FooterStatusDisplayItem footer=null; FooterStatusDisplayItem footer=null;
if((flags & FLAG_NO_FOOTER)==0){ if((flags&FLAG_NO_FOOTER)==0){
footer=new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID); footer=new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID);
footer.hideCounts=hideCounts; footer.hideCounts=hideCounts;
items.add(footer); items.add(footer);
} }
boolean inset=(flags & FLAG_INSET)!=0; boolean inset=(flags&FLAG_INSET)!=0;
boolean isForQuote=(flags & FLAG_IS_FOR_QUOTE)!=0; boolean isForQuote=(flags&FLAG_IS_FOR_QUOTE)!=0;
// add inset dummy so last content item doesn't clip out of inset bounds // add inset dummy so last content item doesn't clip out of inset bounds
if((inset || footer==null) && (flags & FLAG_CHECKABLE)==0 && !isForQuote){ if((inset || footer==null) && (flags&FLAG_CHECKABLE)==0 && !isForQuote){
items.add(new DummyStatusDisplayItem(parentID, fragment)); items.add(new DummyStatusDisplayItem(parentID, fragment));
// in case we ever need the dummy to display a margin for the media grid again: // in case we ever need the dummy to display a margin for the media grid again:
// (i forgot why we apparently don't need this anymore) // (i forgot why we apparently don't need this anymore)
@@ -345,10 +347,10 @@ public abstract class StatusDisplayItem{
// .get(contentItems.size() - 1) instanceof MediaGridStatusDisplayItem)); // .get(contentItems.size() - 1) instanceof MediaGridStatusDisplayItem));
} }
GapStatusDisplayItem gap=null; GapStatusDisplayItem gap=null;
if((flags & FLAG_NO_FOOTER)==0 && status.hasGapAfter!=null && !(fragment instanceof ThreadFragment)) if((flags&FLAG_NO_FOOTER)==0 && status.hasGapAfter!=null && !(fragment instanceof ThreadFragment))
items.add(gap=new GapStatusDisplayItem(parentID, fragment, status)); items.add(gap=new GapStatusDisplayItem(parentID, fragment, status));
int i=1; int i=1;
for(StatusDisplayItem item:items){ for(StatusDisplayItem item : items){
if(inset) if(inset)
item.inset=true; item.inset=true;
if(isForQuote){ if(isForQuote){
@@ -358,7 +360,7 @@ public abstract class StatusDisplayItem{
item.index=i++; item.index=i++;
} }
if(items!=contentItems && !statusForContent.spoilerRevealed){ if(items!=contentItems && !statusForContent.spoilerRevealed){
for(StatusDisplayItem item:contentItems){ for(StatusDisplayItem item : contentItems){
if(inset) if(inset)
item.inset=true; item.inset=true;
if(isForQuote){ if(isForQuote){
@@ -376,6 +378,9 @@ public abstract class StatusDisplayItem{
? List.of(warning, gap) ? List.of(warning, gap)
: Collections.singletonList(warning) : Collections.singletonList(warning)
); );
} catch(Exception e) {
return new ArrayList<>(Collections.singletonList(new ErrorStatusDisplayItem(parentID, fragment, e)));
}
} }
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, Status status, List<StatusDisplayItem> items){ public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, Status status, List<StatusDisplayItem> items){
@@ -411,6 +416,7 @@ public abstract class StatusDisplayItem{
SECTION_HEADER, SECTION_HEADER,
HEADER_CHECKABLE, HEADER_CHECKABLE,
NOTIFICATION_HEADER, NOTIFICATION_HEADER,
ERROR_ITEM,
FILTER_SPOILER, FILTER_SPOILER,
DUMMY DUMMY
} }