More account/post actions

This commit is contained in:
Grishka 2022-04-12 23:04:58 +03:00
parent 6e6f73f2c0
commit 789d02d810
10 changed files with 217 additions and 37 deletions

View File

@ -4,8 +4,20 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Relationship;
public class SetAccountFollowed extends MastodonAPIRequest<Relationship>{ public class SetAccountFollowed extends MastodonAPIRequest<Relationship>{
public SetAccountFollowed(String id, boolean followed){ public SetAccountFollowed(String id, boolean followed, boolean showReblogs){
super(HttpMethod.POST, "/accounts/"+id+"/"+(followed ? "follow" : "unfollow"), Relationship.class); super(HttpMethod.POST, "/accounts/"+id+"/"+(followed ? "follow" : "unfollow"), Relationship.class);
if(followed)
setRequestBody(new Request(showReblogs, null));
else
setRequestBody(new Object()); setRequestBody(new Object());
} }
private static class Request{
public Boolean reblogs, notify;
public Request(Boolean reblogs, Boolean notify){
this.reblogs=reblogs;
this.notify=notify;
}
}
} }

View File

@ -0,0 +1,18 @@
package org.joinmastodon.android.api.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
public class SetDomainBlocked extends MastodonAPIRequest<Object>{
public SetDomainBlocked(String domain, boolean blocked){
super(blocked ? HttpMethod.POST : HttpMethod.DELETE, "/domain_blocks", Object.class);
setRequestBody(new Request(domain));
}
private static class Request{
public String domain;
public Request(String domain){
this.domain=domain;
}
}
}

View File

@ -39,6 +39,7 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses; import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount; import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials; import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment; import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
@ -479,14 +480,17 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(relationship==null) if(relationship==null)
return; return;
inflater.inflate(R.menu.profile, menu); inflater.inflate(R.menu.profile, menu);
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.displayName)); menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.displayName)); menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.displayName)); menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.displayName)); menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));
// String domain=account.getDomain(); if(relationship.following)
// if(domain!=null) menu.findItem(R.id.hide_boosts).setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getDisplayUsername()));
// menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, domain)); else
// else menu.findItem(R.id.hide_boosts).setVisible(false);
if(!account.isLocal())
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
else
menu.findItem(R.id.block_domain).setVisible(false); menu.findItem(R.id.block_domain).setVisible(false);
} }
@ -507,6 +511,28 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
args.putString("account", accountID); args.putString("account", accountID);
args.putParcelable("reportAccount", Parcels.wrap(account)); args.putParcelable("reportAccount", Parcels.wrap(account));
Nav.go(getActivity(), ReportReasonChoiceFragment.class, args); Nav.go(getActivity(), ReportReasonChoiceFragment.class, args);
}else if(id==R.id.open_in_browser){
UiUtils.launchWebBrowser(getActivity(), account.url);
}else if(id==R.id.block_domain){
UiUtils.confirmToggleBlockDomain(getActivity(), accountID, account.getDomain(), relationship.domainBlocking, ()->{
relationship.domainBlocking=!relationship.domainBlocking;
updateRelationship();
});
}else if(id==R.id.hide_boosts){
new SetAccountFollowed(account.id, true, !relationship.showingReblogs)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
updateRelationship(result);
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.loading, false)
.exec(accountID);
} }
return true; return true;
} }

View File

