AND-122 Mute, block, and domain block confirmation screens

This commit is contained in:
Grishka
2024-02-09 03:27:05 +03:00
parent e7295aac07
commit ad2ef39ace
27 changed files with 553 additions and 91 deletions

View File

@@ -29,8 +29,8 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.NonMutualPreReplySheet;
import org.joinmastodon.android.ui.OldPostPreReplySheet;
import org.joinmastodon.android.ui.sheets.NonMutualPreReplySheet;
import org.joinmastodon.android.ui.sheets.OldPostPreReplySheet;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;

View File

@@ -30,7 +30,7 @@ import org.joinmastodon.android.fragments.onboarding.OnboardingFollowSuggestions
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.TabBar;

View File

@@ -630,7 +630,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}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, ()->{
UiUtils.confirmToggleBlockDomain(getActivity(), accountID, account, relationship.domainBlocking, ()->{
relationship.domainBlocking=!relationship.domainBlocking;
updateRelationship();
});

View File

@@ -24,7 +24,7 @@ import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.File;

View File

@@ -1,6 +1,5 @@
package org.joinmastodon.android.fragments.settings;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
@@ -17,7 +16,7 @@ import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils;

View File

@@ -0,0 +1,90 @@
package org.joinmastodon.android.ui.sheets;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.InsetDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.drawables.EmptyDrawable;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ProgressBarButton;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.BottomSheet;
public abstract class AccountRestrictionConfirmationSheet extends BottomSheet{
private LinearLayout contentWrap;
protected Button cancelBtn;
protected ProgressBarButton confirmBtn, secondaryBtn;
protected TextView titleView, subtitleView;
protected ImageView icon;
protected boolean loading;
public AccountRestrictionConfirmationSheet(@NonNull Context context, Account user, ConfirmCallback confirmCallback){
super(context);
View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_restrict_account, null);
setContentView(content);
setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface),
UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
contentWrap=findViewById(R.id.content_wrap);
titleView=findViewById(R.id.title);
subtitleView=findViewById(R.id.text);
cancelBtn=findViewById(R.id.btn_cancel);
confirmBtn=findViewById(R.id.btn_confirm);
secondaryBtn=findViewById(R.id.btn_secondary);
icon=findViewById(R.id.icon);
contentWrap.setDividerDrawable(new EmptyDrawable(1, V.dp(8)));
contentWrap.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
confirmBtn.setOnClickListener(v->{
if(loading)
return;
loading=true;
confirmBtn.setProgressBarVisible(true);
confirmCallback.onConfirmed(this::dismiss, ()->{
confirmBtn.setProgressBarVisible(false);
loading=false;
});
});
cancelBtn.setOnClickListener(v->{
if(!loading)
dismiss();
});
}
protected void addRow(@DrawableRes int icon, CharSequence text){
TextView tv=new TextView(getContext());
tv.setTextAppearance(R.style.m3_body_large);
tv.setTextColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3OnSurfaceVariant));
tv.setCompoundDrawableTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getContext(), R.attr.colorM3Primary)));
tv.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
tv.setText(text);
InsetDrawable drawable=new InsetDrawable(getContext().getResources().getDrawable(icon, getContext().getTheme()), V.dp(8));
drawable.setBounds(0, 0, V.dp(40), V.dp(40));
tv.setCompoundDrawablesRelative(drawable, null, null, null);
tv.setCompoundDrawablePadding(V.dp(16));
contentWrap.addView(tv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
protected void addRow(@DrawableRes int icon, @StringRes int text){
addRow(icon, getContext().getString(text));
}
public interface ConfirmCallback{
void onConfirmed(Runnable onSuccess, Runnable onError);
}
}

View File

@@ -1,14 +1,12 @@
package org.joinmastodon.android.ui;
package org.joinmastodon.android.ui.sheets;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -25,6 +23,9 @@ import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.fragments.SplashFragment;
import org.joinmastodon.android.ui.ClickableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.CheckableRelativeLayout;
@@ -37,7 +38,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.recyclerview.widget.LinearLayoutManager;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;

View File

@@ -0,0 +1,24 @@
package org.joinmastodon.android.ui.sheets;
import android.content.Context;
import android.view.View;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Account;
import androidx.annotation.NonNull;
public class BlockAccountConfirmationSheet extends AccountRestrictionConfirmationSheet{
public BlockAccountConfirmationSheet(@NonNull Context context, Account user, ConfirmCallback confirmCallback){
super(context, user, confirmCallback);
titleView.setText(R.string.block_user_confirm_title);
confirmBtn.setText(R.string.do_block);
secondaryBtn.setVisibility(View.GONE);
icon.setImageResource(R.drawable.ic_block_24px);
subtitleView.setText(user.getDisplayUsername());
addRow(R.drawable.ic_campaign_24px, R.string.user_can_see_blocked);
addRow(R.drawable.ic_visibility_off_24px, R.string.user_cant_see_each_other_posts);
addRow(R.drawable.ic_alternate_email_24px, R.string.you_wont_see_user_mentions);
addRow(R.drawable.ic_reply_24px, R.string.user_cant_mention_or_follow_you);
}
}

