diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountLocalPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountLocalPreferences.java index 2a7a0f3817..27d7663aef 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountLocalPreferences.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountLocalPreferences.java @@ -43,6 +43,7 @@ public class AccountLocalPreferences{ public boolean keepOnlyLatestNotification; public boolean emojiReactionsEnabled; public ShowEmojiReactions showEmojiReactions; + public NewEmojiReactionButton newEmojiReactionButton; public ColorPreference color; public ArrayList recentCustomEmoji; @@ -73,6 +74,7 @@ public AccountLocalPreferences(SharedPreferences prefs, AccountSession session){ keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false); emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma()); showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name())); + newEmojiReactionButton=NewEmojiReactionButton.valueOf(prefs.getString("newEmojiReactionButton", NewEmojiReactionButton.WITH_REACTIONS.name())); color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null; recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>()); } @@ -112,6 +114,7 @@ public void save(){ .putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification) .putBoolean("emojiReactionsEnabled", emojiReactionsEnabled) .putString("showEmojiReactions", showEmojiReactions.name()) + .putString("newEmojiReactionButton", newEmojiReactionButton.name()) .putString("color", color!=null ? color.name() : null) .putString("recentCustomEmoji", gson.toJson(recentCustomEmoji)) .apply(); @@ -146,4 +149,10 @@ public enum ShowEmojiReactions{ ONLY_OPENED, ALWAYS } + + public enum NewEmojiReactionButton{ + WITH_REACTIONS, + REPLACE_BOOKMARK, + REPLACE_SHARE + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java index 590c98c12e..0bcdcc7280 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -22,13 +22,19 @@ import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.requests.polls.SubmitPollVote; +import org.joinmastodon.android.api.requests.statuses.AddStatusReaction; import org.joinmastodon.android.api.requests.statuses.AkkomaTranslateStatus; +import org.joinmastodon.android.api.requests.statuses.DeleteStatusReaction; +import org.joinmastodon.android.api.requests.statuses.PleromaAddStatusReaction; +import org.joinmastodon.android.api.requests.statuses.PleromaDeleteStatusReaction; import org.joinmastodon.android.api.requests.statuses.TranslateStatus; import org.joinmastodon.android.api.session.AccountSessionManager; +import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent; import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.AkkomaTranslation; import org.joinmastodon.android.model.DisplayItemsParent; +import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Status; @@ -36,6 +42,7 @@ import org.joinmastodon.android.ui.BetterItemAnimator; import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem; +import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem; @@ -943,6 +950,28 @@ private void updateTranslation(String itemID) { } } + public void addEmojiReaction(String itemID, Status status, String emoji, Emoji info) { + EmojiReactionsStatusDisplayItem.Holder holder=findHolderOfType(itemID, EmojiReactionsStatusDisplayItem.Holder.class); + if(holder!=null){ + holder.addEmojiReaction(emoji, info); + return; + } + + MastodonAPIRequest req=isInstanceAkkoma() ? new PleromaAddStatusReaction(status.id, emoji) : new AddStatusReaction(status.id, emoji); + req.setCallback(new Callback<>(){ + @Override + public void onSuccess(Status result){ + E.post(new EmojiReactionsUpdatedEvent(result.id, result.reactions, false, null)); + } + + @Override + public void onError(ErrorResponse error){ + error.showToast(getContext()); + } + }); + req.exec(accountID); + } + public void rebuildAllDisplayItems(){ displayItems.clear(); for(T item:data){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsInstanceFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsInstanceFragment.java index 6dd90718a5..10f142c0e7 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsInstanceFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsInstanceFragment.java @@ -26,7 +26,7 @@ public class SettingsInstanceFragment extends BaseSettingsFragment implements HasAccountID{ private CheckableListItem contentTypesItem, emojiReactionsItem, localOnlyItem, glitchModeItem; - private ListItem defaultContentTypeItem, showEmojiReactionsItem; + private ListItem defaultContentTypeItem, showEmojiReactionsItem, newEmojiReactionButton; private AccountLocalPreferences lp; @Override @@ -43,7 +43,8 @@ public void onCreate(Bundle savedInstanceState){ contentTypesItem=new CheckableListItem<>(R.string.sk_settings_content_types, R.string.sk_settings_content_types_explanation, CheckableListItem.Style.SWITCH, lp.contentTypesEnabled, R.drawable.ic_fluent_text_edit_style_24_regular, i->onContentTypeClick()), defaultContentTypeItem=new ListItem<>(R.string.sk_settings_default_content_type, lp.defaultContentType.getName(), R.drawable.ic_fluent_text_bold_24_regular, this::onDefaultContentTypeClick, 0, true), emojiReactionsItem=new CheckableListItem<>(R.string.sk_settings_emoji_reactions, R.string.sk_settings_emoji_reactions_explanation, CheckableListItem.Style.SWITCH, lp.emojiReactionsEnabled, R.drawable.ic_fluent_emoji_laugh_24_regular, i->onEmojiReactionsClick()), - showEmojiReactionsItem=new ListItem<>(R.string.sk_settings_show_emoji_reactions, getShowEmojiReactionsString(), R.drawable.ic_fluent_emoji_24_regular, this::onShowEmojiReactionsClick, 0, true), + showEmojiReactionsItem=new ListItem<>(R.string.sk_settings_show_emoji_reactions, getShowEmojiReactionsString(), R.drawable.ic_fluent_emoji_24_regular, this::onShowEmojiReactionsClick, 0, false), + newEmojiReactionButton=new ListItem<>(R.string.sk_settings_new_emoji_reaction_button, getNewEmojiReactionButton(), R.drawable.ic_fluent_emoji_add_24_regular, this::onNewEmojiReactionButtonClick, 0, true), localOnlyItem=new CheckableListItem<>(R.string.sk_settings_support_local_only, R.string.sk_settings_local_only_explanation, CheckableListItem.Style.SWITCH, lp.localOnlySupported, R.drawable.ic_fluent_eye_24_regular, i->onLocalOnlyClick()), glitchModeItem=new CheckableListItem<>(R.string.sk_settings_glitch_instance, R.string.sk_settings_glitch_mode_explanation, CheckableListItem.Style.SWITCH, lp.glitchInstance, R.drawable.ic_fluent_eye_24_filled, i->toggleCheckableItem(glitchModeItem)) )); @@ -51,6 +52,7 @@ public void onCreate(Bundle savedInstanceState){ defaultContentTypeItem.isEnabled=contentTypesItem.checked; emojiReactionsItem.checkedChangeListener=checked->onEmojiReactionsClick(); showEmojiReactionsItem.isEnabled=emojiReactionsItem.checked; + newEmojiReactionButton.isEnabled=emojiReactionsItem.checked; localOnlyItem.checkedChangeListener=checked->onLocalOnlyClick(); glitchModeItem.isEnabled=localOnlyItem.checked; } @@ -129,6 +131,22 @@ private void onShowEmojiReactionsClick(ListItem item_){ .show(); } + private void onNewEmojiReactionButtonClick(ListItem item_){ + int selected=lp.newEmojiReactionButton.ordinal(); + int[] newSelected={selected}; + new M3AlertDialogBuilder(getActivity()) + .setTitle(R.string.sk_settings_new_emoji_reaction_button) + .setSingleChoiceItems((String[]) IntStream.of(R.string.sk_settings_new_emoji_reaction_button_with_reactions, R.string.sk_settings_new_emoji_reaction_button_replace_bookmark, R.string.sk_settings_new_emoji_reaction_button_replace_share).mapToObj(this::getString).toArray(String[]::new), + selected, (dlg, item)->newSelected[0]=item) + .setPositiveButton(R.string.ok, (dlg, item)->{ + lp.newEmojiReactionButton=AccountLocalPreferences.NewEmojiReactionButton.values()[newSelected[0]]; + newEmojiReactionButton.subtitleRes=getNewEmojiReactionButton(); + rebindItem(newEmojiReactionButton); + }) + .setNegativeButton(R.string.cancel, null) + .show(); + } + private @StringRes int getShowEmojiReactionsString(){ return switch(lp.showEmojiReactions){ case HIDE_EMPTY -> R.string.sk_settings_show_emoji_reactions_hide_empty; @@ -137,10 +155,20 @@ private void onShowEmojiReactionsClick(ListItem item_){ }; } + private @StringRes int getNewEmojiReactionButton(){ + return switch(lp.newEmojiReactionButton){ + case WITH_REACTIONS -> R.string.sk_settings_new_emoji_reaction_button_with_reactions; + case REPLACE_BOOKMARK -> R.string.sk_settings_new_emoji_reaction_button_replace_bookmark; + case REPLACE_SHARE -> R.string.sk_settings_new_emoji_reaction_button_replace_share; + }; + } + private void onEmojiReactionsClick(){ toggleCheckableItem(emojiReactionsItem); showEmojiReactionsItem.isEnabled=emojiReactionsItem.checked; + newEmojiReactionButton.isEnabled=emojiReactionsItem.checked; rebindItem(showEmojiReactionsItem); + rebindItem(newEmojiReactionButton); } private void onLocalOnlyClick(){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/EmojiReactionsStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/EmojiReactionsStatusDisplayItem.java index f75d17987f..5d070f7f58 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/EmojiReactionsStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/EmojiReactionsStatusDisplayItem.java @@ -30,6 +30,7 @@ import org.joinmastodon.android.api.requests.statuses.DeleteStatusReaction; import org.joinmastodon.android.api.requests.statuses.PleromaAddStatusReaction; import org.joinmastodon.android.api.requests.statuses.PleromaDeleteStatusReaction; +import org.joinmastodon.android.api.session.AccountLocalPreferences; import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent; @@ -174,6 +175,9 @@ public void onBind(EmojiReactionsStatusDisplayItem item) { item.status.reactions.forEach(r->r.request=r.getUrl(item.playGifs)!=null ? new UrlImageLoaderRequest(r.getUrl(item.playGifs), V.sp(24), V.sp(24)) : null); + addButton.setVisibility(session.getLocalPreferences().newEmojiReactionButton != AccountLocalPreferences.NewEmojiReactionButton.WITH_REACTIONS + ? View.GONE + : View.VISIBLE); emojiKeyboard=new CustomEmojiPopupKeyboard( (Activity) item.parentFragment.getContext(), item.accountID, @@ -213,7 +217,7 @@ public void onEmojiSelected(String emoji){ hideEmojiKeyboard(); } - private void addEmojiReaction(String emoji, Emoji info) { + public void addEmojiReaction(String emoji, Emoji info) { int countBefore=item.status.reactions.size(); for(int i=0; i{ + public static class Holder extends StatusDisplayItem.Holder implements CustomEmojiPopupKeyboard.Listener { private final TextView replies, boosts, favorites; - private final View reply, boost, favorite, share, bookmark; + private final View reply, boost, favorite, share, bookmark, react; private final ImageView favIcon; + private CustomEmojiPopupKeyboard emojiKeyboard; + private final LinearLayout emojiKeyboardContainer; private View touchingView = null; private boolean longClickPerformed = false; @@ -83,12 +90,15 @@ public Holder(Activity activity, ViewGroup parent){ boosts=findViewById(R.id.boost); favorites=findViewById(R.id.favorite); + emojiKeyboardContainer = findViewById(R.id.footer_emoji_keyboard_container); + reply=findViewById(R.id.reply_btn); boost=findViewById(R.id.boost_btn); favorite=findViewById(R.id.favorite_btn); share=findViewById(R.id.share_btn); bookmark=findViewById(R.id.bookmark_btn); favIcon=findViewById(R.id.favorite_icon); + react=findViewById(R.id.react_btn); reply.setOnTouchListener(this::onButtonTouch); reply.setOnClickListener(this::onReplyClick); @@ -106,6 +116,9 @@ public Holder(Activity activity, ViewGroup parent){ bookmark.setOnClickListener(this::onBookmarkClick); bookmark.setOnLongClickListener(this::onBookmarkLongClick); bookmark.setAccessibilityDelegate(buttonAccessibilityDelegate); + react.setOnTouchListener(this::onButtonTouch); + react.setOnClickListener(this::onReactClick); + react.setAccessibilityDelegate(buttonAccessibilityDelegate); share.setOnTouchListener(this::onButtonTouch); share.setOnClickListener(this::onShareClick); share.setOnLongClickListener(this::onShareLongClick); @@ -144,6 +157,42 @@ public void onBind(FooterStatusDisplayItem item){ condenseBottom ? V.dp(-5) : 0); itemView.requestLayout(); + + AccountSession session = AccountSessionManager.getInstance().getAccount(item.accountID); + if(!session.getLocalPreferences().emojiReactionsEnabled){ + ((FrameLayout) react.getParent()).setVisibility(View.GONE); + ((FrameLayout) bookmark.getParent()).setVisibility(View.VISIBLE); + share.setVisibility(View.VISIBLE); + return; + } + + AccountLocalPreferences.NewEmojiReactionButton newEmojiReactionButton = session.getLocalPreferences().newEmojiReactionButton; + if(newEmojiReactionButton == AccountLocalPreferences.NewEmojiReactionButton.WITH_REACTIONS){ + ((FrameLayout) react.getParent()).setVisibility(View.GONE); + }else{ + ((FrameLayout) react.getParent()).setVisibility(View.VISIBLE); + } + if(newEmojiReactionButton == AccountLocalPreferences.NewEmojiReactionButton.REPLACE_BOOKMARK){ + ((FrameLayout) bookmark.getParent()).setVisibility(View.GONE); + }else{ + ((FrameLayout) bookmark.getParent()).setVisibility(View.VISIBLE); + } + if(newEmojiReactionButton == AccountLocalPreferences.NewEmojiReactionButton.REPLACE_SHARE){ + ((FrameLayout) react.getParent()).setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0)); + share.setVisibility(View.GONE); + }else{ + ((FrameLayout) react.getParent()).setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1)); + share.setVisibility(View.VISIBLE); + } + + emojiKeyboard=new CustomEmojiPopupKeyboard( + (Activity) item.parentFragment.getContext(), + item.accountID, + AccountSessionManager.getInstance().getCustomEmojis(session.domain), + session.domain, true); + emojiKeyboard.setListener(this); + emojiKeyboardContainer.removeAllViews(); + emojiKeyboardContainer.addView(emojiKeyboard.getView()); } private void bindText(TextView btn, long count){ @@ -351,6 +400,22 @@ private boolean onBookmarkLongClick(View v) { return true; } + private void onReactClick(View v){ + emojiKeyboard.toggleKeyboardPopup(null); + if(!emojiKeyboard.isVisible()){ + endEmojiReaction(); + return; + } + DisplayMetrics displayMetrics = new DisplayMetrics(); + int[] locationOnScreen = new int[2]; + ((Activity) v.getContext()).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + v.getLocationOnScreen(locationOnScreen); + double fromScreenTop = (double) locationOnScreen[1] / displayMetrics.heightPixels; + if (fromScreenTop > 0.75) { + item.parentFragment.scrollBy(0, (int) (displayMetrics.heightPixels * 0.3)); + } + } + private void onShareClick(View v){ if(item.status.preview) return; UiUtils.opacityIn(v); @@ -366,6 +431,28 @@ private boolean onShareLongClick(View v){ return true; } + @Override + public void onEmojiSelected(Emoji emoji) { + item.parentFragment.addEmojiReaction(getItemID(), item.status, emoji.shortcode, emoji); + endEmojiReaction(); + } + + @Override + public void onEmojiSelected(String emoji){ + item.parentFragment.addEmojiReaction(getItemID(), item.status, emoji, null); + endEmojiReaction(); + } + + private void endEmojiReaction(){ + if(emojiKeyboard.isVisible()) emojiKeyboard.toggleKeyboardPopup(null); + touchingView = null; + react.animate().scaleX(1).scaleY(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start(); + UiUtils.opacityIn(react); + } + + @Override + public void onBackspace() {} + private int descriptionForId(int id){ if(id==R.id.reply_btn) return R.string.button_reply; @@ -375,6 +462,8 @@ private int descriptionForId(int id){ return R.string.button_favorite; if(id==R.id.bookmark_btn) return R.string.add_bookmark; + if(id==R.id.react_btn) + return R.string.sk_button_react; if(id==R.id.share_btn) return R.string.button_share; return 0; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java index 0d346608cf..189bb03427 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java @@ -27,6 +27,7 @@ import org.joinmastodon.android.api.requests.announcements.DismissAnnouncement; import org.joinmastodon.android.api.requests.statuses.CreateStatus; import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText; +import org.joinmastodon.android.api.session.AccountLocalPreferences; import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.BaseStatusListFragment; @@ -506,16 +507,18 @@ private void updateOptionsMenu(){ MenuItem report=menu.findItem(R.id.report); MenuItem follow=menu.findItem(R.id.follow); MenuItem manageUserLists = menu.findItem(R.id.manage_user_lists); - /* disabled in megalodon: add/remove bookmark is already available through status footer + + AccountLocalPreferences lp = AccountSessionManager.get(item.accountID).getLocalPreferences(); MenuItem bookmark=menu.findItem(R.id.bookmark); - bookmark.setVisible(false); - if(item.status!=null){ + if(lp.newEmojiReactionButton==AccountLocalPreferences.NewEmojiReactionButton.REPLACE_BOOKMARK && item.status!=null){ bookmark.setVisible(true); bookmark.setTitle(item.status.bookmarked ? R.string.remove_bookmark : R.string.add_bookmark); }else{ bookmark.setVisible(false); } - */ + MenuItem share=menu.findItem(R.id.share); + share.setVisible(lp.newEmojiReactionButton==AccountLocalPreferences.NewEmojiReactionButton.REPLACE_SHARE); + if(isPostScheduled || isOwnPost){ mute.setVisible(false); block.setVisible(false); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 4e6193cebe..f295f90472 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -305,8 +305,7 @@ public static ArrayList buildItems(BaseStatusListFragment } AccountLocalPreferences lp=fragment.getLocalPrefs(); if((flags & FLAG_NO_EMOJI_REACTIONS)==0 && !status.preview && lp.emojiReactionsEnabled && - (lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment) && - statusForContent.reactions!=null){ + (lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment)){ 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)); diff --git a/mastodon/src/main/res/drawable/ic_fluent_emoji_add_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_emoji_add_24_regular.xml new file mode 100644 index 0000000000..095f669464 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_emoji_add_24_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/layout/display_item_footer.xml b/mastodon/src/main/res/layout/display_item_footer.xml index e344b1e39f..d2df03cdd4 100644 --- a/mastodon/src/main/res/layout/display_item_footer.xml +++ b/mastodon/src/main/res/layout/display_item_footer.xml @@ -159,6 +159,29 @@ + + + + + + - + + diff --git a/mastodon/src/main/res/values/strings_sk.xml b/mastodon/src/main/res/values/strings_sk.xml index c98a9ec63a..9274e6db74 100644 --- a/mastodon/src/main/res/values/strings_sk.xml +++ b/mastodon/src/main/res/values/strings_sk.xml @@ -365,6 +365,10 @@ Hide empty emoji reactions Only when post is opened Always show add button + New emoji reaction button + With reactions + Replace bookmark button + Replace share button One user reacted with %2$s %1$,d users reacted with %2$s