@ -124,7 +124,7 @@ public class ReportDoneFragment extends ToolbarFragment{
} }
private void onUnfollowClick(){ private void onUnfollowClick(){
new SetAccountFollowed(reportAccount.id, false) new SetAccountFollowed(reportAccount.id, false, false)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(Relationship result){ public void onSuccess(Relationship result){

View File

@ -160,6 +160,10 @@ public class Account extends BaseModel{
return parts.length==1 ? null : parts[1]; return parts.length==1 ? null : parts[1];
} }
public String getDisplayUsername(){
return '@'+acct;
}
@Override @Override
public String toString(){ public String toString(){
return "Account{"+ return "Account{"+

View File

@ -1,6 +1,7 @@
package org.joinmastodon.android.ui.displayitems; package org.joinmastodon.android.ui.displayitems;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Outline; import android.graphics.Outline;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@ -8,21 +9,25 @@ import android.os.Bundle;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewOutlineProvider; import android.view.ViewOutlineProvider;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment; import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper; import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
@ -30,8 +35,13 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.time.Instant; import java.time.Instant;
import java.util.Collections;
import java.util.List;
import me.grishka.appkit.Nav; 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.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
@ -94,6 +104,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{ public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView name, username, timestamp, extraText; private final TextView name, username, timestamp, extraText;
private final ImageView avatar, more, visibility; private final ImageView avatar, more, visibility;
private final PopupMenu optionsMenu;
private Relationship relationship;
private APIRequest<?> currentRelationshipRequest;
private static final ViewOutlineProvider roundCornersOutline=new ViewOutlineProvider(){ private static final ViewOutlineProvider roundCornersOutline=new ViewOutlineProvider(){
@Override @Override
@ -116,6 +129,46 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
avatar.setClipToOutline(true); avatar.setClipToOutline(true);
more.setOnClickListener(this::onMoreClick); more.setOnClickListener(this::onMoreClick);
visibility.setOnClickListener(v->item.parentFragment.onVisibilityIconClick(this)); visibility.setOnClickListener(v->item.parentFragment.onVisibilityIconClick(this));
optionsMenu=new PopupMenu(activity, more);
optionsMenu.inflate(R.menu.post);
optionsMenu.setOnMenuItemClickListener(menuItem->{
Account account=item.user;
int id=menuItem.getItemId();
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));
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->{
relationship=rel;
Toast.makeText(activity, activity.getString(rel.following ? R.string.followed_user : R.string.unfollowed_user, account.getDisplayUsername()), Toast.LENGTH_SHORT).show();
});
}else if(id==R.id.block_domain){
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account.getDomain(), relationship!=null && relationship.domainBlocking, ()->{});
}
return true;
});
} }
@Override @Override
@ -138,6 +191,10 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
more.setVisibility(item.inset ? View.GONE : View.VISIBLE); more.setVisibility(item.inset ? View.GONE : View.VISIBLE);
avatar.setClickable(!item.inset); avatar.setClickable(!item.inset);
avatar.setContentDescription(item.parentFragment.getString(R.string.avatar_description, item.user.acct)); avatar.setContentDescription(item.parentFragment.getString(R.string.avatar_description, item.user.acct));
if(currentRelationshipRequest!=null){
currentRelationshipRequest.cancel();
}
relationship=null;
} }
@Override @Override
@ -165,33 +222,61 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
} }
private void onMoreClick(View v){ private void onMoreClick(View v){
updateOptionsMenu();
optionsMenu.show();
if(relationship==null && currentRelationshipRequest==null){
currentRelationshipRequest=new GetAccountRelationships(Collections.singletonList(item.user.id))
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Relationship> result){
if(!result.isEmpty()){
relationship=result.get(0);
updateOptionsMenu();
}
currentRelationshipRequest=null;
}
@Override
public void onError(ErrorResponse error){
currentRelationshipRequest=null;
}
})
.exec(item.parentFragment.getAccountID());
}
}
private void updateOptionsMenu(){
Account account=item.user; Account account=item.user;
PopupMenu popup=new PopupMenu(v.getContext(), v); Menu menu=optionsMenu.getMenu();
Menu menu=popup.getMenu(); boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
popup.getMenuInflater().inflate(R.menu.post, menu); menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost);
if(item.status==null || !AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account)) MenuItem blockDomain=menu.findItem(R.id.block_domain);
menu.findItem(R.id.delete).setVisible(false); MenuItem mute=menu.findItem(R.id.mute);
menu.findItem(R.id.mute).setTitle(v.getResources().getString(/*relationship.muting ? R.string.unmute_user :*/ R.string.mute_user, account.displayName)); MenuItem block=menu.findItem(R.id.block);
menu.findItem(R.id.block).setTitle(v.getResources().getString(/*relationship.blocking ? R.string.unblock_user :*/ R.string.block_user, account.displayName)); MenuItem report=menu.findItem(R.id.report);
menu.findItem(R.id.report).setTitle(v.getResources().getString(R.string.report_user, account.displayName)); MenuItem follow=menu.findItem(R.id.follow);
popup.setOnMenuItemClickListener(menuItem->{ if(isOwnPost){
int id=menuItem.getItemId(); mute.setVisible(false);
if(id==R.id.delete){ block.setVisible(false);
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{}); report.setVisible(false);
}else if(id==R.id.mute){ follow.setVisible(false);
UiUtils.confirmToggleMuteUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, false, r->{}); blockDomain.setVisible(false);
}else if(id==R.id.block){ }else{
UiUtils.confirmToggleBlockUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, false, r->{}); mute.setVisible(true);
}else if(id==R.id.report){ block.setVisible(true);
Bundle args=new Bundle(); report.setVisible(true);
args.putString("account", item.parentFragment.getAccountID()); follow.setVisible(relationship==null || relationship.following || (!relationship.blocking && !relationship.blockedBy && !relationship.domainBlocking && !relationship.muting));
args.putParcelable("status", Parcels.wrap(item.status)); mute.setTitle(item.parentFragment.getString(relationship!=null && relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
args.putParcelable("reportAccount", Parcels.wrap(item.status.account)); block.setTitle(item.parentFragment.getString(relationship!=null && relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
Nav.go(item.parentFragment.getActivity(), ReportReasonChoiceFragment.class, args); report.setTitle(item.parentFragment.getString(R.string.report_user, account.getDisplayUsername()));
if(!account.isLocal()){
blockDomain.setVisible(true);
blockDomain.setTitle(item.parentFragment.getString(relationship!=null && relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
}else{
blockDomain.setVisible(false);
}
follow.setTitle(item.parentFragment.getString(relationship!=null && relationship.following ? R.string.unfollow_user : R.string.follow_user, account.getDisplayUsername()));
} }
return true;
});
popup.show();
} }
} }
} }

View File

