Merge remote-tracking branch 'megalodon_main/main'

# Conflicts:
#	README.md
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java
#	mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.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/HomeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverAccountsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverNewsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/model/NotificationAction.java
#	mastodon/src/main/java/org/joinmastodon/android/model/Status.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/LinkSpan.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/MediaAttachmentViewController.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
#	mastodon/src/main/res/layout/display_item_gifv.xml
#	mastodon/src/main/res/layout/display_item_photo.xml
#	mastodon/src/main/res/layout/display_item_video.xml
#	mastodon/src/main/res/menu/post.xml
#	mastodon/src/main/res/menu/profile.xml
#	mastodon/src/main/res/values-es-rES/strings_sk.xml
#	mastodon/src/main/res/values-fr-rFR/strings_sk.xml
#	mastodon/src/main/res/values-gl-rES/strings_sk.xml
#	mastodon/src/main/res/values-in-rID/strings_sk.xml
#	mastodon/src/main/res/values-pl-rPL/strings_sk.xml
#	mastodon/src/main/res/values-uk-rUA/strings_sk.xml
#	metadata/it-IT/full_description.txt
#	metadata/zh-Hans/short_description.txt
This commit is contained in:
LucasGGamerM
2023-03-21 20:25:51 -03:00
63 changed files with 904 additions and 273 deletions

View File

