diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/StatusInteractionController.java b/mastodon/src/main/java/org/joinmastodon/android/api/StatusInteractionController.java index a42ebdd07..33df962bf 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/StatusInteractionController.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/StatusInteractionController.java @@ -9,6 +9,7 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited; import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged; import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.model.StatusPrivacy; import java.util.HashMap; import java.util.function.Consumer; @@ -59,7 +60,7 @@ public class StatusInteractionController{ E.post(new StatusCountersUpdatedEvent(status)); } - public void setReblogged(Status status, boolean reblogged, Consumer cb){ + public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer cb){ if(!Looper.getMainLooper().isCurrentThread()) throw new IllegalStateException("Can only be called from main thread"); @@ -67,7 +68,7 @@ public class StatusInteractionController{ if(current!=null){ current.cancel(); } - SetStatusReblogged req=(SetStatusReblogged) new SetStatusReblogged(status.id, reblogged) + SetStatusReblogged req=(SetStatusReblogged) new SetStatusReblogged(status.id, reblogged, visibility) .setCallback(new Callback<>(){ @Override public void onSuccess(Status reblog){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/SetStatusReblogged.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/SetStatusReblogged.java index a28aed7ba..64b236778 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/SetStatusReblogged.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/SetStatusReblogged.java @@ -2,10 +2,17 @@ package org.joinmastodon.android.api.requests.statuses; import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.model.StatusPrivacy; public class SetStatusReblogged extends MastodonAPIRequest{ - public SetStatusReblogged(String id, boolean reblogged){ + public SetStatusReblogged(String id, boolean reblogged, StatusPrivacy visibility){ super(HttpMethod.POST, "/statuses/"+id+"/"+(reblogged ? "reblog" : "unreblog"), Status.class); - setRequestBody(new Object()); + Request req = new Request(); + req.visibility = visibility; + setRequestBody(req); + } + + public static class Request { + public StatusPrivacy visibility; } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java index 73796fc45..f2d847219 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java @@ -139,7 +139,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{ action=getString(R.string.edit_multiple_changed); } } - items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" · "+date, Collections.emptyList(), 0, null)); + items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" · "+date, Collections.emptyList(), 0, null, null)); } return items; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java index ce9f0e324..0bf0a2018 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java @@ -1,9 +1,13 @@ package org.joinmastodon.android.ui.displayitems; import android.app.Activity; +import android.app.Dialog; +import android.content.Context; import android.content.Intent; +import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -18,14 +22,18 @@ import android.widget.TextView; 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.ComposeFragment; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.StatusPrivacy; +import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.utils.UiUtils; import org.parceler.Parcels; +import java.util.function.Consumer; + import me.grishka.appkit.Nav; import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.V; @@ -166,21 +174,68 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{ private void onBoostClick(View v){ boost.setSelected(!item.status.reblogged); - AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged, r->{ - v.startAnimation(opacityIn); - bindButton(boost, r.reblogsCount); - }); + AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged, null, r->boostConsumer(v, r)); + } + + private void boostConsumer(View v, Status r) { + v.startAnimation(opacityIn); + bindButton(boost, r.reblogsCount); } private boolean onBoostLongClick(View v){ - v.setAlpha(1); - v.setScaleX(1); - v.setScaleY(1); - Bundle args=new Bundle(); - args.putString("account", item.accountID); - args.putString("prefilledText", "\n\n" + item.status.url); - args.putInt("selectionStart", 0); - Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args); + Context ctx = itemView.getContext(); + View menu = LayoutInflater.from(ctx).inflate(R.layout.item_boost_menu, null); + Dialog dialog = new M3AlertDialogBuilder(ctx).setView(menu).create(); + AccountSession session = AccountSessionManager.getInstance().getAccount(item.accountID); + + Consumer doReblog = (visibility) -> { + v.startAnimation(opacityOut); + session.getStatusInteractionController() + .setReblogged(item.status, !item.status.reblogged, visibility, r->boostConsumer(v, r)); + dialog.dismiss(); + }; + + View separator = menu.findViewById(R.id.separator); + TextView reblogHeader = menu.findViewById(R.id.reblog_header); + TextView undoReblog = menu.findViewById(R.id.delete_reblog); + TextView itemPublic = menu.findViewById(R.id.vis_public); + TextView itemUnlisted = menu.findViewById(R.id.vis_unlisted); + TextView itemFollowers = menu.findViewById(R.id.vis_followers); + + undoReblog.setVisibility(item.status.reblogged ? View.VISIBLE : View.GONE); + separator.setVisibility(item.status.reblogged ? View.GONE : View.VISIBLE); + reblogHeader.setVisibility(item.status.reblogged ? View.GONE : View.VISIBLE); + + itemPublic.setVisibility(item.status.reblogged || item.status.visibility.isLessVisibleThan(StatusPrivacy.PUBLIC) ? View.GONE : View.VISIBLE); + itemUnlisted.setVisibility(item.status.reblogged || item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) ? View.GONE : View.VISIBLE); + itemFollowers.setVisibility(item.status.reblogged || item.status.visibility.isLessVisibleThan(StatusPrivacy.PRIVATE) ? View.GONE : View.VISIBLE); + + Drawable checkMark = ctx.getDrawable(R.drawable.ic_fluent_checkmark_circle_20_regular); + Drawable publicDrawable = ctx.getDrawable(R.drawable.ic_fluent_earth_24_regular); + Drawable unlistedDrawable = ctx.getDrawable(R.drawable.ic_fluent_people_community_24_regular); + Drawable followersDrawable = ctx.getDrawable(R.drawable.ic_fluent_people_checkmark_24_regular); + + StatusPrivacy defaultVisibility = session.preferences.postingDefaultVisibility; + itemPublic.setCompoundDrawablesWithIntrinsicBounds(publicDrawable, null, StatusPrivacy.PUBLIC.equals(defaultVisibility) ? checkMark : null, null); + itemUnlisted.setCompoundDrawablesWithIntrinsicBounds(unlistedDrawable, null, StatusPrivacy.UNLISTED.equals(defaultVisibility) ? checkMark : null, null); + itemFollowers.setCompoundDrawablesWithIntrinsicBounds(followersDrawable, null, StatusPrivacy.PRIVATE.equals(defaultVisibility) ? checkMark : null, null); + + undoReblog.setOnClickListener(c->doReblog.accept(null)); + itemPublic.setOnClickListener(c->doReblog.accept(StatusPrivacy.PUBLIC)); + itemUnlisted.setOnClickListener(c->doReblog.accept(StatusPrivacy.UNLISTED)); + itemFollowers.setOnClickListener(c->doReblog.accept(StatusPrivacy.PRIVATE)); + + menu.findViewById(R.id.quote).setOnClickListener(c->{ + dialog.dismiss(); + v.startAnimation(opacityIn); + Bundle args=new Bundle(); + args.putString("account", item.accountID); + args.putString("prefilledText", "\n\n" + item.status.url); + args.putInt("selectionStart", 0); + Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args); + }); + + dialog.show(); return true; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java index ab36301c8..14210fd28 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java @@ -3,6 +3,7 @@ package org.joinmastodon.android.ui.displayitems; import static org.joinmastodon.android.MastodonApp.context; import android.app.Activity; +import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.SpannableStringBuilder; @@ -14,6 +15,7 @@ import android.widget.TextView; import org.joinmastodon.android.R; import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.model.Emoji; +import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.utils.CustomEmojiHelper; import org.joinmastodon.android.ui.utils.UiUtils; @@ -30,10 +32,13 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ private CharSequence text; @DrawableRes private int icon; + private StatusPrivacy visibility; + @DrawableRes + private int iconEnd; private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); private View.OnClickListener handleClick; - public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List emojis, @DrawableRes int icon, @Nullable View.OnClickListener handleClick){ + public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick){ super(parentID, parentFragment); SpannableStringBuilder ssb=new SpannableStringBuilder(text); HtmlParser.parseCustomEmoji(ssb, emojis); @@ -43,6 +48,17 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ this.handleClick=handleClick; TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true); + updateVisibility(visibility); + } + + public void updateVisibility(StatusPrivacy visibility) { + this.visibility = visibility; + this.iconEnd = visibility != null ? switch (visibility) { + case PUBLIC -> R.drawable.ic_fluent_earth_20_regular; + case UNLISTED -> R.drawable.ic_fluent_people_community_20_regular; + case PRIVATE -> R.drawable.ic_fluent_people_checkmark_20_regular; + default -> 0; + } : 0; } @Override @@ -70,10 +86,18 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ @Override public void onBind(ReblogOrReplyLineStatusDisplayItem item){ text.setText(item.text); - text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, 0, 0); + text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, item.iconEnd, 0); if(item.handleClick!=null) text.setOnClickListener(item.handleClick); text.setEnabled(!item.inset); text.setClickable(!item.inset); + 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; + default -> 0; + } : 0; + if (visibilityText != 0) text.setContentDescription(item.text + "." + ctx.getString(R.string.post_visibility) + " " + ctx.getString(visibilityText)); if(Build.VERSION.SDK_INT{ + 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, status.visibility, 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, i->{ + 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); })); diff --git a/mastodon/src/main/res/drawable/ic_fluent_checkmark_circle_20_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_checkmark_circle_20_regular.xml new file mode 100644 index 000000000..1626abb0a --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_checkmark_circle_20_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_fluent_compose_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_compose_24_regular.xml new file mode 100644 index 000000000..fbe9739c0 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_compose_24_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/layout/item_boost_menu.xml b/mastodon/src/main/res/layout/item_boost_menu.xml new file mode 100644 index 000000000..fa19a8f6c --- /dev/null +++ b/mastodon/src/main/res/layout/item_boost_menu.xml @@ -0,0 +1,95 @@ + + + + + + + + + + \ No newline at end of file