@ -36,6 +36,7 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked; import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed; import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.requests.accounts.SetAccountMuted; import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
import org.joinmastodon.android.api.requests.accounts.SetDomainBlocked;
import org.joinmastodon.android.api.requests.statuses.DeleteStatus; import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
import org.joinmastodon.android.events.StatusDeletedEvent; import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.fragments.HashtagTimelineFragment; import org.joinmastodon.android.fragments.HashtagTimelineFragment;
@ -270,6 +271,27 @@ public class UiUtils{
}); });
} }
public static void confirmToggleBlockDomain(Activity activity, String accountID, String domain, boolean currentlyBlocked, Runnable resultCallback){
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_domain_title : R.string.confirm_block_domain_title),
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, domain),
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block), ()->{
new SetDomainBlocked(domain, !currentlyBlocked)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
resultCallback.run();
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
}
})
.wrapProgress(activity, R.string.loading, false)
.exec(accountID);
});
}
public static void confirmToggleMuteUser(Activity activity, String accountID, Account account, boolean currentlyMuted, Consumer<Relationship> resultCallback){ public static void confirmToggleMuteUser(Activity activity, String accountID, Account account, boolean currentlyMuted, Consumer<Relationship> resultCallback){
showConfirmationAlert(activity, activity.getString(currentlyMuted ? R.string.confirm_unmute_title : R.string.confirm_mute_title), showConfirmationAlert(activity, activity.getString(currentlyMuted ? R.string.confirm_unmute_title : R.string.confirm_mute_title),
activity.getString(currentlyMuted ? R.string.confirm_unmute : R.string.confirm_mute, account.displayName), activity.getString(currentlyMuted ? R.string.confirm_unmute : R.string.confirm_mute, account.displayName),
@ -328,7 +350,7 @@ public class UiUtils{
confirmToggleMuteUser(activity, accountID, account, true, resultCallback); confirmToggleMuteUser(activity, accountID, account, true, resultCallback);
}else{ }else{
progressCallback.accept(true); progressCallback.accept(true);
new SetAccountFollowed(account.id, !relationship.following) new SetAccountFollowed(account.id, !relationship.following, true)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(Relationship result){ public void onSuccess(Relationship result){

View File

@ -3,5 +3,8 @@
<item android:id="@+id/delete" android:title="@string/delete"/> <item android:id="@+id/delete" android:title="@string/delete"/>
<item android:id="@+id/mute" android:title="@string/mute_user"/> <item android:id="@+id/mute" android:title="@string/mute_user"/>
<item android:id="@+id/block" android:title="@string/block_user"/> <item android:id="@+id/block" android:title="@string/block_user"/>
<item android:id="@+id/block_domain" android:title="@string/block_domain"/>
<item android:id="@+id/follow" android:title="@string/follow_user"/>
<item android:id="@+id/report" android:title="@string/report_user"/> <item android:id="@+id/report" android:title="@string/report_user"/>
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser"/>
</menu> </menu>

View File

@ -5,4 +5,6 @@
<item android:id="@+id/block" android:title="@string/block_user"/> <item android:id="@+id/block" android:title="@string/block_user"/>
<item android:id="@+id/report" android:title="@string/report_user"/> <item android:id="@+id/report" android:title="@string/report_user"/>
<item android:id="@+id/block_domain" android:title="@string/block_domain"/> <item android:id="@+id/block_domain" android:title="@string/block_domain"/>
<item android:id="@+id/hide_boosts" android:title="@string/hide_boosts_from_user"/>
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser"/>
</menu> </menu>

View File

@ -114,9 +114,11 @@
<string name="confirm_unmute">Confirm to unmute %s</string> <string name="confirm_unmute">Confirm to unmute %s</string>
<string name="do_unmute">Unmute</string> <string name="do_unmute">Unmute</string>
<string name="confirm_block_title">Block Account</string> <string name="confirm_block_title">Block Account</string>
<string name="confirm_block_domain_title">Block Domain</string>
<string name="confirm_block">Confirm to block %s</string> <string name="confirm_block">Confirm to block %s</string>
<string name="do_block">Block</string> <string name="do_block">Block</string>
<string name="confirm_unblock_title">Unblock Account</string> <string name="confirm_unblock_title">Unblock Account</string>
<string name="confirm_unblock_domain_title">Unblock Domain</string>
<string name="confirm_unblock">Confirm to unblock %s</string> <string name="confirm_unblock">Confirm to unblock %s</string>
<string name="do_unblock">Unblock</string> <string name="do_unblock">Unblock</string>
<string name="button_muted">Muted</string> <string name="button_muted">Muted</string>
@ -284,4 +286,10 @@
<string name="home_timeline">Home timeline</string> <string name="home_timeline">Home timeline</string>
<string name="my_profile">My profile</string> <string name="my_profile">My profile</string>
<string name="media_viewer">Media viewer</string> <string name="media_viewer">Media viewer</string>
<string name="follow_user">Follow %s</string>
<string name="unfollowed_user">Unfollowed %s</string>
<string name="followed_user">You\'re now following %s</string>
<string name="open_in_browser">Open in browser</string>
<string name="hide_boosts_from_user">Hide boosts from %s</string>
<string name="show_boosts_from_user">Show boosts from %s</string>
</resources> </resources>