@@ -79,10 +79,10 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
}else{
editHistory.setVisibility(View.GONE);
}
String timeStr=TIME_FORMATTER.format(item.status.createdAt.atZone(ZoneId.systemDefault()));
String timeStr=item.status.createdAt != null ? TIME_FORMATTER.format(item.status.createdAt.atZone(ZoneId.systemDefault())) : null;
if (item.status.application!=null && !TextUtils.isEmpty(item.status.application.name)) {
time.setText(item.parentFragment.getString(R.string.timestamp_via_app, timeStr, ""));
time.setText(timeStr != null ? item.parentFragment.getString(R.string.timestamp_via_app, timeStr, "") : "");
applicationName.setText(item.status.application.name);
if (item.status.application.website != null && item.status.application.website.toLowerCase().startsWith("https://")) {
applicationName.setOnClickListener(e -> UiUtils.openURL(context, null, item.status.application.website));

View File

@@ -1,8 +1,12 @@
package org.joinmastodon.android.ui.displayitems;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -270,7 +274,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
progress.dismiss();
}, rel->{
relationship=rel;
Toast.makeText(activity, activity.getString(rel.following ? R.string.followed_user : R.string.unfollowed_user, account.getShortUsername()), Toast.LENGTH_SHORT).show();
Toast.makeText(activity, activity.getString(rel.following ? R.string.followed_user : rel.requested ? R.string.following_user_requested : R.string.unfollowed_user, account.getDisplayUsername()), Toast.LENGTH_SHORT).show();
});
}else if(id==R.id.block_domain){
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account.getDomain(), relationship!=null && relationship.domainBlocking, ()->{});
@@ -497,6 +501,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
}
Account account=item.user;
String username = account.getShortUsername();
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
boolean isPostScheduled=item.scheduledStatus!=null;
menu.findItem(R.id.open_with_account).setVisible(!isPostScheduled && hasMultipleAccounts);
@@ -532,14 +537,15 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
manageUserLists.setVisible(false);
}else{
mute.setVisible(true);
block.setVisible(true);
// hiding when following to keep menu item count equal (trading it for user lists)
block.setVisible(relationship == null || !relationship.following);
report.setVisible(true);
follow.setVisible(relationship==null || relationship.following || (!relationship.blocking && !relationship.blockedBy && !relationship.domainBlocking && !relationship.muting));
mute.setTitle(item.parentFragment.getString(relationship!=null && relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
mute.setTitle(item.parentFragment.getString(relationship!=null && relationship.muting ? R.string.unmute_user : R.string.mute_user, username));
mute.setIcon(relationship!=null && relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), mute);
block.setTitle(item.parentFragment.getString(relationship!=null && relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
report.setTitle(item.parentFragment.getString(R.string.report_user, account.getShortUsername()));
block.setTitle(item.parentFragment.getString(relationship!=null && relationship.blocking ? R.string.unblock_user : R.string.block_user, username));
report.setTitle(item.parentFragment.getString(R.string.report_user, username));
// disabled in megalodon. domain blocks from a post clutters the context menu and looks out of place
// if(!account.isLocal()){
// blockDomain.setVisible(true);
@@ -548,12 +554,53 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
blockDomain.setVisible(false);
// }
boolean following = relationship!=null && relationship.following;
follow.setTitle(item.parentFragment.getString(following ? R.string.unfollow_user : R.string.follow_user, account.getShortUsername()));
follow.setTitle(item.parentFragment.getString(following ? R.string.unfollow_user : R.string.follow_user, username));
follow.setIcon(following ? R.drawable.ic_fluent_person_delete_24_regular : R.drawable.ic_fluent_person_add_24_regular);
manageUserLists.setVisible(relationship != null && relationship.following);
manageUserLists.setTitle(item.parentFragment.getString(R.string.sk_lists_with_user, account.getShortUsername()));
manageUserLists.setTitle(item.parentFragment.getString(R.string.sk_lists_with_user, username));
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), follow);
}
workaroundChangingMenuItemWidths(menu, username);
}
// ugliest piece of code you'll see in a while: i measure the menu items' text widths to
// determine the biggest one, because it's probably not being displayed at first
// (before the relationship loaded). i take the largest one's size and add a space to the
// last item ("open in browser") until it takes up as much space as the largest item.
// goal: no more ugly ellipsis after the relationship loads in when opening the context menu
// of a post
private void workaroundChangingMenuItemWidths(Menu menu, String username) {
String openInBrowserText = item.parentFragment.getString(R.string.open_in_browser);
if (relationship == null) {
float largestWidth = 0;
Paint paint = new Paint();
paint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL));
String[] otherStrings = new String[] {
item.parentFragment.getString(R.string.unfollow_user, username),
item.parentFragment.getString(R.string.unblock_user, username),
item.parentFragment.getString(R.string.unmute_user, username),
item.parentFragment.getString(R.string.sk_lists_with_user, username),
};
for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
if (item.getItemId() == R.id.open_in_browser || !item.isVisible()) continue;
float width = paint.measureText(menu.getItem(i).getTitle().toString());
if (width > largestWidth) largestWidth = width;
}
for (String str : otherStrings) {
float width = paint.measureText(str);
if (width > largestWidth) largestWidth = width;
}
float textWidth = paint.measureText(openInBrowserText);
float missingWidth = Math.max(0, largestWidth - textWidth);
float singleSpaceWidth = paint.measureText("");
int howManySpaces = (int) Math.ceil(missingWidth / singleSpaceWidth);
String enlargedText = openInBrowserText + "".repeat(howManySpaces);
menu.findItem(R.id.open_in_browser).setTitle(enlargedText);
} else {
menu.findItem(R.id.open_in_browser).setTitle(openInBrowserText);
}
}
}
}

View File

