From 53e8c0d2f45b4d577a9e39ee94d31b439f7db109 Mon Sep 17 00:00:00 2001 From: Jacocococo Date: Wed, 14 Feb 2024 19:22:50 +0100 Subject: [PATCH 01/33] Quoting --- .../joinmastodon/android/fragments/ThreadFragment.java | 2 +- .../java/org/joinmastodon/android/model/Instance.java | 4 ++++ .../android/ui/displayitems/FooterStatusDisplayItem.java | 9 ++++----- .../android/ui/displayitems/StatusDisplayItem.java | 4 +++- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java index e85cad37b2..5e6b1ccbdc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java @@ -289,7 +289,7 @@ public static List mapNeighborhoodAncestry(Status mainStat // descendant neighbor Optional .ofNullable(count > index + 1 ? statuses.get(index + 1) : null) - .filter(s -> s.inReplyToId.equals(current.id)) + .filter(s -> s.inReplyToId!=null && s.inReplyToId.equals(current.id)) // inReplyToId is null for quote posts on Iceshrimp .orElse(null), // ancestoring neighbor Optional.ofNullable(index > 0 ? ancestry.get(index - 1) : null) diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java b/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java index aacf41b9a9..51538d6b47 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java @@ -152,6 +152,10 @@ public boolean isPixelfed() { return version.contains("compatible; Pixelfed"); } + public boolean isIceshrimp() { + return version.contains("compatible; Iceshrimp"); + } + public boolean hasFeature(Feature feature) { Optional> pleromaFeatures = Optional.ofNullable(pleroma) .map(p -> p.metadata) 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 6e343be514..ae67ed330d 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 @@ -285,17 +285,16 @@ private boolean onBoostLongClick(View v){ UiUtils.opacityIn(v); Bundle args=new Bundle(); args.putString("account", item.accountID); - AccountSession accountSession=AccountSessionManager.getInstance().getAccount(item.accountID); - Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain); - if(instance.pleroma == null){ + Instance instance=AccountSessionManager.get(item.accountID).getInstance().get(); + if(instance.isAkkoma() || instance.isIceshrimp()){ + args.putParcelable("quote", Parcels.wrap(item.status)); + }else{ StringBuilder prefilledText = new StringBuilder().append("\n\n"); String ownID = AccountSessionManager.getInstance().getAccount(item.accountID).self.id; if (!item.status.account.id.equals(ownID)) prefilledText.append('@').append(item.status.account.acct).append(' '); prefilledText.append(item.status.url); args.putString("prefilledText", prefilledText.toString()); args.putInt("selectionStart", 0); - }else{ - args.putParcelable("quote", Parcels.wrap(item.status)); } Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args); }); 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..9ea9d9b755 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 @@ -247,7 +247,9 @@ public static ArrayList buildItems(BaseStatusListFragment if(statusForContent.quote!=null) { int quoteInlineIndex=statusForContent.content.lastIndexOf("

RE:"); - if (quoteInlineIndex!=-1) + if(quoteInlineIndex==-1) + quoteInlineIndex=statusForContent.content.lastIndexOf("

RE:"); + if(quoteInlineIndex!=-1) statusForContent.content=statusForContent.content.substring(0, quoteInlineIndex); } From c8bb0de7d422d713b07bcc76b95d1ef13d8d3c14 Mon Sep 17 00:00:00 2001 From: Jacocococo Date: Wed, 14 Feb 2024 22:35:32 +0100 Subject: [PATCH 02/33] Don't load instance info in background This information is very useful and it's loaded very quickly anyways --- .../joinmastodon/android/api/session/AccountSessionManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java index cd959a85ae..328bde9ac7 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java @@ -99,7 +99,7 @@ private AccountSessionManager(){ Log.e(TAG, "Error loading accounts", x); } lastActiveAccountID=prefs.getString("lastActiveAccount", null); - MastodonAPIController.runInBackground(()->readInstanceInfo(domains)); + readInstanceInfo(domains); maybeUpdateShortcuts(); } From 2892a31c722718bcde2a5c42955938f4fad583e3 Mon Sep 17 00:00:00 2001 From: Jacocococo Date: Wed, 14 Feb 2024 23:35:13 +0100 Subject: [PATCH 03/33] Hide news discovery on Iceshrimp --- .../fragments/discover/DiscoverFragment.java | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java index 366e8fe5c4..a94b955f03 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java @@ -1,6 +1,7 @@ package org.joinmastodon.android.fragments.discover; import android.app.Fragment; +import android.app.FragmentTransaction; import android.os.Build; import android.os.Bundle; import android.text.TextUtils; @@ -28,6 +29,9 @@ import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager2.widget.ViewPager2; + +import java.util.Optional; + import me.grishka.appkit.Nav; import me.grishka.appkit.fragments.AppKitFragment; import me.grishka.appkit.fragments.BaseRecyclerFragment; @@ -57,6 +61,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, private String currentQuery; private boolean disableDiscover; + private boolean isIceshrimp; @Override public void onCreate(Bundle savedInstanceState){ @@ -75,13 +80,17 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, tabLayout=view.findViewById(R.id.tabbar); pager=view.findViewById(R.id.pager); - tabViews=new FrameLayout[4]; + Optional instance=AccountSessionManager.get(accountID).getInstance(); + disableDiscover=instance.map(Instance::isAkkoma).orElse(false); + isIceshrimp=instance.map(Instance::isIceshrimp).orElse(false); + + tabViews=new FrameLayout[isIceshrimp ? 3 : 4]; for(int i=0;i R.id.discover_hashtags; case 1 -> R.id.discover_posts; - case 2 -> R.id.discover_news; + case 2 -> isIceshrimp ? R.id.discover_users : R.id.discover_news; case 3 -> R.id.discover_users; default -> throw new IllegalStateException("Unexpected value: "+i); }); @@ -123,12 +132,15 @@ public void onPageSelected(int position){ accountsFragment=new DiscoverAccountsFragment(); accountsFragment.setArguments(args); - getChildFragmentManager().beginTransaction() - .add(R.id.discover_hashtags, hashtagsFragment) - .add(R.id.discover_posts, postsFragment) - .add(R.id.discover_news, newsFragment) - .add(R.id.discover_users, accountsFragment) - .commit(); + FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); + transaction + .add(R.id.discover_hashtags, hashtagsFragment) + .add(R.id.discover_posts, postsFragment); + if(!isIceshrimp) + transaction.add(R.id.discover_news, newsFragment); + transaction + .add(R.id.discover_users, accountsFragment) + .commit(); } tabLayoutMediator=new TabLayoutMediator(tabLayout, pager, new TabLayoutMediator.TabConfigurationStrategy(){ @@ -137,7 +149,7 @@ public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){ tab.setText(switch(position){ case 0 -> R.string.hashtags; case 1 -> R.string.posts; - case 2 -> R.string.news; + case 2 -> isIceshrimp ? R.string.for_you : R.string.news; case 3 -> R.string.for_you; default -> throw new IllegalStateException("Unexpected value: "+position); }); @@ -157,7 +169,6 @@ public void onTabReselected(TabLayout.Tab tab){ } }); - disableDiscover=AccountSessionManager.get(accountID).getInstance().map(Instance::isAkkoma).orElse(false); searchView=view.findViewById(R.id.search_fragment); if(searchFragment==null){ searchFragment=new SearchFragment(); @@ -254,7 +265,7 @@ private Fragment getFragmentForPage(int page){ return switch(page){ case 0 -> hashtagsFragment; case 1 -> postsFragment; - case 2 -> newsFragment; + case 2 -> isIceshrimp ? accountsFragment : newsFragment; case 3 -> accountsFragment; default -> throw new IllegalStateException("Unexpected value: "+page); }; From 0e96e23cfa91861e894a9ecfefbabaacc49669d8 Mon Sep 17 00:00:00 2001 From: Jacocococo Date: Thu, 15 Feb 2024 00:06:08 +0100 Subject: [PATCH 04/33] Hide language selector on Iceshrimp --- .../android/fragments/ComposeFragment.java | 20 +++++++++++-------- .../android/fragments/HasAccountID.java | 4 ++++ .../settings/SettingsBehaviorFragment.java | 6 +++++- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java index 0764b7d7ae..492678c269 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java @@ -829,14 +829,18 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ publishButton=wrap.findViewById(R.id.publish_btn); languageButton=wrap.findViewById(R.id.language_btn); - languageButton.setOnClickListener(v->showLanguageAlert()); - languageButton.setOnLongClickListener(v->{ - if(!getLocalPrefs().bottomEncoding){ - getLocalPrefs().bottomEncoding=true; - getLocalPrefs().save(); - } - return false; - }); + if(instance.isIceshrimp()) + languageButton.setVisibility(View.GONE); + else { + languageButton.setOnClickListener(v->showLanguageAlert()); + languageButton.setOnLongClickListener(v->{ + if(!getLocalPrefs().bottomEncoding){ + getLocalPrefs().bottomEncoding=true; + getLocalPrefs().save(); + } + return false; + }); + } publishButton.post(()->publishButton.setMinimumWidth(publishButton.getWidth())); publishButton.setOnClickListener(v->{ diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HasAccountID.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HasAccountID.java index 9934b2e17f..d66e7f4c92 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HasAccountID.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HasAccountID.java @@ -22,6 +22,10 @@ default boolean isInstancePixelfed() { return getInstance().map(Instance::isPixelfed).orElse(false); } + default boolean isInstanceIceshrimp() { + return getInstance().map(Instance::isIceshrimp).orElse(false); + } + default Optional getInstance() { return getSession().getInstance(); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsBehaviorFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsBehaviorFragment.java index 62c32d8fb3..1c75b27934 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsBehaviorFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsBehaviorFragment.java @@ -45,7 +45,6 @@ public void onCreate(Bundle savedInstanceState){ languageResolver.from(s.preferences.postingDefaultLanguage).orElse(null); List> items = new ArrayList<>(List.of( - languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(getContext()) : null, R.drawable.ic_fluent_local_language_24_regular, this::onDefaultLanguageClick), altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_fluent_image_alt_text_24_regular, i->toggleCheckableItem(altTextItem)), playGifsItem=new CheckableListItem<>(R.string.settings_gif, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.playGifs, R.drawable.ic_fluent_gif_24_regular, i->toggleCheckableItem(playGifsItem)), overlayMediaItem=new CheckableListItem<>(R.string.sk_settings_continues_playback, R.string.sk_settings_continues_playback_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.overlayMedia, R.drawable.ic_fluent_play_circle_hint_24_regular, i->toggleCheckableItem(overlayMediaItem)), @@ -62,6 +61,11 @@ public void onCreate(Bundle savedInstanceState){ showRepliesItem=new CheckableListItem<>(R.string.sk_settings_show_replies, 0, CheckableListItem.Style.SWITCH, lp.showReplies, R.drawable.ic_fluent_arrow_reply_24_regular, i->toggleCheckableItem(showRepliesItem)) )); + if(!isInstanceIceshrimp()) items.add( + 0, + languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(getContext()) : null, R.drawable.ic_fluent_local_language_24_regular, this::onDefaultLanguageClick) + ); + if(isInstanceAkkoma()) items.add( replyVisibilityItem=new ListItem<>(R.string.sk_settings_reply_visibility, getReplyVisibilityString(), R.drawable.ic_fluent_chat_24_regular, this::onReplyVisibilityClick) ); From 325eda58cbaa7d8fc30f485b643588ff04051efb Mon Sep 17 00:00:00 2001 From: Jacocococo Date: Thu, 15 Feb 2024 22:58:40 +0100 Subject: [PATCH 05/33] Fix Iceshrimp quote notification --- .../android/fragments/NotificationsListFragment.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java index fbb5edcd8f..af6ac93047 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java @@ -19,6 +19,7 @@ import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.RemoveAccountPostsEvent; import org.joinmastodon.android.events.StatusCountersUpdatedEvent; +import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.PaginatedResponse; import org.joinmastodon.android.model.Status; @@ -88,7 +89,9 @@ public void onAttach(Activity activity){ @Override protected List buildDisplayItems(Notification n){ NotificationHeaderStatusDisplayItem titleItem; - if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS){ + Account self=AccountSessionManager.get(accountID).self; + if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS + || (n.type==Notification.Type.REBLOG && !n.status.account.id.equals(self.id))){ // Iceshrimp quote titleItem=null; }else{ titleItem=new NotificationHeaderStatusDisplayItem(n.id, this, n, accountID); From 454660fe892c10ed1d156c9b1f89441cdb868a82 Mon Sep 17 00:00:00 2001 From: Jacocococo Date: Thu, 15 Feb 2024 23:25:09 +0100 Subject: [PATCH 06/33] Hide profile notify button on Iceshrimp --- .../org/joinmastodon/android/fragments/ProfileFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java index 3cd5bfa234..e1a0d3f789 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -1020,7 +1020,7 @@ private void updateRelationship(){ else hidePrivateNote(); invalidateOptionsMenu(); actionButton.setVisibility(View.VISIBLE); - notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE); + notifyButton.setVisibility(relationship.following && !isInstanceIceshrimp() ? View.VISIBLE : View.GONE); UiUtils.setRelationshipToActionButtonM3(relationship, actionButton); actionProgress.setIndeterminateTintList(actionButton.getTextColors()); notifyProgress.setIndeterminateTintList(notifyButton.getTextColors()); From 3593d8d80f9d1ce953891c7ba391dbd6ead26492 Mon Sep 17 00:00:00 2001 From: Jacocococo Date: Fri, 16 Feb 2024 00:10:20 +0100 Subject: [PATCH 07/33] Announcements fixes on Iceshrimp - Hide emoji reactions - Correctly set editedAt --- .../joinmastodon/android/fragments/AnnouncementsFragment.java | 4 ++-- .../java/org/joinmastodon/android/model/Announcement.java | 4 ++-- .../android/ui/displayitems/HeaderStatusDisplayItem.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/AnnouncementsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/AnnouncementsFragment.java index b376f24dc3..e267b45b15 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/AnnouncementsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/AnnouncementsFragment.java @@ -68,14 +68,14 @@ protected List buildDisplayItems(Announcement a) { instanceUser.url = "https://"+session.domain+"/about"; instanceUser.avatar = instanceUser.avatarStatic = instance.thumbnail; instanceUser.emojis = List.of(); - Status fakeStatus = a.toStatus(); + Status fakeStatus = a.toStatus(isInstanceIceshrimp()); TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus, true); textItem.textSelectable = true; List items=new ArrayList<>(); items.add(HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead)); items.add(textItem); - if(!isInstanceAkkoma()) items.add(new EmojiReactionsStatusDisplayItem(a.id, this, fakeStatus, accountID, false, true)); + if(!isInstanceAkkoma() && !isInstanceIceshrimp()) items.add(new EmojiReactionsStatusDisplayItem(a.id, this, fakeStatus, accountID, false, true)); return items; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Announcement.java b/mastodon/src/main/java/org/joinmastodon/android/model/Announcement.java index 9c769ed03f..ba8a949ed8 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Announcement.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Announcement.java @@ -50,11 +50,11 @@ public void postprocess() throws ObjectValidationException{ if(reactions==null) reactions=new ArrayList<>(); } - public Status toStatus() { + public Status toStatus(boolean isIceshrimp) { Status s=Status.ofFake(id, content, publishedAt); s.createdAt=startsAt != null ? startsAt : publishedAt; s.reactions=reactions; - if(updatedAt != null) s.editedAt=updatedAt; + if(updatedAt != null && (!isIceshrimp || !updatedAt.equals(publishedAt))) s.editedAt=updatedAt; return s; } 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..a6fce3772f 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 @@ -111,7 +111,7 @@ public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, } public static HeaderStatusDisplayItem fromAnnouncement(Announcement a, Status fakeStatus, Account instanceUser, BaseStatusListFragment parentFragment, String accountID, Consumer consumeReadID) { - HeaderStatusDisplayItem item = new HeaderStatusDisplayItem(a.id, instanceUser, a.startsAt, parentFragment, accountID, fakeStatus, null, null, null); + HeaderStatusDisplayItem item = new HeaderStatusDisplayItem(a.id, instanceUser, a.startsAt!=null ? a.startsAt : fakeStatus.createdAt, parentFragment, accountID, fakeStatus, null, null, null); item.announcement = a; item.consumeReadAnnouncement = consumeReadID; return item; From 86f54f5a024b04a23e25194dfe148131a8919483 Mon Sep 17 00:00:00 2001 From: Jacocococo Date: Fri, 15 Mar 2024 23:00:25 +0100 Subject: [PATCH 08/33] Respect instance max reaction count --- .../joinmastodon/android/model/Instance.java | 6 +++++ .../EmojiReactionsStatusDisplayItem.java | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java b/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java index 51538d6b47..d41f4e05d2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java @@ -196,6 +196,7 @@ public static class Configuration{ public StatusesConfiguration statuses; public MediaAttachmentsConfiguration mediaAttachments; public PollsConfiguration polls; + public ReactionsConfiguration reactions; } @Parcel @@ -223,6 +224,11 @@ public static class PollsConfiguration{ public int maxExpiration; } + @Parcel + public static class ReactionsConfiguration { + public int maxReactions; + } + @Parcel public static class V2 extends BaseModel { public V2.Configuration configuration; 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..5aa6a00143 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 @@ -1,5 +1,6 @@ package org.joinmastodon.android.ui.displayitems; +import android.animation.ObjectAnimator; import android.app.Activity; import android.content.Context; import android.graphics.Paint; @@ -38,6 +39,7 @@ import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.EmojiReaction; +import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.CustomEmojiPopupKeyboard; import org.joinmastodon.android.ui.utils.TextDrawable; @@ -151,6 +153,7 @@ public static class Holder extends StatusDisplayItem.Holderr.me).count(); + boolean canReact=meReactionCountr.request=r.getUrl(item.playGifs)!=null ? new UrlImageLoaderRequest(r.getUrl(item.playGifs), V.sp(24), V.sp(24)) : null); @@ -392,6 +402,20 @@ public void onBind(Pair item){ adapter.parentHolder.root.setVisibility(View.GONE); adapter.parentHolder.line.setVisibility(View.GONE); } + + Instance instance=parent.parentFragment.getInstance().get(); + if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0){ + adapter.parentHolder.meReactionCount+=deleting ? -1 : 1; + boolean canReact=adapter.parentHolder.meReactionCount Date: Sat, 16 Mar 2024 14:25:30 +0100 Subject: [PATCH 09/33] Disable remote emoji reaction buttons on Iceshrimp --- .../EmojiReactionsStatusDisplayItem.java | 12 +++++-- .../android/ui/views/EmojiReactionButton.java | 34 +++++++++++++++++++ .../main/res/layout/item_emoji_reaction.xml | 2 +- 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/views/EmojiReactionButton.java 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 5aa6a00143..009c476420 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 @@ -44,7 +44,7 @@ import org.joinmastodon.android.ui.CustomEmojiPopupKeyboard; import org.joinmastodon.android.ui.utils.TextDrawable; import org.joinmastodon.android.ui.utils.UiUtils; -import org.joinmastodon.android.ui.views.ProgressBarButton; +import org.joinmastodon.android.ui.views.EmojiReactionButton; import me.grishka.appkit.Nav; import me.grishka.appkit.api.Callback; @@ -340,7 +340,7 @@ public ImageLoaderRequest getImageRequest(int position, int image){ } private static class EmojiReactionViewHolder extends BindableViewHolder> implements ImageLoaderViewHolder{ - private final ProgressBarButton btn; + private final EmojiReactionButton btn; private final ProgressBar progress; public EmojiReactionViewHolder(Context context, RecyclerView list){ @@ -379,6 +379,14 @@ public void onBind(Pair item){ btn.setCompoundDrawablesRelative(item.first.placeholder, null, null, null); } btn.setSelected(reaction.me); + if(parent.parentFragment.isInstanceIceshrimp() && reaction.name.contains("@")){ + btn.setEnabled(false); + btn.setClickable(false); + btn.setLongClickable(true); + }else{ + btn.setEnabled(true); + btn.setClickable(true); + } btn.setOnClickListener(e->{ boolean deleting=reaction.me; parent.createRequest(reaction.name, reaction.count, deleting, this, ()->{ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/EmojiReactionButton.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/EmojiReactionButton.java new file mode 100644 index 0000000000..27322e456c --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/EmojiReactionButton.java @@ -0,0 +1,34 @@ +package org.joinmastodon.android.ui.views; + +import android.content.Context; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +public class EmojiReactionButton extends ProgressBarButton { + private final Handler handler=new Handler(); + + public EmojiReactionButton(Context context){ + super(context); + } + + public EmojiReactionButton(Context context, AttributeSet attrs){ + super(context, attrs); + } + + public EmojiReactionButton(Context context, AttributeSet attrs, int defStyleAttr){ + super(context, attrs, defStyleAttr); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // allow long click even if button is disabled + int action=event.getAction(); + if(action==MotionEvent.ACTION_DOWN) + handler.postDelayed(this::performLongClick, ViewConfiguration.getLongPressTimeout()); + if(action==MotionEvent.ACTION_UP) + handler.removeCallbacksAndMessages(null); + return super.onTouchEvent(event); + } +} diff --git a/mastodon/src/main/res/layout/item_emoji_reaction.xml b/mastodon/src/main/res/layout/item_emoji_reaction.xml index b5e9882c46..e9f131caf2 100644 --- a/mastodon/src/main/res/layout/item_emoji_reaction.xml +++ b/mastodon/src/main/res/layout/item_emoji_reaction.xml @@ -15,7 +15,7 @@ android:indeterminate="true" android:outlineProvider="none" android:visibility="gone"/> - Date: Sat, 16 Mar 2024 23:08:55 +0100 Subject: [PATCH 10/33] Disable add reaction button upon reaching limit when using that button --- .../EmojiReactionsStatusDisplayItem.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) 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 009c476420..798a2b393e 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 @@ -154,6 +154,7 @@ public static class Holder extends StatusDisplayItem.Holderr.me).count(); boolean canReact=meReactionCount item){ Instance instance=parent.parentFragment.getInstance().get(); if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0){ - adapter.parentHolder.meReactionCount+=deleting ? -1 : 1; - boolean canReact=adapter.parentHolder.meReactionCount Date: Sun, 17 Mar 2024 00:05:47 +0100 Subject: [PATCH 11/33] Update favorite when reacting on Iceshrimp --- .../fragments/BaseStatusListFragment.java | 8 +++++++ .../EmojiReactionsStatusDisplayItem.java | 21 ++++++++++++++----- .../displayitems/FooterStatusDisplayItem.java | 11 ++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) 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..6e7fc376da 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -659,6 +659,14 @@ public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){ warning.getItem().status.filterRevealed = true; } + public void onFavoriteChanged(Status status, String itemID) { + FooterStatusDisplayItem.Holder footer=findHolderOfType(itemID, FooterStatusDisplayItem.Holder.class); + if(footer!=null){ + footer.getItem().status=status; + footer.onFavoriteClick(); + } + } + @Override public String getAccountID(){ return accountID; 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 798a2b393e..d7dd67c62e 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 @@ -34,6 +34,7 @@ import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent; +import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.account_list.StatusEmojiReactionsListFragment; import org.joinmastodon.android.model.Account; @@ -46,6 +47,8 @@ import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.views.EmojiReactionButton; +import java.util.function.Consumer; + import me.grishka.appkit.Nav; import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.ErrorResponse; @@ -103,7 +106,7 @@ private void setActionProgressVisible(Holder.EmojiReactionViewHolder vh, boolean vh.btn.setAlpha(visible ? ALPHA_DISABLED : 1); } - private MastodonAPIRequest createRequest(String name, int count, boolean delete, Holder.EmojiReactionViewHolder vh, Runnable cb, Runnable err){ + private MastodonAPIRequest createRequest(String name, int count, boolean delete, Holder.EmojiReactionViewHolder vh, Consumer cb, Runnable err){ setActionProgressVisible(vh, true); boolean ak=parentFragment.isInstanceAkkoma(); boolean keepSpinning=delete && count == 1; @@ -115,7 +118,7 @@ private MastodonAPIRequest createRequest(String name, int count, boolean dele @Override public void onSuccess(Object result){ if(!keepSpinning) setActionProgressVisible(vh, false); - cb.run(); + cb.accept(null); } @Override public void onError(ErrorResponse error){ @@ -132,7 +135,7 @@ public void onError(ErrorResponse error){ @Override public void onSuccess(Status result){ if(!keepSpinning) setActionProgressVisible(vh, false); - cb.run(); + cb.accept(result); } @Override public void onError(ErrorResponse error){ @@ -255,7 +258,7 @@ private void addEmojiReaction(String emoji, Emoji info) { } } EmojiReaction finalExisting=existing; - item.createRequest(emoji, existing==null ? 1 : existing.count, false, null, ()->{ + item.createRequest(emoji, existing==null ? 1 : existing.count, false, null, (status)->{ resetBtn.run(); if(finalExisting==null){ int pos=item.status.reactions.size(); @@ -269,6 +272,10 @@ private void addEmojiReaction(String emoji, Emoji info) { finalExisting.add(me); adapter.notifyItemChanged(item.status.reactions.indexOf(finalExisting)); } + if(instance.isIceshrimp() && status!=null){ + item.parentFragment.onFavoriteChanged(status, getItemID()); + E.post(new StatusCountersUpdatedEvent(status)); + } E.post(new EmojiReactionsUpdatedEvent(item.status.id, item.status.reactions, countBefore==0, adapter.parentHolder)); }, resetBtn).exec(item.accountID); } @@ -404,7 +411,7 @@ public void onBind(Pair item){ } btn.setOnClickListener(e->{ boolean deleting=reaction.me; - parent.createRequest(reaction.name, reaction.count, deleting, this, ()->{ + parent.createRequest(reaction.name, reaction.count, deleting, this, (status)->{ EmojiReactionsAdapter adapter = (EmojiReactionsAdapter) getBindingAdapter(); for(int i=0; i item){ if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0){ adapter.parentHolder.updateAddButtonClickable(deleting); } + if(instance.isIceshrimp() && status!=null){ + parent.parentFragment.onFavoriteChanged(status, adapter.parentHolder.getItemID()); + E.post(new StatusCountersUpdatedEvent(status)); + } E.post(new EmojiReactionsUpdatedEvent(parent.status.id, parent.status.reactions, parent.status.reactions.isEmpty(), adapter.parentHolder)); adapter.parentHolder.imgLoader.updateImages(); }, null).exec(parent.parentFragment.getAccountID()); 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 ae67ed330d..bd4bc8bff6 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 @@ -303,6 +303,17 @@ private boolean onBoostLongClick(View v){ return true; } + public void onFavoriteClick() { + favorite.setSelected(item.status.favourited); + favorite.animate().scaleX(0.95f).scaleY(0.95f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(75).start(); + UiUtils.opacityOut(favorite); + favorite.postDelayed(() -> { + favorite.animate().scaleX(1).scaleY(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start(); + UiUtils.opacityIn(favorite); + }, 300); + bindText(favorites, item.status.favouritesCount); + } + private void onFavoriteClick(View v){ if(item.status.preview) return; favorite.setSelected(!item.status.favourited); From 86b6adf228e147a111ee2fe7ad09199db7ce7525 Mon Sep 17 00:00:00 2001 From: Jacocococo Date: Fri, 29 Mar 2024 14:38:06 +0100 Subject: [PATCH 12/33] Move unauthenticatedApiController The constructor of AccountSessionManager may need this object which could lead to a situation where it was being used before it had been created. Making it static and moving it to MastodonAPIRequest, the only place it was being used anyways, seems to fix the issue. --- .../org/joinmastodon/android/api/MastodonAPIRequest.java | 6 ++++-- .../android/api/session/AccountSessionManager.java | 6 ------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java index 09cb7ca60b..abdd9b99ed 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java @@ -37,6 +37,8 @@ public abstract class MastodonAPIRequest extends APIRequest{ private static final String TAG="MastodonAPIRequest"; + private static MastodonAPIController unauthenticatedApiController=new MastodonAPIController(null); + private String domain; private AccountSession account; private String path; @@ -95,14 +97,14 @@ public MastodonAPIRequest exec(String accountID){ public MastodonAPIRequest execNoAuth(String domain){ this.domain=domain; - AccountSessionManager.getInstance().getUnauthenticatedApiController().submitRequest(this); + unauthenticatedApiController.submitRequest(this); return this; } public MastodonAPIRequest exec(String domain, Token token){ this.domain=domain; this.token=token; - AccountSessionManager.getInstance().getUnauthenticatedApiController().submitRequest(this); + unauthenticatedApiController.submitRequest(this); return this; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java index 328bde9ac7..0a3db0f1f5 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java @@ -72,7 +72,6 @@ public class AccountSessionManager{ private HashMap> customEmojis=new HashMap<>(); private HashMap instancesLastUpdated=new HashMap<>(); private HashMap instances=new HashMap<>(); - private MastodonAPIController unauthenticatedApiController=new MastodonAPIController(null); private Instance authenticatingInstance; private Application authenticatingApp; private String lastActiveAccountID; @@ -232,11 +231,6 @@ public void removeAccount(String id){ maybeUpdateShortcuts(); } - @NonNull - public MastodonAPIController getUnauthenticatedApiController(){ - return unauthenticatedApiController; - } - public void authenticate(Activity activity, Instance instance){ authenticatingInstance=instance; new CreateOAuthApp() From 3266a490be021641f77ba1084a78b4e5fb4bab45 Mon Sep 17 00:00:00 2001 From: Jacocococo Date: Mon, 5 Aug 2024 18:02:27 +0200 Subject: [PATCH 13/33] Add reaction when favoriting post on Iceshrimp --- .../api/StatusInteractionController.java | 58 ++++++++++- .../fragments/NotificationsListFragment.java | 12 ++- .../android/fragments/StatusListFragment.java | 10 +- .../android/model/EmojiReaction.java | 15 +++ .../joinmastodon/android/model/Instance.java | 1 + .../EmojiReactionsStatusDisplayItem.java | 97 +++++++++++++++++-- 6 files changed, 175 insertions(+), 18 deletions(-) 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 05f3d41565..f2524d046f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/StatusInteractionController.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/StatusInteractionController.java @@ -7,14 +7,23 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked; import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited; import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged; +import org.joinmastodon.android.api.session.AccountSession; +import org.joinmastodon.android.api.session.AccountSessionManager; +import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent; import org.joinmastodon.android.events.ReblogDeletedEvent; import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.events.StatusCreatedEvent; -import org.joinmastodon.android.events.StatusDeletedEvent; +import org.joinmastodon.android.model.Emoji; +import org.joinmastodon.android.model.EmojiCategory; +import org.joinmastodon.android.model.EmojiReaction; +import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.StatusPrivacy; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Optional; import java.util.function.Consumer; import me.grishka.appkit.api.Callback; @@ -40,6 +49,9 @@ public void setFavorited(Status status, boolean favorited, Consumer cb){ if(!Looper.getMainLooper().isCurrentThread()) throw new IllegalStateException("Can only be called from main thread"); + AccountSession session=AccountSessionManager.get(accountID); + Instance instance=session.getInstance().get(); + SetStatusFavorited current=runningFavoriteRequests.remove(status.id); if(current!=null){ current.cancel(); @@ -52,6 +64,7 @@ public void onSuccess(Status result){ result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1)); cb.accept(result); if(updateCounters) E.post(new StatusCountersUpdatedEvent(result)); + if(instance.isIceshrimp()) E.post(new EmojiReactionsUpdatedEvent(status.id, result.reactions, false, null)); } @Override @@ -61,12 +74,55 @@ public void onError(ErrorResponse error){ status.favourited=!favorited; cb.accept(status); if(updateCounters) E.post(new StatusCountersUpdatedEvent(status)); + if(instance.isIceshrimp()) E.post(new EmojiReactionsUpdatedEvent(status.id, status.reactions, false, null)); } }) .exec(accountID); runningFavoriteRequests.put(status.id, req); status.favourited=favorited; if(updateCounters) E.post(new StatusCountersUpdatedEvent(status)); + + String defaultReactionEmojiRaw=instance.configuration.reactions.defaultReaction; + if(!instance.isIceshrimp() || defaultReactionEmojiRaw==null) + return; + + boolean reactionIsCustom=defaultReactionEmojiRaw.startsWith(":"); + String defaultReactionEmoji=reactionIsCustom ? defaultReactionEmojiRaw.substring(1, defaultReactionEmojiRaw.length()-1) : defaultReactionEmojiRaw; + ArrayList reactions=new ArrayList<>(status.reactions.size()); + for(EmojiReaction reaction:status.reactions){ + reactions.add(reaction.copy()); + } + Optional existingReaction=reactions.stream().filter(r->r.me).findFirst(); + Optional existingDefaultReaction=reactions.stream().filter(r->r.name.equals(defaultReactionEmoji)).findFirst(); + if(existingReaction.isPresent() && !favorited){ + existingReaction.get().me=false; + existingReaction.get().count--; + existingReaction.get().pendingChange=true; + }else if(existingDefaultReaction.isPresent() && favorited){ + existingDefaultReaction.get().count++; + existingDefaultReaction.get().me=true; + existingDefaultReaction.get().pendingChange=true; + }else if(favorited){ + EmojiReaction reaction=null; + if(reactionIsCustom){ + List customEmojis=AccountSessionManager.getInstance().getCustomEmojis(session.domain); + for(EmojiCategory category:customEmojis){ + for(Emoji emoji:category.emojis){ + if(emoji.shortcode.equals(defaultReactionEmoji)){ + reaction=EmojiReaction.of(emoji, session.self); + break; + } + } + } + if(reaction==null) + reaction=EmojiReaction.of(defaultReactionEmoji, session.self); + }else{ + reaction=EmojiReaction.of(defaultReactionEmoji, session.self); + } + reaction.pendingChange=true; + reactions.add(reaction); + } + E.post(new EmojiReactionsUpdatedEvent(status.id, reactions, false, null)); } public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer cb){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java index af6ac93047..9644a498cc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java @@ -275,13 +275,17 @@ public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){ public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){ for(Notification n : data){ if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){ - n.status.getContentStatus().update(ev); - AccountSessionManager.get(accountID).getCacheController().updateNotification(n); for(int i=0; i