374 lines
15 KiB
Java
374 lines
15 KiB
Java
package org.joinmastodon.android.ui.displayitems;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.app.Activity;
|
|
import android.app.ProgressDialog;
|
|
import android.content.ClipData;
|
|
import android.content.ClipboardManager;
|
|
import android.graphics.drawable.Animatable;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.os.Parcel;
|
|
import android.text.SpannableStringBuilder;
|
|
import android.text.TextUtils;
|
|
import android.view.Menu;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.ImageView;
|
|
import android.widget.PopupMenu;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import org.joinmastodon.android.GlobalUserPreferences;
|
|
import org.joinmastodon.android.R;
|
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
|
import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText;
|
|
import org.joinmastodon.android.api.requests.statuses.SetStatusConversationMuted;
|
|
import org.joinmastodon.android.api.requests.statuses.SetStatusPinned;
|
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
import org.joinmastodon.android.fragments.AddAccountToListsFragment;
|
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
|
import org.joinmastodon.android.fragments.NotificationsListFragment;
|
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
|
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
|
import org.joinmastodon.android.model.Account;
|
|
import org.joinmastodon.android.model.Attachment;
|
|
import org.joinmastodon.android.model.Relationship;
|
|
import org.joinmastodon.android.model.Status;
|
|
import org.joinmastodon.android.ui.OutlineProviders;
|
|
import org.joinmastodon.android.ui.Snackbar;
|
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
|
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
import org.parceler.Parcels;
|
|
|
|
import java.time.Instant;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
|
|
import androidx.annotation.LayoutRes;
|
|
import me.grishka.appkit.Nav;
|
|
import me.grishka.appkit.api.APIRequest;
|
|
import me.grishka.appkit.api.Callback;
|
|
import me.grishka.appkit.api.ErrorResponse;
|
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
|
import me.grishka.appkit.utils.V;
|
|
|
|
public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|
private Account user;
|
|
private Instant createdAt;
|
|
private ImageLoaderRequest avaRequest;
|
|
private String accountID;
|
|
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
|
private SpannableStringBuilder parsedName;
|
|
public final Status status;
|
|
private boolean hasVisibilityToggle;
|
|
boolean needBottomPadding;
|
|
private String extraText;
|
|
|
|
public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID, Status status, String extraText){
|
|
super(parentID, parentFragment);
|
|
this.user=user;
|
|
this.createdAt=createdAt;
|
|
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? user.avatar : user.avatarStatic, V.dp(50), V.dp(50));
|
|
this.accountID=accountID;
|
|
parsedName=new SpannableStringBuilder(user.displayName);
|
|
this.status=status;
|
|
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
|
|
HtmlParser.parseCustomEmoji(parsedName, user.emojis);
|
|
emojiHelper.setText(parsedName);
|
|
if(status!=null){
|
|
hasVisibilityToggle=status.sensitive || !TextUtils.isEmpty(status.spoilerText);
|
|
if(!hasVisibilityToggle && !status.mediaAttachments.isEmpty()){
|
|
for(Attachment att:status.mediaAttachments){
|
|
if(att.type!=Attachment.Type.AUDIO){
|
|
hasVisibilityToggle=true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.extraText=extraText;
|
|
}
|
|
|
|
@Override
|
|
public Type getType(){
|
|
return Type.HEADER;
|
|
}
|
|
|
|
@Override
|
|
public int getImageCount(){
|
|
return 1+emojiHelper.getImageCount();
|
|
}
|
|
|
|
@Override
|
|
public ImageLoaderRequest getImageRequest(int index){
|
|
if(index>0){
|
|
return emojiHelper.getImageRequest(index-1);
|
|
}
|
|
return avaRequest;
|
|
}
|
|
|
|
public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
|
|
private final TextView name, timeAndUsername, extraText;
|
|
private final ImageView avatar, more;
|
|
private final PopupMenu optionsMenu;
|
|
private final View clickableThing;
|
|
|
|
public Holder(Activity activity, ViewGroup parent){
|
|
this(activity, R.layout.display_item_header, parent);
|
|
}
|
|
|
|
protected Holder(Activity activity, @LayoutRes int layout, ViewGroup parent){
|
|
super(activity, layout, parent);
|
|
name=findViewById(R.id.name);
|
|
timeAndUsername=findViewById(R.id.time_and_username);
|
|
avatar=findViewById(R.id.avatar);
|
|
more=findViewById(R.id.more);
|
|
extraText=findViewById(R.id.extra_text);
|
|
clickableThing=findViewById(R.id.clickable_thing);
|
|
clickableThing.setOnClickListener(this::onAvaClick);
|
|
avatar.setOutlineProvider(OutlineProviders.roundedRect(10));
|
|
avatar.setClipToOutline(true);
|
|
more.setOnClickListener(this::onMoreClick);
|
|
|
|
optionsMenu=new PopupMenu(activity, more);
|
|
optionsMenu.inflate(R.menu.post);
|
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI())
|
|
optionsMenu.getMenu().setGroupDividerEnabled(true);
|
|
optionsMenu.setOnMenuItemClickListener(menuItem->{
|
|
Account account=item.user;
|
|
Relationship relationship=item.parentFragment.getRelationship(account.id);
|
|
int id=menuItem.getItemId();
|
|
if(id==R.id.edit){
|
|
final Bundle args=new Bundle();
|
|
args.putString("account", item.parentFragment.getAccountID());
|
|
args.putParcelable("editStatus", Parcels.wrap(item.status));
|
|
if(TextUtils.isEmpty(item.status.content) && TextUtils.isEmpty(item.status.spoilerText)){
|
|
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
|
}else{
|
|
new GetStatusSourceText(item.status.id)
|
|
.setCallback(new Callback<>(){
|
|
@Override
|
|
public void onSuccess(GetStatusSourceText.Response result){
|
|
args.putString("sourceText", result.text);
|
|
args.putString("sourceSpoiler", result.spoilerText);
|
|
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
|
}
|
|
|
|
@Override
|
|
public void onError(ErrorResponse error){
|
|
error.showToast(item.parentFragment.getActivity());
|
|
}
|
|
})
|
|
.wrapProgress(item.parentFragment.getActivity(), R.string.loading, true)
|
|
.exec(item.parentFragment.getAccountID());
|
|
}
|
|
}else if(id==R.id.delete){
|
|
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
|
|
}else if(id==R.id.mute){
|
|
UiUtils.confirmToggleMuteUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, relationship!=null && relationship.muting, r->{});
|
|
}else if(id==R.id.block){
|
|
UiUtils.confirmToggleBlockUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, relationship!=null && relationship.blocking, r->{});
|
|
}else if(id==R.id.report){
|
|
Bundle args=new Bundle();
|
|
args.putString("account", item.parentFragment.getAccountID());
|
|
args.putParcelable("status", Parcels.wrap(item.status));
|
|
args.putParcelable("reportAccount", Parcels.wrap(item.status.account));
|
|
args.putParcelable("relationship", Parcels.wrap(relationship));
|
|
Nav.go(item.parentFragment.getActivity(), ReportReasonChoiceFragment.class, args);
|
|
}else if(id==R.id.open_in_browser){
|
|
UiUtils.launchWebBrowser(activity, item.status.url);
|
|
}else if(id==R.id.follow){
|
|
if(relationship==null)
|
|
return true;
|
|
ProgressDialog progress=new ProgressDialog(activity);
|
|
progress.setCancelable(false);
|
|
progress.setMessage(activity.getString(R.string.loading));
|
|
UiUtils.performAccountAction(activity, account, item.parentFragment.getAccountID(), relationship, null, visible->{
|
|
if(visible)
|
|
progress.show();
|
|
else
|
|
progress.dismiss();
|
|
}, rel->{
|
|
item.parentFragment.putRelationship(account.id, rel);
|
|
Toast.makeText(activity, activity.getString(rel.following ? R.string.followed_user : rel.requested ? R.string.following_user_requested : R.string.unfollowed_user, account.getDisplayUsername()), Toast.LENGTH_SHORT).show();
|
|
});
|
|
}else if(id==R.id.bookmark){
|
|
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked);
|
|
}else if(id==R.id.share){
|
|
UiUtils.openSystemShareSheet(activity, item.status);
|
|
}else if(id==R.id.translate){
|
|
item.parentFragment.togglePostTranslation(item.status, item.parentID);
|
|
}else if(id==R.id.add_to_list){
|
|
Bundle args=new Bundle();
|
|
args.putString("account", item.parentFragment.getAccountID());
|
|
args.putParcelable("targetAccount", Parcels.wrap(account));
|
|
Nav.go(activity, AddAccountToListsFragment.class, args);
|
|
}else if(id==R.id.copy_link){
|
|
activity.getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, item.status.url));
|
|
UiUtils.maybeShowTextCopiedToast(activity);
|
|
}else if(id==R.id.pin){
|
|
new SetStatusPinned(item.status.id, !item.status.pinned)
|
|
.setCallback(new Callback<>(){
|
|
@Override
|
|
public void onSuccess(Status result){
|
|
item.status.pinned=!item.status.pinned;
|
|
new Snackbar.Builder(activity)
|
|
.setText(item.status.pinned ? R.string.post_pinned : R.string.post_unpinned)
|
|
.show();
|
|
}
|
|
|
|
@Override
|
|
public void onError(ErrorResponse error){
|
|
error.showToast(activity);
|
|
}
|
|
})
|
|
.wrapProgress(activity, R.string.loading, true)
|
|
.exec(item.accountID);
|
|
}else if(id==R.id.mute_conversation){
|
|
new SetStatusConversationMuted(item.status.id, !item.status.muted)
|
|
.setCallback(new Callback<>(){
|
|
@Override
|
|
public void onSuccess(Status result){
|
|
// TODO snackbar?
|
|
item.status.muted=result.muted;
|
|
}
|
|
|
|
@Override
|
|
public void onError(ErrorResponse error){
|
|
error.showToast(activity);
|
|
}
|
|
})
|
|
.wrapProgress(activity, R.string.loading, true)
|
|
.exec(item.accountID);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
@SuppressLint("SetTextI18n")
|
|
@Override
|
|
public void onBind(HeaderStatusDisplayItem item){
|
|
name.setText(item.parsedName);
|
|
String time;
|
|
if(item.status==null || item.status.editedAt==null)
|
|
time=UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt);
|
|
else
|
|
time=item.parentFragment.getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(itemView.getContext(), item.status.editedAt));
|
|
|
|
timeAndUsername.setText(time+" · @"+item.user.acct);
|
|
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : V.dp(4));
|
|
if(TextUtils.isEmpty(item.extraText)){
|
|
extraText.setVisibility(View.GONE);
|
|
}else{
|
|
extraText.setVisibility(View.VISIBLE);
|
|
extraText.setText(item.extraText);
|
|
}
|
|
more.setVisibility(item.inset ? View.GONE : View.VISIBLE);
|
|
clickableThing.setClickable(!item.inset);
|
|
clickableThing.setContentDescription(item.parentFragment.getString(R.string.avatar_description, item.user.acct));
|
|
}
|
|
|
|
@Override
|
|
public void setImage(int index, Drawable drawable){
|
|
if(index>0){
|
|
item.emojiHelper.setImageDrawable(index-1, drawable);
|
|
name.invalidate();
|
|
}else{
|
|
avatar.setImageDrawable(drawable);
|
|
}
|
|
if(drawable instanceof Animatable)
|
|
((Animatable) drawable).start();
|
|
}
|
|
|
|
@Override
|
|
public void clearImage(int index){
|
|
if(index==0){
|
|
avatar.setImageResource(R.drawable.image_placeholder);
|
|
return;
|
|
}
|
|
setImage(index, null);
|
|
}
|
|
|
|
private void onAvaClick(View v){
|
|
Bundle args=new Bundle();
|
|
args.putString("account", item.accountID);
|
|
args.putParcelable("profileAccount", Parcels.wrap(item.user));
|
|
Nav.go(item.parentFragment.getActivity(), ProfileFragment.class, args);
|
|
}
|
|
|
|
private void onMoreClick(View v){
|
|
updateOptionsMenu();
|
|
optionsMenu.show();
|
|
}
|
|
|
|
private void updateOptionsMenu(){
|
|
if(item.parentFragment.getActivity()==null)
|
|
return;
|
|
Account account=item.user;
|
|
Relationship relationship=item.parentFragment.getRelationship(account.id);
|
|
Menu menu=optionsMenu.getMenu();
|
|
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
|
|
boolean canTranslate=item.status!=null && item.status.getContentStatus().isEligibleForTranslation();
|
|
MenuItem translate=menu.findItem(R.id.translate);
|
|
translate.setVisible(canTranslate);
|
|
if(canTranslate){
|
|
if(item.status.translationState==Status.TranslationState.SHOWN)
|
|
translate.setTitle(R.string.translation_show_original);
|
|
else
|
|
translate.setTitle(item.parentFragment.getString(R.string.translate_post, Locale.forLanguageTag(item.status.getContentStatus().language).getDisplayLanguage()));
|
|
}
|
|
menu.findItem(R.id.edit).setVisible(item.status!=null && isOwnPost);
|
|
menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost);
|
|
menu.findItem(R.id.open_in_browser).setVisible(item.status!=null);
|
|
MenuItem mute=menu.findItem(R.id.mute);
|
|
MenuItem block=menu.findItem(R.id.block);
|
|
MenuItem report=menu.findItem(R.id.report);
|
|
MenuItem follow=menu.findItem(R.id.follow);
|
|
MenuItem bookmark=menu.findItem(R.id.bookmark);
|
|
MenuItem pin=menu.findItem(R.id.pin);
|
|
MenuItem muteConversation=menu.findItem(R.id.mute_conversation);
|
|
if(item.status!=null){
|
|
bookmark.setVisible(true);
|
|
bookmark.setTitle(item.status.bookmarked ? R.string.remove_bookmark : R.string.add_bookmark);
|
|
pin.setVisible(item.status.pinned!=null);
|
|
if(item.status.pinned!=null){
|
|
pin.setTitle(item.status.pinned ? R.string.unpin_post : R.string.pin_post);
|
|
}
|
|
}else{
|
|
bookmark.setVisible(false);
|
|
pin.setVisible(false);
|
|
}
|
|
if(isOwnPost){
|
|
mute.setVisible(false);
|
|
block.setVisible(false);
|
|
report.setVisible(false);
|
|
follow.setVisible(false);
|
|
}else{
|
|
mute.setVisible(true);
|
|
block.setVisible(true);
|
|
report.setVisible(true);
|
|
follow.setVisible(relationship==null || relationship.following || (!relationship.blocking && !relationship.blockedBy && !relationship.domainBlocking && !relationship.muting));
|
|
mute.setTitle(item.parentFragment.getString(relationship!=null && relationship.muting ? R.string.unmute_user : R.string.mute_user, account.displayName));
|
|
block.setTitle(item.parentFragment.getString(relationship!=null && relationship.blocking ? R.string.unblock_user : R.string.block_user, account.displayName));
|
|
report.setTitle(item.parentFragment.getString(R.string.report_user, account.displayName));
|
|
follow.setTitle(item.parentFragment.getString(relationship!=null && relationship.following ? R.string.unfollow_user : R.string.follow_user, account.displayName));
|
|
}
|
|
if(item.status.muted!=null){
|
|
muteConversation.setVisible(isOwnPost || item.parentFragment instanceof NotificationsListFragment);
|
|
muteConversation.setTitle(item.status.muted ? R.string.unmute_conversation : R.string.mute_conversation);
|
|
}else{
|
|
muteConversation.setVisible(false);
|
|
}
|
|
menu.findItem(R.id.add_to_list).setVisible(relationship!=null && relationship.following);
|
|
}
|
|
}
|
|
}
|