@@ -1,18 +1,23 @@
package org.joinmastodon.android.ui.displayitems;
import static org.joinmastodon.android.ui.utils.MediaAttachmentViewController.altWrapPadding;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment;
@@ -85,9 +90,10 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
private final FrameLayout altTextWrapper;
private final TextView altTextButton;
private final ImageView noAltTextButton;
private final View altTextScroller;
private final ImageButton altTextClose;
private final TextView altText;
private final TextView altText, noAltText;
private int altTextIndex=-1;
private Animator altTextAnimator;
@@ -101,9 +107,11 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, wrapper);
altTextWrapper=findViewById(R.id.alt_text_wrapper);
altTextButton=findViewById(R.id.alt_button);
noAltTextButton=findViewById(R.id.no_alt_button);
altTextScroller=findViewById(R.id.alt_text_scroller);
altTextClose=findViewById(R.id.alt_text_close);
altText=findViewById(R.id.alt_text);
noAltText=findViewById(R.id.no_alt_text);
altTextClose.setOnClickListener(this::onAltTextCloseClick);
}
@@ -133,15 +141,17 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
layout.addView(c.view);
c.view.setOnClickListener(clickListener);
c.view.setTag(i);
if(c.altButton!=null){
c.altButton.setOnClickListener(altTextClickListener);
c.altButton.setTag(i);
c.altButton.setAlpha(1f);
if(c.btnsWrap!=null){
c.btnsWrap.setOnClickListener(altTextClickListener);
c.btnsWrap.setTag(i);
c.btnsWrap.setAlpha(1f);
}
controllers.add(c);
c.bind(att, item.status);
i++;
}
altTextButton.setVisibility(View.VISIBLE);
noAltTextButton.setVisibility(View.VISIBLE);
altTextWrapper.setVisibility(View.GONE);
altTextIndex=-1;
}
@@ -172,8 +182,14 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
int index=(Integer)v.getTag();
altTextIndex=index;
Attachment att=item.attachments.get(index);
boolean hasAltText = !TextUtils.isEmpty(att.description);
altTextButton.setVisibility(hasAltText && GlobalUserPreferences.showAltIndicator ? View.VISIBLE : View.GONE);
noAltTextButton.setVisibility(!hasAltText && GlobalUserPreferences.showNoAltIndicator ? View.VISIBLE : View.GONE);
altText.setVisibility(hasAltText && GlobalUserPreferences.showAltIndicator ? View.VISIBLE : View.GONE);
noAltText.setVisibility(!hasAltText && GlobalUserPreferences.showNoAltIndicator ? View.VISIBLE : View.GONE);
altText.setText(att.description);
altTextWrapper.setVisibility(View.VISIBLE);
altTextWrapper.setBackgroundResource(hasAltText ? R.drawable.bg_image_alt_overlay : R.drawable.bg_image_no_alt_overlay);
altTextWrapper.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){
@@ -188,18 +204,19 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
ArrayList<Animator> anims=new ArrayList<>();
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1, 0));
anims.add(ObjectAnimator.ofFloat(noAltTextButton, View.ALPHA, 1, 0));
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0, 1));
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0, 1));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL, altTextWrapper.getLeft()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT, altTextWrapper.getTop()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+v.getWidth(), altTextWrapper.getRight()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+v.getHeight(), altTextWrapper.getBottom()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL+altWrapPadding[0], altTextWrapper.getLeft()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT+altWrapPadding[1], altTextWrapper.getTop()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+v.getWidth()-altWrapPadding[2], altTextWrapper.getRight()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+v.getHeight()-altWrapPadding[3], altTextWrapper.getBottom()));
for(Animator a:anims)
a.setDuration(300);
for(MediaAttachmentViewController c:controllers){
if(c.altButton!=null && c.altButton!=v){
anims.add(ObjectAnimator.ofFloat(c.altButton, View.ALPHA, 1, 0).setDuration(150));
if(c.btnsWrap!=null && c.btnsWrap!=v){
anims.add(ObjectAnimator.ofFloat(c.btnsWrap, View.ALPHA, 1, 0).setDuration(150));
}
}
@@ -211,8 +228,8 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
public void onAnimationEnd(Animator animation){
altTextAnimator=null;
for(MediaAttachmentViewController c:controllers){
if(c.altButton!=null){
c.altButton.setVisibility(View.INVISIBLE);
if(c.btnsWrap!=null){
c.btnsWrap.setVisibility(View.INVISIBLE);
}
}
}
@@ -229,10 +246,11 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
if(altTextAnimator!=null)
altTextAnimator.cancel();
View btn=controllers.get(altTextIndex).altButton;
View btn=controllers.get(altTextIndex).btnsWrap;
for(MediaAttachmentViewController c:controllers){
if(c.altButton!=null && c.altButton!=btn)
c.altButton.setVisibility(View.VISIBLE);
if(c.btnsWrap!=null && c.btnsWrap!=btn) {
c.btnsWrap.setVisibility(View.VISIBLE);
}
}
int[] loc={0, 0};
@@ -244,19 +262,20 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
ArrayList<Animator> anims=new ArrayList<>();
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1));
anims.add(ObjectAnimator.ofFloat(noAltTextButton, View.ALPHA, 1));
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0));
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+btn.getWidth()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+btn.getHeight()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL+altWrapPadding[0]));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT+altWrapPadding[1]));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+btn.getWidth()-altWrapPadding[2]));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+btn.getHeight()-altWrapPadding[3]));
for(Animator a:anims)
a.setDuration(300);
for(MediaAttachmentViewController c:controllers){
if(c.altButton!=null && c.altButton!=btn){
anims.add(ObjectAnimator.ofFloat(c.altButton, View.ALPHA, 1).setDuration(150));
}
// if(c.btnsWrap!=null && c.btnsWrap!=btn){
anims.add(ObjectAnimator.ofFloat(c.btnsWrap, View.ALPHA, 1).setDuration(150));
// }
}
AnimatorSet set=new AnimatorSet();

