From 6b9b6710cfd2071375f8b8f7c453c4fba6acbcfd Mon Sep 17 00:00:00 2001 From: sk Date: Thu, 16 Feb 2023 19:44:39 +0100 Subject: [PATCH] enable remote-following accounts closes sk22#431 --- .../android/fragments/ProfileFragment.java | 38 +++++++++++++++++ .../joinmastodon/android/model/Account.java | 7 +++- .../android/model/Searchable.java | 5 +++ .../joinmastodon/android/model/Status.java | 7 +++- .../displayitems/FooterStatusDisplayItem.java | 1 + .../displayitems/HeaderStatusDisplayItem.java | 4 +- .../android/ui/utils/UiUtils.java | 41 ++++++++++++++++--- .../ic_fluent_person_add_28_regular.xml | 3 ++ mastodon/src/main/res/menu/profile.xml | 3 ++ mastodon/src/main/res/menu/profile_own.xml | 3 ++ mastodon/src/main/res/values/strings_sk.xml | 2 + 11 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/model/Searchable.java create mode 100644 mastodon/src/main/res/drawable/ic_fluent_person_add_28_regular.xml 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 ab7f987a6..2ca180bae 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -24,6 +24,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.SubMenu; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; @@ -37,6 +38,7 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; +import android.widget.Toast; import android.widget.Toolbar; import org.joinmastodon.android.GlobalUserPreferences; @@ -298,6 +300,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList }); actionButton.setOnClickListener(this::onActionButtonClick); + actionButton.setOnLongClickListener(this::onActionButtonLongClick); notifyButton.setOnClickListener(this::onNotifyButtonClick); avatar.setOnClickListener(this::onAvatarClick); cover.setOnClickListener(this::onCoverClick); @@ -601,6 +604,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList return; inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu); UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.bookmarks, R.id.followed_hashtags); + boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1; + MenuItem openWithAccounts = menu.findItem(R.id.open_with_account); + openWithAccounts.setVisible(hasMultipleAccounts); + SubMenu accountsMenu = openWithAccounts.getSubMenu(); + if (hasMultipleAccounts) { + accountsMenu.clear(); + UiUtils.populateAccountsMenu(accountID, accountsMenu, s-> UiUtils.openURL( + getActivity(), s.getID(), account.url, false + )); + } menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername())); if(isOwnProfile) return; @@ -794,6 +807,31 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList } } + private boolean onActionButtonLongClick(View v) { + if (isOwnProfile || AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false; + UiUtils.pickAccount(getActivity(), accountID, R.string.sk_follow_as, R.drawable.ic_fluent_person_add_28_regular, session -> { + UiUtils.lookupAccount(getActivity(), account, session.getID(), accountID, acc -> { + if (acc == null) return; + new SetAccountFollowed(acc.id, true, true).setCallback(new Callback<>() { + @Override + public void onSuccess(Relationship relationship) { + Toast.makeText( + getActivity(), + getString(R.string.sk_followed_as, session.self.getShortUsername()), + Toast.LENGTH_SHORT + ).show(); + } + + @Override + public void onError(ErrorResponse error) { + error.showToast(getActivity()); + } + }).exec(session.getID()); + }); + }, null); + return true; + } + private void setActionProgressVisible(boolean visible){ actionButton.setTextVisible(!visible); actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE); 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 9f996d769..362406f85 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Account.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Account.java @@ -14,7 +14,7 @@ import java.util.List; * Represents a user of Mastodon and their associated profile. */ @Parcel -public class Account extends BaseModel{ +public class Account extends BaseModel implements Searchable{ // Base attributes /** @@ -135,6 +135,11 @@ public class Account extends BaseModel{ public List roles; + @Override + public String getQuery() { + return url; + } + @Parcel public static class Role { public String name; diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Searchable.java b/mastodon/src/main/java/org/joinmastodon/android/model/Searchable.java new file mode 100644 index 000000000..6580a3cea --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Searchable.java @@ -0,0 +1,5 @@ +package org.joinmastodon.android.model; + +public interface Searchable { + String getQuery(); +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Status.java b/mastodon/src/main/java/org/joinmastodon/android/model/Status.java index 455299287..8574b520c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Status.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Status.java @@ -11,7 +11,7 @@ import java.time.Instant; import java.util.List; @Parcel -public class Status extends BaseModel implements DisplayItemsParent{ +public class Status extends BaseModel implements DisplayItemsParent, Searchable{ @RequiredField public String id; @RequiredField @@ -163,4 +163,9 @@ public class Status extends BaseModel implements DisplayItemsParent{ s.filtered = List.of(); return s; } + + @Override + public String getQuery() { + return url; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java index 48634cfed..c23841fac 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java @@ -190,6 +190,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{ String accountID = session.getID(); args.putString("account", accountID); UiUtils.lookupStatus(v.getContext(), item.status, accountID, item.accountID, status -> { + if (status == null) return; args.putParcelable("replyTo", Parcels.wrap(status)); Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args); }); 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 f5ce471cf..ad9279e00 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 @@ -455,7 +455,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ if (hasMultipleAccounts && accountsMenu != null) { openWithAccounts.setVisible(true); accountsMenu.clear(); - populateAccountsMenu(accountsMenu); + UiUtils.populateAccountsMenu(item.accountID, accountsMenu, s-> UiUtils.openURL( + item.parentFragment.getActivity(), s.getID(), item.status.url, false + )); } else if (openWithAccounts != null) { openWithAccounts.setVisible(false); } 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 c2f63a038..c720958a5 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 @@ -87,6 +87,7 @@ import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.ScheduledStatus; import org.joinmastodon.android.model.SearchResults; +import org.joinmastodon.android.model.Searchable; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.ui.M3AlertDialogBuilder; @@ -107,8 +108,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.BiPredicate; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -982,6 +985,8 @@ public class UiUtils { public static void pickInteractAs(Context context, String accountID, Status sourceStatus, Predicate checkInteracted, InteractionPerformer interactionPerformer, @StringRes int interactAsRes, @StringRes int interactedAsAccountRes, @StringRes int alreadyInteractedRes, @DrawableRes int iconRes) { pickAccount(context, accountID, interactAsRes, iconRes, session -> { lookupStatus(context, sourceStatus, session.getID(), accountID, status -> { + if (status == null) return; + if (checkInteracted.test(status)) { Toast.makeText(context, alreadyInteractedRes, Toast.LENGTH_SHORT).show(); return; @@ -997,18 +1002,33 @@ public class UiUtils { }, null); } - public static void lookupStatus(Context context, Status queryStatus, String targetAccountID, @Nullable String sourceAccountID, Consumer statusConsumer) { + public static void lookupStatus(Context context, Status queryStatus, String targetAccountID, @Nullable String sourceAccountID, Consumer resultConsumer) { + lookup(context, queryStatus, targetAccountID, sourceAccountID, GetSearchResults.Type.STATUSES, resultConsumer, results -> + !results.statuses.isEmpty() ? Optional.of(results.statuses.get(0)) : Optional.empty() + ); + } + + public static void lookupAccount(Context context, Account queryAccount, String targetAccountID, @Nullable String sourceAccountID, Consumer resultConsumer) { + lookup(context, queryAccount, targetAccountID, sourceAccountID, GetSearchResults.Type.ACCOUNTS, resultConsumer, results -> + !results.accounts.isEmpty() ? Optional.of(results.accounts.get(0)) : Optional.empty() + ); + } + + public static void lookup(Context context, T query, String targetAccountID, @Nullable String sourceAccountID, @Nullable GetSearchResults.Type type, Consumer resultConsumer, Function> extractResult) { if (sourceAccountID != null && targetAccountID.startsWith(sourceAccountID.substring(0, sourceAccountID.indexOf('_')))) { - statusConsumer.accept(queryStatus); + resultConsumer.accept(query); return; } - new GetSearchResults(queryStatus.url, GetSearchResults.Type.STATUSES, true).setCallback(new Callback<>() { + new GetSearchResults(query.getQuery(), type, true).setCallback(new Callback<>() { @Override public void onSuccess(SearchResults results) { - if (!results.statuses.isEmpty()) statusConsumer.accept(results.statuses.get(0)); - else + Optional result = extractResult.apply(results); + if (result.isPresent()) resultConsumer.accept(result.get()); + else { Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show(); + resultConsumer.accept(null); + } } @Override @@ -1245,6 +1265,17 @@ public class UiUtils { return intent; } + public static void populateAccountsMenu(String excludeAccountID, Menu menu, Consumer onClick) { + List sessions=AccountSessionManager.getInstance().getLoggedInAccounts(); + sessions.stream().filter(s -> !s.getID().equals(excludeAccountID)).forEach(s -> { + String username = "@"+s.self.username+"@"+s.domain; + menu.add(username).setOnMenuItemClickListener((c) -> { + onClick.accept(s); + return true; + }); + }); + } + /** * Wraps a View.OnClickListener to filter multiple clicks in succession. * Useful for buttons that perform some action that changes their state asynchronously. diff --git a/mastodon/src/main/res/drawable/ic_fluent_person_add_28_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_person_add_28_regular.xml new file mode 100644 index 000000000..5fa82eb93 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_person_add_28_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/menu/profile.xml b/mastodon/src/main/res/menu/profile.xml index 30679843e..e3cc847d1 100644 --- a/mastodon/src/main/res/menu/profile.xml +++ b/mastodon/src/main/res/menu/profile.xml @@ -1,5 +1,8 @@ + + + diff --git a/mastodon/src/main/res/menu/profile_own.xml b/mastodon/src/main/res/menu/profile_own.xml index 5eb30f244..61543c3e5 100644 --- a/mastodon/src/main/res/menu/profile_own.xml +++ b/mastodon/src/main/res/menu/profile_own.xml @@ -2,6 +2,9 @@ + + + diff --git a/mastodon/src/main/res/values/strings_sk.xml b/mastodon/src/main/res/values/strings_sk.xml index 1f80d4ae1..7bad22ee0 100644 --- a/mastodon/src/main/res/values/strings_sk.xml +++ b/mastodon/src/main/res/values/strings_sk.xml @@ -259,4 +259,6 @@ Fix attachments? Some attachments haven’t finished uploading. Hide interaction buttons + Follow from other account + Followed from %s \ No newline at end of file