View File

@@ -0,0 +1,36 @@
package org.joinmastodon.android.ui.sheets;
import android.content.Context;
import android.view.View;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Account;
import androidx.annotation.NonNull;
public class BlockDomainConfirmationSheet extends AccountRestrictionConfirmationSheet{
public BlockDomainConfirmationSheet(@NonNull Context context, Account user, ConfirmCallback confirmCallback, ConfirmCallback blockUserConfirmCallback){
super(context, user, confirmCallback);
titleView.setText(R.string.block_domain_confirm_title);
confirmBtn.setText(R.string.do_block_server);
secondaryBtn.setText(context.getString(R.string.block_user_x_instead, user.getDisplayUsername()));
icon.setImageResource(R.drawable.ic_domain_disabled_24px);
subtitleView.setText(user.getDomain());
addRow(R.drawable.ic_campaign_24px, R.string.users_cant_see_blocked);
addRow(R.drawable.ic_visibility_off_24px, R.string.you_wont_see_server_posts);
addRow(R.drawable.ic_person_remove_24px, R.string.server_followers_will_be_removed);
addRow(R.drawable.ic_reply_24px, R.string.server_cant_mention_or_follow_you);
addRow(R.drawable.ic_history_24px, R.string.server_can_interact_with_older);
secondaryBtn.setOnClickListener(v->{
if(loading)
return;
loading=true;
secondaryBtn.setProgressBarVisible(true);
blockUserConfirmCallback.onConfirmed(this::dismiss, ()->{
secondaryBtn.setProgressBarVisible(false);
loading=false;
});
});
}
}

View File

@@ -0,0 +1,24 @@
package org.joinmastodon.android.ui.sheets;
import android.content.Context;
import android.view.View;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Account;
import androidx.annotation.NonNull;
public class MuteAccountConfirmationSheet extends AccountRestrictionConfirmationSheet{
public MuteAccountConfirmationSheet(@NonNull Context context, Account user, ConfirmCallback confirmCallback){
super(context, user, confirmCallback);
titleView.setText(R.string.mute_user_confirm_title);
confirmBtn.setText(R.string.do_mute);
secondaryBtn.setVisibility(View.GONE);
icon.setImageResource(R.drawable.ic_volume_off_24px);
subtitleView.setText(user.getDisplayUsername());
addRow(R.drawable.ic_campaign_24px, R.string.user_wont_know_muted);
addRow(R.drawable.ic_visibility_off_24px, R.string.user_can_still_see_your_posts);
addRow(R.drawable.ic_alternate_email_24px, R.string.you_wont_see_user_mentions);
addRow(R.drawable.ic_reply_24px, R.string.user_can_mention_and_follow_you);
}
}

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.ui;
package org.joinmastodon.android.ui.sheets;
import android.annotation.SuppressLint;
import android.content.Context;
@@ -14,6 +14,7 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.ui;
package org.joinmastodon.android.ui.sheets;
import android.content.Context;

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.ui;
package org.joinmastodon.android.ui.sheets;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;

View File

