commit
ab72435347
25 changed files with 439 additions and 74 deletions
|
@ -37,6 +37,8 @@ import okhttp3.Response;
|
|||
public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
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 abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||
|
||||
public MastodonAPIRequest<T> execNoAuth(String domain){
|
||||
this.domain=domain;
|
||||
AccountSessionManager.getInstance().getUnauthenticatedApiController().submitRequest(this);
|
||||
unauthenticatedApiController.submitRequest(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MastodonAPIRequest<T> exec(String domain, Token token){
|
||||
this.domain=domain;
|
||||
this.token=token;
|
||||
AccountSessionManager.getInstance().getUnauthenticatedApiController().submitRequest(this);
|
||||
unauthenticatedApiController.submitRequest(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,14 +8,23 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
|
|||
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusMuted;
|
||||
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;
|
||||
|
@ -42,6 +51,9 @@ public class StatusInteractionController{
|
|||
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();
|
||||
|
@ -54,6 +66,7 @@ public class StatusInteractionController{
|
|||
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
|
||||
cb.accept(result);
|
||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||
if(instance.isIceshrimpJs()) E.post(new EmojiReactionsUpdatedEvent(status.id, result.reactions, false, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,12 +76,58 @@ public class StatusInteractionController{
|
|||
status.favourited=!favorited;
|
||||
cb.accept(status);
|
||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
if(instance.isIceshrimpJs()) 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));
|
||||
|
||||
if(instance.configuration==null || instance.configuration.reactions==null)
|
||||
return;
|
||||
|
||||
String defaultReactionEmojiRaw=instance.configuration.reactions.defaultReaction;
|
||||
if(!instance.isIceshrimpJs() || defaultReactionEmojiRaw==null)
|
||||
return;
|
||||
|
||||
boolean reactionIsCustom=defaultReactionEmojiRaw.startsWith(":");
|
||||
String defaultReactionEmoji=reactionIsCustom ? defaultReactionEmojiRaw.substring(1, defaultReactionEmojiRaw.length()-1) : defaultReactionEmojiRaw;
|
||||
ArrayList<EmojiReaction> reactions=new ArrayList<>(status.reactions.size());
|
||||
for(EmojiReaction reaction:status.reactions){
|
||||
reactions.add(reaction.copy());
|
||||
}
|
||||
Optional<EmojiReaction> existingReaction=reactions.stream().filter(r->r.me).findFirst();
|
||||
Optional<EmojiReaction> 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<EmojiCategory> 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<Status> cb){
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.joinmastodon.android.model.ContentType;
|
|||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.TimelineDefinition;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
@ -23,6 +24,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class AccountLocalPreferences{
|
||||
private final SharedPreferences prefs;
|
||||
|
@ -72,19 +74,20 @@ public class AccountLocalPreferences{
|
|||
// preReplySheet=prefs.getBoolean("preReplySheet", false);
|
||||
|
||||
// MEGALODON
|
||||
Optional<Instance> instance=session.getInstance();
|
||||
showReplies=prefs.getBoolean("showReplies", true);
|
||||
showBoosts=prefs.getBoolean("showBoosts", true);
|
||||
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new ArrayList<>());
|
||||
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
||||
defaultContentType=enumValue(ContentType.class, prefs.getString("defaultContentType", ContentType.PLAIN.name()));
|
||||
contentTypesEnabled=prefs.getBoolean("contentTypesEnabled", true);
|
||||
defaultContentType=enumValue(ContentType.class, prefs.getString("defaultContentType", instance.map(Instance::isIceshrimp).orElse(false) ? ContentType.MISSKEY_MARKDOWN.name() : ContentType.PLAIN.name()));
|
||||
contentTypesEnabled=prefs.getBoolean("contentTypesEnabled", instance.map(i->!i.isIceshrimp()).orElse(false));
|
||||
timelines=fromJson(prefs.getString("timelines", null), timelinesType, TimelineDefinition.getDefaultTimelines(session.getID()));
|
||||
localOnlySupported=prefs.getBoolean("localOnlySupported", false);
|
||||
glitchInstance=prefs.getBoolean("glitchInstance", false);
|
||||
publishButtonText=prefs.getString("publishButtonText", null);
|
||||
timelineReplyVisibility=prefs.getString("timelineReplyVisibility", null);
|
||||
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
||||
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
|
||||
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", instance.map(i->i.isAkkoma() || i.isIceshrimp()).orElse(false));
|
||||
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
|
||||
color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null;
|
||||
recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>());
|
||||
|
|
|
@ -71,7 +71,6 @@ public class AccountSessionManager{
|
|||
private HashMap<String, List<EmojiCategory>> customEmojis=new HashMap<>();
|
||||
private HashMap<String, Long> instancesLastUpdated=new HashMap<>();
|
||||
private HashMap<String, Instance> instances=new HashMap<>();
|
||||
private MastodonAPIController unauthenticatedApiController=new MastodonAPIController(null);
|
||||
private Instance authenticatingInstance;
|
||||
private Application authenticatingApp;
|
||||
private String lastActiveAccountID;
|
||||
|
@ -110,7 +109,7 @@ public class AccountSessionManager{
|
|||
Log.e(TAG, "Error loading accounts", x);
|
||||
}
|
||||
lastActiveAccountID=prefs.getString("lastActiveAccount", null);
|
||||
MastodonAPIController.runInBackground(()->readInstanceInfo(domains));
|
||||
readInstanceInfo(domains);
|
||||
maybeUpdateShortcuts();
|
||||
}
|
||||
|
||||
|
@ -248,11 +247,6 @@ public class AccountSessionManager{
|
|||
maybeUpdateShortcuts();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public MastodonAPIController getUnauthenticatedApiController(){
|
||||
return unauthenticatedApiController;
|
||||
}
|
||||
|
||||
public void authenticate(Activity activity, Instance instance){
|
||||
authenticatingInstance=instance;
|
||||
new CreateOAuthApp()
|
||||
|
|
|
@ -68,14 +68,14 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
|
|||
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<StatusDisplayItem> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -838,6 +838,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||
list.invalidateItemDecorations();
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -927,6 +927,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||
}
|
||||
return false;
|
||||
});
|
||||
if(instance.isIceshrimpJs())
|
||||
languageButton.setVisibility(View.GONE); // hide language selector on Iceshrimp-JS because the feature is not supported
|
||||
|
||||
if (!GlobalUserPreferences.relocatePublishButton)
|
||||
publishButton.post(()->publishButton.setMinimumWidth(publishButton.getWidth()));
|
||||
|
||||
|
|
|
@ -22,6 +22,14 @@ public interface HasAccountID {
|
|||
return getInstance().map(Instance::isPixelfed).orElse(false);
|
||||
}
|
||||
|
||||
default boolean isInstanceIceshrimp() {
|
||||
return getInstance().map(Instance::isIceshrimp).orElse(false);
|
||||
}
|
||||
|
||||
default boolean isInstanceIceshrimpJs() {
|
||||
return getInstance().map(Instance::isIceshrimpJs).orElse(false);
|
||||
}
|
||||
|
||||
default Optional<Instance> getInstance() {
|
||||
return getSession().getInstance();
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
|||
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;
|
||||
|
@ -122,7 +123,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||
}
|
||||
|
||||
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);
|
||||
|
@ -316,13 +319,16 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||
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<list.getChildCount(); i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==n.status.getContentStatus() && ev.viewHolder!=holder){
|
||||
reactions.rebind();
|
||||
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(n.getID())){
|
||||
reactions.updateReactions(ev.reactions);
|
||||
}
|
||||
}
|
||||
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(n.getID())){
|
||||
text.rebind();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -991,7 +991,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
else hidePrivateNote();
|
||||
invalidateOptionsMenu();
|
||||
actionButton.setVisibility(View.VISIBLE);
|
||||
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
|
||||
notifyButton.setVisibility(relationship.following && !isInstanceIceshrimpJs() ? View.VISIBLE : View.GONE); // always hide notify button on Iceshrimp-JS because it's unsupported on the server
|
||||
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
|
||||
actionProgress.setIndeterminateTintList(actionButton.getTextColors());
|
||||
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
|
||||
|
|
|
@ -327,13 +327,16 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
|||
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
|
||||
for(Status s:data){
|
||||
if(s.getContentStatus().id.equals(ev.id)){
|
||||
s.getContentStatus().update(ev);
|
||||
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==s.getContentStatus() && ev.viewHolder!=holder){
|
||||
reactions.rebind();
|
||||
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(s.getID())){
|
||||
reactions.updateReactions(ev.reactions);
|
||||
}
|
||||
}
|
||||
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(s.getID())){
|
||||
text.rebind();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
@ -31,6 +32,9 @@ import androidx.annotation.NonNull;
|
|||
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;
|
||||
|
@ -60,6 +64,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||
private String currentQuery;
|
||||
|
||||
private boolean disableDiscover;
|
||||
private boolean isIceshrimp;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
|
@ -78,13 +83,17 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||
tabLayout=view.findViewById(R.id.tabbar);
|
||||
pager=view.findViewById(R.id.pager);
|
||||
|
||||
tabViews=new FrameLayout[4];
|
||||
Optional<Instance> 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]; // reduce array size on Iceshrimp to hide news feed because it's unsupported and always returns an empty list
|
||||
for(int i=0;i<tabViews.length;i++){
|
||||
FrameLayout tabView=new FrameLayout(getActivity());
|
||||
tabView.setId(switch(i){
|
||||
case 0 -> R.id.discover_posts;
|
||||
case 1 -> R.id.discover_hashtags;
|
||||
case 2 -> R.id.discover_news;
|
||||
case 2 -> isIceshrimp ? R.id.discover_users : R.id.discover_news; // skip unsupported news discovery on Iceshrimp
|
||||
case 3 -> R.id.discover_users;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||
});
|
||||
|
@ -126,10 +135,13 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||
accountsFragment=new DiscoverAccountsFragment();
|
||||
accountsFragment.setArguments(args);
|
||||
|
||||
getChildFragmentManager().beginTransaction()
|
||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||
transaction
|
||||
.add(R.id.discover_posts, postsFragment)
|
||||
.add(R.id.discover_hashtags, hashtagsFragment)
|
||||
.add(R.id.discover_news, newsFragment)
|
||||
.add(R.id.discover_hashtags, hashtagsFragment);
|
||||
if(!isIceshrimp) // skip unsupported news discovery on Iceshrimp
|
||||
transaction.add(R.id.discover_news, newsFragment);
|
||||
transaction
|
||||
.add(R.id.discover_users, accountsFragment)
|
||||
.commit();
|
||||
}
|
||||
|
@ -140,7 +152,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||
tab.setText(switch(position){
|
||||
case 0 -> R.string.posts;
|
||||
case 1 -> R.string.hashtags;
|
||||
case 2 -> R.string.news;
|
||||
case 2 -> isIceshrimp ? R.string.for_you : R.string.news; // skip unsupported news discovery on Iceshrimp
|
||||
case 3 -> R.string.for_you;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||
});
|
||||
|
@ -160,7 +172,6 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||
}
|
||||
});
|
||||
|
||||
disableDiscover=AccountSessionManager.get(accountID).getInstance().map(Instance::isAkkoma).orElse(false);
|
||||
searchView=view.findViewById(R.id.search_fragment);
|
||||
if(searchFragment==null){
|
||||
searchFragment=new SearchFragment();
|
||||
|
@ -262,7 +273,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||
return switch(page){
|
||||
case 0 -> postsFragment;
|
||||
case 1 -> hashtagsFragment;
|
||||
case 2 -> newsFragment;
|
||||
case 2 -> isIceshrimp ? accountsFragment : newsFragment; // skip unsupported news discovery on Iceshrimp
|
||||
case 3 -> accountsFragment;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||
};
|
||||
|
|
|
@ -54,7 +54,6 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
|
|||
languageResolver.from(s.preferences.postingDefaultLanguage).orElse(null);
|
||||
|
||||
List<ListItem<Void>> 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),
|
||||
customTabsItem=new ListItem<>(getString(R.string.settings_custom_tabs), getString(GlobalUserPreferences.useCustomTabs ? R.string.in_app_browser : R.string.system_browser), R.drawable.ic_fluent_open_24_regular, this::onCustomTabsClick),
|
||||
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)),
|
||||
showPostsWithoutAltItem=new CheckableListItem<>(R.string.mo_settings_show_posts_without_alt, R.string.mo_settings_show_posts_without_alt_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showPostsWithoutAlt, R.drawable.ic_fluent_eye_tracking_on_24_regular, i->toggleCheckableItem(showPostsWithoutAltItem)),
|
||||
|
@ -73,6 +72,11 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
|
|||
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(!isInstanceIceshrimpJs()) 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)
|
||||
);
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.joinmastodon.android.model.viewmodel.ListItem;
|
|||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -35,24 +36,27 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
|
|||
setTitle(R.string.sk_settings_instance);
|
||||
AccountSession s=AccountSessionManager.get(accountID);
|
||||
lp=s.getLocalPreferences();
|
||||
onDataLoaded(List.of(
|
||||
ArrayList<ListItem<Void>> items=new ArrayList<>(List.of(
|
||||
new ListItem<>(AccountSessionManager.get(accountID).domain, getString(R.string.settings_server_explanation), R.drawable.ic_fluent_server_24_regular, this::onServerClick),
|
||||
new ListItem<>(R.string.sk_settings_profile, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/profile")),
|
||||
new ListItem<>(R.string.sk_settings_posting, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/preferences/other")),
|
||||
new ListItem<>(R.string.sk_settings_auth, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/auth/edit"), 0, true),
|
||||
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),
|
||||
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))
|
||||
));
|
||||
if(!isInstanceIceshrimp()){
|
||||
items.add(4, 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()));
|
||||
items.add(5, 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));
|
||||
contentTypesItem.checkedChangeListener=checked->onContentTypeClick();
|
||||
defaultContentTypeItem.isEnabled=contentTypesItem.checked;
|
||||
}
|
||||
emojiReactionsItem.checkedChangeListener=checked->onEmojiReactionsClick();
|
||||
showEmojiReactionsItem.isEnabled=emojiReactionsItem.checked;
|
||||
localOnlyItem.checkedChangeListener=checked->onLocalOnlyClick();
|
||||
glitchModeItem.isEnabled=localOnlyItem.checked;
|
||||
onDataLoaded(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -61,6 +65,7 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
|
|||
@Override
|
||||
protected void onHidden(){
|
||||
super.onHidden();
|
||||
if(contentTypesItem!=null)
|
||||
lp.contentTypesEnabled=contentTypesItem.checked;
|
||||
lp.emojiReactionsEnabled=emojiReactionsItem.checked;
|
||||
lp.localOnlySupported=localOnlyItem.checked;
|
||||
|
@ -84,7 +89,8 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
|
|||
|
||||
private void resetDefaultContentType(){
|
||||
lp.defaultContentType=defaultContentTypeItem.isEnabled
|
||||
? ContentType.PLAIN : ContentType.UNSPECIFIED;
|
||||
? isInstanceIceshrimp() ? ContentType.MISSKEY_MARKDOWN
|
||||
: ContentType.PLAIN : ContentType.UNSPECIFIED;
|
||||
defaultContentTypeItem.subtitleRes=lp.defaultContentType.getName();
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
|||
));
|
||||
|
||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(account.domain);
|
||||
if(!instance.isAkkoma()){
|
||||
if(!instance.isAkkoma() && !instance.isIceshrimpJs()){ // hide filter settings on Akkoma and Iceshrimp-JS because the servers don't support the feature
|
||||
data.add(3, new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_fluent_filter_24_regular, this::onFiltersClick));
|
||||
}
|
||||
|
||||
|
|
|
@ -50,11 +50,11 @@ public class Announcement extends BaseModel implements DisplayItemsParent {
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,6 @@ public enum ContentType {
|
|||
}
|
||||
|
||||
public boolean supportedByInstance(Instance i) {
|
||||
return i.isAkkoma() || (this!=BBCODE && this!=MISSKEY_MARKDOWN);
|
||||
return i.isAkkoma() || i.isIceshrimp() || (this!=BBCODE && this!=MISSKEY_MARKDOWN);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ public class EmojiReaction {
|
|||
public String staticUrl;
|
||||
|
||||
public transient ImageLoaderRequest request;
|
||||
public transient boolean pendingChange=false;
|
||||
|
||||
public String getUrl(boolean playGifs){
|
||||
String idealUrl=playGifs ? url : staticUrl;
|
||||
|
@ -60,4 +61,18 @@ public class EmojiReaction {
|
|||
accounts.add(self);
|
||||
accountIds.add(self.id);
|
||||
}
|
||||
|
||||
public EmojiReaction copy() {
|
||||
EmojiReaction r=new EmojiReaction();
|
||||
r.accounts=accounts;
|
||||
r.accountIds=accountIds;
|
||||
r.count=count;
|
||||
r.me=me;
|
||||
r.name=name;
|
||||
r.url=url;
|
||||
r.staticUrl=staticUrl;
|
||||
r.request=request;
|
||||
r.pendingChange=pendingChange;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,14 +146,28 @@ public class Instance extends BaseModel{
|
|||
return ci;
|
||||
}
|
||||
|
||||
// This method has almost exclusively been used to improve support for
|
||||
// Akkoma with no regard for Pleroma, hence its name. However, it is
|
||||
// more likely than not that most uses should also apply to Pleroma,
|
||||
// so checking for that too probably causes more good than harm.
|
||||
public boolean isAkkoma() {
|
||||
return pleroma != null;
|
||||
return version.contains("compatible; Akkoma") || version.contains("compatible; Pleroma");
|
||||
}
|
||||
|
||||
public boolean isPixelfed() {
|
||||
return version.contains("compatible; Pixelfed");
|
||||
}
|
||||
|
||||
// For both Iceshrimp-JS and Iceshrimp.NET
|
||||
public boolean isIceshrimp() {
|
||||
return version.contains("compatible; Iceshrimp");
|
||||
}
|
||||
|
||||
// Only for Iceshrimp-JS
|
||||
public boolean isIceshrimpJs() {
|
||||
return version.contains("compatible; Iceshrimp "); // Iceshrimp.NET will not have a space immediately after
|
||||
}
|
||||
|
||||
public boolean hasFeature(Feature feature) {
|
||||
Optional<List<String>> pleromaFeatures = Optional.ofNullable(pleroma)
|
||||
.map(p -> p.metadata)
|
||||
|
@ -219,6 +233,7 @@ public class Instance extends BaseModel{
|
|||
public StatusesConfiguration statuses;
|
||||
public MediaAttachmentsConfiguration mediaAttachments;
|
||||
public PollsConfiguration polls;
|
||||
public ReactionsConfiguration reactions;
|
||||
}
|
||||
|
||||
@Parcel
|
||||
|
@ -246,6 +261,12 @@ public class Instance extends BaseModel{
|
|||
public long maxExpiration;
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class ReactionsConfiguration {
|
||||
public int maxReactions;
|
||||
public String defaultReaction;
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class V2 extends BaseModel {
|
||||
public V2.Configuration configuration;
|
||||
|
|
|
@ -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;
|
||||
|
@ -33,16 +34,24 @@ import org.joinmastodon.android.api.requests.statuses.PleromaDeleteStatusReactio
|
|||
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;
|
||||
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;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import org.joinmastodon.android.ui.views.EmojiReactionButton;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
|
@ -62,6 +71,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
private final boolean hideEmpty, forAnnouncement, playGifs;
|
||||
private final String accountID;
|
||||
private static final float ALPHA_DISABLED=0.55f;
|
||||
private boolean forceShow=false;
|
||||
|
||||
public EmojiReactionsStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status, String accountID, boolean hideEmpty, boolean forAnnouncement) {
|
||||
super(parentID, parentFragment);
|
||||
|
@ -90,6 +100,10 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
}
|
||||
|
||||
public boolean isHidden(){
|
||||
if(forceShow){
|
||||
forceShow=false;
|
||||
return false;
|
||||
}
|
||||
return status.reactions.isEmpty() && hideEmpty;
|
||||
}
|
||||
|
||||
|
@ -101,7 +115,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
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<Status> cb, Runnable err){
|
||||
setActionProgressVisible(vh, true);
|
||||
boolean ak=parentFragment.isInstanceAkkoma();
|
||||
boolean keepSpinning=delete && count == 1;
|
||||
|
@ -113,7 +127,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
@Override
|
||||
public void onSuccess(Object result){
|
||||
if(!keepSpinning) setActionProgressVisible(vh, false);
|
||||
cb.run();
|
||||
cb.accept(null);
|
||||
}
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
|
@ -130,7 +144,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
@Override
|
||||
public void onSuccess(Status result){
|
||||
if(!keepSpinning) setActionProgressVisible(vh, false);
|
||||
cb.run();
|
||||
cb.accept(result);
|
||||
}
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
|
@ -151,6 +165,8 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
private final ProgressBar progress;
|
||||
private final EmojiReactionsAdapter adapter;
|
||||
private final ListImageLoaderWrapper imgLoader;
|
||||
private int meReactionCount=0;
|
||||
private Instance instance;
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent) {
|
||||
super(activity, R.layout.display_item_emoji_reactions, parent);
|
||||
|
@ -171,6 +187,13 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
if(emojiKeyboard != null) root.removeView(emojiKeyboard.getView());
|
||||
addButton.setSelected(false);
|
||||
AccountSession session=item.parentFragment.getSession();
|
||||
instance=item.parentFragment.getInstance().get();
|
||||
if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0){
|
||||
meReactionCount=(int) item.status.reactions.stream().filter(r->r.me).count();
|
||||
boolean canReact=meReactionCount<instance.configuration.reactions.maxReactions;
|
||||
addButton.setClickable(canReact);
|
||||
addButton.setAlpha(canReact ? 1 : ALPHA_DISABLED);
|
||||
}
|
||||
item.status.reactions.forEach(r->r.request=r.getUrl(item.playGifs)!=null
|
||||
? new UrlImageLoaderRequest(r.getUrl(item.playGifs), 0, V.sp(24))
|
||||
: null);
|
||||
|
@ -182,17 +205,33 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
emojiKeyboard.setListener(this);
|
||||
space.setVisibility(View.GONE);
|
||||
root.addView(emojiKeyboard.getView());
|
||||
boolean hidden=item.isHidden();
|
||||
root.setVisibility(hidden ? View.GONE : View.VISIBLE);
|
||||
line.setVisibility(hidden ? View.GONE : View.VISIBLE);
|
||||
updateVisibility(item.isHidden(), true);
|
||||
imgLoader.updateImages();
|
||||
adapter.notifyDataSetChanged();
|
||||
|
||||
if(!GlobalUserPreferences.showDividers || item.isHidden())
|
||||
return;
|
||||
|
||||
StatusDisplayItem next=getNextVisibleDisplayItem().orElse(null);
|
||||
if(next!=null && !next.parentID.equals(item.parentID)) next=null;
|
||||
if(next instanceof ExtendedFooterStatusDisplayItem)
|
||||
itemView.setPadding(0, 0, 0, V.dp(12));
|
||||
else
|
||||
itemView.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
private void updateVisibility(boolean hidden, boolean force){
|
||||
int visibility=hidden ? View.GONE : View.VISIBLE;
|
||||
if(!force && visibility==root.getVisibility())
|
||||
return;
|
||||
root.setVisibility(visibility);
|
||||
line.setVisibility(visibility);
|
||||
line.setPadding(
|
||||
list.getPaddingLeft(),
|
||||
hidden ? 0 : V.dp(8),
|
||||
list.getPaddingRight(),
|
||||
item.forAnnouncement ? V.dp(8) : 0
|
||||
);
|
||||
imgLoader.updateImages();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void hideEmojiKeyboard(){
|
||||
|
@ -244,19 +283,32 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
}
|
||||
}
|
||||
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();
|
||||
int pos=status.reactions.stream()
|
||||
.filter(r->r.name.equals(info!=null ? info.shortcode : emoji))
|
||||
.findFirst()
|
||||
.map(r->status.reactions.indexOf(r))
|
||||
.orElse(item.status.reactions.size());
|
||||
boolean previouslyEmpty=item.status.reactions.isEmpty();
|
||||
item.status.reactions.add(pos, info!=null ? EmojiReaction.of(info, me) : EmojiReaction.of(emoji, me));
|
||||
adapter.notifyItemRangeInserted(pos, 1);
|
||||
if(previouslyEmpty)
|
||||
adapter.notifyItemChanged(pos);
|
||||
else
|
||||
adapter.notifyItemInserted(pos);
|
||||
RecyclerView.SmoothScroller scroller=new LinearSmoothScroller(list.getContext());
|
||||
scroller.setTargetPosition(pos);
|
||||
list.getLayoutManager().startSmoothScroll(scroller);
|
||||
updateMeReactionCount(false);
|
||||
}else{
|
||||
finalExisting.add(me);
|
||||
adapter.notifyItemChanged(item.status.reactions.indexOf(finalExisting));
|
||||
}
|
||||
if(instance.isIceshrimpJs() && 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);
|
||||
}
|
||||
|
@ -278,6 +330,99 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
}
|
||||
}
|
||||
|
||||
private void updateAddButtonClickable() {
|
||||
if(instance==null || instance.configuration==null || instance.configuration.reactions==null || instance.configuration.reactions.maxReactions==0)
|
||||
return;
|
||||
boolean canReact=meReactionCount<instance.configuration.reactions.maxReactions;
|
||||
addButton.setClickable(canReact);
|
||||
|
||||
ObjectAnimator anim=ObjectAnimator.ofFloat(
|
||||
addButton, View.ALPHA,
|
||||
canReact ? ALPHA_DISABLED : 1,
|
||||
canReact ? 1 : ALPHA_DISABLED);
|
||||
anim.setDuration(200);
|
||||
anim.start();
|
||||
}
|
||||
|
||||
private void updateMeReactionCount(boolean deleting) {
|
||||
meReactionCount=Math.max(0, meReactionCount + (deleting ? -1 : 1));
|
||||
updateAddButtonClickable();
|
||||
}
|
||||
|
||||
public void updateReactions(List<EmojiReaction> reactions){
|
||||
item.status.reactions=new ArrayList<>(item.status.reactions); // I don't know how, but this seemingly fixes a bug
|
||||
|
||||
List<EmojiReaction> toRemove=new ArrayList<>();
|
||||
for(int i=0;i<item.status.reactions.size();i++){
|
||||
EmojiReaction reaction=item.status.reactions.get(i);
|
||||
Optional<EmojiReaction> newReactionOptional=reactions.stream().filter(r->r.name.equals(reaction.name)).findFirst();
|
||||
if(newReactionOptional.isEmpty()){ // deleted reactions
|
||||
toRemove.add(reaction);
|
||||
continue;
|
||||
}
|
||||
|
||||
// changed reactions
|
||||
EmojiReaction newReaction=newReactionOptional.get();
|
||||
if(reaction.count!=newReaction.count || reaction.me!=newReaction.me || reaction.pendingChange!=newReaction.pendingChange){
|
||||
if(newReaction.pendingChange){
|
||||
View holderView=list.getChildAt(i);
|
||||
if(holderView!=null){
|
||||
EmojiReactionViewHolder reactionHolder=(EmojiReactionViewHolder) list.getChildViewHolder(holderView);
|
||||
item.setActionProgressVisible(reactionHolder, true);
|
||||
}
|
||||
}else{
|
||||
item.status.reactions.set(i, newReaction);
|
||||
adapter.notifyItemChanged(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.reverse(toRemove);
|
||||
for(EmojiReaction r:toRemove){
|
||||
int index=item.status.reactions.indexOf(r);
|
||||
item.status.reactions.remove(index);
|
||||
adapter.notifyItemRemoved(index);
|
||||
}
|
||||
|
||||
boolean pendingAddReaction=false;
|
||||
for(int i=0;i<reactions.size();i++){
|
||||
EmojiReaction reaction=reactions.get(i);
|
||||
if(item.status.reactions.stream().anyMatch(r->r.name.equals(reaction.name)))
|
||||
continue;
|
||||
|
||||
// new reactions
|
||||
if(reaction.pendingChange){
|
||||
pendingAddReaction=true;
|
||||
item.forceShow=true;
|
||||
continue;
|
||||
}
|
||||
boolean previouslyEmpty=item.status.reactions.isEmpty();
|
||||
item.status.reactions.add(i, reaction);
|
||||
if(previouslyEmpty)
|
||||
adapter.notifyItemChanged(i);
|
||||
else
|
||||
adapter.notifyItemInserted(i);
|
||||
RecyclerView.SmoothScroller scroller=new LinearSmoothScroller(list.getContext());
|
||||
scroller.setTargetPosition(i);
|
||||
list.getLayoutManager().startSmoothScroll(scroller);
|
||||
}
|
||||
if(pendingAddReaction){
|
||||
progress.setVisibility(View.VISIBLE);
|
||||
addButton.setClickable(false);
|
||||
addButton.setAlpha(ALPHA_DISABLED);
|
||||
}else{
|
||||
progress.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
int newMeReactionCount=(int) reactions.stream().filter(r->r.me || r.pendingChange).count();
|
||||
if (newMeReactionCount!=meReactionCount){
|
||||
meReactionCount=newMeReactionCount;
|
||||
updateAddButtonClickable();
|
||||
}
|
||||
|
||||
updateVisibility(reactions.isEmpty() && item.hideEmpty, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
View child=list.getChildAt(index);
|
||||
|
@ -330,7 +475,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
}
|
||||
|
||||
private static class EmojiReactionViewHolder extends BindableViewHolder<Pair<EmojiReactionsStatusDisplayItem, EmojiReaction>> implements ImageLoaderViewHolder{
|
||||
private final ProgressBarButton btn;
|
||||
private final EmojiReactionButton btn;
|
||||
private final ProgressBar progress;
|
||||
|
||||
public EmojiReactionViewHolder(Context context, RecyclerView list){
|
||||
|
@ -356,6 +501,12 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
|
||||
@Override
|
||||
public void onBind(Pair<EmojiReactionsStatusDisplayItem, EmojiReaction> item){
|
||||
if(item.second.pendingChange){
|
||||
itemView.setVisibility(View.GONE);
|
||||
return;
|
||||
}else{
|
||||
itemView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
item.first.setActionProgressVisible(this, false);
|
||||
EmojiReactionsStatusDisplayItem parent=item.first;
|
||||
EmojiReaction reaction=item.second;
|
||||
|
@ -371,10 +522,25 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
btn.setCompoundDrawablesRelative(item.first.placeholder, null, null, null);
|
||||
}
|
||||
btn.setSelected(reaction.me);
|
||||
if(parent.parentFragment.isInstanceIceshrimpJs() && 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, ()->{
|
||||
EmojiReactionsAdapter adapter = (EmojiReactionsAdapter) getBindingAdapter();
|
||||
Instance instance = adapter.parentHolder.instance;
|
||||
if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0 &&
|
||||
adapter.parentHolder.meReactionCount >= instance.configuration.reactions.maxReactions &&
|
||||
!reaction.me){
|
||||
return;
|
||||
}
|
||||
|
||||
boolean deleting=reaction.me;
|
||||
parent.createRequest(reaction.name, reaction.count, deleting, this, (status)->{
|
||||
for(int i=0; i<parent.status.reactions.size(); i++){
|
||||
EmojiReaction r=parent.status.reactions.get(i);
|
||||
if(!r.name.equals(reaction.name)) continue;
|
||||
|
@ -394,6 +560,14 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
|||
adapter.parentHolder.root.setVisibility(View.GONE);
|
||||
adapter.parentHolder.line.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0){
|
||||
adapter.parentHolder.updateMeReactionCount(deleting);
|
||||
}
|
||||
if(instance.isIceshrimpJs() && 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());
|
||||
|
|
|
@ -46,7 +46,6 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
|
|||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
public final Status status;
|
||||
private final String accountID;
|
||||
public boolean hideCounts;
|
||||
|
||||
|
@ -316,17 +315,16 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||
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);
|
||||
});
|
||||
|
@ -335,6 +333,20 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||
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);
|
||||
if(item.status.favourited && !GlobalUserPreferences.reduceMotion && !GlobalUserPreferences.likeIcon) {
|
||||
favorite.startAnimation(spin);
|
||||
}
|
||||
}, 300);
|
||||
bindText(favorites, item.status.favouritesCount);
|
||||
}
|
||||
|
||||
private void onFavoriteClick(View v){
|
||||
if(item.status.preview) return;
|
||||
applyInteraction(v, status -> {
|
||||
|
|
|
@ -113,7 +113,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||
}
|
||||
|
||||
public static HeaderStatusDisplayItem fromAnnouncement(Announcement a, Status fakeStatus, Account instanceUser, BaseStatusListFragment parentFragment, String accountID, Consumer<String> 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;
|
||||
|
|
|
@ -276,6 +276,8 @@ public abstract class StatusDisplayItem{
|
|||
|
||||
if(statusForContent.quote!=null) {
|
||||
int quoteInlineIndex=statusForContent.content.lastIndexOf("<span class=\"quote-inline\"><br/><br/>RE:");
|
||||
if(quoteInlineIndex==-1)
|
||||
quoteInlineIndex=statusForContent.content.lastIndexOf("<span class=\"quote-inline\"><br><br>RE:");
|
||||
if(quoteInlineIndex!=-1)
|
||||
statusForContent.content=statusForContent.content.substring(0, quoteInlineIndex);
|
||||
else {
|
||||
|
|
|
@ -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 && !isEnabled())
|
||||
handler.postDelayed(this::performLongClick, ViewConfiguration.getLongPressTimeout());
|
||||
if(action==MotionEvent.ACTION_UP)
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
android:indeterminate="true"
|
||||
android:outlineProvider="none"
|
||||
android:visibility="gone"/>
|
||||
<org.joinmastodon.android.ui.views.ProgressBarButton
|
||||
<org.joinmastodon.android.ui.views.EmojiReactionButton
|
||||
android:id="@+id/btn"
|
||||
style="@style/Widget.Mastodon.M3.Button.Outlined.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
Loading…
Add table
Reference in a new issue