diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetAccountFollowed.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetAccountFollowed.java index 150e6f86b..e38479f18 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetAccountFollowed.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetAccountFollowed.java @@ -4,8 +4,20 @@ import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.model.Relationship; public class SetAccountFollowed extends MastodonAPIRequest{ - public SetAccountFollowed(String id, boolean followed){ + public SetAccountFollowed(String id, boolean followed, boolean showReblogs){ super(HttpMethod.POST, "/accounts/"+id+"/"+(followed ? "follow" : "unfollow"), Relationship.class); - setRequestBody(new Object()); + if(followed) + setRequestBody(new Request(showReblogs, null)); + else + setRequestBody(new Object()); + } + + private static class Request{ + public Boolean reblogs, notify; + + public Request(Boolean reblogs, Boolean notify){ + this.reblogs=reblogs; + this.notify=notify; + } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetDomainBlocked.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetDomainBlocked.java new file mode 100644 index 000000000..dc033efaa --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetDomainBlocked.java @@ -0,0 +1,18 @@ +package org.joinmastodon.android.api.requests.accounts; + +import org.joinmastodon.android.api.MastodonAPIRequest; + +public class SetDomainBlocked extends MastodonAPIRequest{ + 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; + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java index ff159f83c..d36fe7225 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -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.GetAccountStatuses; 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.session.AccountSessionManager; import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment; @@ -479,14 +480,17 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList if(relationship==null) return; inflater.inflate(R.menu.profile, menu); - menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.displayName)); - menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.displayName)); - menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.displayName)); - menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.displayName)); -// String domain=account.getDomain(); -// if(domain!=null) -// menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, domain)); -// else + 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.getDisplayUsername())); + 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.getDisplayUsername())); + if(relationship.following) + menu.findItem(R.id.hide_boosts).setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getDisplayUsername())); + 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); } @@ -507,6 +511,28 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList args.putString("account", accountID); args.putParcelable("reportAccount", Parcels.wrap(account)); 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; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportDoneFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportDoneFragment.java index 096ebae35..8ce6d19c0 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportDoneFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportDoneFragment.java @@ -124,7 +124,7 @@ public class ReportDoneFragment extends ToolbarFragment{ } private void onUnfollowClick(){ - new SetAccountFollowed(reportAccount.id, false) + new SetAccountFollowed(reportAccount.id, false, false) .setCallback(new Callback<>(){ @Override public void onSuccess(Relationship result){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Account.java b/mastodon/src/main/java/org/joinmastodon/android/model/Account.java index dc8d6ab01..e510d717f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Account.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Account.java @@ -160,6 +160,10 @@ public class Account extends BaseModel{ return parts.length==1 ? null : parts[1]; } + public String getDisplayUsername(){ + return '@'+acct; + } + @Override public String toString(){ return "Account{"+ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java index ebda50fa8..06900e52a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java @@ -1,6 +1,7 @@ package org.joinmastodon.android.ui.displayitems; import android.app.Activity; +import android.app.ProgressDialog; import android.graphics.Outline; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; @@ -8,21 +9,25 @@ import android.os.Bundle; 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.view.ViewOutlineProvider; 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.session.AccountSessionManager; import org.joinmastodon.android.fragments.BaseStatusListFragment; 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.text.HtmlParser; import org.joinmastodon.android.ui.utils.CustomEmojiHelper; @@ -30,8 +35,13 @@ import org.joinmastodon.android.ui.utils.UiUtils; import org.parceler.Parcels; import java.time.Instant; +import java.util.Collections; +import java.util.List; 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; @@ -94,6 +104,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ public static class Holder extends StatusDisplayItem.Holder implements ImageLoaderViewHolder{ private final TextView name, username, timestamp, extraText; private final ImageView avatar, more, visibility; + private final PopupMenu optionsMenu; + private Relationship relationship; + private APIRequest currentRelationshipRequest; private static final ViewOutlineProvider roundCornersOutline=new ViewOutlineProvider(){ @Override @@ -116,6 +129,46 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ avatar.setClipToOutline(true); more.setOnClickListener(this::onMoreClick); 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 @@ -138,6 +191,10 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ more.setVisibility(item.inset ? View.GONE : View.VISIBLE); avatar.setClickable(!item.inset); avatar.setContentDescription(item.parentFragment.getString(R.string.avatar_description, item.user.acct)); + if(currentRelationshipRequest!=null){ + currentRelationshipRequest.cancel(); + } + relationship=null; } @Override @@ -165,33 +222,61 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ } 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 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; - PopupMenu popup=new PopupMenu(v.getContext(), v); - Menu menu=popup.getMenu(); - popup.getMenuInflater().inflate(R.menu.post, menu); - if(item.status==null || !AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account)) - menu.findItem(R.id.delete).setVisible(false); - menu.findItem(R.id.mute).setTitle(v.getResources().getString(/*relationship.muting ? R.string.unmute_user :*/ R.string.mute_user, account.displayName)); - menu.findItem(R.id.block).setTitle(v.getResources().getString(/*relationship.blocking ? R.string.unblock_user :*/ R.string.block_user, account.displayName)); - menu.findItem(R.id.report).setTitle(v.getResources().getString(R.string.report_user, account.displayName)); - popup.setOnMenuItemClickListener(menuItem->{ - 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, false, r->{}); - }else if(id==R.id.block){ - UiUtils.confirmToggleBlockUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, false, 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); + Menu menu=optionsMenu.getMenu(); + boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account); + menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost); + MenuItem blockDomain=menu.findItem(R.id.block_domain); + 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); + if(isOwnPost){ + mute.setVisible(false); + block.setVisible(false); + report.setVisible(false); + follow.setVisible(false); + blockDomain.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.getDisplayUsername())); + block.setTitle(item.parentFragment.getString(relationship!=null && relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername())); + 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); } - return true; - }); - popup.show(); + follow.setTitle(item.parentFragment.getString(relationship!=null && relationship.following ? R.string.unfollow_user : R.string.follow_user, account.getDisplayUsername())); + } } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index a300be2be..b51d5ad07 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -36,6 +36,7 @@ import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked; import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed; 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.events.StatusDeletedEvent; 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 resultCallback){ 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), @@ -328,7 +350,7 @@ public class UiUtils{ confirmToggleMuteUser(activity, accountID, account, true, resultCallback); }else{ progressCallback.accept(true); - new SetAccountFollowed(account.id, !relationship.following) + new SetAccountFollowed(account.id, !relationship.following, true) .setCallback(new Callback<>(){ @Override public void onSuccess(Relationship result){ diff --git a/mastodon/src/main/res/menu/post.xml b/mastodon/src/main/res/menu/post.xml index 1efd2ebcd..877bd3e26 100644 --- a/mastodon/src/main/res/menu/post.xml +++ b/mastodon/src/main/res/menu/post.xml @@ -3,5 +3,8 @@ + + + \ No newline at end of file diff --git a/mastodon/src/main/res/menu/profile.xml b/mastodon/src/main/res/menu/profile.xml index 9ed137548..e140a5339 100644 --- a/mastodon/src/main/res/menu/profile.xml +++ b/mastodon/src/main/res/menu/profile.xml @@ -5,4 +5,6 @@ + + \ No newline at end of file diff --git a/mastodon/src/main/res/values/strings.xml b/mastodon/src/main/res/values/strings.xml index 59749f885..271ff905f 100644 --- a/mastodon/src/main/res/values/strings.xml +++ b/mastodon/src/main/res/values/strings.xml @@ -114,9 +114,11 @@ Confirm to unmute %s Unmute Block Account + Block Domain Confirm to block %s Block Unblock Account + Unblock Domain Confirm to unblock %s Unblock Muted @@ -284,4 +286,10 @@ Home timeline My profile Media viewer + Follow %s + Unfollowed %s + You\'re now following %s + Open in browser + Hide boosts from %s + Show boosts from %s \ No newline at end of file