@@ -75,6 +75,10 @@ import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.SearchResults;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.Snackbar;
import org.joinmastodon.android.ui.sheets.BlockAccountConfirmationSheet;
import org.joinmastodon.android.ui.sheets.BlockDomainConfirmationSheet;
import org.joinmastodon.android.ui.sheets.MuteAccountConfirmationSheet;
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
import org.joinmastodon.android.ui.text.SpacerSpan;
import org.parceler.Parcels;
@@ -382,72 +386,142 @@ public class UiUtils{
}
public static void confirmToggleBlockUser(Activity activity, String accountID, Account account, boolean currentlyBlocked, Consumer<Relationship> resultCallback){
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_title : R.string.confirm_block_title),
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, account.displayName),
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block), ()->{
new SetAccountBlocked(account.id, !currentlyBlocked)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
resultCallback.accept(result);
if(!currentlyBlocked){
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
}
}
if(!currentlyBlocked){
new BlockAccountConfirmationSheet(activity, account, (onSuccess, onError)->{
new SetAccountBlocked(account.id, true)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
resultCallback.accept(result);
onSuccess.run();
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
}
})
.wrapProgress(activity, R.string.loading, false)
.exec(accountID);
});
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
onError.run();
}
})
.exec(accountID);
}).show();
}else{
new SetAccountBlocked(account.id, false)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
resultCallback.accept(result);
new Snackbar.Builder(activity)
.setText(activity.getString(R.string.unblocked_user_x, account.getDisplayUsername()))
.show();
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
}
})
.wrapProgress(activity, R.string.loading, false)
.exec(accountID);
}
}
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();
}
public static void confirmToggleBlockDomain(Activity activity, String accountID, Account account, boolean currentlyBlocked, Runnable resultCallback){
if(!currentlyBlocked){
new BlockDomainConfirmationSheet(activity, account, (onSuccess, onError)->{
new SetDomainBlocked(account.getDomain(), true)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
resultCallback.run();
onSuccess.run();
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
}
})
.wrapProgress(activity, R.string.loading, false)
.exec(accountID);
});
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
onError.run();
}
})
.exec(accountID);
}, (onSuccess, onError)->{
new SetAccountBlocked(account.id, true)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
resultCallback.run();
onSuccess.run();
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
onError.run();
}
})
.exec(accountID);
}).show();
}else{
new SetDomainBlocked(account.getDomain(), false)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
resultCallback.run();
new Snackbar.Builder(activity)
.setText(activity.getString(R.string.unblocked_domain_x, account.getDomain()))
.show();
}
@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){
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.do_unmute : R.string.do_mute), ()->{
new SetAccountMuted(account.id, !currentlyMuted)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
resultCallback.accept(result);
if(!currentlyMuted){
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
}
}
if(!currentlyMuted){
new MuteAccountConfirmationSheet(activity, account, (onSuccess, onError)->{
new SetAccountMuted(account.id, true)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
resultCallback.accept(result);
onSuccess.run();
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
}
})
.wrapProgress(activity, R.string.loading, false)
.exec(accountID);
});
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
onError.run();
}
})
.exec(accountID);
}).show();
}else{
new SetAccountMuted(account.id, false)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
resultCallback.accept(result);
new Snackbar.Builder(activity)
.setText(activity.getString(R.string.unmuted_user_x, account.getDisplayUsername()))
.show();
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
}
})
.wrapProgress(activity, R.string.loading, false)
.exec(accountID);
}
}
public static void confirmDeletePost(Activity activity, String accountID, Status status, Consumer<Status> resultCallback){

View File

@@ -28,7 +28,6 @@ import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import androidx.annotation.NonNull;

View File

@@ -252,7 +252,7 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
}else if(id==R.id.open_in_browser){
UiUtils.launchWebBrowser(fragment.getActivity(), account.url);
}else if(id==R.id.block_domain){
UiUtils.confirmToggleBlockDomain(fragment.getActivity(), accountID, account.getDomain(), relationship.domainBlocking, ()->{
UiUtils.confirmToggleBlockDomain(fragment.getActivity(), accountID, account, relationship.domainBlocking, ()->{
relationship.domainBlocking=!relationship.domainBlocking;
bindRelationship();
});

View File

@@ -1,23 +1,42 @@
package org.joinmastodon.android.ui.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import org.joinmastodon.android.R;
public class ProgressBarButton extends Button{
private boolean textVisible=true;
private ProgressBar progressBar;
private int progressBarID;
public ProgressBarButton(Context context){
super(context);
this(context, null);
}
public ProgressBarButton(Context context, AttributeSet attrs){
super(context, attrs);
this(context, attrs, 0);
}
public ProgressBarButton(Context context, AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
public ProgressBarButton(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.ProgressBarButton);
progressBarID=ta.getResourceId(R.styleable.ProgressBarButton_progressBar, 0);
ta.recycle();
}
@Override
protected void onAttachedToWindow(){
super.onAttachedToWindow();
if(progressBarID!=0){
progressBar=((ViewGroup)getParent()).findViewById(progressBarID);
}
}
public void setTextVisible(boolean textVisible){
@@ -29,6 +48,19 @@ public class ProgressBarButton extends Button{
return textVisible;
}
public void setProgressBarVisible(boolean visible){
if(progressBar==null)
throw new IllegalStateException("progressBar is not set");
if(visible){
setTextVisible(false);
progressBar.setIndeterminateTintList(getTextColors());
progressBar.setVisibility(View.VISIBLE);
}else{
setTextVisible(true);
progressBar.setVisibility(View.GONE);
}
}
@Override
protected void onDraw(Canvas canvas){
if(textVisible){