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>{
|
public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||||
private static final String TAG="MastodonAPIRequest";
|
private static final String TAG="MastodonAPIRequest";
|
||||||
|
|
||||||
|
private static MastodonAPIController unauthenticatedApiController=new MastodonAPIController(null);
|
||||||
|
|
||||||
private String domain;
|
private String domain;
|
||||||
private AccountSession account;
|
private AccountSession account;
|
||||||
private String path;
|
private String path;
|
||||||
|
@ -95,14 +97,14 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||||
|
|
||||||
public MastodonAPIRequest<T> execNoAuth(String domain){
|
public MastodonAPIRequest<T> execNoAuth(String domain){
|
||||||
this.domain=domain;
|
this.domain=domain;
|
||||||
AccountSessionManager.getInstance().getUnauthenticatedApiController().submitRequest(this);
|
unauthenticatedApiController.submitRequest(this);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MastodonAPIRequest<T> exec(String domain, Token token){
|
public MastodonAPIRequest<T> exec(String domain, Token token){
|
||||||
this.domain=domain;
|
this.domain=domain;
|
||||||
this.token=token;
|
this.token=token;
|
||||||
AccountSessionManager.getInstance().getUnauthenticatedApiController().submitRequest(this);
|
unauthenticatedApiController.submitRequest(this);
|
||||||
return 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.SetStatusFavorited;
|
||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusMuted;
|
import org.joinmastodon.android.api.requests.statuses.SetStatusMuted;
|
||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
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.ReblogDeletedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
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.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
|
@ -42,6 +51,9 @@ public class StatusInteractionController{
|
||||||
if(!Looper.getMainLooper().isCurrentThread())
|
if(!Looper.getMainLooper().isCurrentThread())
|
||||||
throw new IllegalStateException("Can only be called from main thread");
|
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);
|
SetStatusFavorited current=runningFavoriteRequests.remove(status.id);
|
||||||
if(current!=null){
|
if(current!=null){
|
||||||
current.cancel();
|
current.cancel();
|
||||||
|
@ -54,6 +66,7 @@ public class StatusInteractionController{
|
||||||
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
|
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
|
||||||
cb.accept(result);
|
cb.accept(result);
|
||||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
|
if(instance.isIceshrimpJs()) E.post(new EmojiReactionsUpdatedEvent(status.id, result.reactions, false, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -63,12 +76,58 @@ public class StatusInteractionController{
|
||||||
status.favourited=!favorited;
|
status.favourited=!favorited;
|
||||||
cb.accept(status);
|
cb.accept(status);
|
||||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
|
if(instance.isIceshrimpJs()) E.post(new EmojiReactionsUpdatedEvent(status.id, status.reactions, false, null));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
runningFavoriteRequests.put(status.id, req);
|
runningFavoriteRequests.put(status.id, req);
|
||||||
status.favourited=favorited;
|
status.favourited=favorited;
|
||||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
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){
|
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.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
@ -23,6 +24,7 @@ import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class AccountLocalPreferences{
|
public class AccountLocalPreferences{
|
||||||
private final SharedPreferences prefs;
|
private final SharedPreferences prefs;
|
||||||
|
@ -72,19 +74,20 @@ public class AccountLocalPreferences{
|
||||||
// preReplySheet=prefs.getBoolean("preReplySheet", false);
|
// preReplySheet=prefs.getBoolean("preReplySheet", false);
|
||||||
|
|
||||||
// MEGALODON
|
// MEGALODON
|
||||||
|
Optional<Instance> instance=session.getInstance();
|
||||||
showReplies=prefs.getBoolean("showReplies", true);
|
showReplies=prefs.getBoolean("showReplies", true);
|
||||||
showBoosts=prefs.getBoolean("showBoosts", true);
|
showBoosts=prefs.getBoolean("showBoosts", true);
|
||||||
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new ArrayList<>());
|
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new ArrayList<>());
|
||||||
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
||||||
defaultContentType=enumValue(ContentType.class, prefs.getString("defaultContentType", ContentType.PLAIN.name()));
|
defaultContentType=enumValue(ContentType.class, prefs.getString("defaultContentType", instance.map(Instance::isIceshrimp).orElse(false) ? ContentType.MISSKEY_MARKDOWN.name() : ContentType.PLAIN.name()));
|
||||||
contentTypesEnabled=prefs.getBoolean("contentTypesEnabled", true);
|
contentTypesEnabled=prefs.getBoolean("contentTypesEnabled", instance.map(i->!i.isIceshrimp()).orElse(false));
|
||||||
timelines=fromJson(prefs.getString("timelines", null), timelinesType, TimelineDefinition.getDefaultTimelines(session.getID()));
|
timelines=fromJson(prefs.getString("timelines", null), timelinesType, TimelineDefinition.getDefaultTimelines(session.getID()));
|
||||||
localOnlySupported=prefs.getBoolean("localOnlySupported", false);
|
localOnlySupported=prefs.getBoolean("localOnlySupported", false);
|
||||||
glitchInstance=prefs.getBoolean("glitchInstance", false);
|
glitchInstance=prefs.getBoolean("glitchInstance", false);
|
||||||
publishButtonText=prefs.getString("publishButtonText", null);
|
publishButtonText=prefs.getString("publishButtonText", null);
|
||||||
timelineReplyVisibility=prefs.getString("timelineReplyVisibility", null);
|
timelineReplyVisibility=prefs.getString("timelineReplyVisibility", null);
|
||||||
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
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()));
|
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
|
||||||
color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null;
|
color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null;
|
||||||
recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>());
|
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, List<EmojiCategory>> customEmojis=new HashMap<>();
|
||||||
private HashMap<String, Long> instancesLastUpdated=new HashMap<>();
|
private HashMap<String, Long> instancesLastUpdated=new HashMap<>();
|
||||||
private HashMap<String, Instance> instances=new HashMap<>();
|
private HashMap<String, Instance> instances=new HashMap<>();
|
||||||
private MastodonAPIController unauthenticatedApiController=new MastodonAPIController(null);
|
|
||||||
private Instance authenticatingInstance;
|
private Instance authenticatingInstance;
|
||||||
private Application authenticatingApp;
|
private Application authenticatingApp;
|
||||||
private String lastActiveAccountID;
|
private String lastActiveAccountID;
|
||||||
|
@ -110,7 +109,7 @@ public class AccountSessionManager{
|
||||||
Log.e(TAG, "Error loading accounts", x);
|
Log.e(TAG, "Error loading accounts", x);
|
||||||
}
|
}
|
||||||
lastActiveAccountID=prefs.getString("lastActiveAccount", null);
|
lastActiveAccountID=prefs.getString("lastActiveAccount", null);
|
||||||
MastodonAPIController.runInBackground(()->readInstanceInfo(domains));
|
readInstanceInfo(domains);
|
||||||
maybeUpdateShortcuts();
|
maybeUpdateShortcuts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,11 +247,6 @@ public class AccountSessionManager{
|
||||||
maybeUpdateShortcuts();
|
maybeUpdateShortcuts();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public MastodonAPIController getUnauthenticatedApiController(){
|
|
||||||
return unauthenticatedApiController;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void authenticate(Activity activity, Instance instance){
|
public void authenticate(Activity activity, Instance instance){
|
||||||
authenticatingInstance=instance;
|
authenticatingInstance=instance;
|
||||||
new CreateOAuthApp()
|
new CreateOAuthApp()
|
||||||
|
|
|
@ -68,14 +68,14 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
|
||||||
instanceUser.url = "https://"+session.domain+"/about";
|
instanceUser.url = "https://"+session.domain+"/about";
|
||||||
instanceUser.avatar = instanceUser.avatarStatic = instance.thumbnail;
|
instanceUser.avatar = instanceUser.avatarStatic = instance.thumbnail;
|
||||||
instanceUser.emojis = List.of();
|
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);
|
TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus, true);
|
||||||
textItem.textSelectable = true;
|
textItem.textSelectable = true;
|
||||||
|
|
||||||
List<StatusDisplayItem> items=new ArrayList<>();
|
List<StatusDisplayItem> items=new ArrayList<>();
|
||||||
items.add(HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead));
|
items.add(HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead));
|
||||||
items.add(textItem);
|
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;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -838,6 +838,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||||
list.invalidateItemDecorations();
|
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
|
@Override
|
||||||
public String getAccountID(){
|
public String getAccountID(){
|
||||||
return accountID;
|
return accountID;
|
||||||
|
|
|
@ -927,6 +927,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
if(instance.isIceshrimpJs())
|
||||||
|
languageButton.setVisibility(View.GONE); // hide language selector on Iceshrimp-JS because the feature is not supported
|
||||||
|
|
||||||
if (!GlobalUserPreferences.relocatePublishButton)
|
if (!GlobalUserPreferences.relocatePublishButton)
|
||||||
publishButton.post(()->publishButton.setMinimumWidth(publishButton.getWidth()));
|
publishButton.post(()->publishButton.setMinimumWidth(publishButton.getWidth()));
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,14 @@ public interface HasAccountID {
|
||||||
return getInstance().map(Instance::isPixelfed).orElse(false);
|
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() {
|
default Optional<Instance> getInstance() {
|
||||||
return getSession().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.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.PaginatedResponse;
|
import org.joinmastodon.android.model.PaginatedResponse;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
@ -122,7 +123,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationHeaderStatusDisplayItem titleItem;
|
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;
|
titleItem=null;
|
||||||
}else{
|
}else{
|
||||||
titleItem=new NotificationHeaderStatusDisplayItem(n.id, this, n, accountID);
|
titleItem=new NotificationHeaderStatusDisplayItem(n.id, this, n, accountID);
|
||||||
|
@ -316,13 +319,16 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||||
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
|
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
|
||||||
for(Notification n : data){
|
for(Notification n : data){
|
||||||
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
|
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++){
|
for(int i=0; i<list.getChildCount(); i++){
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||||
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==n.status.getContentStatus() && ev.viewHolder!=holder){
|
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==n.status.getContentStatus() && ev.viewHolder!=holder){
|
||||||
reactions.rebind();
|
reactions.updateReactions(ev.reactions);
|
||||||
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(n.getID())){
|
}
|
||||||
|
}
|
||||||
|
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();
|
text.rebind();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -991,7 +991,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||||
else hidePrivateNote();
|
else hidePrivateNote();
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
actionButton.setVisibility(View.VISIBLE);
|
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);
|
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
|
||||||
actionProgress.setIndeterminateTintList(actionButton.getTextColors());
|
actionProgress.setIndeterminateTintList(actionButton.getTextColors());
|
||||||
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
|
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
|
||||||
|
|
|
@ -327,13 +327,16 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||||
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
|
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
|
||||||
for(Status s:data){
|
for(Status s:data){
|
||||||
if(s.getContentStatus().id.equals(ev.id)){
|
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++){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||||
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==s.getContentStatus() && ev.viewHolder!=holder){
|
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==s.getContentStatus() && ev.viewHolder!=holder){
|
||||||
reactions.rebind();
|
reactions.updateReactions(ev.reactions);
|
||||||
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(s.getID())){
|
}
|
||||||
|
}
|
||||||
|
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();
|
text.rebind();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.joinmastodon.android.fragments.discover;
|
package org.joinmastodon.android.fragments.discover;
|
||||||
|
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
|
import android.app.FragmentTransaction;
|
||||||
import android.app.assist.AssistContent;
|
import android.app.assist.AssistContent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -31,6 +32,9 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
|
@ -60,6 +64,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||||
private String currentQuery;
|
private String currentQuery;
|
||||||
|
|
||||||
private boolean disableDiscover;
|
private boolean disableDiscover;
|
||||||
|
private boolean isIceshrimp;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
@ -78,13 +83,17 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||||
tabLayout=view.findViewById(R.id.tabbar);
|
tabLayout=view.findViewById(R.id.tabbar);
|
||||||
pager=view.findViewById(R.id.pager);
|
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++){
|
for(int i=0;i<tabViews.length;i++){
|
||||||
FrameLayout tabView=new FrameLayout(getActivity());
|
FrameLayout tabView=new FrameLayout(getActivity());
|
||||||
tabView.setId(switch(i){
|
tabView.setId(switch(i){
|
||||||
case 0 -> R.id.discover_posts;
|
case 0 -> R.id.discover_posts;
|
||||||
case 1 -> R.id.discover_hashtags;
|
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;
|
case 3 -> R.id.discover_users;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||||
});
|
});
|
||||||
|
@ -126,10 +135,13 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||||
accountsFragment=new DiscoverAccountsFragment();
|
accountsFragment=new DiscoverAccountsFragment();
|
||||||
accountsFragment.setArguments(args);
|
accountsFragment.setArguments(args);
|
||||||
|
|
||||||
getChildFragmentManager().beginTransaction()
|
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||||
|
transaction
|
||||||
.add(R.id.discover_posts, postsFragment)
|
.add(R.id.discover_posts, postsFragment)
|
||||||
.add(R.id.discover_hashtags, hashtagsFragment)
|
.add(R.id.discover_hashtags, hashtagsFragment);
|
||||||
.add(R.id.discover_news, newsFragment)
|
if(!isIceshrimp) // skip unsupported news discovery on Iceshrimp
|
||||||
|
transaction.add(R.id.discover_news, newsFragment);
|
||||||
|
transaction
|
||||||
.add(R.id.discover_users, accountsFragment)
|
.add(R.id.discover_users, accountsFragment)
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
@ -140,7 +152,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||||
tab.setText(switch(position){
|
tab.setText(switch(position){
|
||||||
case 0 -> R.string.posts;
|
case 0 -> R.string.posts;
|
||||||
case 1 -> R.string.hashtags;
|
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;
|
case 3 -> R.string.for_you;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
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);
|
searchView=view.findViewById(R.id.search_fragment);
|
||||||
if(searchFragment==null){
|
if(searchFragment==null){
|
||||||
searchFragment=new SearchFragment();
|
searchFragment=new SearchFragment();
|
||||||
|
@ -262,7 +273,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||||
return switch(page){
|
return switch(page){
|
||||||
case 0 -> postsFragment;
|
case 0 -> postsFragment;
|
||||||
case 1 -> hashtagsFragment;
|
case 1 -> hashtagsFragment;
|
||||||
case 2 -> newsFragment;
|
case 2 -> isIceshrimp ? accountsFragment : newsFragment; // skip unsupported news discovery on Iceshrimp
|
||||||
case 3 -> accountsFragment;
|
case 3 -> accountsFragment;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
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);
|
languageResolver.from(s.preferences.postingDefaultLanguage).orElse(null);
|
||||||
|
|
||||||
List<ListItem<Void>> items = new ArrayList<>(List.of(
|
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),
|
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)),
|
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)),
|
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))
|
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(
|
if(isInstanceAkkoma()) items.add(
|
||||||
replyVisibilityItem=new ListItem<>(R.string.sk_settings_reply_visibility, getReplyVisibilityString(), R.drawable.ic_fluent_chat_24_regular, this::onReplyVisibilityClick)
|
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.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -35,24 +36,27 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
|
||||||
setTitle(R.string.sk_settings_instance);
|
setTitle(R.string.sk_settings_instance);
|
||||||
AccountSession s=AccountSessionManager.get(accountID);
|
AccountSession s=AccountSessionManager.get(accountID);
|
||||||
lp=s.getLocalPreferences();
|
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<>(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_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_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),
|
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()),
|
emojiReactionsItem=new CheckableListItem<>(R.string.sk_settings_emoji_reactions, R.string.sk_settings_emoji_reactions_explanation, CheckableListItem.Style.SWITCH, lp.emojiReactionsEnabled, R.drawable.ic_fluent_emoji_laugh_24_regular, i->onEmojiReactionsClick()),
|
||||||
showEmojiReactionsItem=new ListItem<>(R.string.sk_settings_show_emoji_reactions, getShowEmojiReactionsString(), R.drawable.ic_fluent_emoji_24_regular, this::onShowEmojiReactionsClick, 0, true),
|
showEmojiReactionsItem=new ListItem<>(R.string.sk_settings_show_emoji_reactions, getShowEmojiReactionsString(), R.drawable.ic_fluent_emoji_24_regular, this::onShowEmojiReactionsClick, 0, 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()),
|
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))
|
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();
|
contentTypesItem.checkedChangeListener=checked->onContentTypeClick();
|
||||||
defaultContentTypeItem.isEnabled=contentTypesItem.checked;
|
defaultContentTypeItem.isEnabled=contentTypesItem.checked;
|
||||||
|
}
|
||||||
emojiReactionsItem.checkedChangeListener=checked->onEmojiReactionsClick();
|
emojiReactionsItem.checkedChangeListener=checked->onEmojiReactionsClick();
|
||||||
showEmojiReactionsItem.isEnabled=emojiReactionsItem.checked;
|
showEmojiReactionsItem.isEnabled=emojiReactionsItem.checked;
|
||||||
localOnlyItem.checkedChangeListener=checked->onLocalOnlyClick();
|
localOnlyItem.checkedChangeListener=checked->onLocalOnlyClick();
|
||||||
glitchModeItem.isEnabled=localOnlyItem.checked;
|
glitchModeItem.isEnabled=localOnlyItem.checked;
|
||||||
|
onDataLoaded(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -61,6 +65,7 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
|
||||||
@Override
|
@Override
|
||||||
protected void onHidden(){
|
protected void onHidden(){
|
||||||
super.onHidden();
|
super.onHidden();
|
||||||
|
if(contentTypesItem!=null)
|
||||||
lp.contentTypesEnabled=contentTypesItem.checked;
|
lp.contentTypesEnabled=contentTypesItem.checked;
|
||||||
lp.emojiReactionsEnabled=emojiReactionsItem.checked;
|
lp.emojiReactionsEnabled=emojiReactionsItem.checked;
|
||||||
lp.localOnlySupported=localOnlyItem.checked;
|
lp.localOnlySupported=localOnlyItem.checked;
|
||||||
|
@ -84,7 +89,8 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
|
||||||
|
|
||||||
private void resetDefaultContentType(){
|
private void resetDefaultContentType(){
|
||||||
lp.defaultContentType=defaultContentTypeItem.isEnabled
|
lp.defaultContentType=defaultContentTypeItem.isEnabled
|
||||||
? ContentType.PLAIN : ContentType.UNSPECIFIED;
|
? isInstanceIceshrimp() ? ContentType.MISSKEY_MARKDOWN
|
||||||
|
: ContentType.PLAIN : ContentType.UNSPECIFIED;
|
||||||
defaultContentTypeItem.subtitleRes=lp.defaultContentType.getName();
|
defaultContentTypeItem.subtitleRes=lp.defaultContentType.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
||||||
));
|
));
|
||||||
|
|
||||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(account.domain);
|
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));
|
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<>();
|
if(reactions==null) reactions=new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Status toStatus() {
|
public Status toStatus(boolean isIceshrimp) {
|
||||||
Status s=Status.ofFake(id, content, publishedAt);
|
Status s=Status.ofFake(id, content, publishedAt);
|
||||||
s.createdAt=startsAt != null ? startsAt : publishedAt;
|
s.createdAt=startsAt != null ? startsAt : publishedAt;
|
||||||
s.reactions=reactions;
|
s.reactions=reactions;
|
||||||
if(updatedAt != null) s.editedAt=updatedAt;
|
if(updatedAt != null && (!isIceshrimp || !updatedAt.equals(publishedAt))) s.editedAt=updatedAt;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,6 @@ public enum ContentType {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean supportedByInstance(Instance i) {
|
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 String staticUrl;
|
||||||
|
|
||||||
public transient ImageLoaderRequest request;
|
public transient ImageLoaderRequest request;
|
||||||
|
public transient boolean pendingChange=false;
|
||||||
|
|
||||||
public String getUrl(boolean playGifs){
|
public String getUrl(boolean playGifs){
|
||||||
String idealUrl=playGifs ? url : staticUrl;
|
String idealUrl=playGifs ? url : staticUrl;
|
||||||
|
@ -60,4 +61,18 @@ public class EmojiReaction {
|
||||||
accounts.add(self);
|
accounts.add(self);
|
||||||
accountIds.add(self.id);
|
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;
|
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() {
|
public boolean isAkkoma() {
|
||||||
return pleroma != null;
|
return version.contains("compatible; Akkoma") || version.contains("compatible; Pleroma");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPixelfed() {
|
public boolean isPixelfed() {
|
||||||
return version.contains("compatible; Pixelfed");
|
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) {
|
public boolean hasFeature(Feature feature) {
|
||||||
Optional<List<String>> pleromaFeatures = Optional.ofNullable(pleroma)
|
Optional<List<String>> pleromaFeatures = Optional.ofNullable(pleroma)
|
||||||
.map(p -> p.metadata)
|
.map(p -> p.metadata)
|
||||||
|
@ -219,6 +233,7 @@ public class Instance extends BaseModel{
|
||||||
public StatusesConfiguration statuses;
|
public StatusesConfiguration statuses;
|
||||||
public MediaAttachmentsConfiguration mediaAttachments;
|
public MediaAttachmentsConfiguration mediaAttachments;
|
||||||
public PollsConfiguration polls;
|
public PollsConfiguration polls;
|
||||||
|
public ReactionsConfiguration reactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
|
@ -246,6 +261,12 @@ public class Instance extends BaseModel{
|
||||||
public long maxExpiration;
|
public long maxExpiration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Parcel
|
||||||
|
public static class ReactionsConfiguration {
|
||||||
|
public int maxReactions;
|
||||||
|
public String defaultReaction;
|
||||||
|
}
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
public static class V2 extends BaseModel {
|
public static class V2 extends BaseModel {
|
||||||
public V2.Configuration configuration;
|
public V2.Configuration configuration;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.joinmastodon.android.ui.displayitems;
|
package org.joinmastodon.android.ui.displayitems;
|
||||||
|
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Paint;
|
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.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
||||||
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.account_list.StatusEmojiReactionsListFragment;
|
import org.joinmastodon.android.fragments.account_list.StatusEmojiReactionsListFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
import org.joinmastodon.android.model.EmojiReaction;
|
import org.joinmastodon.android.model.EmojiReaction;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.CustomEmojiPopupKeyboard;
|
import org.joinmastodon.android.ui.CustomEmojiPopupKeyboard;
|
||||||
import org.joinmastodon.android.ui.utils.TextDrawable;
|
import org.joinmastodon.android.ui.utils.TextDrawable;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
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.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
|
@ -62,6 +71,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||||
private final boolean hideEmpty, forAnnouncement, playGifs;
|
private final boolean hideEmpty, forAnnouncement, playGifs;
|
||||||
private final String accountID;
|
private final String accountID;
|
||||||
private static final float ALPHA_DISABLED=0.55f;
|
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) {
|
public EmojiReactionsStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status, String accountID, boolean hideEmpty, boolean forAnnouncement) {
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
|
@ -90,6 +100,10 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHidden(){
|
public boolean isHidden(){
|
||||||
|
if(forceShow){
|
||||||
|
forceShow=false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return status.reactions.isEmpty() && hideEmpty;
|
return status.reactions.isEmpty() && hideEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +115,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||||
vh.btn.setAlpha(visible ? ALPHA_DISABLED : 1);
|
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);
|
setActionProgressVisible(vh, true);
|
||||||
boolean ak=parentFragment.isInstanceAkkoma();
|
boolean ak=parentFragment.isInstanceAkkoma();
|
||||||
boolean keepSpinning=delete && count == 1;
|
boolean keepSpinning=delete && count == 1;
|
||||||
|
@ -113,7 +127,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Object result){
|
public void onSuccess(Object result){
|
||||||
if(!keepSpinning) setActionProgressVisible(vh, false);
|
if(!keepSpinning) setActionProgressVisible(vh, false);
|
||||||
cb.run();
|
cb.accept(null);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
|
@ -130,7 +144,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
if(!keepSpinning) setActionProgressVisible(vh, false);
|
if(!keepSpinning) setActionProgressVisible(vh, false);
|
||||||
cb.run();
|
cb.accept(result);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
|
@ -151,6 +165,8 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||||
private final ProgressBar progress;
|
private final ProgressBar progress;
|
||||||
private final EmojiReactionsAdapter adapter;
|
private final EmojiReactionsAdapter adapter;
|
||||||
private final ListImageLoaderWrapper imgLoader;
|
private final ListImageLoaderWrapper imgLoader;
|
||||||
|
private int meReactionCount=0;
|
||||||
|
private Instance instance;
|
||||||
|
|
||||||
public Holder(Activity activity, ViewGroup parent) {
|
public Holder(Activity activity, ViewGroup parent) {
|
||||||
super(activity, R.layout.display_item_emoji_reactions, 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());
|
if(emojiKeyboard != null) root.removeView(emojiKeyboard.getView());
|
||||||
addButton.setSelected(false);
|
addButton.setSelected(false);
|
||||||
AccountSession session=item.parentFragment.getSession();
|
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
|
item.status.reactions.forEach(r->r.request=r.getUrl(item.playGifs)!=null
|
||||||
? new UrlImageLoaderRequest(r.getUrl(item.playGifs), 0, V.sp(24))
|
? new UrlImageLoaderRequest(r.getUrl(item.playGifs), 0, V.sp(24))
|
||||||
: null);
|
: null);
|
||||||
|
@ -182,17 +205,33 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||||
emojiKeyboard.setListener(this);
|
emojiKeyboard.setListener(this);
|
||||||
space.setVisibility(View.GONE);
|
space.setVisibility(View.GONE);
|
||||||
root.addView(emojiKeyboard.getView());
|
root.addView(emojiKeyboard.getView());
|
||||||
boolean hidden=item.isHidden();
|
updateVisibility(item.isHidden(), true);
|
||||||
root.setVisibility(hidden ? View.GONE : View.VISIBLE);
|
imgLoader.updateImages();
|
||||||
line.setVisibility(hidden ? View.GONE : View.VISIBLE);
|
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(
|
line.setPadding(
|
||||||
list.getPaddingLeft(),
|
list.getPaddingLeft(),
|
||||||
hidden ? 0 : V.dp(8),
|
hidden ? 0 : V.dp(8),
|
||||||
list.getPaddingRight(),
|
list.getPaddingRight(),
|
||||||
item.forAnnouncement ? V.dp(8) : 0
|
item.forAnnouncement ? V.dp(8) : 0
|
||||||
);
|
);
|
||||||
imgLoader.updateImages();
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideEmojiKeyboard(){
|
private void hideEmojiKeyboard(){
|
||||||
|
@ -244,19 +283,32 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EmojiReaction finalExisting=existing;
|
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();
|
resetBtn.run();
|
||||||
if(finalExisting==null){
|
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));
|
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());
|
RecyclerView.SmoothScroller scroller=new LinearSmoothScroller(list.getContext());
|
||||||
scroller.setTargetPosition(pos);
|
scroller.setTargetPosition(pos);
|
||||||
list.getLayoutManager().startSmoothScroll(scroller);
|
list.getLayoutManager().startSmoothScroll(scroller);
|
||||||
|
updateMeReactionCount(false);
|
||||||
}else{
|
}else{
|
||||||
finalExisting.add(me);
|
finalExisting.add(me);
|
||||||
adapter.notifyItemChanged(item.status.reactions.indexOf(finalExisting));
|
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));
|
E.post(new EmojiReactionsUpdatedEvent(item.status.id, item.status.reactions, countBefore==0, adapter.parentHolder));
|
||||||
}, resetBtn).exec(item.accountID);
|
}, 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
|
@Override
|
||||||
public void setImage(int index, Drawable image){
|
public void setImage(int index, Drawable image){
|
||||||
View child=list.getChildAt(index);
|
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 static class EmojiReactionViewHolder extends BindableViewHolder<Pair<EmojiReactionsStatusDisplayItem, EmojiReaction>> implements ImageLoaderViewHolder{
|
||||||
private final ProgressBarButton btn;
|
private final EmojiReactionButton btn;
|
||||||
private final ProgressBar progress;
|
private final ProgressBar progress;
|
||||||
|
|
||||||
public EmojiReactionViewHolder(Context context, RecyclerView list){
|
public EmojiReactionViewHolder(Context context, RecyclerView list){
|
||||||
|
@ -356,6 +501,12 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(Pair<EmojiReactionsStatusDisplayItem, EmojiReaction> item){
|
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);
|
item.first.setActionProgressVisible(this, false);
|
||||||
EmojiReactionsStatusDisplayItem parent=item.first;
|
EmojiReactionsStatusDisplayItem parent=item.first;
|
||||||
EmojiReaction reaction=item.second;
|
EmojiReaction reaction=item.second;
|
||||||
|
@ -371,10 +522,25 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||||
btn.setCompoundDrawablesRelative(item.first.placeholder, null, null, null);
|
btn.setCompoundDrawablesRelative(item.first.placeholder, null, null, null);
|
||||||
}
|
}
|
||||||
btn.setSelected(reaction.me);
|
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->{
|
btn.setOnClickListener(e->{
|
||||||
boolean deleting=reaction.me;
|
|
||||||
parent.createRequest(reaction.name, reaction.count, deleting, this, ()->{
|
|
||||||
EmojiReactionsAdapter adapter = (EmojiReactionsAdapter) getBindingAdapter();
|
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++){
|
for(int i=0; i<parent.status.reactions.size(); i++){
|
||||||
EmojiReaction r=parent.status.reactions.get(i);
|
EmojiReaction r=parent.status.reactions.get(i);
|
||||||
if(!r.name.equals(reaction.name)) continue;
|
if(!r.name.equals(reaction.name)) continue;
|
||||||
|
@ -394,6 +560,14 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||||
adapter.parentHolder.root.setVisibility(View.GONE);
|
adapter.parentHolder.root.setVisibility(View.GONE);
|
||||||
adapter.parentHolder.line.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));
|
E.post(new EmojiReactionsUpdatedEvent(parent.status.id, parent.status.reactions, parent.status.reactions.isEmpty(), adapter.parentHolder));
|
||||||
adapter.parentHolder.imgLoader.updateImages();
|
adapter.parentHolder.imgLoader.updateImages();
|
||||||
}, null).exec(parent.parentFragment.getAccountID());
|
}, null).exec(parent.parentFragment.getAccountID());
|
||||||
|
|
|
@ -46,7 +46,6 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class FooterStatusDisplayItem extends StatusDisplayItem{
|
public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||||
public final Status status;
|
|
||||||
private final String accountID;
|
private final String accountID;
|
||||||
public boolean hideCounts;
|
public boolean hideCounts;
|
||||||
|
|
||||||
|
@ -316,17 +315,16 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||||
UiUtils.opacityIn(v);
|
UiUtils.opacityIn(v);
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", item.accountID);
|
args.putString("account", item.accountID);
|
||||||
AccountSession accountSession=AccountSessionManager.getInstance().getAccount(item.accountID);
|
Instance instance=AccountSessionManager.get(item.accountID).getInstance().get();
|
||||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
|
if(instance.isAkkoma() || instance.isIceshrimp()){
|
||||||
if(instance.pleroma == null){
|
args.putParcelable("quote", Parcels.wrap(item.status));
|
||||||
|
}else{
|
||||||
StringBuilder prefilledText = new StringBuilder().append("\n\n");
|
StringBuilder prefilledText = new StringBuilder().append("\n\n");
|
||||||
String ownID = AccountSessionManager.getInstance().getAccount(item.accountID).self.id;
|
String ownID = AccountSessionManager.getInstance().getAccount(item.accountID).self.id;
|
||||||
if (!item.status.account.id.equals(ownID)) prefilledText.append('@').append(item.status.account.acct).append(' ');
|
if (!item.status.account.id.equals(ownID)) prefilledText.append('@').append(item.status.account.acct).append(' ');
|
||||||
prefilledText.append(item.status.url);
|
prefilledText.append(item.status.url);
|
||||||
args.putString("prefilledText", prefilledText.toString());
|
args.putString("prefilledText", prefilledText.toString());
|
||||||
args.putInt("selectionStart", 0);
|
args.putInt("selectionStart", 0);
|
||||||
}else{
|
|
||||||
args.putParcelable("quote", Parcels.wrap(item.status));
|
|
||||||
}
|
}
|
||||||
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||||
});
|
});
|
||||||
|
@ -335,6 +333,20 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||||
return true;
|
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){
|
private void onFavoriteClick(View v){
|
||||||
if(item.status.preview) return;
|
if(item.status.preview) return;
|
||||||
applyInteraction(v, status -> {
|
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) {
|
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.announcement = a;
|
||||||
item.consumeReadAnnouncement = consumeReadID;
|
item.consumeReadAnnouncement = consumeReadID;
|
||||||
return item;
|
return item;
|
||||||
|
|
|
@ -276,6 +276,8 @@ public abstract class StatusDisplayItem{
|
||||||
|
|
||||||
if(statusForContent.quote!=null) {
|
if(statusForContent.quote!=null) {
|
||||||
int quoteInlineIndex=statusForContent.content.lastIndexOf("<span class=\"quote-inline\"><br/><br/>RE:");
|
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)
|
if(quoteInlineIndex!=-1)
|
||||||
statusForContent.content=statusForContent.content.substring(0, quoteInlineIndex);
|
statusForContent.content=statusForContent.content.substring(0, quoteInlineIndex);
|
||||||
else {
|
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:indeterminate="true"
|
||||||
android:outlineProvider="none"
|
android:outlineProvider="none"
|
||||||
android:visibility="gone"/>
|
android:visibility="gone"/>
|
||||||
<org.joinmastodon.android.ui.views.ProgressBarButton
|
<org.joinmastodon.android.ui.views.EmojiReactionButton
|
||||||
android:id="@+id/btn"
|
android:id="@+id/btn"
|
||||||
style="@style/Widget.Mastodon.M3.Button.Outlined.Icon"
|
style="@style/Widget.Mastodon.M3.Button.Outlined.Icon"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
Loading…
Add table
Reference in a new issue