Improvements for Pleroma/Akkoma (#445)

* Reply Visibility on Plemora

* Sort statuses in thread

* Get default visibility and language from account if preferences fail

* Fix for Mentions tab in notifications on Pleroma

* Mark status as sensitive if not already when spoilertext is present

* Integrating Pleroma quoting for new posts

* move string to strings_sk

* use null instead of empty string

* change string

* fix crash due to null value

* update string

---------

Co-authored-by: sk <sk22@mailbox.org>
This commit is contained in:
Jacoco
2023-03-16 00:49:11 +01:00
committed by GitHub
parent 374626cb32
commit f852dac1e5
13 changed files with 184 additions and 39 deletions

View File

@@ -204,6 +204,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private List<EmojiCategory> customEmojis;
private CustomEmojiPopupKeyboard emojiKeyboard;
private Status replyTo;
private Status quote;
private String initialText;
private String uuid;
private int pollDuration=24*3600;
@@ -252,6 +253,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
editingStatus=Parcels.unwrap(getArguments().getParcelable("editStatus"));
if(getArguments().containsKey("replyTo"))
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
if(getArguments().containsKey("quote"))
quote=Parcels.unwrap(getArguments().getParcelable("quote"));
if(instance==null){
Nav.finish(this);
return;
@@ -607,7 +610,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
});
spoilerEdit.addTextChangedListener(new SimpleTextWatcher(e->updateCharCounter()));
if(replyTo!=null){
if(replyTo!=null || quote!=null){
Status status = quote!=null ? quote : replyTo;
View replyWrap = view.findViewById(R.id.reply_wrap);
scrollView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
int scrollHeight = scrollView.getHeight();
@@ -633,13 +637,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
originalPost.setOnClickListener(v->{
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(replyTo));
args.putParcelable("status", Parcels.wrap(status));
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
Nav.go(getActivity(), ThreadFragment.class, args);
});
ImageView avatar = view.findViewById(R.id.avatar);
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(replyTo.account.avatar));
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(status.account.avatar));
ViewOutlineProvider roundCornersOutline=new ViewOutlineProvider(){
@Override
public void getOutline(View view, Outline outline){
@@ -651,15 +655,15 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
avatar.setOnClickListener(v->{
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("profileAccount", Parcels.wrap(replyTo.account));
args.putParcelable("profileAccount", Parcels.wrap(status.account));
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
Nav.go(getActivity(), ProfileFragment.class, args);
});
((TextView) view.findViewById(R.id.name)).setText(replyTo.account.displayName);
((TextView) view.findViewById(R.id.username)).setText(replyTo.account.getDisplayUsername());
((TextView) view.findViewById(R.id.name)).setText(status.account.displayName);
((TextView) view.findViewById(R.id.username)).setText(status.account.getDisplayUsername());
view.findViewById(R.id.visibility).setVisibility(View.GONE);
Drawable visibilityIcon = getActivity().getDrawable(switch(replyTo.visibility){
Drawable visibilityIcon = getActivity().getDrawable(switch(status.visibility){
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
case UNLISTED -> R.drawable.ic_fluent_lock_open_20_regular;
case PRIVATE -> R.drawable.ic_fluent_lock_closed_20_filled;
@@ -670,36 +674,36 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
moreBtn.setImageDrawable(visibilityIcon);
moreBtn.setBackground(null);
TextView timestamp = view.findViewById(R.id.timestamp);
if (replyTo.editedAt==null) timestamp.setText(UiUtils.formatRelativeTimestamp(getContext(), replyTo.createdAt));
else timestamp.setText(getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(getContext(), replyTo.editedAt)));
if (replyTo.spoilerText != null && !replyTo.spoilerText.isBlank()) {
if (status.editedAt==null) timestamp.setText(UiUtils.formatRelativeTimestamp(getContext(), status.createdAt));
else timestamp.setText(getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(getContext(), status.editedAt)));
if (status.spoilerText != null && !status.spoilerText.isBlank()) {
view.findViewById(R.id.spoiler_header).setVisibility(View.VISIBLE);
((TextView) view.findViewById(R.id.spoiler_title_inline)).setText(replyTo.spoilerText);
((TextView) view.findViewById(R.id.spoiler_title_inline)).setText(status.spoilerText);
}
SpannableStringBuilder content = HtmlParser.parse(replyTo.content, replyTo.emojis, replyTo.mentions, replyTo.tags, accountID);
SpannableStringBuilder content = HtmlParser.parse(status.content, status.emojis, status.mentions, status.tags, accountID);
LinkedTextView text = view.findViewById(R.id.text);
if (content.length() > 0) text.setText(content);
else view.findViewById(R.id.display_item_text).setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16)));
replyText.setText(getString(R.string.in_reply_to, replyTo.account.displayName));
int visibilityNameRes = switch (replyTo.visibility) {
replyText.setText(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.displayName));
int visibilityNameRes = switch (status.visibility) {
case PUBLIC -> R.string.visibility_public;
case UNLISTED -> R.string.sk_visibility_unlisted;
case PRIVATE -> R.string.visibility_followers_only;
case DIRECT -> R.string.visibility_private;
case LOCAL -> R.string.sk_local_only;
};
replyText.setContentDescription(getString(R.string.in_reply_to, replyTo.account.displayName) + ". " + getString(R.string.post_visibility) + ": " + getString(visibilityNameRes));
replyText.setContentDescription(getString(R.string.in_reply_to, status.account.displayName) + ". " + getString(R.string.post_visibility) + ": " + getString(visibilityNameRes));
replyText.setOnClickListener(v->{
scrollView.smoothScrollTo(0, 0);
});
ArrayList<String> mentions=new ArrayList<>();
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
if(!replyTo.account.id.equals(ownID))
mentions.add('@'+replyTo.account.acct);
for(Mention mention:replyTo.mentions){
if(!status.account.id.equals(ownID))
mentions.add('@'+status.account.acct);
for(Mention mention:status.mentions){
if(mention.id.equals(ownID))
continue;
String m='@'+mention.acct;
@@ -712,17 +716,17 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
ignoreSelectionChanges=true;
mainEditText.setSelection(mainEditText.length());
ignoreSelectionChanges=false;
if(!TextUtils.isEmpty(replyTo.spoilerText)){
if(!TextUtils.isEmpty(status.spoilerText)){
hasSpoiler=true;
spoilerEdit.setVisibility(View.VISIBLE);
if(GlobalUserPreferences.prefixRepliesWithRe && !replyTo.spoilerText.startsWith("re: ")){
spoilerEdit.setText("re: " + replyTo.spoilerText);
if(GlobalUserPreferences.prefixRepliesWithRe && !status.spoilerText.startsWith("re: ")){
spoilerEdit.setText("re: " + status.spoilerText);
}else{
spoilerEdit.setText(replyTo.spoilerText);
spoilerEdit.setText(status.spoilerText);
}
spoilerBtn.setSelected(true);
}
if (replyTo.language != null && !replyTo.language.isEmpty()) updateLanguage(replyTo.language);
if (status.language != null && !status.language.isEmpty()) updateLanguage(status.language);
}
}else if (editingStatus==null || editingStatus.inReplyToId==null){
// TODO: remove workaround after https://github.com/mastodon/mastodon-android/issues/341 gets fixed
@@ -1089,6 +1093,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(hasSpoiler && spoilerEdit.length()>0){
req.spoilerText=spoilerEdit.getText().toString();
}
if(quote != null){
req.quoteId=quote.id;
}
if(uuid==null)
uuid=UUID.randomUUID().toString();

View File

@@ -213,6 +213,22 @@ public class SettingsFragment extends MastodonToolbarFragment{
GlobalUserPreferences.showReplies=i.checked;
GlobalUserPreferences.save();
}));
if (instance.pleroma != null) {
items.add(new ButtonItem(R.string.sk_settings_reply_visibility, R.drawable.ic_fluent_chat_24_regular, b->{
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
popupMenu.inflate(R.menu.reply_visibility);
popupMenu.setOnMenuItemClickListener(item -> this.onReplyVisibilityChanged(item, b));
b.setOnTouchListener(popupMenu.getDragToOpenListener());
b.setOnClickListener(v->popupMenu.show());
b.setText(GlobalUserPreferences.replyVisibility == null ?
R.string.sk_settings_reply_visibility_all :
switch(GlobalUserPreferences.replyVisibility){
case "following" -> R.string.sk_settings_reply_visibility_following;
case "self" -> R.string.sk_settings_reply_visibility_self;
default -> R.string.sk_settings_reply_visibility_all;
});
}));
}
items.add(new SwitchItem(R.string.sk_settings_show_boosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, GlobalUserPreferences.showBoosts, i->{
GlobalUserPreferences.showBoosts=i.checked;
GlobalUserPreferences.save();
@@ -480,6 +496,25 @@ public class SettingsFragment extends MastodonToolbarFragment{
}
}
private boolean onReplyVisibilityChanged(MenuItem item, Button btn){
String pref = null;
int id = item.getItemId();
if (id == R.id.reply_visibility_following) pref = "following";
else if (id == R.id.reply_visibility_self) pref = "self";
GlobalUserPreferences.replyVisibility=pref;
GlobalUserPreferences.save();
btn.setText(GlobalUserPreferences.replyVisibility == null ?
R.string.sk_settings_reply_visibility_all :
switch(GlobalUserPreferences.replyVisibility){
case "following" -> R.string.sk_settings_reply_visibility_following;
case "self" -> R.string.sk_settings_reply_visibility_self;
default -> R.string.sk_settings_reply_visibility_all;
});
return true;
}
private void restartActivityToApplyNewTheme(){
// Calling activity.recreate() causes a black screen for like half a second.
// So, let's take a screenshot and overlay it on top to create the illusion of a smoother transition.

View File

@@ -5,9 +5,12 @@ import android.view.View;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusContext;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
@@ -19,6 +22,7 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -68,6 +72,30 @@ public class ThreadFragment extends StatusListFragment{
data.add(mainStatus);
onAppendItems(Collections.singletonList(mainStatus));
}
AccountSession account=AccountSessionManager.getInstance().getAccount(accountID);
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(account.domain);
if(instance.pleroma != null){
List<String> threadIds=new ArrayList<>();
threadIds.add(mainStatus.id);
for(Status s:result.descendants){
if(threadIds.contains(s.inReplyToId)){
threadIds.add(s.id);
}
}
threadIds.add(mainStatus.inReplyToId);
for(int i=result.ancestors.size()-1; i >= 0; i--){
Status s=result.ancestors.get(i);
if(s.inReplyToId != null && threadIds.contains(s.id)){
threadIds.add(s.inReplyToId);
}
}
result.ancestors=result.ancestors.stream().filter(s -> threadIds.contains(s.id)).collect(Collectors.toList());
result.descendants=getDescendantsOrdered(mainStatus.id,
result.descendants.stream()
.filter(s -> threadIds.contains(s.id))
.collect(Collectors.toList()));
}
result.descendants=filterStatuses(result.descendants);
result.ancestors=filterStatuses(result.ancestors);
if(footerProgress!=null)
@@ -90,6 +118,24 @@ public class ThreadFragment extends StatusListFragment{
.exec(accountID);
}
private List<Status> getDescendantsOrdered(String id, List<Status> statuses){
List<Status> out=new ArrayList<>();
for(Status s:getDirectDescendants(id, statuses)){
out.add(s);
getDirectDescendants(s.id, statuses).forEach(d ->{
out.add(d);
out.addAll(getDescendantsOrdered(d.id, statuses));
});
}
return out;
}
private List<Status> getDirectDescendants(String id, List<Status> statuses){
return statuses.stream()
.filter(s -> s.inReplyToId.equals(id))
.collect(Collectors.toList());
}
private List<Status> filterStatuses(List<Status> statuses){
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,Filter.FilterContext.THREAD);
return statuses.stream()