View File

@@ -10,8 +10,10 @@ import android.text.SpannableStringBuilder;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Emoji;
@@ -27,6 +29,7 @@ import androidx.annotation.Nullable;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.V;
public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
private CharSequence text;
@@ -35,10 +38,17 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
private StatusPrivacy visibility;
@DrawableRes
private int iconEnd;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(), fullTextEmojiHelper;
private View.OnClickListener handleClick;
boolean belowHeader, needBottomPadding;
ReblogOrReplyLineStatusDisplayItem extra;
CharSequence fullText;
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick){
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick) {
this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, text);
}
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, CharSequence fullText) {
super(parentID, parentFragment);
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
HtmlParser.parseCustomEmoji(ssb, emojis);
@@ -49,6 +59,15 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
updateVisibility(visibility);
if (fullText != null) {
fullTextEmojiHelper = new CustomEmojiHelper();
SpannableStringBuilder fullTextSsb = new SpannableStringBuilder(fullText);
HtmlParser.parseCustomEmoji(fullTextSsb, emojis);
this.fullText=fullTextSsb;
fullTextEmojiHelper.setText(fullTextSsb);
}
}
public void updateVisibility(StatusPrivacy visibility) {
@@ -77,30 +96,77 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
}
public static class Holder extends StatusDisplayItem.Holder<ReblogOrReplyLineStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView text;
private final TextView text, extraText;
private final View separator;
private final ViewGroup parent;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_reblog_or_reply_line, parent);
this.parent = parent;
text=findViewById(R.id.text);
extraText=findViewById(R.id.extra_text);
separator=findViewById(R.id.separator);
if (GlobalUserPreferences.replyLineAboveHeader && GlobalUserPreferences.compactReblogReplyLine) {
parent.addOnLayoutChangeListener((v, l, t, right, b, ol, ot, oldRight, ob) -> {
if (right != oldRight) layoutLine();
});
}
}
@Override
public void onBind(ReblogOrReplyLineStatusDisplayItem item){
private void bindLine(ReblogOrReplyLineStatusDisplayItem item, TextView text) {
if (item.fullText != null) text.setContentDescription(item.fullText);
text.setText(item.text);
text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, item.iconEnd, 0);
if(item.handleClick!=null) text.setOnClickListener(item.handleClick);
text.setEnabled(!item.inset);
text.setClickable(!item.inset);
text.setOnClickListener(item.handleClick);
text.setEnabled(!item.inset && item.handleClick != null);
text.setClickable(!item.inset && item.handleClick != null);
Context ctx = itemView.getContext();
int visibilityText = item.visibility != null ? switch (item.visibility) {
case PUBLIC -> R.string.visibility_public;
case UNLISTED -> R.string.sk_visibility_unlisted;
case PRIVATE -> R.string.visibility_followers_only;
case LOCAL -> R.string.sk_local_only;
default -> 0;
} : 0;
if (visibilityText != 0) text.setContentDescription(item.text + " (" + ctx.getString(visibilityText) + ")");
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
UiUtils.fixCompoundDrawableTintOnAndroid6(text);
text.setTextAppearance(item.belowHeader ? R.style.m3_label_large : R.style.m3_title_small);
text.setCompoundDrawableTintList(text.getTextColors());
}
@Override
public void onBind(ReblogOrReplyLineStatusDisplayItem item){
bindLine(item, text);
if (item.extra != null) bindLine(item.extra, extraText);
extraText.setVisibility(item.extra == null ? View.GONE : View.VISIBLE);
separator.setVisibility(item.extra == null ? View.GONE : View.VISIBLE);
ViewGroup.MarginLayoutParams params = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.bottomMargin = item.belowHeader ? V.dp(-6) : V.dp(-12);
params.topMargin = item.belowHeader ? V.dp(-6) : 0;
itemView.setLayoutParams(params);
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
layoutLine();
}
private void layoutLine() {
// layout line only if above header, compact and has extra
if (!GlobalUserPreferences.replyLineAboveHeader
|| !GlobalUserPreferences.compactReblogReplyLine
|| item.extra == null) return;
itemView.measure(
View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.UNSPECIFIED);
boolean isVertical = ((LinearLayout) itemView).getOrientation() == LinearLayout.VERTICAL;
extraText.setPaddingRelative(extraText.getPaddingStart(), item.extra != null && isVertical ? 0 : V.dp(16), extraText.getPaddingEnd(), extraText.getPaddingBottom());
separator.setVisibility(item.extra != null && !isVertical ? View.VISIBLE : View.GONE);
((LinearLayout) itemView).removeView(extraText);
if (isVertical) ((LinearLayout) itemView).addView(extraText);
else ((LinearLayout) itemView).addView(extraText, 0);
text.setText(isVertical ? item.fullText : item.text);
if (item.extra != null) {
extraText.setText(isVertical ? item.extra.fullText : item.extra.text);
}
}
@Override

