mirror of
https://codeberg.org/Bazsalanszky/Infinity-For-Lemmy.git
synced 2024-11-07 11:17:25 +01:00
Reporting posts is now available.
This commit is contained in:
parent
9d451d65dd
commit
d444a3a084
@ -14,6 +14,9 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.appbar.AppBarLayout;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
@ -23,11 +26,13 @@ import butterknife.ButterKnife;
|
||||
import ml.docilealligator.infinityforreddit.Adapter.ReportReasonRecyclerViewAdapter;
|
||||
import ml.docilealligator.infinityforreddit.AsyncTask.GetCurrentAccountAsyncTask;
|
||||
import ml.docilealligator.infinityforreddit.CustomTheme.CustomThemeWrapper;
|
||||
import ml.docilealligator.infinityforreddit.FetchRules;
|
||||
import ml.docilealligator.infinityforreddit.Infinity;
|
||||
import ml.docilealligator.infinityforreddit.R;
|
||||
import ml.docilealligator.infinityforreddit.RedditDataRoomDatabase;
|
||||
import ml.docilealligator.infinityforreddit.ReportReason;
|
||||
import ml.docilealligator.infinityforreddit.ReportThing;
|
||||
import ml.docilealligator.infinityforreddit.Rule;
|
||||
import retrofit2.Retrofit;
|
||||
|
||||
public class ReportActivity extends BaseActivity {
|
||||
@ -36,6 +41,8 @@ public class ReportActivity extends BaseActivity {
|
||||
public static final String EXTRA_THING_FULLNAME = "ETF";
|
||||
private static final String NULL_ACCESS_TOKEN_STATE = "NATS";
|
||||
private static final String ACCESS_TOKEN_STATE = "ATS";
|
||||
private static final String GENERAL_REASONS_STATE = "GRS";
|
||||
private static final String RULES_REASON_STATE = "RRS";
|
||||
|
||||
@BindView(R.id.coordinator_layout_report_activity)
|
||||
CoordinatorLayout coordinatorLayout;
|
||||
@ -49,6 +56,9 @@ public class ReportActivity extends BaseActivity {
|
||||
@Named("oauth")
|
||||
Retrofit mOauthRetrofit;
|
||||
@Inject
|
||||
@Named("no_oauth")
|
||||
Retrofit mRetrofit;
|
||||
@Inject
|
||||
@Named("default")
|
||||
SharedPreferences mSharedPreferences;
|
||||
@Inject
|
||||
@ -59,6 +69,8 @@ public class ReportActivity extends BaseActivity {
|
||||
private String mAccessToken;
|
||||
private String mFullname;
|
||||
private String mSubredditName;
|
||||
private ArrayList<ReportReason> generalReasons;
|
||||
private ArrayList<ReportReason> rulesReasons;
|
||||
private ReportReasonRecyclerViewAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
@ -83,9 +95,6 @@ public class ReportActivity extends BaseActivity {
|
||||
|
||||
mFullname = getIntent().getStringExtra(EXTRA_THING_FULLNAME);
|
||||
mSubredditName = getIntent().getStringExtra(EXTRA_SUBREDDIT_NAME);
|
||||
mAdapter = new ReportReasonRecyclerViewAdapter(mCustomThemeWrapper, ReportReason.getGeneralReasons(this));
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
mNullAccessToken = savedInstanceState.getBoolean(NULL_ACCESS_TOKEN_STATE);
|
||||
@ -94,9 +103,36 @@ public class ReportActivity extends BaseActivity {
|
||||
if (!mNullAccessToken && mAccessToken == null) {
|
||||
getCurrentAccount();
|
||||
}
|
||||
|
||||
generalReasons = savedInstanceState.getParcelableArrayList(GENERAL_REASONS_STATE);
|
||||
rulesReasons = savedInstanceState.getParcelableArrayList(RULES_REASON_STATE);
|
||||
} else {
|
||||
getCurrentAccount();
|
||||
}
|
||||
|
||||
if (generalReasons != null) {
|
||||
mAdapter = new ReportReasonRecyclerViewAdapter(mCustomThemeWrapper, generalReasons);
|
||||
} else {
|
||||
mAdapter = new ReportReasonRecyclerViewAdapter(mCustomThemeWrapper, ReportReason.getGeneralReasons(this));
|
||||
}
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
|
||||
if (rulesReasons == null) {
|
||||
FetchRules.fetchRules(mRetrofit, mSubredditName, new FetchRules.FetchRulesListener() {
|
||||
@Override
|
||||
public void success(ArrayList<Rule> rules) {
|
||||
mAdapter.setRules(ReportReason.convertRulesToReasons(rules));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed() {
|
||||
Snackbar.make(coordinatorLayout, R.string.error_loading_rules_without_retry, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mAdapter.setRules(rulesReasons);
|
||||
}
|
||||
}
|
||||
|
||||
private void getCurrentAccount() {
|
||||
@ -152,6 +188,10 @@ public class ReportActivity extends BaseActivity {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(NULL_ACCESS_TOKEN_STATE, mNullAccessToken);
|
||||
outState.putString(ACCESS_TOKEN_STATE, mAccessToken);
|
||||
if (mAdapter != null) {
|
||||
outState.putParcelableArrayList(GENERAL_REASONS_STATE, mAdapter.getGeneralReasons());
|
||||
outState.putParcelableArrayList(RULES_REASON_STATE, mAdapter.getRules());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2,7 +2,6 @@ package ml.docilealligator.infinityforreddit.Activity;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
@ -22,9 +21,6 @@ import com.google.android.material.appbar.AppBarLayout;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@ -36,15 +32,10 @@ import butterknife.ButterKnife;
|
||||
import ml.docilealligator.infinityforreddit.Adapter.RulesRecyclerViewAdapter;
|
||||
import ml.docilealligator.infinityforreddit.CustomTheme.CustomThemeWrapper;
|
||||
import ml.docilealligator.infinityforreddit.Event.SwitchAccountEvent;
|
||||
import ml.docilealligator.infinityforreddit.FetchRules;
|
||||
import ml.docilealligator.infinityforreddit.Infinity;
|
||||
import ml.docilealligator.infinityforreddit.R;
|
||||
import ml.docilealligator.infinityforreddit.RedditAPI;
|
||||
import ml.docilealligator.infinityforreddit.Rule;
|
||||
import ml.docilealligator.infinityforreddit.Utils.JSONUtils;
|
||||
import ml.docilealligator.infinityforreddit.Utils.Utils;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
|
||||
public class RulesActivity extends BaseActivity {
|
||||
@ -116,7 +107,26 @@ public class RulesActivity extends BaseActivity {
|
||||
mAdapter = new RulesRecyclerViewAdapter(this, mCustomThemeWrapper);
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
|
||||
fetchRules();
|
||||
//fetchRules();
|
||||
|
||||
FetchRules.fetchRules(mRetrofit, mSubredditName, new FetchRules.FetchRulesListener() {
|
||||
@Override
|
||||
public void success(ArrayList<Rule> rules) {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (rules == null || rules.size() == 0) {
|
||||
errorTextView.setVisibility(View.VISIBLE);
|
||||
errorTextView.setText(R.string.no_rule);
|
||||
errorTextView.setOnClickListener(view -> {
|
||||
});
|
||||
}
|
||||
mAdapter.changeDataset(rules);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed() {
|
||||
displayError();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -137,7 +147,7 @@ public class RulesActivity extends BaseActivity {
|
||||
errorTextView.setTextColor(mCustomThemeWrapper.getSecondaryTextColor());
|
||||
}
|
||||
|
||||
private void fetchRules() {
|
||||
/*private void fetchRules() {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
errorTextView.setVisibility(View.GONE);
|
||||
|
||||
@ -175,13 +185,34 @@ public class RulesActivity extends BaseActivity {
|
||||
displayError();
|
||||
}
|
||||
});
|
||||
}
|
||||
}*/
|
||||
|
||||
private void displayError() {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
errorTextView.setVisibility(View.VISIBLE);
|
||||
errorTextView.setText(R.string.error_loading_rules);
|
||||
errorTextView.setOnClickListener(view -> fetchRules());
|
||||
errorTextView.setOnClickListener(view -> {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
errorTextView.setVisibility(View.GONE);
|
||||
FetchRules.fetchRules(mRetrofit, mSubredditName, new FetchRules.FetchRulesListener() {
|
||||
@Override
|
||||
public void success(ArrayList<Rule> rules) {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (rules == null || rules.size() == 0) {
|
||||
errorTextView.setVisibility(View.VISIBLE);
|
||||
errorTextView.setText(R.string.no_rule);
|
||||
errorTextView.setOnClickListener(view -> {
|
||||
});
|
||||
}
|
||||
mAdapter.changeDataset(rules);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed() {
|
||||
displayError();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -205,7 +236,7 @@ public class RulesActivity extends BaseActivity {
|
||||
finish();
|
||||
}
|
||||
|
||||
private static class ParseRulesAsyncTask extends AsyncTask<Void, ArrayList<Rule>, ArrayList<Rule>> {
|
||||
/*private static class ParseRulesAsyncTask extends AsyncTask<Void, ArrayList<Rule>, ArrayList<Rule>> {
|
||||
private String response;
|
||||
private ParseRulesAsyncTaskListener parseRulesAsyncTaskListener;
|
||||
|
||||
@ -248,5 +279,5 @@ public class RulesActivity extends BaseActivity {
|
||||
|
||||
void parseFailed();
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ public class ReportReasonRecyclerViewAdapter extends RecyclerView.Adapter<Recycl
|
||||
|
||||
public ReportReasonRecyclerViewAdapter(CustomThemeWrapper customThemeWrapper, ArrayList<ReportReason> generalReasons) {
|
||||
this.generalReasons = generalReasons;
|
||||
rules = new ArrayList<>();
|
||||
primaryTextColor = customThemeWrapper.getPrimaryTextColor();
|
||||
colorAccent = customThemeWrapper.getColorAccent();
|
||||
}
|
||||
@ -54,7 +53,7 @@ public class ReportReasonRecyclerViewAdapter extends RecyclerView.Adapter<Recycl
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return rules.size() + generalReasons.size();
|
||||
return rules == null ? generalReasons.size() : rules.size() + generalReasons.size();
|
||||
}
|
||||
|
||||
public void setRules(ArrayList<ReportReason> reportReasons) {
|
||||
@ -78,6 +77,14 @@ public class ReportReasonRecyclerViewAdapter extends RecyclerView.Adapter<Recycl
|
||||
return null;
|
||||
}
|
||||
|
||||
public ArrayList<ReportReason> getGeneralReasons() {
|
||||
return generalReasons;
|
||||
}
|
||||
|
||||
public ArrayList<ReportReason> getRules() {
|
||||
return rules;
|
||||
}
|
||||
|
||||
class ReasonViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.reason_text_view_item_report_reason)
|
||||
@ -101,10 +108,12 @@ public class ReportReasonRecyclerViewAdapter extends RecyclerView.Adapter<Recycl
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < rules.size(); i++) {
|
||||
if (rules.get(i).isSelected()) {
|
||||
rules.get(i).setSelected(false);
|
||||
notifyItemChanged(i + generalReasons.size());
|
||||
if (rules != null) {
|
||||
for (int i = 0; i < rules.size(); i++) {
|
||||
if (rules.get(i).isSelected()) {
|
||||
rules.get(i).setSelected(false);
|
||||
notifyItemChanged(i + generalReasons.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,101 @@
|
||||
package ml.docilealligator.infinityforreddit;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import ml.docilealligator.infinityforreddit.Utils.JSONUtils;
|
||||
import ml.docilealligator.infinityforreddit.Utils.Utils;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
|
||||
public class FetchRules {
|
||||
public interface FetchRulesListener {
|
||||
void success(ArrayList<Rule> rules);
|
||||
void failed();
|
||||
}
|
||||
|
||||
public static void fetchRules(Retrofit retrofit, String subredditName, FetchRulesListener fetchRulesListener) {
|
||||
|
||||
RedditAPI api = retrofit.create(RedditAPI.class);
|
||||
Call<String> rulesCall = api.getRules(subredditName);
|
||||
rulesCall.enqueue(new Callback<String>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) {
|
||||
if (response.isSuccessful()) {
|
||||
new ParseRulesAsyncTask(response.body(), new ParseRulesAsyncTask.ParseRulesAsyncTaskListener() {
|
||||
@Override
|
||||
public void parseSuccessful(ArrayList<Rule> rules) {
|
||||
fetchRulesListener.success(rules);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseFailed() {
|
||||
fetchRulesListener.failed();
|
||||
}
|
||||
}).execute();
|
||||
} else {
|
||||
fetchRulesListener.failed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) {
|
||||
fetchRulesListener.failed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class ParseRulesAsyncTask extends AsyncTask<Void, ArrayList<Rule>, ArrayList<Rule>> {
|
||||
private String response;
|
||||
private ParseRulesAsyncTask.ParseRulesAsyncTaskListener parseRulesAsyncTaskListener;
|
||||
|
||||
ParseRulesAsyncTask(String response, ParseRulesAsyncTask.ParseRulesAsyncTaskListener parseRulesAsyncTaskListener) {
|
||||
this.response = response;
|
||||
this.parseRulesAsyncTaskListener = parseRulesAsyncTaskListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ArrayList<Rule> doInBackground(Void... voids) {
|
||||
try {
|
||||
JSONArray rulesArray = new JSONObject(response).getJSONArray(JSONUtils.RULES_KEY);
|
||||
ArrayList<Rule> rules = new ArrayList<>();
|
||||
for (int i = 0; i < rulesArray.length(); i++) {
|
||||
String shortName = rulesArray.getJSONObject(i).getString(JSONUtils.SHORT_NAME_KEY);
|
||||
String description = null;
|
||||
if (rulesArray.getJSONObject(i).has(JSONUtils.DESCRIPTION_KEY)) {
|
||||
description = Utils.modifyMarkdown(rulesArray.getJSONObject(i).getString(JSONUtils.DESCRIPTION_KEY));
|
||||
}
|
||||
rules.add(new Rule(shortName, description));
|
||||
}
|
||||
return rules;
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(ArrayList<Rule> rules) {
|
||||
if (rules != null) {
|
||||
parseRulesAsyncTaskListener.parseSuccessful(rules);
|
||||
} else {
|
||||
parseRulesAsyncTaskListener.parseFailed();
|
||||
}
|
||||
}
|
||||
|
||||
interface ParseRulesAsyncTaskListener {
|
||||
void parseSuccessful(ArrayList<Rule> rules);
|
||||
|
||||
void parseFailed();
|
||||
}
|
||||
}
|
||||
}
|
@ -7,8 +7,12 @@ import android.os.Parcelable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ReportReason implements Parcelable {
|
||||
public static final String REASON_TYPE_SITE_REASON = "site_reason";
|
||||
public static final String REASON_TYPE_RULE_REASON = "rule_reason";
|
||||
public static final String REASON_TYPE_REASON = "reason";
|
||||
public static final String REASON_TYPE_OTHER_REASON = "other_reason";
|
||||
public static final String REASON_SITE_REASON_SELECTED = "site_reason_selected";
|
||||
public static final String REASON_RULE_REASON_SELECTED = "rule_reason_selected";
|
||||
public static final String REASON_OTHER = "other";
|
||||
|
||||
private String reportReason;
|
||||
private String reasonType;
|
||||
@ -68,10 +72,19 @@ public class ReportReason implements Parcelable {
|
||||
|
||||
public static ArrayList<ReportReason> getGeneralReasons(Context context) {
|
||||
ArrayList<ReportReason> reportReasons = new ArrayList<>();
|
||||
reportReasons.add(new ReportReason(context.getString(R.string.report_reason_general_spam), REASON_TYPE_REASON));
|
||||
reportReasons.add(new ReportReason(context.getString(R.string.report_reason_general_copyright_issue), REASON_TYPE_REASON));
|
||||
reportReasons.add(new ReportReason(context.getString(R.string.report_reason_general_child_pornography), REASON_TYPE_REASON));
|
||||
reportReasons.add(new ReportReason(context.getString(R.string.report_reason_general_abusive_content), REASON_TYPE_REASON));
|
||||
reportReasons.add(new ReportReason(context.getString(R.string.report_reason_general_spam), REASON_TYPE_SITE_REASON));
|
||||
reportReasons.add(new ReportReason(context.getString(R.string.report_reason_general_copyright_issue), REASON_TYPE_SITE_REASON));
|
||||
reportReasons.add(new ReportReason(context.getString(R.string.report_reason_general_child_pornography), REASON_TYPE_SITE_REASON));
|
||||
reportReasons.add(new ReportReason(context.getString(R.string.report_reason_general_abusive_content), REASON_TYPE_SITE_REASON));
|
||||
return reportReasons;
|
||||
}
|
||||
|
||||
public static ArrayList<ReportReason> convertRulesToReasons(ArrayList<Rule> rules) {
|
||||
ArrayList<ReportReason> reportReasons = new ArrayList<>();
|
||||
for (Rule r : rules) {
|
||||
reportReasons.add(new ReportReason(r.getShortName(), REASON_TYPE_RULE_REASON));
|
||||
}
|
||||
|
||||
return reportReasons;
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,14 @@ public class ReportThing {
|
||||
params.put(RedditUtils.THING_ID_KEY, thingFullname);
|
||||
params.put(RedditUtils.SR_NAME_KEY, subredditName);
|
||||
params.put(reasonType, reason);
|
||||
if (reasonType.equals(ReportReason.REASON_TYPE_SITE_REASON)) {
|
||||
params.put(RedditUtils.REASON_KEY, ReportReason.REASON_SITE_REASON_SELECTED);
|
||||
} else if (reasonType.equals(ReportReason.REASON_TYPE_RULE_REASON)) {
|
||||
params.put(RedditUtils.REASON_KEY, ReportReason.REASON_RULE_REASON_SELECTED);
|
||||
} else {
|
||||
params.put(RedditUtils.REASON_KEY, ReportReason.REASON_OTHER);
|
||||
}
|
||||
params.put(RedditUtils.API_TYPE_KEY, RedditUtils.API_TYPE_JSON);
|
||||
|
||||
oauthRetrofit.create(RedditAPI.class).report(header, params).enqueue(new Callback<String>() {
|
||||
@Override
|
||||
|
@ -84,6 +84,8 @@ public class RedditUtils {
|
||||
public static final String MULTIPATH_KEY = "multipath";
|
||||
public static final String MODEL_KEY = "model";
|
||||
|
||||
public static final String REASON_KEY = "reason";
|
||||
|
||||
public static Map<String, String> getHttpBasicAuthHeader() {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
String credentials = String.format("%s:%s", RedditUtils.CLIENT_ID, "");
|
||||
|
@ -3,7 +3,7 @@
|
||||
android:id="@+id/linear_layout_exo_playback_control_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:layout_gravity="bottom">
|
||||
|
||||
|
@ -198,6 +198,7 @@
|
||||
|
||||
<string name="no_rule">No rule</string>
|
||||
<string name="error_loading_rules">Error loading rules.\nTap to retry.</string>
|
||||
<string name="error_loading_rules_without_retry">Error Loading Rules</string>
|
||||
|
||||
<string name="search_in">Search in</string>
|
||||
<string name="all_subreddits">All subreddits</string>
|
||||
@ -708,9 +709,9 @@
|
||||
<string name="report_failed">Report failed</string>
|
||||
<string name="report_reason_not_selected">You haven\'t selected a reason</string>
|
||||
|
||||
<string name="report_reason_general_spam">Spam</string>
|
||||
<string name="report_reason_general_copyright_issue">Copyright Issue</string>
|
||||
<string name="report_reason_general_child_pornography">Child Pornography</string>
|
||||
<string name="report_reason_general_abusive_content">Abusive Content</string>
|
||||
<string name="report_reason_general_spam">It Is Spam</string>
|
||||
<string name="report_reason_general_copyright_issue">It Contains Copyright Issue</string>
|
||||
<string name="report_reason_general_child_pornography">It Contains Child Pornography</string>
|
||||
<string name="report_reason_general_abusive_content">It Contains Abusive Content</string>
|
||||
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user