Show reblog/favourite confirmations as menus not dialogs (#3418)

* Show reblog/favourite confirmations as menus not dialogs

The previous code used dialogs and displayed the text of the status when
reblogging or favouriting.

This didn't work when the post just contained images, and other material
from the status (content warning, polls) was not shown either.

Fix this by displaying a popup menu instead. The status remains visible so
the user can clearly see what they're acting on.

In addition, this lays the groundwork for supporting a long-press menu
in the future to allow the user to reblog/favourite from a different
account.

Fixes https://github.com/tuskyapp/Tusky/issues/3308

* Revert the change that puts the menu immediately over the icon

Although this behavious is consistent with how the option menu works, I
decided that the risk of someone inadvertently double-tapping in the same
location, and the first tap opens the menu and the second tap confirms the
action was too great.

So now the menu appears either above or below the icon depending on space,
and the user has to tap in two slightly different spaces.

This is also consistent with the previous behaviour, where it's highly
unlikely that the confirm button on the dialog would have been directly
under the user's finger if they double-tapped.
This commit is contained in:
Nik Clayton 2023-03-18 09:37:55 +01:00 committed by GitHub
parent 61720c3472
commit c36b243745
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 68 additions and 36 deletions

View file

@ -9,6 +9,7 @@ import android.graphics.drawable.Drawable;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.view.Menu;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
@ -21,7 +22,7 @@ import android.widget.Toast;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.PopupMenu;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.text.HtmlCompat; import androidx.core.text.HtmlCompat;
@ -78,6 +79,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
public static final String KEY_CREATED = "created"; public static final String KEY_CREATED = "created";
} }
private final String TAG = "StatusBaseViewHolder";
private final TextView displayName; private final TextView displayName;
private final TextView username; private final TextView username;
private final ImageButton replyButton; private final ImageButton replyButton;
@ -645,7 +648,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
int position = getBindingAdapterPosition(); int position = getBindingAdapterPosition();
if (position != RecyclerView.NO_POSITION) { if (position != RecyclerView.NO_POSITION) {
if (statusDisplayOptions.confirmReblogs()) { if (statusDisplayOptions.confirmReblogs()) {
showConfirmReblogDialog(listener, statusContent, buttonState, position); showConfirmReblog(listener, buttonState, position);
return false; return false;
} else { } else {
listener.onReblog(!buttonState, position); listener.onReblog(!buttonState, position);
@ -663,7 +666,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
int position = getBindingAdapterPosition(); int position = getBindingAdapterPosition();
if (position != RecyclerView.NO_POSITION) { if (position != RecyclerView.NO_POSITION) {
if (statusDisplayOptions.confirmFavourites()) { if (statusDisplayOptions.confirmFavourites()) {
showConfirmFavouriteDialog(listener, statusContent, buttonState, position); showConfirmFavourite(listener, buttonState, position);
return false; return false;
} else { } else {
listener.onFavourite(!buttonState, position); listener.onFavourite(!buttonState, position);
@ -702,38 +705,46 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
itemView.setOnClickListener(viewThreadListener); itemView.setOnClickListener(viewThreadListener);
} }
private void showConfirmReblogDialog(StatusActionListener listener, private void showConfirmReblog(StatusActionListener listener,
String statusContent, boolean buttonState,
boolean buttonState, int position) {
int position) { PopupMenu popup = new PopupMenu(itemView.getContext(), reblogButton);
int okButtonTextId = buttonState ? R.string.action_unreblog : R.string.action_reblog; popup.inflate(R.menu.status_reblog);
new AlertDialog.Builder(reblogButton.getContext()) Menu menu = popup.getMenu();
.setMessage(statusContent) if (buttonState) {
.setPositiveButton(okButtonTextId, (__, ___) -> { menu.findItem(R.id.menu_action_reblog).setVisible(false);
listener.onReblog(!buttonState, position); } else {
if (!buttonState) { menu.findItem(R.id.menu_action_unreblog).setVisible(false);
// Play animation only when it's reblog, not unreblog }
reblogButton.playAnimation(); popup.setOnMenuItemClickListener(item -> {
} listener.onReblog(!buttonState, position);
}) if(!buttonState) {
.show(); reblogButton.playAnimation();
}
return true;
});
popup.show();
} }
private void showConfirmFavouriteDialog(StatusActionListener listener, private void showConfirmFavourite(StatusActionListener listener,
String statusContent, boolean buttonState,
boolean buttonState, int position) {
int position) { PopupMenu popup = new PopupMenu(itemView.getContext(), favouriteButton);
int okButtonTextId = buttonState ? R.string.action_unfavourite : R.string.action_favourite; popup.inflate(R.menu.status_favourite);
new AlertDialog.Builder(favouriteButton.getContext()) Menu menu = popup.getMenu();
.setMessage(statusContent) if (buttonState) {
.setPositiveButton(okButtonTextId, (__, ___) -> { menu.findItem(R.id.menu_action_favourite).setVisible(false);
listener.onFavourite(!buttonState, position); } else {
if (!buttonState) { menu.findItem(R.id.menu_action_unfavourite).setVisible(false);
// Play animation only when it's favourite, not unfavourite }
favouriteButton.playAnimation(); popup.setOnMenuItemClickListener(item -> {
} listener.onFavourite(!buttonState, position);
}) if(!buttonState) {
.show(); favouriteButton.playAnimation();
}
return true;
});
popup.show();
} }
public void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener, public void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener,

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_action_favourite"
android:icon="@drawable/ic_favourite_24dp"
android:title="@string/action_favourite" />
<item
android:id="@+id/menu_action_unfavourite"
android:icon="@drawable/ic_favourite_24dp"
android:title="@string/action_unfavourite" />
</menu>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_action_reblog"
android:icon="@drawable/ic_reblog_24dp"
android:title="@string/action_reblog" />
<item
android:id="@+id/menu_action_unreblog"
android:icon="@drawable/ic_reblog_24dp"
android:title="@string/action_unreblog" />
</menu>

View file

@ -697,8 +697,8 @@
<string name="warning_scheduling_interval">Mastodon has a minimum scheduling interval of 5 minutes.</string> <string name="warning_scheduling_interval">Mastodon has a minimum scheduling interval of 5 minutes.</string>
<string name="pref_title_show_self_username">Show username in toolbars</string> <string name="pref_title_show_self_username">Show username in toolbars</string>
<string name="pref_title_show_cards_in_timelines">Show link previews in timelines</string> <string name="pref_title_show_cards_in_timelines">Show link previews in timelines</string>
<string name="pref_title_confirm_reblogs">Show confirmation dialog before boosting</string> <string name="pref_title_confirm_reblogs">Show confirmation before boosting</string>
<string name="pref_title_confirm_favourites">Show confirmation dialog before favoriting</string> <string name="pref_title_confirm_favourites">Show confirmation before favoriting</string>
<string name="pref_title_hide_top_toolbar">Hide the title of the top toolbar</string> <string name="pref_title_hide_top_toolbar">Hide the title of the top toolbar</string>
<string name="pref_title_wellbeing_mode">Wellbeing</string> <string name="pref_title_wellbeing_mode">Wellbeing</string>
<string name="account_note_hint">Your private note about this account</string> <string name="account_note_hint">Your private note about this account</string>

View file

@ -184,5 +184,4 @@
<item name="cornerFamily">rounded</item> <item name="cornerFamily">rounded</item>
<item name="cornerSize">12.5%</item> <item name="cornerSize">12.5%</item>
</style> </style>
</resources> </resources>