View File

@@ -7,13 +7,13 @@ import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
import org.joinmastodon.android.fragments.HomeTabFragment;
import org.joinmastodon.android.fragments.HomeTimelineFragment;
import org.joinmastodon.android.fragments.ListTimelineFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.ThreadFragment;
@@ -21,7 +21,6 @@ import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.ScheduledStatus;
@@ -32,10 +31,8 @@ import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -98,10 +95,6 @@ public abstract class StatusDisplayItem{
}
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext){
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, disableTranslate, filterContext, null);
}
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext, StatusDisplayItem titleItem){
String parentID=parentObject.getID();
ArrayList<StatusDisplayItem> items=new ArrayList<>();
@@ -118,24 +111,37 @@ public abstract class StatusDisplayItem{
statusForContent.filterRevealed = filterPredicate.testWithWarning(status);
}
ReblogOrReplyLineStatusDisplayItem replyLine = null;
boolean threadReply = statusForContent.inReplyToAccountId != null &&
statusForContent.inReplyToAccountId.equals(statusForContent.account.id);
if(statusForContent.inReplyToAccountId!=null && !(threadReply && fragment instanceof ThreadFragment)){
Account account = knownAccounts.get(statusForContent.inReplyToAccountId);
String text = threadReply ? fragment.getString(R.string.sk_show_thread)
: account == null ? fragment.getString(R.string.sk_in_reply)
: GlobalUserPreferences.compactReblogReplyLine && status.reblog != null ? account.displayName
: fragment.getString(R.string.in_reply_to, account.displayName);
String fullText = threadReply ? fragment.getString(R.string.sk_show_thread)
: account == null ? fragment.getString(R.string.sk_in_reply)
: fragment.getString(R.string.in_reply_to, account.displayName);
replyLine = new ReblogOrReplyLineStatusDisplayItem(
parentID, fragment, text, account == null ? List.of() : account.emojis,
R.drawable.ic_fluent_arrow_reply_20_filled, null, null, fullText
);
}
if(status.reblog!=null){
boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account);
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, isOwnPost ? status.visibility : null, i->{
String fullText = fragment.getString(R.string.user_boosted, status.account.displayName);
String text = GlobalUserPreferences.compactReblogReplyLine && replyLine != null ? status.account.displayName : fullText;
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, isOwnPost ? status.visibility : null, i->{
args.putParcelable("profileAccount", Parcels.wrap(status.account));
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
}));
}else if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)){
Account account=Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId));
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled, null, i->{
args.putParcelable("profileAccount", Parcels.wrap(account));
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
}));
} else if (
!(status.tags.isEmpty() ||
fragment instanceof HashtagTimelineFragment ||
fragment instanceof ListTimelineFragment
) && fragment.getParentFragment() instanceof HomeTabFragment home
) {
}, fullText));
} else if (!(status.tags.isEmpty() ||
fragment instanceof HashtagTimelineFragment ||
fragment instanceof ListTimelineFragment
) && fragment.getParentFragment() instanceof HomeTabFragment home) {
home.getHashtags().stream()
.filter(followed -> status.tags.stream()
.anyMatch(hashtag -> followed.name.equalsIgnoreCase(hashtag.name)))
@@ -151,11 +157,32 @@ public abstract class StatusDisplayItem{
)));
}
if (replyLine != null && GlobalUserPreferences.replyLineAboveHeader) {
Optional<ReblogOrReplyLineStatusDisplayItem> primaryLine = items.stream()
.filter(i -> i instanceof ReblogOrReplyLineStatusDisplayItem)
.map(ReblogOrReplyLineStatusDisplayItem.class::cast)
.findFirst();
if (primaryLine.isPresent() && GlobalUserPreferences.compactReblogReplyLine) {
primaryLine.get().extra = replyLine;
} else {
items.add(replyLine);
}
}
HeaderStatusDisplayItem header;
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus));
if(!TextUtils.isEmpty(statusForContent.content)){
if (replyLine != null && !GlobalUserPreferences.replyLineAboveHeader) {
replyLine.belowHeader = true;
items.add(replyLine);
}
if(!TextUtils.isEmpty(statusForContent.content))
items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, disableTranslate));
} else
else if (!GlobalUserPreferences.replyLineAboveHeader && replyLine != null)
replyLine.needBottomPadding=true;
else
header.needBottomPadding=true;
List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList());
if(!imageAttachments.isEmpty()){
@@ -186,8 +213,6 @@ public abstract class StatusDisplayItem{
item.index=i++;
}
if (titleItem != null) items.add(0, titleItem);
if (!statusForContent.filterRevealed) {
return new ArrayList<>(List.of(
new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items)

View File

@@ -31,6 +31,7 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.LinkedTextView;
import org.joinmastodon.android.utils.StatusTextEncoder;
import java.util.Locale;
import java.util.regex.Pattern;
import me.grishka.appkit.api.Callback;
@@ -47,9 +48,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
private CharSequence parsedSpoilerText;
public boolean textSelectable;
public final Status status;
public boolean disableTranslate;
public boolean translated = false;
public TranslatedStatus translation = null;
public boolean disableTranslate, translationShown;
private AccountSession session;
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");
@@ -58,6 +57,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
this.text=text;
this.status=status;
this.disableTranslate=disableTranslate;
this.translationShown=status.translationShown;
emojiHelper.setText(text);
if(!TextUtils.isEmpty(status.spoilerText)){
parsedSpoilerText=HtmlParser.parseCustomEmoji(status.spoilerText, status.emojis);
@@ -67,6 +67,11 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
}
public void setTranslationShown(boolean translationShown) {
this.translationShown = translationShown;
status.translationShown = translationShown;
}
@Override
public Type getType(){
return Type.TEXT;
@@ -97,9 +102,11 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
private final float textMaxHeight, textCollapsedHeight;
private final LinearLayout.LayoutParams collapseParams, wrapParams;
private final ViewGroup parent;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_text, parent);
this.parent=parent;
text=findViewById(R.id.text);
spoilerTitle=findViewById(R.id.spoiler_title);
spoilerTitleInline=findViewById(R.id.spoiler_title_inline);
@@ -127,9 +134,9 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(TextStatusDisplayItem item){
text.setText(item.translated
? HtmlParser.parse(item.translation.content, item.status.emojis, item.status.mentions, item.status.tags, item.parentFragment.getAccountID())
: item.text);
text.setText(item.translationShown
? HtmlParser.parse(item.status.translation.content, item.status.emojis, item.status.mentions, item.status.tags, item.parentFragment.getAccountID())
: item.text);
text.setTextIsSelectable(item.textSelectable);
if (item.textSelectable) {
textScrollView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
@@ -165,26 +172,33 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
boolean translateEnabled = !item.disableTranslate && instanceInfo != null &&
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null &&
instanceInfo.v2.configuration.translation.enabled;
String bottomText = null;
try {
bottomText = BOTTOM_TEXT_PATTERN.matcher(item.status.getStrippedText()).find()
? new StatusTextEncoder(Bottom::decode).decode(item.status.getStrippedText(), BOTTOM_TEXT_PATTERN)
: null;
} catch (TranslationError ignored) {}
boolean isBottomText = BOTTOM_TEXT_PATTERN.matcher(item.status.getStrippedText()).find();
boolean translateVisible = (isBottomText || (
boolean translateVisible = (bottomText != null || (
translateEnabled &&
!item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) &&
item.status.language != null &&
(item.session.preferences == null || !item.status.language.equalsIgnoreCase(item.session.preferences.postingDefaultLanguage))));
// && (!GlobalUserPreferences.translateButtonOpenedOnly || item.textSelectable);
// todo: compare to mastodon locale instead (how do i query that?!)
!item.status.language.equalsIgnoreCase(Locale.getDefault().getLanguage())))
&& (!GlobalUserPreferences.translateButtonOpenedOnly || item.textSelectable);
translateWrap.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
translateButton.setText(item.translated ? R.string.sk_translate_show_original : R.string.sk_translate_post);
translateInfo.setText(item.translated ? itemView.getResources().getString(R.string.sk_translated_using, isBottomText ? "bottom-java" : item.translation.provider) : "");
translateButton.setText(item.translationShown ? R.string.sk_translate_show_original : R.string.sk_translate_post);
translateInfo.setText(item.translationShown ? itemView.getResources().getString(R.string.sk_translated_using, bottomText != null ? "bottom-java" : item.status.translation.provider) : "");
String finalBottomText = bottomText;
translateButton.setOnClickListener(v->{
if (item.translation == null) {
if (isBottomText) {
if (item.status.translation == null) {
if (finalBottomText != null) {
try {
item.translation = new TranslatedStatus();
item.translation.content = new StatusTextEncoder(Bottom::decode).decode(item.status.getStrippedText(), BOTTOM_TEXT_PATTERN);
item.translated = true;
item.status.translation = new TranslatedStatus();
item.status.translation.content = finalBottomText;
item.setTranslationShown(true);
} catch (TranslationError err) {
item.translation = null;
item.status.translation = null;
Toast.makeText(itemView.getContext(), err.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
rebind();
@@ -193,13 +207,24 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
translateProgress.setVisibility(View.VISIBLE);
translateButton.setClickable(false);
translateButton.animate().alpha(0.5f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
@Override
public void onSuccess(TranslatedStatus translatedStatus) {
item.status.translation = translatedStatus;
item.setTranslationShown(true);
if (item.parentFragment.getActivity() == null) return;
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
rebind();
}
if(item.status.reloadWhenClicked){
UiUtils.lookupStatus(item.parentFragment.getContext(), item.status, item.parentFragment.getAccountID(), null, status1 -> {
new TranslateStatus(status1.id).setCallback(new Callback<>() {
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
@Override
public void onSuccess(TranslatedStatus translatedStatus) {
item.translation = translatedStatus;
item.translated = true;
item.status.translation = translatedStatus;
item.setTranslationShown(true);
if (item.parentFragment.getActivity() == null) return;
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
@@ -220,8 +245,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
@Override
public void onSuccess(TranslatedStatus translatedStatus) {
item.translation = translatedStatus;
item.translated = true;
item.status.translation = translatedStatus;
item.setTranslationShown(true);
if (item.parentFragment.getActivity() == null) return;
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
@@ -239,7 +264,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
}).exec(item.parentFragment.getAccountID());
}
} else {
item.translated = !item.translated;
item.setTranslationShown(!item.translationShown);
rebind();
}
});
@@ -252,13 +277,16 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
readMore.setVisibility(View.GONE);
}
if (GlobalUserPreferences.collapseLongPosts) text.post(() -> {
text.measure(
View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
if (GlobalUserPreferences.collapseLongPosts && !item.status.textExpandable) {
boolean tooBig = text.getMeasuredHeight() > textMaxHeight;
boolean inTimeline = !item.textSelectable;
boolean hasSpoiler = !TextUtils.isEmpty(item.status.spoilerText);
boolean expandable = inTimeline && tooBig && !hasSpoiler;
item.parentFragment.onEnableExpandable(this, expandable);
});
boolean expandable = tooBig && !hasSpoiler;
item.parentFragment.onEnableExpandable(Holder.this, expandable);
}
readMore.setVisibility(item.status.textExpandable && !item.status.textExpanded ? View.VISIBLE : View.GONE);
textScrollView.setLayoutParams(item.status.textExpandable && !item.status.textExpanded ? collapseParams : wrapParams);

View File

@@ -63,6 +63,10 @@ public class LinkSpan extends CharacterStyle {
return link;
}
public String getText() {
return text;
}
public Type getType(){
return type;
}

View File

@@ -8,7 +8,6 @@ import android.view.View;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;

View File

@@ -18,7 +18,8 @@ public class MediaAttachmentViewController{
public final View view;
public final MediaGridStatusDisplayItem.GridItemType type;
public final ImageView photo;
public final View altButton;
public final View altButton, noAltButton, btnsWrap;
public static int[] altWrapPadding = null;
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
private final Context context;
private boolean didClear;
@@ -32,8 +33,13 @@ public class MediaAttachmentViewController{
}, null);
photo=view.findViewById(R.id.photo);
altButton=view.findViewById(R.id.alt_button);
noAltButton=view.findViewById(R.id.no_alt_button);
btnsWrap=view.findViewById(R.id.alt_badges);
this.type=type;
this.context=context;
if (altWrapPadding == null) {
altWrapPadding = new int[] { btnsWrap.getPaddingLeft(), btnsWrap.getPaddingTop(), btnsWrap.getPaddingRight(), btnsWrap.getPaddingBottom() };
}
}
public void bind(Attachment attachment, Status status){
@@ -43,13 +49,12 @@ public class MediaAttachmentViewController{
crossfadeDrawable.setCrossfadeAlpha(status.spoilerRevealed ? 0f : 1f);
photo.setImageDrawable(null);
photo.setImageDrawable(crossfadeDrawable);
photo.setContentDescription(TextUtils.isEmpty(attachment.description) ? context.getString(R.string.media_no_description) : attachment.description);
if(altButton!=null){
if(GlobalUserPreferences.showAltIndicator){
altButton.setVisibility(TextUtils.isEmpty(attachment.description) ? View.GONE : View.VISIBLE);
}else{
altButton.setVisibility(View.GONE);
}
boolean hasAltText = !TextUtils.isEmpty(attachment.description);
photo.setContentDescription(!hasAltText ? context.getString(R.string.media_no_description) : attachment.description);
if(btnsWrap!=null){
btnsWrap.setVisibility(View.VISIBLE);
altButton.setVisibility(hasAltText && GlobalUserPreferences.showAltIndicator ? View.VISIBLE : View.GONE);
noAltButton.setVisibility(!hasAltText && GlobalUserPreferences.showNoAltIndicator ? View.VISIBLE : View.GONE);
}
didClear=false;
}

View File

@@ -344,15 +344,24 @@ public class UiUtils{
}
}
public static int getThemeColor(Context context, @AttrRes int attr){
TypedArray ta=context.obtainStyledAttributes(new int[]{attr});
int color=ta.getColor(0, 0xff00ff00);
public static int getThemeColor(Context context, @AttrRes int attr) {
if (context == null) return 0xff00ff00;
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
int color = ta.getColor(0, 0xff00ff00);
ta.recycle();
return color;
}
public static void openProfileByID(Context context, String selfID, String id){
Bundle args=new Bundle();
public static int getThemeColorRes(Context context, @AttrRes int attr) {
if (context == null) return 0xff00ff00;
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
int color = ta.getResourceId(0, R.color.black);
ta.recycle();
return color;
}
public static void openProfileByID(Context context, String selfID, String id) {
Bundle args = new Bundle();
args.putString("account", selfID);
args.putString("profileAccountID", id);
Nav.go((Activity)context, ProfileFragment.class, args);
@@ -754,7 +763,7 @@ public class UiUtils{
public void onSuccess(Relationship result){
resultCallback.accept(result);
progressCallback.accept(false);
if(!result.following){
if(!result.following && !result.requested){
E.post(new RemoveAccountPostsEvent(accountID, account.id, true));
}
}

View File

@@ -2,14 +2,11 @@ package org.joinmastodon.android.ui.views;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import java.util.Arrays;
import me.grishka.appkit.utils.V;
public class MediaGridLayout extends ViewGroup{