Merge remote-tracking branch 'megalodon_main/main'
# Conflicts: # mastodon/build.gradle # mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetPrivateNote.java # mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java # mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java # mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java # mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java # mastodon/src/main/java/org/joinmastodon/android/ui/text/DiffRemovedSpan.java # mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java # mastodon/src/main/res/drawable/bg_note_edit.xml # mastodon/src/main/res/layout/fragment_profile.xml # metadata/uk/full_description.txt
This commit is contained in:
@@ -32,7 +32,6 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class AudioStatusDisplayItem extends StatusDisplayItem{
|
||||
public final Status status;
|
||||
public final Attachment attachment;
|
||||
private final ImageLoaderRequest imageRequest;
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ package org.joinmastodon.android.ui.displayitems;
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.CheckBox;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.report.ReportAddPostsChoiceFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.views.CheckableRelativeLayout;
|
||||
|
||||
@@ -34,8 +34,16 @@ public class CheckableHeaderStatusDisplayItem extends HeaderStatusDisplayItem{
|
||||
public Holder(Activity activity, ViewGroup parent){
|
||||
super(activity, R.layout.display_item_header_checkable, parent);
|
||||
checkbox=findViewById(R.id.checkbox);
|
||||
view=(CheckableRelativeLayout) itemView;
|
||||
view=findViewById(R.id.checkbox_wrap);
|
||||
checkbox.setBackground(new CheckBox(activity).getButtonDrawable());
|
||||
view.setOnClickListener(this::onToggle);
|
||||
view.setAccessibilityDelegate(new View.AccessibilityDelegate(){
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info){
|
||||
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||
info.setClassName(CheckBox.class.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,6 +54,12 @@ public class CheckableHeaderStatusDisplayItem extends HeaderStatusDisplayItem{
|
||||
}
|
||||
}
|
||||
|
||||
private void onToggle(View v){
|
||||
if(item.parentFragment instanceof ReportAddPostsChoiceFragment reportFragment){
|
||||
reportFragment.onToggleItem(item.parentID);
|
||||
}
|
||||
}
|
||||
|
||||
public void setIsChecked(Predicate<Holder> isChecked){
|
||||
this.isChecked=isChecked;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,6 @@ import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
public final Status status;
|
||||
private final Drawable placeholder;
|
||||
private final boolean hideEmpty, forAnnouncement, playGifs;
|
||||
private final String accountID;
|
||||
|
||||
@@ -37,7 +37,6 @@ import androidx.annotation.PluralsRes;
|
||||
import me.grishka.appkit.Nav;
|
||||
|
||||
public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
public final Status status;
|
||||
public final String accountID;
|
||||
|
||||
private static final DateTimeFormatter TIME_FORMATTER=DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
|
||||
@@ -131,6 +130,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void startAccountListFragment(Class<? extends StatusRelatedAccountListFragment> cls){
|
||||
if(item.status.preview) return;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putParcelable("status", Parcels.wrap(item.status));
|
||||
@@ -138,6 +138,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void startEditHistoryFragment(){
|
||||
if(item.status.preview) return;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putString("id", item.status.id);
|
||||
|
||||
@@ -176,6 +176,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private boolean onButtonTouch(View v, MotionEvent event){
|
||||
if(item.status.preview) return false;
|
||||
boolean disabled = !v.isEnabled() || (v instanceof FrameLayout parentFrame &&
|
||||
parentFrame.getChildCount() > 0 && !parentFrame.getChildAt(0).isEnabled());
|
||||
int action = event.getAction();
|
||||
@@ -198,6 +199,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void onReplyClick(View v){
|
||||
if(item.status.preview) return;
|
||||
if(item.status.isRemote){
|
||||
UiUtils.lookupStatus(v.getContext(),
|
||||
item.status, item.accountID, null,
|
||||
@@ -219,6 +221,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private boolean onReplyLongClick(View v) {
|
||||
if(item.status.preview) return false;
|
||||
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
|
||||
UiUtils.pickAccount(v.getContext(), item.accountID, R.string.sk_reply_as, R.drawable.ic_fluent_arrow_reply_28_regular, session -> {
|
||||
Bundle args=new Bundle();
|
||||
@@ -234,6 +237,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void onBoostClick(View v){
|
||||
if(item.status.preview) return;
|
||||
if (GlobalUserPreferences.confirmBoost) {
|
||||
UiUtils.opacityIn(v);
|
||||
onBoostLongClick(v);
|
||||
@@ -263,6 +267,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private boolean onBoostLongClick(View v){
|
||||
if(item.status.preview) return false;
|
||||
Context ctx = itemView.getContext();
|
||||
View menu = LayoutInflater.from(ctx).inflate(R.layout.item_boost_menu, null);
|
||||
Dialog dialog = new M3AlertDialogBuilder(ctx).setView(menu).create();
|
||||
@@ -358,6 +363,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void onFavoriteClick(View v){
|
||||
if(item.status.preview) return;
|
||||
if(item.status.isRemote){
|
||||
UiUtils.lookupStatus(v.getContext(),
|
||||
item.status, item.accountID, null,
|
||||
@@ -389,6 +395,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private boolean onFavoriteLongClick(View v) {
|
||||
if(item.status.preview) return false;
|
||||
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
|
||||
UiUtils.pickInteractAs(v.getContext(),
|
||||
item.accountID, item.status,
|
||||
@@ -403,6 +410,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void onBookmarkClick(View v){
|
||||
if(item.status.preview) return;
|
||||
if(item.status.isRemote){
|
||||
UiUtils.lookupStatus(v.getContext(),
|
||||
item.status, item.accountID, null,
|
||||
@@ -426,6 +434,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private boolean onBookmarkLongClick(View v) {
|
||||
if(item.status.preview) return false;
|
||||
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
|
||||
UiUtils.pickInteractAs(v.getContext(),
|
||||
item.accountID, item.status,
|
||||
@@ -440,6 +449,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void onShareClick(View v){
|
||||
if(item.status.preview) return;
|
||||
UiUtils.opacityIn(v);
|
||||
Intent intent=new Intent(Intent.ACTION_SEND);
|
||||
intent.setType("text/plain");
|
||||
@@ -448,6 +458,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private boolean onShareLongClick(View v){
|
||||
if(item.status.preview) return false;
|
||||
UiUtils.copyText(v, item.status.url);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import me.grishka.appkit.utils.V;
|
||||
|
||||
public class GapStatusDisplayItem extends StatusDisplayItem{
|
||||
public boolean loading;
|
||||
private final Status status;
|
||||
|
||||
public GapStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status){
|
||||
super(parentID, parentFragment);
|
||||
|
||||
@@ -78,7 +78,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
private String accountID;
|
||||
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
||||
private SpannableStringBuilder parsedName;
|
||||
public final Status status;
|
||||
public boolean hasVisibilityToggle;
|
||||
boolean needBottomPadding;
|
||||
private CharSequence extraText;
|
||||
@@ -458,6 +457,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void onMoreClick(View v){
|
||||
if(item.status.preview) return;
|
||||
updateOptionsMenu();
|
||||
optionsMenu.show();
|
||||
if(relationship==null && currentRelationshipRequest==null){
|
||||
|
||||
@@ -24,7 +24,6 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class LinkCardStatusDisplayItem extends StatusDisplayItem{
|
||||
private final Status status;
|
||||
private final UrlImageLoaderRequest imgRequest;
|
||||
|
||||
public LinkCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status, boolean showImagePreview){
|
||||
|
||||
@@ -61,7 +61,6 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
private final List<Attachment> attachments;
|
||||
private final Map<String, Pair<String, String>> translatedAttachments = new HashMap<>();
|
||||
private final ArrayList<ImageLoaderRequest> requests=new ArrayList<>();
|
||||
public final Status status;
|
||||
public String sensitiveTitle;
|
||||
|
||||
public MediaGridStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, PhotoLayoutHelper.TiledLayoutResult tiledLayout, List<Attachment> attachments, Status status){
|
||||
|
||||
@@ -43,7 +43,6 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
||||
public boolean needBottomPadding;
|
||||
ReblogOrReplyLineStatusDisplayItem extra;
|
||||
CharSequence fullText;
|
||||
Status status;
|
||||
|
||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, Status status) {
|
||||
this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, text, status);
|
||||
|
||||
@@ -24,7 +24,6 @@ import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
|
||||
public class SpoilerStatusDisplayItem extends StatusDisplayItem{
|
||||
public final Status status;
|
||||
public final ArrayList<StatusDisplayItem> contentItems=new ArrayList<>();
|
||||
private final CharSequence parsedTitle;
|
||||
private CharSequence translatedTitle;
|
||||
|
||||
@@ -59,13 +59,15 @@ import me.grishka.appkit.views.UsableRecyclerView;
|
||||
public abstract class StatusDisplayItem{
|
||||
public final String parentID;
|
||||
public final BaseStatusListFragment<?> parentFragment;
|
||||
public Status status;
|
||||
public boolean inset;
|
||||
public int index;
|
||||
public boolean
|
||||
hasDescendantNeighbor=false,
|
||||
hasAncestoringNeighbor=false,
|
||||
isMainStatus=true,
|
||||
isDirectDescendant=false;
|
||||
isDirectDescendant=false,
|
||||
isForQuote=false;
|
||||
|
||||
public static final int FLAG_INSET=1;
|
||||
public static final int FLAG_NO_FOOTER=1 << 1;
|
||||
@@ -74,7 +76,8 @@ public abstract class StatusDisplayItem{
|
||||
public static final int FLAG_NO_HEADER=1 << 4;
|
||||
public static final int FLAG_NO_TRANSLATE=1 << 5;
|
||||
public static final int FLAG_NO_EMOJI_REACTIONS=1 << 6;
|
||||
public static final int FLAG_NO_MEDIA_PREVIEW=1 << 7;
|
||||
public static final int FLAG_IS_FOR_QUOTE=1 << 7;
|
||||
public static final int FLAG_NO_MEDIA_PREVIEW=1 << 8;
|
||||
|
||||
public void setAncestryInfo(
|
||||
boolean hasDescendantNeighbor,
|
||||
@@ -238,27 +241,28 @@ public abstract class StatusDisplayItem{
|
||||
if(statusForContent.hasSpoiler()){
|
||||
if (AccountSessionManager.get(accountID).getLocalPreferences().revealCWs) statusForContent.spoilerRevealed = true;
|
||||
SpoilerStatusDisplayItem spoilerItem=new SpoilerStatusDisplayItem(parentID, fragment, null, statusForContent, Type.SPOILER);
|
||||
if((flags & FLAG_IS_FOR_QUOTE)!=0){
|
||||
for(StatusDisplayItem item:spoilerItem.contentItems){
|
||||
item.isForQuote=true;
|
||||
}
|
||||
}
|
||||
items.add(spoilerItem);
|
||||
contentItems=spoilerItem.contentItems;
|
||||
}else{
|
||||
contentItems=items;
|
||||
}
|
||||
|
||||
if (statusForContent.quote != null) {
|
||||
boolean hasQuoteInlineTag = statusForContent.content.contains("<span class=\"quote-inline\">");
|
||||
if (!hasQuoteInlineTag) {
|
||||
String quoteUrl = statusForContent.quote.url;
|
||||
String quoteInline = String.format("<span class=\"quote-inline\">%sRE: <a href=\"%s\">%s</a></span>",
|
||||
statusForContent.content.endsWith("</p>") ? "" : "<br/><br/>", quoteUrl, quoteUrl);
|
||||
statusForContent.content += quoteInline;
|
||||
}
|
||||
if(statusForContent.quote!=null) {
|
||||
int quoteInlineIndex=statusForContent.content.lastIndexOf("<span class=\"quote-inline\"><br/><br/>RE:");
|
||||
if (quoteInlineIndex!=-1)
|
||||
statusForContent.content=statusForContent.content.substring(0, quoteInlineIndex);
|
||||
}
|
||||
|
||||
boolean hasSpoiler=!TextUtils.isEmpty(statusForContent.spoilerText);
|
||||
if(!TextUtils.isEmpty(statusForContent.content)){
|
||||
SpannableStringBuilder parsedText=HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID);
|
||||
SpannableStringBuilder parsedText=HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID, fragment.getContext());
|
||||
HtmlParser.applyFilterHighlights(fragment.getActivity(), parsedText, status.filtered);
|
||||
TextStatusDisplayItem text=new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, (flags & FLAG_NO_TRANSLATE) != 0);
|
||||
TextStatusDisplayItem text=new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID, fragment.getContext()), fragment, statusForContent, (flags & FLAG_NO_TRANSLATE) != 0);
|
||||
contentItems.add(text);
|
||||
}else if(!hasSpoiler && header!=null){
|
||||
header.needBottomPadding=true;
|
||||
@@ -276,9 +280,11 @@ public abstract class StatusDisplayItem{
|
||||
}
|
||||
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments);
|
||||
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);
|
||||
else if(statusForContent.sensitive && AccountSessionManager.get(accountID).getLocalPreferences().revealCWs && !AccountSessionManager.get(accountID).getLocalPreferences().hideSensitiveMedia)
|
||||
statusForContent.sensitiveRevealed=false;
|
||||
statusForContent.sensitive=true;
|
||||
} else if(statusForContent.sensitive && AccountSessionManager.get(accountID).getLocalPreferences().revealCWs && !AccountSessionManager.get(accountID).getLocalPreferences().hideSensitiveMedia)
|
||||
statusForContent.sensitiveRevealed=true;
|
||||
contentItems.add(mediaGrid);
|
||||
}
|
||||
@@ -295,17 +301,23 @@ public abstract class StatusDisplayItem{
|
||||
}
|
||||
}
|
||||
if(statusForContent.poll!=null){
|
||||
buildPollItems(parentID, fragment, statusForContent.poll, contentItems, statusForContent);
|
||||
buildPollItems(parentID, fragment, statusForContent.poll, status, contentItems, statusForContent);
|
||||
}
|
||||
if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty()){
|
||||
if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty() && statusForContent.quote==null){
|
||||
contentItems.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent, (flags & FLAG_NO_MEDIA_PREVIEW)==0));
|
||||
}
|
||||
if(statusForContent.quote!=null && !(parentObject instanceof Notification)){
|
||||
if(!statusForContent.mediaAttachments.isEmpty() && statusForContent.poll==null) // add spacing if immediately preceded by attachment
|
||||
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));
|
||||
}
|
||||
if(contentItems!=items && statusForContent.spoilerRevealed){
|
||||
items.addAll(contentItems);
|
||||
}
|
||||
AccountLocalPreferences lp=fragment.getLocalPrefs();
|
||||
if((flags & FLAG_NO_EMOJI_REACTIONS)==0 && lp.emojiReactionsEnabled &&
|
||||
(lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment)){
|
||||
if((flags & FLAG_NO_EMOJI_REACTIONS)==0 && !status.preview && lp.emojiReactionsEnabled &&
|
||||
(lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment) &&
|
||||
statusForContent.reactions!=null){
|
||||
boolean isMainStatus=fragment instanceof ThreadFragment t && t.getMainStatus().id.equals(statusForContent.id);
|
||||
boolean showAddButton=lp.showEmojiReactions==ALWAYS || isMainStatus;
|
||||
items.add(new EmojiReactionsStatusDisplayItem(parentID, fragment, statusForContent, accountID, !showAddButton, false));
|
||||
@@ -317,8 +329,9 @@ public abstract class StatusDisplayItem{
|
||||
items.add(footer);
|
||||
}
|
||||
boolean inset=(flags & FLAG_INSET)!=0;
|
||||
boolean isForQuote=(flags & FLAG_IS_FOR_QUOTE)!=0;
|
||||
// add inset dummy so last content item doesn't clip out of inset bounds
|
||||
if((inset || footer==null) && (flags & FLAG_CHECKABLE)==0){
|
||||
if((inset || footer==null) && (flags & FLAG_CHECKABLE)==0 && !isForQuote){
|
||||
items.add(new DummyStatusDisplayItem(parentID, fragment));
|
||||
// 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)
|
||||
@@ -330,12 +343,22 @@ public abstract class StatusDisplayItem{
|
||||
items.add(gap=new GapStatusDisplayItem(parentID, fragment, status));
|
||||
int i=1;
|
||||
for(StatusDisplayItem item:items){
|
||||
item.inset=inset;
|
||||
if(inset)
|
||||
item.inset=true;
|
||||
if(isForQuote){
|
||||
item.status=statusForContent;
|
||||
item.isForQuote=true;
|
||||
}
|
||||
item.index=i++;
|
||||
}
|
||||
if(items!=contentItems && !statusForContent.spoilerRevealed){
|
||||
for(StatusDisplayItem item:contentItems){
|
||||
item.inset=inset;
|
||||
if(inset)
|
||||
item.inset=true;
|
||||
if(isForQuote){
|
||||
item.status=statusForContent;
|
||||
item.isForQuote=true;
|
||||
}
|
||||
item.index=i++;
|
||||
}
|
||||
}
|
||||
@@ -353,7 +376,7 @@ public abstract class StatusDisplayItem{
|
||||
);
|
||||
}
|
||||
|
||||
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List<StatusDisplayItem> items, Status status){
|
||||
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, Status status, List<StatusDisplayItem> items, Status status){
|
||||
int i=0;
|
||||
for(Poll.Option opt:poll.options){
|
||||
items.add(new PollOptionStatusDisplayItem(parentID, poll, i, fragment, status));
|
||||
@@ -390,12 +413,15 @@ public abstract class StatusDisplayItem{
|
||||
}
|
||||
|
||||
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
|
||||
private Context context;
|
||||
|
||||
public Holder(View itemView){
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public Holder(Context context, int layout, ViewGroup parent){
|
||||
super(context, layout, parent);
|
||||
this.context=context;
|
||||
}
|
||||
|
||||
public String getItemID(){
|
||||
@@ -404,6 +430,16 @@ public abstract class StatusDisplayItem{
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
if(item.isForQuote){
|
||||
item.status.filterRevealed=true;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putParcelable("status", Parcels.wrap(item.status.clone()));
|
||||
args.putBoolean("refresh", true);
|
||||
Nav.go((Activity) context, ThreadFragment.class, args);
|
||||
return;
|
||||
}
|
||||
|
||||
item.parentFragment.onItemClick(item.parentID);
|
||||
}
|
||||
|
||||
@@ -435,13 +471,13 @@ public abstract class StatusDisplayItem{
|
||||
|
||||
public boolean isLastDisplayItemForStatus(){
|
||||
return getNextVisibleDisplayItem()
|
||||
.map(n->!n.parentID.equals(item.parentID))
|
||||
.map(next->!next.parentID.equals(item.parentID) || item.inset && !next.inset)
|
||||
.orElse(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(){
|
||||
return item.parentFragment.isItemEnabled(item.parentID);
|
||||
return item.parentFragment.isItemEnabled(item.parentID) || item.isForQuote;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
public boolean textSelectable;
|
||||
public boolean reduceTopPadding;
|
||||
public boolean disableTranslate;
|
||||
public final Status status;
|
||||
|
||||
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status, boolean disableTranslate){
|
||||
super(parentID, parentFragment);
|
||||
@@ -116,7 +115,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
text.setText(item.text);
|
||||
}
|
||||
text.setTextIsSelectable(false);
|
||||
if(item.textSelectable) itemView.post(() -> text.setTextIsSelectable(true));
|
||||
if(item.textSelectable && !item.isForQuote) itemView.post(() -> text.setTextIsSelectable(true));
|
||||
text.setInvalidateOnEveryFrame(false);
|
||||
itemView.setClickable(false);
|
||||
itemView.setPadding(itemView.getPaddingLeft(), item.reduceTopPadding ? V.dp(6) : V.dp(12), itemView.getPaddingRight(), itemView.getPaddingBottom());
|
||||
@@ -127,8 +126,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
|
||||
StatusDisplayItem next=getNextVisibleDisplayItem().orElse(null);
|
||||
if(next!=null && !next.parentID.equals(item.parentID)) next=null;
|
||||
int bottomPadding=next instanceof FooterStatusDisplayItem ? V.dp(6)
|
||||
: item.inset ? V.dp(12)
|
||||
int bottomPadding=item.inset ? V.dp(12)
|
||||
: next instanceof FooterStatusDisplayItem ? V.dp(6)
|
||||
: (next instanceof EmojiReactionsStatusDisplayItem || next==null) ? 0
|
||||
: V.dp(12);
|
||||
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), bottomPadding);
|
||||
@@ -195,7 +194,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
public void updateTranslation(boolean updateText){
|
||||
if(item.status==null)
|
||||
return;
|
||||
boolean translateEnabled=!item.disableTranslate && item.status.isEligibleForTranslation(item.parentFragment.getSession());
|
||||
boolean translateEnabled=!item.disableTranslate && item.status.isEligibleForTranslation(item.parentFragment.getSession()) && !item.isForQuote;
|
||||
if(translationFooter==null && translateEnabled){
|
||||
translationFooter=translationFooterStub.inflate();
|
||||
translationInfo=findViewById(R.id.translation_info_text);
|
||||
@@ -214,8 +213,9 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
String existingTransLang=existingTrans!=null ? existingTrans.detectedSourceLanguage : null;
|
||||
String lang=existingTransLang!=null ? existingTransLang : item.status.getContentStatus().language;
|
||||
Locale locale=lang!=null ? Locale.forLanguageTag(lang) : null;
|
||||
translationButton.setText(locale!=null
|
||||
? item.parentFragment.getString(R.string.translate_post, locale.getDisplayLanguage())
|
||||
String displayLang=locale==null || locale.getDisplayLanguage().isBlank() ? lang : locale.getDisplayLanguage();
|
||||
translationButton.setText(displayLang!=null
|
||||
? item.parentFragment.getString(R.string.translate_post, displayLang)
|
||||
: item.parentFragment.getString(R.string.sk_translate_post));
|
||||
translationButton.setClickable(true);
|
||||
translationButton.animate().alpha(1).setDuration(100).start();
|
||||
|
||||
@@ -18,7 +18,6 @@ import java.util.List;
|
||||
// Mind the gap!
|
||||
public class WarningFilteredStatusDisplayItem extends StatusDisplayItem{
|
||||
public boolean loading;
|
||||
public final Status status;
|
||||
public List<StatusDisplayItem> filteredItems;
|
||||
public LegacyFilter applyingFilter;
|
||||
|
||||
|
||||
@@ -3,21 +3,21 @@ package org.joinmastodon.android.ui.text;
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.CharacterStyle;
|
||||
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
public class DiffRemovedSpan extends CharacterStyle {
|
||||
|
||||
private final String text;
|
||||
private final int color;
|
||||
|
||||
public DiffRemovedSpan(String text){
|
||||
public DiffRemovedSpan(String text, int color){
|
||||
this.text=text;
|
||||
this.color=color;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void updateDrawState(TextPaint tp) {
|
||||
tp.setStrikeThruText(true);
|
||||
tp.setColor(0xFFCA5B63);
|
||||
tp.setColor(color);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
|
||||
@@ -70,6 +70,10 @@ public class HtmlParser{
|
||||
|
||||
private HtmlParser(){}
|
||||
|
||||
public static SpannableStringBuilder parse(String source, List<Emoji> emojis, List<Mention> mentions, List<Hashtag> tags, String accountID){
|
||||
return parse(source, emojis, mentions, tags, accountID, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse HTML and custom emoji into a spanned string for display.
|
||||
* Supported tags: <ul>
|
||||
@@ -82,7 +86,7 @@ public class HtmlParser{
|
||||
* @param emojis Custom emojis that are present in source as <code>:code:</code>
|
||||
* @return a spanned string
|
||||
*/
|
||||
public static SpannableStringBuilder parse(String source, List<Emoji> emojis, List<Mention> mentions, List<Hashtag> tags, String accountID){
|
||||
public static SpannableStringBuilder parse(String source, List<Emoji> emojis, List<Mention> mentions, List<Hashtag> tags, String accountID, Context context){
|
||||
class SpanInfo{
|
||||
public Object span;
|
||||
public int start;
|
||||
@@ -107,6 +111,9 @@ public class HtmlParser{
|
||||
Map<String, Hashtag> tagsByTag=tags.stream().distinct().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity()));
|
||||
|
||||
final SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||
int colorInsert=UiUtils.getThemeColor(context, R.attr.colorM3Success);
|
||||
int colorDelete=UiUtils.getThemeColor(context, R.attr.colorM3Error);
|
||||
|
||||
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
|
||||
private final ArrayList<SpanInfo> openSpans=new ArrayList<>();
|
||||
|
||||
@@ -172,9 +179,9 @@ public class HtmlParser{
|
||||
}
|
||||
case "code", "pre" -> openSpans.add(new SpanInfo(new TypefaceSpan("monospace"), ssb.length(), el));
|
||||
case "blockquote" -> openSpans.add(new SpanInfo(new LeadingMarginSpan.Standard(V.dp(10)), ssb.length(), el));
|
||||
//fake elements for the edit history diff view
|
||||
case "edit_diff_added" -> openSpans.add(new SpanInfo(new ForegroundColorSpan(UiUtils.isDarkTheme() ? 0xFF89bb9c : 0xFF5b8e63), ssb.length(), el));
|
||||
case "edit_diff_removed" -> openSpans.add(new SpanInfo(new DiffRemovedSpan(el.text()), ssb.length(), el));
|
||||
// fake elements for the edit history diff view
|
||||
case "edit-diff-insert" -> openSpans.add(new SpanInfo(new ForegroundColorSpan(colorInsert), ssb.length(), el));
|
||||
case "edit-diff-delete" -> openSpans.add(new SpanInfo(new DiffRemovedSpan(el.text(), colorDelete), ssb.length(), el));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1720,12 +1720,14 @@ public class UiUtils {
|
||||
"pronouns.page/"
|
||||
};
|
||||
|
||||
private static final Pattern trimPronouns=Pattern.compile("[^\\w*]*([\\w*].*[\\w*]|[\\w*])\\W*");
|
||||
private static final String PRONOUN_CHARS="\\w*¿¡!?";
|
||||
private static final Pattern trimPronouns=
|
||||
Pattern.compile("[^"+PRONOUN_CHARS+"]*(["+PRONOUN_CHARS+"].*["+PRONOUN_CHARS+"]|["+PRONOUN_CHARS+"])\\W*");
|
||||
private static String extractPronounsFromField(String localizedPronouns, AccountField field) {
|
||||
if(!field.name.toLowerCase().contains(localizedPronouns) &&
|
||||
!field.name.toLowerCase().contains("pronouns")) return null;
|
||||
String text=HtmlParser.text(field.value);
|
||||
if(field.value.toLowerCase().contains("https://")){
|
||||
if(text.toLowerCase().contains("https://")){
|
||||
for(String pronounUrl : pronounsUrls){
|
||||
int index=text.indexOf(pronounUrl);
|
||||
int beginPronouns=index+pronounUrl.length();
|
||||
@@ -1744,13 +1746,20 @@ public class UiUtils {
|
||||
Matcher matcher=trimPronouns.matcher(text);
|
||||
if(!matcher.find()) return null;
|
||||
String pronouns=matcher.group(1);
|
||||
// crude fix to allow for pronouns like "it(/she)"
|
||||
int missingClosingParens=0;
|
||||
|
||||
// crude fix to allow for pronouns like "it(/she)" or "(de) sie/ihr"
|
||||
int missingParens=0, missingBrackets=0;
|
||||
for(char c : pronouns.toCharArray()){
|
||||
if(c=='(') missingClosingParens++;
|
||||
if(c==')') missingClosingParens--;
|
||||
if(c=='(') missingParens++;
|
||||
else if(c=='[') missingBrackets++;
|
||||
else if(c==')') missingParens--;
|
||||
else if(c==']') missingBrackets--;
|
||||
}
|
||||
pronouns+=")".repeat(Math.max(0, missingClosingParens));
|
||||
if(missingParens > 0) pronouns+=")".repeat(missingParens);
|
||||
else if(missingParens < 0) pronouns="(".repeat(missingParens*-1)+pronouns;
|
||||
if(missingBrackets > 0) pronouns+="]".repeat(missingBrackets);
|
||||
else if(missingBrackets < 0) pronouns="[".repeat(missingBrackets*-1)+pronouns;
|
||||
|
||||
// if ends with an un-closed custom emoji
|
||||
if(pronouns.matches("^.*\\s+:[a-zA-Z_]+$")) pronouns+=':';
|
||||
return pronouns;
|
||||
|
||||
@@ -217,6 +217,7 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
|
||||
Menu menu=contextMenu.getMenu();
|
||||
Account account=item.account;
|
||||
|
||||
menu.findItem(R.id.edit_note).setVisible(false);
|
||||
menu.findItem(R.id.manage_user_lists).setTitle(fragment.getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
||||
MenuItem mute=menu.findItem(R.id.mute);
|
||||
mute.setTitle(fragment.getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
||||
|
||||
Reference in New Issue
Block a user