added domain block support

This commit is contained in:
nuclearfog 2023-05-07 22:16:42 +02:00
parent 1b574992ed
commit 899a82989e
No known key found for this signature in database
GPG Key ID: 03488A185C476379
26 changed files with 909 additions and 98 deletions

View File

@ -2,6 +2,7 @@ package org.nuclearfog.twidda.backend.api;
import org.nuclearfog.twidda.backend.helper.ConnectionConfig; import org.nuclearfog.twidda.backend.helper.ConnectionConfig;
import org.nuclearfog.twidda.backend.helper.MediaStatus; import org.nuclearfog.twidda.backend.helper.MediaStatus;
import org.nuclearfog.twidda.lists.Domains;
import org.nuclearfog.twidda.lists.Messages; import org.nuclearfog.twidda.lists.Messages;
import org.nuclearfog.twidda.backend.helper.update.ProfileUpdate; import org.nuclearfog.twidda.backend.helper.update.ProfileUpdate;
import org.nuclearfog.twidda.backend.helper.update.StatusUpdate; import org.nuclearfog.twidda.backend.helper.update.StatusUpdate;
@ -448,6 +449,28 @@ public interface Connection {
*/ */
Status uploadStatus(StatusUpdate update, List<Long> mediaIds) throws ConnectionException; Status uploadStatus(StatusUpdate update, List<Long> mediaIds) throws ConnectionException;
/**
* return a list of domain names the current user has blocked
*
* @param cursor cursor to parse the pages or 0L if not defined
* @return domain list
*/
Domains getDomainBlocks(long cursor) throws ConnectionException;
/**
* block specific domain name
*
* @param domain domain name (without "https://")
*/
void blockDomain(String domain) throws ConnectionException;
/**
* remove block of a specific domain name
*
* @param domain domain name (without "https://")
*/
void unblockDomain(String domain) throws ConnectionException;
/** /**
* create userlist * create userlist
* *

View File

@ -24,6 +24,7 @@ import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonTrend;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonUser; import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonUser;
import org.nuclearfog.twidda.backend.helper.ConnectionConfig; import org.nuclearfog.twidda.backend.helper.ConnectionConfig;
import org.nuclearfog.twidda.backend.helper.MediaStatus; import org.nuclearfog.twidda.backend.helper.MediaStatus;
import org.nuclearfog.twidda.lists.Domains;
import org.nuclearfog.twidda.lists.Messages; import org.nuclearfog.twidda.lists.Messages;
import org.nuclearfog.twidda.backend.helper.update.PollUpdate; import org.nuclearfog.twidda.backend.helper.update.PollUpdate;
import org.nuclearfog.twidda.backend.helper.update.ProfileUpdate; import org.nuclearfog.twidda.backend.helper.update.ProfileUpdate;
@ -124,6 +125,7 @@ public class Mastodon implements Connection {
private static final String ENDPOINT_PUBLIC_TIMELINE = "/api/v1/timelines/public"; private static final String ENDPOINT_PUBLIC_TIMELINE = "/api/v1/timelines/public";
private static final String ENDPOINT_CUSTOM_EMOJIS = "/api/v1/custom_emojis"; private static final String ENDPOINT_CUSTOM_EMOJIS = "/api/v1/custom_emojis";
private static final String ENDPOINT_POLL = "/api/v1/polls/"; private static final String ENDPOINT_POLL = "/api/v1/polls/";
private static final String ENDPOINT_DOMAIN_BLOCK = "/api/v1/domain_blocks";
private static final MediaType TYPE_TEXT = MediaType.parse("text/plain"); private static final MediaType TYPE_TEXT = MediaType.parse("text/plain");
private static final MediaType TYPE_STREAM = MediaType.parse("application/octet-stream"); private static final MediaType TYPE_STREAM = MediaType.parse("application/octet-stream");
@ -663,6 +665,61 @@ public class Mastodon implements Connection {
} }
@Override
public Domains getDomainBlocks(long cursor) throws ConnectionException {
try {
List<String> params = new ArrayList<>();
params.add("limit=" + settings.getListSize());
params.add("maxId=" + cursor);
Response response = get(ENDPOINT_DOMAIN_BLOCK, params);
ResponseBody body = response.body();
if (response.code() == 200 && body != null) {
JSONArray array = new JSONArray(body.string());
long[] cursors = getCursors(response);
Domains result = new Domains(cursors[0], cursors[1]);
for (int i = 0 ; i < array.length() ; i++) {
result.add(array.getString(i));
}
return result;
}
throw new MastodonException(response);
} catch (JSONException | IOException e) {
throw new MastodonException(e);
}
}
@Override
public void blockDomain(String domain) throws ConnectionException {
try {
List<String> params = new ArrayList<>();
params.add("domain=" + domain);
Response response = post(ENDPOINT_DOMAIN_BLOCK, params);
if (response.code() != 200) {
throw new MastodonException(response);
}
} catch (IOException e) {
throw new MastodonException(e);
}
}
@Override
public void unblockDomain(String domain) throws ConnectionException {
try {
List<String> params = new ArrayList<>();
params.add("domain=" + domain);
Response response = delete(ENDPOINT_DOMAIN_BLOCK, params);
if (response.code() != 200) {
throw new MastodonException(response);
}
} catch (IOException e) {
throw new MastodonException(e);
}
}
@Override @Override
public UserList createUserlist(UserListUpdate update) throws MastodonException { public UserList createUserlist(UserListUpdate update) throws MastodonException {
List<String> params = new ArrayList<>(); List<String> params = new ArrayList<>();

View File

@ -23,6 +23,7 @@ import org.nuclearfog.twidda.backend.api.twitter.v1.impl.UserListV1;
import org.nuclearfog.twidda.backend.api.twitter.v1.impl.UserV1; import org.nuclearfog.twidda.backend.api.twitter.v1.impl.UserV1;
import org.nuclearfog.twidda.backend.helper.ConnectionConfig; import org.nuclearfog.twidda.backend.helper.ConnectionConfig;
import org.nuclearfog.twidda.backend.helper.MediaStatus; import org.nuclearfog.twidda.backend.helper.MediaStatus;
import org.nuclearfog.twidda.lists.Domains;
import org.nuclearfog.twidda.lists.Messages; import org.nuclearfog.twidda.lists.Messages;
import org.nuclearfog.twidda.backend.helper.update.ProfileUpdate; import org.nuclearfog.twidda.backend.helper.update.ProfileUpdate;
import org.nuclearfog.twidda.backend.helper.update.StatusUpdate; import org.nuclearfog.twidda.backend.helper.update.StatusUpdate;
@ -764,6 +765,24 @@ public class TwitterV1 implements Connection {
} }
@Override
public Domains getDomainBlocks(long cursor) throws ConnectionException {
throw new TwitterException("not supported");
}
@Override
public void blockDomain(String domain) throws ConnectionException {
throw new TwitterException("not supported");
}
@Override
public void unblockDomain(String domain) throws ConnectionException {
throw new TwitterException("not supported");
}
@Override @Override
public UserList createUserlist(UserListUpdate update) throws TwitterException { public UserList createUserlist(UserListUpdate update) throws TwitterException {
List<String> params = new ArrayList<>(); List<String> params = new ArrayList<>();

View File

@ -0,0 +1,105 @@
package org.nuclearfog.twidda.backend.async;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.nuclearfog.twidda.backend.api.Connection;
import org.nuclearfog.twidda.backend.api.ConnectionException;
import org.nuclearfog.twidda.backend.api.ConnectionManager;
import org.nuclearfog.twidda.lists.Domains;
/**
* background executor to load/block domains
*
* @author nuclearfog
*/
public class DomainAction extends AsyncExecutor<DomainAction.DomainParam, DomainAction.DomainResult> {
private Connection connection;
/**
*
*/
public DomainAction(Context context) {
connection = ConnectionManager.getDefaultConnection(context);
}
@Override
protected DomainResult doInBackground(@NonNull DomainParam param) {
try {
switch (param.mode) {
case DomainParam.MODE_LOAD:
Domains result = connection.getDomainBlocks(param.cursor);
return new DomainResult(DomainResult.MODE_LOAD, param.index, result, param.domain, null);
case DomainParam.MODE_BLOCK:
connection.blockDomain(param.domain);
return new DomainResult(DomainResult.MODE_BLOCK, param.index, null, param.domain, null);
case DomainParam.MODE_UNBLOCK:
connection.unblockDomain(param.domain);
return new DomainResult(DomainResult.MODE_UNBLOCK, param.index, null, param.domain, null);
}
} catch (ConnectionException exception) {
return new DomainResult(DomainResult.ERROR, param.index, null, param.domain, exception);
} catch (Exception exception) {
exception.printStackTrace();
}
return new DomainResult(DomainResult.ERROR, param.index, null, param.domain, null);
}
/**
*
*/
public static class DomainParam {
public static final int MODE_LOAD = 1;
public static final int MODE_BLOCK = 2;
public static final int MODE_UNBLOCK = 3;
public static final long NO_CURSOR = 0L;
final String domain;
final long cursor;
final int mode;
final int index;
public DomainParam(int mode, int index, long cursor, String domain) {
this.mode = mode;
this.cursor = cursor;
this.domain = domain;
this.index = index;
}
}
/**
*
*/
public static class DomainResult {
public static final int ERROR = -1;
public static final int MODE_LOAD = 4;
public static final int MODE_BLOCK = 5;
public static final int MODE_UNBLOCK = 6;
public final int mode;
public final int index;
@Nullable
public final Domains domains;
@Nullable
public final ConnectionException exception;
@Nullable
public final String domain;
DomainResult(int mode, int index, @Nullable Domains domains, @Nullable String domain, @Nullable ConnectionException exception) {
this.domains = domains;
this.domain = domain;
this.exception = exception;
this.mode = mode;
this.index = index;
}
}
}

View File

@ -44,15 +44,19 @@ public class FilterLoader extends AsyncExecutor<FilterLoader.FilterParam, Filter
db.saveFilterlist(ids); db.saveFilterlist(ids);
return new FilterResult(FilterResult.RELOAD, null); return new FilterResult(FilterResult.RELOAD, null);
case FilterParam.MUTE: case FilterParam.MUTE_USER:
Relation relation = connection.muteUser(param.name); Relation relation = connection.muteUser(param.name);
db.muteUser(relation.getId(), true); db.muteUser(relation.getId(), true);
return new FilterResult(FilterResult.MUTE, null); return new FilterResult(FilterResult.MUTE_USER, null);
case FilterParam.BLOCK: case FilterParam.BLOCK_USER:
relation = connection.blockUser(param.name); relation = connection.blockUser(param.name);
db.muteUser(relation.getId(), true); db.muteUser(relation.getId(), true);
return new FilterResult(FilterResult.BLOCK, null); return new FilterResult(FilterResult.BLOCK_USER, null);
case FilterParam.BLOCK_DOMAIN:
connection.blockDomain(param.name);
return new FilterResult(FilterResult.BLOCK_DOMAIN, null);
} }
} catch (ConnectionException exception) { } catch (ConnectionException exception) {
return new FilterResult(FilterResult.ERROR, exception); return new FilterResult(FilterResult.ERROR, exception);
@ -68,8 +72,9 @@ public class FilterLoader extends AsyncExecutor<FilterLoader.FilterParam, Filter
public static class FilterParam { public static class FilterParam {
public static final int RELOAD = 1; public static final int RELOAD = 1;
public static final int MUTE = 2; public static final int MUTE_USER = 2;
public static final int BLOCK = 3; public static final int BLOCK_USER = 3;
public static final int BLOCK_DOMAIN = 4;
final String name; final String name;
final int mode; final int mode;
@ -91,9 +96,10 @@ public class FilterLoader extends AsyncExecutor<FilterLoader.FilterParam, Filter
public static class FilterResult { public static class FilterResult {
public static final int ERROR = -1; public static final int ERROR = -1;
public static final int RELOAD = 4; public static final int RELOAD = 5;
public static final int MUTE = 5; public static final int MUTE_USER = 6;
public static final int BLOCK = 6; public static final int BLOCK_USER = 7;
public static final int BLOCK_DOMAIN = 8;
public final int mode; public final int mode;
@Nullable @Nullable

View File

@ -0,0 +1,80 @@
package org.nuclearfog.twidda.lists;
import java.util.LinkedList;
/**
* represents a list of domain url
*
* @author nuclearfog
*/
public class Domains extends LinkedList<String> {
private static final long serialVersionUID = 7642308259992697427L;
private long prevCursor, nextCursor;
/**
*
*/
public Domains() {
this(0L, 0L);
}
/**
* @param prevCursor cursor to the previous page
* @param nextCursor cursor to the next page
*/
public Domains(long prevCursor, long nextCursor) {
super();
this.prevCursor = prevCursor;
this.nextCursor = nextCursor;
}
/**
* @param domains list to clone
*/
public Domains(Domains domains) {
super(domains);
prevCursor = domains.prevCursor;
nextCursor = domains.nextCursor;
}
/**
* clone existing list
*
* @param domains list to clone
*/
public void replaceAll(Domains domains) {
clear();
addAll(domains);
prevCursor = domains.prevCursor;
nextCursor = domains.nextCursor;
}
/**
* add new items at specific index
*
* @param index index where to insert new items
* @param list items to add
*/
public void addAll(int index, Domains list) {
if (isEmpty()) {
prevCursor = list.prevCursor;
nextCursor = list.nextCursor;
} else if (index == 0) {
prevCursor = list.prevCursor;
} else if (index == size() - 1) {
nextCursor = list.nextCursor;
}
super.addAll(index, list);
}
/**
* get cursor for next items
*
* @return cursor or 0L if not set
*/
public long getNextCursor() {
return nextCursor;
}
}

View File

@ -42,6 +42,9 @@ import org.nuclearfog.textviewtool.LinkAndScrollMovement;
import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.api.ConnectionException; import org.nuclearfog.twidda.backend.api.ConnectionException;
import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback; import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback;
import org.nuclearfog.twidda.backend.async.DomainAction;
import org.nuclearfog.twidda.backend.async.DomainAction.DomainParam;
import org.nuclearfog.twidda.backend.async.DomainAction.DomainResult;
import org.nuclearfog.twidda.backend.async.RelationLoader; import org.nuclearfog.twidda.backend.async.RelationLoader;
import org.nuclearfog.twidda.backend.async.RelationLoader.RelationParam; import org.nuclearfog.twidda.backend.async.RelationLoader.RelationParam;
import org.nuclearfog.twidda.backend.async.RelationLoader.RelationResult; import org.nuclearfog.twidda.backend.async.RelationLoader.RelationResult;
@ -121,6 +124,7 @@ public class ProfileActivity extends AppCompatActivity implements ActivityResult
private static final int SCROLL_THRESHOLD = 10; private static final int SCROLL_THRESHOLD = 10;
private ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), this); private ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), this);
private AsyncCallback<DomainResult> domainCallback = this::setDomainResult;
private AsyncCallback<RelationResult> relationCallback = this::setRelationResult; private AsyncCallback<RelationResult> relationCallback = this::setRelationResult;
private AsyncCallback<UserResult> userCallback = this::setUserResult; private AsyncCallback<UserResult> userCallback = this::setUserResult;
private AsyncCallback<EmojiResult> usernameUpdate = this::onUsernameUpdate; private AsyncCallback<EmojiResult> usernameUpdate = this::onUsernameUpdate;
@ -131,6 +135,7 @@ public class ProfileActivity extends AppCompatActivity implements ActivityResult
private Picasso picasso; private Picasso picasso;
private ConfirmDialog confirmDialog; private ConfirmDialog confirmDialog;
private DomainAction domainAction;
private RelationLoader relationLoader; private RelationLoader relationLoader;
private UserLoader userLoader; private UserLoader userLoader;
private TextEmojiLoader emojiLoader; private TextEmojiLoader emojiLoader;
@ -181,6 +186,7 @@ public class ProfileActivity extends AppCompatActivity implements ActivityResult
viewPager = findViewById(R.id.profile_pager); viewPager = findViewById(R.id.profile_pager);
relationLoader = new RelationLoader(this); relationLoader = new RelationLoader(this);
domainAction = new DomainAction(this);
userLoader = new UserLoader(this); userLoader = new UserLoader(this);
emojiLoader = new TextEmojiLoader(this); emojiLoader = new TextEmojiLoader(this);
picasso = PicassoBuilder.get(this); picasso = PicassoBuilder.get(this);
@ -323,6 +329,7 @@ public class ProfileActivity extends AppCompatActivity implements ActivityResult
boolean result = super.onPrepareOptionsMenu(m); boolean result = super.onPrepareOptionsMenu(m);
if (user != null) { if (user != null) {
MenuItem listItem = m.findItem(R.id.profile_lists); MenuItem listItem = m.findItem(R.id.profile_lists);
MenuItem domainBlock = m.findItem(R.id.profile_block_domain);
switch (settings.getLogin().getConfiguration()) { switch (settings.getLogin().getConfiguration()) {
case TWITTER1: case TWITTER1:
@ -339,6 +346,8 @@ public class ProfileActivity extends AppCompatActivity implements ActivityResult
case MASTODON: case MASTODON:
if (user.isCurrentUser()) { if (user.isCurrentUser()) {
listItem.setVisible(true); listItem.setVisible(true);
} else {
domainBlock.setVisible(true);
} }
break; break;
} }
@ -472,6 +481,12 @@ public class ProfileActivity extends AppCompatActivity implements ActivityResult
startActivity(usersIntent); startActivity(usersIntent);
return true; return true;
} }
// block user domain
else if (item.getItemId() == R.id.profile_block_domain) {
if (user != null) {
confirmDialog.show(ConfirmDialog.DOMAIN_BLOCK_ADD);
}
}
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@ -594,6 +609,12 @@ public class ProfileActivity extends AppCompatActivity implements ActivityResult
RelationParam param = new RelationParam(user.getId(), RelationParam.MUTE); RelationParam param = new RelationParam(user.getId(), RelationParam.MUTE);
relationLoader.execute(param, relationCallback); relationLoader.execute(param, relationCallback);
} }
// confirmed domain block
else if (type == ConfirmDialog.DOMAIN_BLOCK_ADD) {
String url = Uri.parse(user.getProfileUrl()).getHost();
DomainParam param = new DomainParam(DomainParam.MODE_BLOCK, 0, DomainParam.NO_CURSOR, url);
domainAction.execute(param, domainCallback);
}
} }
} }
@ -675,7 +696,7 @@ public class ProfileActivity extends AppCompatActivity implements ActivityResult
private void setRelationResult(@NonNull RelationResult result) { private void setRelationResult(@NonNull RelationResult result) {
switch (result.mode) { switch (result.mode) {
case RelationResult.BLOCK: case RelationResult.BLOCK:
Toast.makeText(getApplicationContext(), R.string.info_user_blocked, Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), R.string.info_blocked, Toast.LENGTH_SHORT).show();
break; break;
case RelationResult.UNBLOCK: case RelationResult.UNBLOCK:
@ -709,6 +730,18 @@ public class ProfileActivity extends AppCompatActivity implements ActivityResult
} }
} }
/**
* set domain block result
*/
private void setDomainResult(DomainResult result) {
if (result.mode == DomainResult.MODE_BLOCK) {
Toast.makeText(getApplicationContext(), R.string.info_domain_blocked, Toast.LENGTH_SHORT).show();
} else if (result.mode == DomainResult.ERROR) {
String message = ErrorHandler.getErrorMessage(this, result.exception);
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
}
}
/** /**
* Set User Information * Set User Information
* *

View File

@ -19,7 +19,6 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.SearchView.OnQueryTextListener; import androidx.appcompat.widget.SearchView.OnQueryTextListener;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.widget.ViewPager2; import androidx.viewpager2.widget.ViewPager2;
import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.R;
@ -34,12 +33,10 @@ import org.nuclearfog.twidda.backend.async.UserlistManager.ListManagerResult;
import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.ErrorHandler; import org.nuclearfog.twidda.backend.utils.ErrorHandler;
import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.model.UserList; import org.nuclearfog.twidda.model.UserList;
import org.nuclearfog.twidda.ui.adapter.FragmentAdapter; import org.nuclearfog.twidda.ui.adapter.FragmentAdapter;
import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog; import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog;
import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog.OnConfirmListener; import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog.OnConfirmListener;
import org.nuclearfog.twidda.ui.fragments.UserFragment;
import org.nuclearfog.twidda.ui.views.TabSelector; import org.nuclearfog.twidda.ui.views.TabSelector;
import org.nuclearfog.twidda.ui.views.TabSelector.OnTabSelectedListener; import org.nuclearfog.twidda.ui.views.TabSelector.OnTabSelectedListener;
@ -100,8 +97,6 @@ public class UserlistActivity extends AppCompatActivity implements ActivityResul
@Nullable @Nullable
private UserList userList; private UserList userList;
@Nullable
private User user;
@Override @Override
@ -283,13 +278,6 @@ public class UserlistActivity extends AppCompatActivity implements ActivityResul
listLoaderAsync.execute(param, userlistSet); listLoaderAsync.execute(param, userlistSet);
} }
} }
// remove user from list
else if (type == ConfirmDialog.LIST_REMOVE_USER) {
if (listManagerAsync.isIdle() && userList != null && user != null) {
ListManagerParam param = new ListManagerParam(ListManagerParam.REMOVE, userList.getId(), user.getScreenname());
listManagerAsync.execute(param, userlistUpdate);
}
}
} }
@ -322,19 +310,6 @@ public class UserlistActivity extends AppCompatActivity implements ActivityResul
return false; return false;
} }
/**
* called from {@link org.nuclearfog.twidda.ui.fragments.UserFragment} when an user should be removed from a list
*
* @param user user to remove from the lsit
*/
public void onDelete(User user) {
if (!confirmDialog.isShowing()) {
confirmDialog.show(ConfirmDialog.LIST_REMOVE_USER);
this.user = user;
}
}
/** /**
* update userlist member * update userlist member
*/ */
@ -351,19 +326,6 @@ public class UserlistActivity extends AppCompatActivity implements ActivityResul
invalidateOptionsMenu(); invalidateOptionsMenu();
break; break;
case ListManagerResult.DEL_USER:
if (user != null) {
info = getString(R.string.info_user_removed, user.getScreenname());
Toast.makeText(getApplicationContext(), info, Toast.LENGTH_SHORT).show();
// remove user from list member page
Fragment fragment = adapter.getItem(1);
if (fragment instanceof UserFragment) {
UserFragment callback = (UserFragment) fragment;
callback.removeUser(user);
}
}
break;
case ListManagerResult.ERROR: case ListManagerResult.ERROR:
String message = ErrorHandler.getErrorMessage(this, result.exception); String message = ErrorHandler.getErrorMessage(this, result.exception);
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();

View File

@ -2,7 +2,9 @@ package org.nuclearfog.twidda.ui.activities;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.Patterns;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -170,11 +172,17 @@ public class UsersActivity extends AppCompatActivity implements OnTabSelectedLis
break; break;
case USERS_EXCLUDED: case USERS_EXCLUDED:
adapter.setupMuteBlockPage(); if (settings.getLogin().getConfiguration() == Configuration.MASTODON) {
adapter.setupBlockPage(true);
viewPager.setOffscreenPageLimit(3);
tabSelector.addTabIcons(R.array.user_domain_exclude_icons);
} else {
adapter.setupBlockPage(false);
viewPager.setOffscreenPageLimit(2); viewPager.setOffscreenPageLimit(2);
tabSelector.addTabIcons(R.array.user_exclude_icons);
}
tabSelector.addViewPager(viewPager); tabSelector.addViewPager(viewPager);
tabSelector.addOnTabSelectedListener(this); tabSelector.addOnTabSelectedListener(this);
tabSelector.addTabIcons(R.array.user_exclude_icons);
toolbar.setTitle(R.string.menu_toolbar_excluded_users); toolbar.setTitle(R.string.menu_toolbar_excluded_users);
break; break;
@ -230,6 +238,9 @@ public class UsersActivity extends AppCompatActivity implements OnTabSelectedLis
} else if (viewPager.getCurrentItem() == 1) { } else if (viewPager.getCurrentItem() == 1) {
String hint = getString(R.string.menu_hint_block_user); String hint = getString(R.string.menu_hint_block_user);
searchView.setQueryHint(hint); searchView.setQueryHint(hint);
} else if (viewPager.getCurrentItem() == 2) {
String hint = getString(R.string.menu_hint_block_domain);
searchView.setQueryHint(hint);
} }
return true; return true;
} }
@ -261,19 +272,33 @@ public class UsersActivity extends AppCompatActivity implements OnTabSelectedLis
@Override @Override
public boolean onQueryTextSubmit(String query) { public boolean onQueryTextSubmit(String query) {
if (USERNAME_PATTERN.matcher(query).matches()) { if (!filterLoader.isIdle())
if (filterLoader.isIdle()) { return false;
if (viewPager.getCurrentItem() == 0) { if (viewPager.getCurrentItem() == 0) {
FilterParam param = new FilterParam(FilterParam.MUTE, query); if (USERNAME_PATTERN.matcher(query).matches()) {
FilterParam param = new FilterParam(FilterParam.MUTE_USER, query);
filterLoader.execute(param, this); filterLoader.execute(param, this);
} else if (viewPager.getCurrentItem() == 1) {
FilterParam param = new FilterParam(FilterParam.BLOCK, query);
filterLoader.execute(param, this);
}
return true; return true;
} }
} else {
Toast.makeText(getApplicationContext(), R.string.error_username_format, Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), R.string.error_username_format, Toast.LENGTH_SHORT).show();
} else if (viewPager.getCurrentItem() == 1) {
if (USERNAME_PATTERN.matcher(query).matches()) {
FilterParam param = new FilterParam(FilterParam.BLOCK_USER, query);
filterLoader.execute(param, this);
return true;
}
Toast.makeText(getApplicationContext(), R.string.error_username_format, Toast.LENGTH_SHORT).show();
} else if (viewPager.getCurrentItem() == 2) {
if (Patterns.WEB_URL.matcher(query).matches()) {
FilterParam param;
if (query.startsWith("https://"))
param = new FilterParam(FilterParam.BLOCK_DOMAIN, Uri.parse(query).getHost());
else
param = new FilterParam(FilterParam.BLOCK_DOMAIN, query);
filterLoader.execute(param, this);
return true;
}
Toast.makeText(getApplicationContext(), R.string.error_domain_format, Toast.LENGTH_SHORT).show();
} }
return false; return false;
} }
@ -288,13 +313,14 @@ public class UsersActivity extends AppCompatActivity implements OnTabSelectedLis
@Override @Override
public void onResult(@NonNull FilterResult result) { public void onResult(@NonNull FilterResult result) {
switch (result.mode) { switch (result.mode) {
case FilterResult.MUTE: case FilterResult.MUTE_USER:
Toast.makeText(getApplicationContext(), R.string.info_user_muted, Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), R.string.info_user_muted, Toast.LENGTH_SHORT).show();
invalidateOptionsMenu(); invalidateOptionsMenu();
break; break;
case FilterResult.BLOCK: case FilterResult.BLOCK_DOMAIN:
Toast.makeText(getApplicationContext(), R.string.info_user_blocked, Toast.LENGTH_SHORT).show(); case FilterResult.BLOCK_USER:
Toast.makeText(getApplicationContext(), R.string.info_blocked, Toast.LENGTH_SHORT).show();
invalidateOptionsMenu(); invalidateOptionsMenu();
break; break;

View File

@ -0,0 +1,190 @@
package org.nuclearfog.twidda.ui.adapter;
import android.content.Context;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.lists.Domains;
import org.nuclearfog.twidda.ui.adapter.holder.DomainHolder;
import org.nuclearfog.twidda.ui.adapter.holder.OnHolderClickListener;
import org.nuclearfog.twidda.ui.adapter.holder.PlaceHolder;
/**
* RecyclerView adapter for domain list
*
* @author nuclearfog
*/
public class DomainAdapter extends Adapter<ViewHolder> implements OnHolderClickListener {
private static final int TYPE_ITEM = 0;
private static final int TYPE_FOOTER = 1;
private static final int NO_LOADING = -1;
public static final int NO_INDEX = -1;
private OnDomainClickListener listener;
private GlobalSettings settings;
private Domains items = new Domains();
private int loadingIndex = NO_LOADING;
/**
*
*/
public DomainAdapter(Context context, OnDomainClickListener listener) {
settings = GlobalSettings.getInstance(context);
this.listener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == TYPE_ITEM) {
return new DomainHolder(parent, settings, this);
} else {
return new PlaceHolder(parent, settings, false, this);
}
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
if (holder instanceof DomainHolder) {
DomainHolder domainHolder = (DomainHolder) holder;
domainHolder.setDomain(items.get(position));
} else if (holder instanceof PlaceHolder) {
PlaceHolder placeHolder = (PlaceHolder) holder;
placeHolder.setLoading(loadingIndex == position);
}
}
@Override
public int getItemCount() {
return items.size();
}
@Override
public int getItemViewType(int position) {
if (items.get(position) != null)
return TYPE_ITEM;
return TYPE_FOOTER;
}
@Override
public void onItemClick(int position, int type, int... extras) {
if (type == DOMAIN_REMOVE) {
listener.onDomainRemove(items.get(position));
}
}
@Override
public boolean onPlaceholderClick(int index) {
return listener.onPlaceholderClick(index, items.getNextCursor());
}
/**
* replace old items with new items
*
* @param domains new items
*/
public void replaceItems(Domains domains) {
disableLoading();
items.replaceAll(domains);
if (items.getNextCursor() != 0L && items.peekLast() != null) {
items.add(null);
}
notifyDataSetChanged();
}
/**
* insert new items at specific position
*
* @param domains new items
* @param index index where to insert new items
*/
public void addItems(Domains domains, int index) {
disableLoading();
if (index < 0) {
items.replaceAll(domains);
if (items.getNextCursor() != 0L) {
items.add(null);
}
notifyDataSetChanged();
} else {
items.addAll(index, domains);
if (items.getNextCursor() != 0L && items.peekLast() != null) {
items.add(null);
notifyItemRangeInserted(index, domains.size() + 1);
} else if (items.getNextCursor() == 0L && items.peekLast() == null) {
items.pollLast();
notifyItemRangeInserted(index, domains.size() - 1);
} else if (!domains.isEmpty()) {
notifyItemRangeInserted(index, domains.size());
}
}
}
/**
* get all items used in this adapter
*
* @return domain list
*/
public Domains getItems() {
return new Domains(items);
}
/**
* remove specific item from list
*
* @param item domain name item
*/
public void removeItem(String item) {
int index = items.indexOf(item);
if (index >= 0) {
items.remove(index);
notifyItemRemoved(index);
}
}
/**
* disable placeholder view loading animation
*/
public void disableLoading() {
if (loadingIndex != NO_LOADING) {
int oldIndex = loadingIndex;
loadingIndex = NO_LOADING;
notifyItemChanged(oldIndex);
}
}
/**
* listener for list item
*/
public interface OnDomainClickListener {
/**
* called on remove button click
*
* @param domain item of selected item
*/
void onDomainRemove(String domain);
/**
* called on footer click
*
* @param index index of the footer
* @param cursor cursor for the next items
* @return true if click was performed
*/
boolean onPlaceholderClick(int index, long cursor);
}
}

View File

@ -9,6 +9,7 @@ import androidx.viewpager2.adapter.FragmentStateAdapter;
import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.model.Account; import org.nuclearfog.twidda.model.Account;
import org.nuclearfog.twidda.ui.fragments.DomainFragment;
import org.nuclearfog.twidda.ui.fragments.ListFragment; import org.nuclearfog.twidda.ui.fragments.ListFragment;
import org.nuclearfog.twidda.ui.fragments.MessageFragment; import org.nuclearfog.twidda.ui.fragments.MessageFragment;
import org.nuclearfog.twidda.ui.fragments.NotificationFragment; import org.nuclearfog.twidda.ui.fragments.NotificationFragment;
@ -64,19 +65,6 @@ public class FragmentAdapter extends FragmentStateAdapter {
return fragments.length; return fragments.length;
} }
/**
* get fragment at index
*
* @param index index of the fragment
* @return fragment
*/
public ListFragment getItem(int index) {
if (index >= 0 && index < fragments.length) {
return fragments[index];
}
return null;
}
/** /**
* Check if adapter is empty * Check if adapter is empty
* *
@ -297,12 +285,17 @@ public class FragmentAdapter extends FragmentStateAdapter {
/** /**
* setup adapter for a page of muted / blocked users * setup adapter for a page of muted / blocked users
*/ */
public void setupMuteBlockPage() { public void setupBlockPage(boolean enableDomainBlock) {
Bundle paramMuteList = new Bundle(); Bundle paramMuteList = new Bundle();
Bundle paramBlockList = new Bundle(); Bundle paramBlockList = new Bundle();
paramMuteList.putInt(UserFragment.KEY_MODE, UserFragment.MODE_MUTES); paramMuteList.putInt(UserFragment.KEY_MODE, UserFragment.MODE_MUTES);
paramBlockList.putInt(UserFragment.KEY_MODE, UserFragment.MODE_BLOCKS); paramBlockList.putInt(UserFragment.KEY_MODE, UserFragment.MODE_BLOCKS);
if (enableDomainBlock) {
fragments = new ListFragment[3];
fragments[2] = new DomainFragment();
} else {
fragments = new ListFragment[2]; fragments = new ListFragment[2];
}
fragments[0] = new UserFragment(); fragments[0] = new UserFragment();
fragments[1] = new UserFragment(); fragments[1] = new UserFragment();
fragments[0].setArguments(paramMuteList); fragments[0].setArguments(paramMuteList);

View File

@ -0,0 +1,62 @@
package org.nuclearfog.twidda.ui.adapter.holder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.config.GlobalSettings;
/**
* domain holder class for {@link org.nuclearfog.twidda.ui.adapter.DomainAdapter}
*
* @author nuclearfog
*/
public class DomainHolder extends ViewHolder implements OnClickListener {
private TextView domain_name;
private OnHolderClickListener listener;
/**
*
*/
public DomainHolder(ViewGroup parent, GlobalSettings settings, OnHolderClickListener listener) {
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_domain, parent, false));
this.listener = listener;
CardView card = (CardView) itemView;
View deleteButton = itemView.findViewById(R.id.item_domain_delete);
domain_name = itemView.findViewById(R.id.item_domain_name);
domain_name.setTextColor(settings.getTextColor());
domain_name.setTypeface(settings.getTypeFace());
card.setCardBackgroundColor(settings.getCardColor());
deleteButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.item_domain_delete) {
int position = getLayoutPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(position, OnHolderClickListener.DOMAIN_REMOVE);
}
}
}
/**
* set domain information
*
* @param name domain information
*/
public void setDomain(String name) {
domain_name.setText(name);
}
}

View File

@ -49,6 +49,8 @@ public interface OnHolderClickListener {
int EMOJI_CLICK = 20; int EMOJI_CLICK = 20;
int DOMAIN_REMOVE = 21;
/** /**
* called when an item was clicked * called when an item was clicked
* *

View File

@ -127,6 +127,10 @@ public class ConfirmDialog extends Dialog implements OnClickListener {
*/ */
public static final int NOTIFICATION_DISMISS = 623; public static final int NOTIFICATION_DISMISS = 623;
public static final int DOMAIN_BLOCK_ADD = 624;
public static final int DOMAIN_BLOCK_REMOVE = 625;
private TextView title, message; private TextView title, message;
private Button confirm, cancel; private Button confirm, cancel;
@ -236,6 +240,14 @@ public class ConfirmDialog extends Dialog implements OnClickListener {
messageRes = R.string.confirm_dismiss_notification; messageRes = R.string.confirm_dismiss_notification;
break; break;
case DOMAIN_BLOCK_ADD:
messageRes = R.string.confirm_add_domain_block;
break;
case DOMAIN_BLOCK_REMOVE:
messageRes = R.string.confirm_remove_domain_block;
break;
case PROFILE_UNFOLLOW: case PROFILE_UNFOLLOW:
messageRes = R.string.confirm_unfollow; messageRes = R.string.confirm_unfollow;
break; break;

View File

@ -0,0 +1,139 @@
package org.nuclearfog.twidda.ui.fragments;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback;
import org.nuclearfog.twidda.backend.async.DomainAction;
import org.nuclearfog.twidda.backend.async.DomainAction.DomainParam;
import org.nuclearfog.twidda.backend.async.DomainAction.DomainResult;
import org.nuclearfog.twidda.lists.Domains;
import org.nuclearfog.twidda.ui.adapter.DomainAdapter;
import org.nuclearfog.twidda.ui.adapter.DomainAdapter.OnDomainClickListener;
import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog;
import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog.OnConfirmListener;
import java.io.Serializable;
/**
* Fragment containing a list of domain names
*
* @author nuclearfog
*/
public class DomainFragment extends ListFragment implements OnDomainClickListener, OnConfirmListener, AsyncCallback<DomainResult> {
private static final String KEY_DATA = "domain-data";
private DomainAction domainAction;
private DomainAdapter adapter;
private ConfirmDialog dialog;
private String selectedDomain = "";
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
adapter = new DomainAdapter(requireContext(), this);
domainAction = new DomainAction(requireContext());
dialog = new ConfirmDialog(requireContext());
setAdapter(adapter);
dialog.setConfirmListener(this);
if (savedInstanceState != null) {
Serializable data = savedInstanceState.getSerializable(KEY_DATA);
if (data instanceof Domains) {
adapter.replaceItems((Domains) data);
return;
}
}
load(DomainAdapter.NO_INDEX, DomainParam.NO_CURSOR);
setRefresh(true);
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putSerializable(KEY_DATA, adapter.getItems());
super.onSaveInstanceState(outState);
}
@Override
public void onDestroy() {
domainAction.cancel();
super.onDestroy();
}
@Override
protected void onReload() {
load(DomainAdapter.NO_INDEX, DomainParam.NO_CURSOR);
}
@Override
protected void onReset() {
setRefresh(true);
load(DomainAdapter.NO_INDEX, DomainParam.NO_CURSOR);
}
@Override
public void onDomainRemove(String domain) {
if (!isRefreshing() && !dialog.isShowing()) {
dialog.show(ConfirmDialog.DOMAIN_BLOCK_REMOVE);
selectedDomain = domain;
}
}
@Override
public boolean onPlaceholderClick(int index, long cursor) {
if (!isRefreshing()) {
load(index, cursor);
return true;
}
return false;
}
@Override
public void onResult(@NonNull DomainResult result) {
setRefresh(false);
if (result.mode == DomainResult.MODE_LOAD) {
if (result.domains != null) {
adapter.addItems(result.domains, result.index);
}
} else if (result.mode == DomainResult.MODE_UNBLOCK) {
if (result.domain != null) {
adapter.removeItem(result.domain);
Toast.makeText(requireContext(), R.string.info_domain_removed, Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onConfirm(int type) {
if (type == ConfirmDialog.DOMAIN_BLOCK_REMOVE) {
DomainParam param = new DomainParam(DomainParam.MODE_UNBLOCK, DomainAdapter.NO_INDEX, DomainParam.NO_CURSOR, selectedDomain);
domainAction.execute(param, this);
}
}
/**
* load domain list
*
* @param index index where to insert domains into the list
* @param cursor cursor used to page through results
*/
private void load(int index, long cursor) {
DomainParam param = new DomainParam(DomainParam.MODE_LOAD, index, cursor, null);
domainAction.execute(param, this);
}
}

View File

@ -67,6 +67,7 @@ public class NotificationFragment extends ListFragment implements OnNotification
notificationAction = new NotificationAction(requireContext()); notificationAction = new NotificationAction(requireContext());
adapter = new NotificationAdapter(requireContext(), this); adapter = new NotificationAdapter(requireContext(), this);
setAdapter(adapter); setAdapter(adapter);
confirmDialog.setConfirmListener(this);
if (savedInstanceState != null) { if (savedInstanceState != null) {
Serializable data = savedInstanceState.getSerializable(KEY_DATA); Serializable data = savedInstanceState.getSerializable(KEY_DATA);
@ -75,7 +76,6 @@ public class NotificationFragment extends ListFragment implements OnNotification
return; return;
} }
} }
confirmDialog.setConfirmListener(this);
load(0L, 0L, 0); load(0L, 0L, 0);
setRefresh(true); setRefresh(true);
} }

View File

@ -12,7 +12,10 @@ import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback; import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback;
import org.nuclearfog.twidda.backend.async.UserlistManager;
import org.nuclearfog.twidda.backend.async.UserlistManager.ListManagerResult;
import org.nuclearfog.twidda.backend.async.UsersLoader; import org.nuclearfog.twidda.backend.async.UsersLoader;
import org.nuclearfog.twidda.backend.async.UsersLoader.UserParam; import org.nuclearfog.twidda.backend.async.UsersLoader.UserParam;
import org.nuclearfog.twidda.backend.async.UsersLoader.UserResult; import org.nuclearfog.twidda.backend.async.UsersLoader.UserResult;
@ -20,9 +23,10 @@ import org.nuclearfog.twidda.lists.Users;
import org.nuclearfog.twidda.backend.utils.ErrorHandler; import org.nuclearfog.twidda.backend.utils.ErrorHandler;
import org.nuclearfog.twidda.model.User; import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.ui.activities.ProfileActivity; import org.nuclearfog.twidda.ui.activities.ProfileActivity;
import org.nuclearfog.twidda.ui.activities.UserlistActivity;
import org.nuclearfog.twidda.ui.adapter.UserAdapter; import org.nuclearfog.twidda.ui.adapter.UserAdapter;
import org.nuclearfog.twidda.ui.adapter.UserAdapter.UserClickListener; import org.nuclearfog.twidda.ui.adapter.UserAdapter.UserClickListener;
import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog;
import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog.OnConfirmListener;
import java.io.Serializable; import java.io.Serializable;
@ -31,7 +35,7 @@ import java.io.Serializable;
* *
* @author nuclearfog * @author nuclearfog
*/ */
public class UserFragment extends ListFragment implements UserClickListener, AsyncCallback<UserResult>, ActivityResultCallback<ActivityResult> { public class UserFragment extends ListFragment implements UserClickListener, OnConfirmListener, AsyncCallback<UserResult>, ActivityResultCallback<ActivityResult> {
/** /**
* key to set the type of user list to show * key to set the type of user list to show
@ -145,10 +149,14 @@ public class UserFragment extends ListFragment implements UserClickListener, Asy
private ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), this); private ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), this);
private AsyncCallback<ListManagerResult> userlistUpdate = this::updateUsers;
private ConfirmDialog confirmDialog;
private UsersLoader userLoader; private UsersLoader userLoader;
private UserlistManager userlistManager;
private UserAdapter adapter; private UserAdapter adapter;
private User selectedUser;
private String search = ""; private String search = "";
private long id = 0; private long id = 0;
private int mode = 0; private int mode = 0;
@ -158,8 +166,11 @@ public class UserFragment extends ListFragment implements UserClickListener, Asy
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
userLoader = new UsersLoader(requireContext()); userLoader = new UsersLoader(requireContext());
userlistManager = new UserlistManager(requireContext());
confirmDialog = new ConfirmDialog(requireContext());
adapter = new UserAdapter(requireContext(), this); adapter = new UserAdapter(requireContext(), this);
setAdapter(adapter); setAdapter(adapter);
confirmDialog.setConfirmListener(this);
Bundle param = getArguments(); Bundle param = getArguments();
if (param != null) { if (param != null) {
@ -244,10 +255,9 @@ public class UserFragment extends ListFragment implements UserClickListener, Asy
@Override @Override
public void onDelete(User user) { public void onDelete(User user) {
if (getActivity() instanceof UserlistActivity) { if (!confirmDialog.isShowing()) {
// call parent activity to handle user delete confirmDialog.show(ConfirmDialog.LIST_REMOVE_USER);
UserlistActivity callback = (UserlistActivity) getActivity(); this.selectedUser = user;
callback.onDelete(user);
} }
} }
@ -264,15 +274,30 @@ public class UserFragment extends ListFragment implements UserClickListener, Asy
setRefresh(false); setRefresh(false);
} }
/**
* remove specific user from fragment list @Override
* public void onConfirm(int type) {
* @param user user to remove // remove user from list
*/ if (type == ConfirmDialog.LIST_REMOVE_USER) {
public void removeUser(User user) { if (userlistManager.isIdle() && selectedUser != null) {
adapter.removeItem(user); UserlistManager.ListManagerParam param = new UserlistManager.ListManagerParam(UserlistManager.ListManagerParam.REMOVE, id, selectedUser.getScreenname());
userlistManager.execute(param, userlistUpdate);
}
}
} }
/**
* callback for userlist changes
*/
private void updateUsers(ListManagerResult result) {
if (result.mode == ListManagerResult.DEL_USER) {
if (selectedUser != null) {
String info = getString(R.string.info_user_removed, selectedUser.getScreenname());
Toast.makeText(requireContext(), info, Toast.LENGTH_SHORT).show();
adapter.removeItem(selectedUser);
}
}
}
/** /**
* load content into the list * load content into the list

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="m20.025,10.016c0,0.921 -0.08,1.665 -1.002,1.665h-7.332v7.325c0,0.919 -0.745,1.001 -1.667,1.001 -0.922,0 -1.667,-0.082 -1.667,-1.001V11.681H1.025c-0.92,0 -1.002,-0.744 -1.002,-1.665 0,-0.921 0.082,-1.665 1.002,-1.665H8.358V1.026c0,-0.921 0.745,-1.001 1.667,-1.001 0.922,0 1.667,0.08 1.667,1.001v7.325h7.332c0.922,0 1.002,0.744 1.002,1.665z"
android:strokeWidth="1.66597"
android:fillColor="#ffffff"/>
</vector>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/CardViewStyle">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<TextView
android:id="@+id/item_domain_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/item_domain_layout_margin"
android:textSize="@dimen/item_domain_text_size"
android:layout_weight="1"
android:lines="1"/>
<ImageButton
android:id="@+id/item_domain_delete"
android:layout_width="@dimen/item_domain_button_size"
android:layout_height="@dimen/item_domain_button_size"
android:padding="@dimen/item_domain_button_padding"
android:layout_margin="@dimen/item_domain_layout_margin"
android:contentDescription="@string/descr_remove_domain"
android:scaleType="fitCenter"
android:src="@drawable/cross"
style="@style/RoundButton" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -5,7 +5,7 @@
<item <item
android:id="@+id/menu_exclude_user" android:id="@+id/menu_exclude_user"
android:title="@string/menu_exclude_user" android:title="@string/menu_exclude_user"
android:icon="@drawable/add_user" android:icon="@drawable/plus"
app:actionViewClass="androidx.appcompat.widget.SearchView" app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom|collapseActionView" /> app:showAsAction="ifRoom|collapseActionView" />

View File

@ -20,6 +20,11 @@
android:title="@string/menu_block_user" android:title="@string/menu_block_user"
android:visible="false" /> android:visible="false" />
<item
android:id="@+id/profile_block_domain"
android:title="@string/menu_block_domain"
android:visible="false" />
<item <item
android:id="@+id/profile_mute" android:id="@+id/profile_mute"
android:title="@string/menu_mute_user" android:title="@string/menu_mute_user"

View File

@ -16,6 +16,8 @@
<string name="settings_background">Hintergrund</string> <string name="settings_background">Hintergrund</string>
<string name="confirm_delete_status">Status löschen?</string> <string name="confirm_delete_status">Status löschen?</string>
<string name="error_enter_code">PIN eingeben!</string> <string name="error_enter_code">PIN eingeben!</string>
<string name="confirm_add_domain_block">Nutzerdomain sperren?</string>
<string name="confirm_remove_domain_block">Domain von der Liste entfernen?</string>
<string name="confirm_delete_database">App Daten Löschen?</string> <string name="confirm_delete_database">App Daten Löschen?</string>
<string name="error_limit_exceeded">"Anfragelimit erreicht! Entsperrung nach:"</string> <string name="error_limit_exceeded">"Anfragelimit erreicht! Entsperrung nach:"</string>
<string name="info_status_sent">Status gesendet</string> <string name="info_status_sent">Status gesendet</string>
@ -26,6 +28,7 @@
<string name="info_user_repost">%1$s hat deinen Status geteilt</string> <string name="info_user_repost">%1$s hat deinen Status geteilt</string>
<string name="info_database_cleared">Datenbank gelöscht</string> <string name="info_database_cleared">Datenbank gelöscht</string>
<string name="info_notification_dismiss">Nachricht verworfen</string> <string name="info_notification_dismiss">Nachricht verworfen</string>
<string name="info_domain_blocked">Domain gesperrt!</string>
<string name="info_error">Fehler!</string> <string name="info_error">Fehler!</string>
<string name="menu_status_edit">bearbeiten</string> <string name="menu_status_edit">bearbeiten</string>
<string name="menu_status_delete">löschen</string> <string name="menu_status_delete">löschen</string>
@ -35,7 +38,7 @@
<string name="menu_block_user">blocken</string> <string name="menu_block_user">blocken</string>
<string name="menu_user_unblock">entblocken</string> <string name="menu_user_unblock">entblocken</string>
<string name="info_followed">gefolgt!</string> <string name="info_followed">gefolgt!</string>
<string name="info_user_blocked">geblockt!</string> <string name="info_blocked">geblockt!</string>
<string name="info_user_unblocked">entblockt!</string> <string name="info_user_unblocked">entblockt!</string>
<string name="info_status_removed">Status gelöscht!</string> <string name="info_status_removed">Status gelöscht!</string>
<string name="status_sent_from">"gesendet von: "</string> <string name="status_sent_from">"gesendet von: "</string>
@ -171,6 +174,7 @@
<string name="info_user_added_to_list">%1$s wurde zur Liste hinzugefügt!</string> <string name="info_user_added_to_list">%1$s wurde zur Liste hinzugefügt!</string>
<string name="error_username_format">Nutzername falsch!</string> <string name="error_username_format">Nutzername falsch!</string>
<string name="error_not_defined">Undefinierter Fehler!</string> <string name="error_not_defined">Undefinierter Fehler!</string>
<string name="error_domain_format">Falsches Domainformat!</string>
<string name="error_cant_reply_to_status">Status ist nicht für Antworten freigegeben!</string> <string name="error_cant_reply_to_status">Status ist nicht für Antworten freigegeben!</string>
<string name="error_corrupt_api_key">Fehler, API Schlüssel ist fehlerhaft!</string> <string name="error_corrupt_api_key">Fehler, API Schlüssel ist fehlerhaft!</string>
<string name="error_acc_update">Account Informationen konnten nicht aktualisiert werden! Bitte Eingaben überprüfen.</string> <string name="error_acc_update">Account Informationen konnten nicht aktualisiert werden! Bitte Eingaben überprüfen.</string>
@ -200,6 +204,7 @@
<string name="menu_hint_block_user">\@name blockieren</string> <string name="menu_hint_block_user">\@name blockieren</string>
<string name="menu_exclude_user">Nutzer ausschließen</string> <string name="menu_exclude_user">Nutzer ausschließen</string>
<string name="menu_search_filter">Ergebnisse filtern</string> <string name="menu_search_filter">Ergebnisse filtern</string>
<string name="menu_hint_block_domain">domain.namen eingeben</string>
<string name="menu_refresh_exclude">Filterliste aktualisieren</string> <string name="menu_refresh_exclude">Filterliste aktualisieren</string>
<string name="info_refreshing_exclude_list">aktualisiere Filterliste</string> <string name="info_refreshing_exclude_list">aktualisiere Filterliste</string>
<string name="info_exclude_list_updated">Filterliste aktualisiert!</string> <string name="info_exclude_list_updated">Filterliste aktualisiert!</string>
@ -229,6 +234,7 @@
<string name="info_reply_hidden">Antwort wurde ausgeblendet</string> <string name="info_reply_hidden">Antwort wurde ausgeblendet</string>
<string name="info_reply_unhidden">Antwort wurde eingeblendet</string> <string name="info_reply_unhidden">Antwort wurde eingeblendet</string>
<string name="settings_description_enable_twitter_alt">öffne Twitter-Links mit Nitter</string> <string name="settings_description_enable_twitter_alt">öffne Twitter-Links mit Nitter</string>
<string name="descr_remove_domain">entferne Domain von der Liste</string>
<string name="confirm_unknown_error">Unbekannter Fehler!</string> <string name="confirm_unknown_error">Unbekannter Fehler!</string>
<string name="error_invalid_media">Ungültige Mediendatei!</string> <string name="error_invalid_media">Ungültige Mediendatei!</string>
<string name="menu_follow_requests">Anfragen</string> <string name="menu_follow_requests">Anfragen</string>
@ -265,6 +271,7 @@
<string name="info_poll_voted">Umfrage abgestimmt!</string> <string name="info_poll_voted">Umfrage abgestimmt!</string>
<string name="confirm_dismiss_notification">Nachricht verwerfen?</string> <string name="confirm_dismiss_notification">Nachricht verwerfen?</string>
<string name="error_json_format">JSON Fehler!</string> <string name="error_json_format">JSON Fehler!</string>
<string name="menu_block_domain">Domain sperren</string>
<string name="menu_bookmark_add">Lesezeichen hinzufügen</string> <string name="menu_bookmark_add">Lesezeichen hinzufügen</string>
<string name="menu_bookmark_remove">Lesezeichen entfernen</string> <string name="menu_bookmark_remove">Lesezeichen entfernen</string>
<string name="notification_dismiss">Benachrichtigung verwerfen</string> <string name="notification_dismiss">Benachrichtigung verwerfen</string>
@ -294,5 +301,6 @@
<string name="menu_hashtag_follow">folge Hashtag</string> <string name="menu_hashtag_follow">folge Hashtag</string>
<string name="menu_hashtag_unfollow">entfolge Hashtag</string> <string name="menu_hashtag_unfollow">entfolge Hashtag</string>
<string name="info_hashtag_unfollowed">Hashtag entfolgt</string> <string name="info_hashtag_unfollowed">Hashtag entfolgt</string>
<string name="info_domain_removed">Domain von der Liste entfernt</string>
<string name="info_hashtag_followed">Hashtag gefolgt</string> <string name="info_hashtag_followed">Hashtag gefolgt</string>
</resources> </resources>

View File

@ -109,7 +109,7 @@
<string name="info_profile_updated">个人资料已更新</string> <string name="info_profile_updated">个人资料已更新</string>
<string name="info_followed">已关注</string> <string name="info_followed">已关注</string>
<string name="info_unfollowed">已取消关注</string> <string name="info_unfollowed">已取消关注</string>
<string name="info_user_blocked">已屏蔽</string> <string name="info_blocked">已屏蔽</string>
<string name="info_user_unblocked">已取消屏蔽</string> <string name="info_user_unblocked">已取消屏蔽</string>
<string name="info_status_removed">推文已删除</string> <string name="info_status_removed">推文已删除</string>
<string name="info_status_sent">推文已发送</string> <string name="info_status_sent">推文已发送</string>

View File

@ -32,6 +32,12 @@
<item>@drawable/block</item> <item>@drawable/block</item>
</integer-array> </integer-array>
<integer-array name="user_domain_exclude_icons">
<item>@drawable/mute</item>
<item>@drawable/block</item>
<item>@drawable/link</item>
</integer-array>
<integer-array name="user_hashtag_following"> <integer-array name="user_hashtag_following">
<item>@drawable/user</item> <item>@drawable/user</item>
<item>@drawable/hash</item> <item>@drawable/hash</item>

View File

@ -310,4 +310,10 @@
<!--dimens of DescriptionView--> <!--dimens of DescriptionView-->
<dimen name="descriptionview_layout_padding">5dp</dimen> <dimen name="descriptionview_layout_padding">5dp</dimen>
<!--dimens of item_domain.xml-->
<dimen name="item_domain_layout_margin">8dp</dimen>
<dimen name="item_domain_text_size">20sp</dimen>
<dimen name="item_domain_button_size">36dp</dimen>
<dimen name="item_domain_button_padding">7dp</dimen>
</resources> </resources>

View File

@ -41,7 +41,7 @@
<string name="info_profile_updated">Profile updated</string> <string name="info_profile_updated">Profile updated</string>
<string name="info_followed">followed</string> <string name="info_followed">followed</string>
<string name="info_unfollowed">unfollowed</string> <string name="info_unfollowed">unfollowed</string>
<string name="info_user_blocked">blocked</string> <string name="info_blocked">blocked</string>
<string name="info_user_unblocked">unblocked</string> <string name="info_user_unblocked">unblocked</string>
<string name="info_status_removed">Status removed</string> <string name="info_status_removed">Status removed</string>
<string name="info_status_sent">Status shared</string> <string name="info_status_sent">Status shared</string>
@ -66,7 +66,9 @@
<string name="info_database_cleared">Database cleared</string> <string name="info_database_cleared">Database cleared</string>
<string name="info_notification_dismiss">Notification dismissed</string> <string name="info_notification_dismiss">Notification dismissed</string>
<string name="info_hashtag_unfollowed">hashtag unfollowed</string> <string name="info_hashtag_unfollowed">hashtag unfollowed</string>
<string name="info_domain_removed">domain removed from the list</string>
<string name="info_hashtag_followed">hashtag followed</string> <string name="info_hashtag_followed">hashtag followed</string>
<string name="info_domain_blocked">domain blocked!</string>
<string name="info_error">Error</string> <string name="info_error">Error</string>
<!-- toast messages for error information --> <!-- toast messages for error information -->
@ -97,6 +99,7 @@
<string name="error_dm_length">Directmessage is too long!</string> <string name="error_dm_length">Directmessage is too long!</string>
<string name="error_list_title_empty">Empty list title!</string> <string name="error_list_title_empty">Empty list title!</string>
<string name="error_username_format">Wrong username format!</string> <string name="error_username_format">Wrong username format!</string>
<string name="error_domain_format">Wrong domain format!</string>
<string name="error_cant_reply_to_status">You can\'t reply to this status!</string> <string name="error_cant_reply_to_status">You can\'t reply to this status!</string>
<string name="error_corrupt_api_key">Error, corrupt API key!</string> <string name="error_corrupt_api_key">Error, corrupt API key!</string>
<string name="error_acc_update">Account update failed! Please check your input!</string> <string name="error_acc_update">Account update failed! Please check your input!</string>
@ -129,7 +132,8 @@
<string name="menu_goto_lists">Userlists</string> <string name="menu_goto_lists">Userlists</string>
<string name="menu_app_info">About</string> <string name="menu_app_info">About</string>
<string name="menu_licenses">Licenses</string> <string name="menu_licenses">Licenses</string>
<string name="menu_block_user">block</string> <string name="menu_block_user">block user</string>
<string name="menu_block_domain">block domain</string>
<string name="menu_bookmark_add">bookmark</string> <string name="menu_bookmark_add">bookmark</string>
<string name="menu_bookmark_remove">remove bookmark</string> <string name="menu_bookmark_remove">remove bookmark</string>
<string name="menu_unmute_user">unmute</string> <string name="menu_unmute_user">unmute</string>
@ -155,6 +159,7 @@
<string name="menu_search_filter">filter results</string> <string name="menu_search_filter">filter results</string>
<string name="menu_hint_mute_user">enter @name to mute</string> <string name="menu_hint_mute_user">enter @name to mute</string>
<string name="menu_hint_block_user">enter @name to block</string> <string name="menu_hint_block_user">enter @name to block</string>
<string name="menu_hint_block_domain">enter domain.name</string>
<string name="menu_refresh_exclude">refresh exclude list</string> <string name="menu_refresh_exclude">refresh exclude list</string>
<string name="menu_select_account">Logins</string> <string name="menu_select_account">Logins</string>
<string name="menu_add_account">add account</string> <string name="menu_add_account">add account</string>
@ -205,6 +210,8 @@
<string name="settings_clear_data">clear app data</string> <string name="settings_clear_data">clear app data</string>
<string name="confirm_delete_status">delete status?</string> <string name="confirm_delete_status">delete status?</string>
<string name="confirm_dismiss_notification">dismiss notification?</string> <string name="confirm_dismiss_notification">dismiss notification?</string>
<string name="confirm_add_domain_block">block user domain?</string>
<string name="confirm_remove_domain_block">remove domain from list?</string>
<string name="confirm_delete_database">clear app data?</string> <string name="confirm_delete_database">clear app data?</string>
<string name="status_sent_from">"sent from: "</string> <string name="status_sent_from">"sent from: "</string>
<string name="username">Username</string> <string name="username">Username</string>
@ -289,6 +296,7 @@
<string name="update_list">update list</string> <string name="update_list">update list</string>
<string name="confirm_remove_user_from_list">remove user from list?</string> <string name="confirm_remove_user_from_list">remove user from list?</string>
<string name="descr_remove_user">remove user from list</string> <string name="descr_remove_user">remove user from list</string>
<string name="descr_remove_domain">remove domain from list</string>
<string name="confirm_unknown_error">unknown error!</string> <string name="confirm_unknown_error">unknown error!</string>
<string name="list_following_indicator">following list</string> <string name="list_following_indicator">following list</string>
<string name="descr_add_profile_image">change profile image</string> <string name="descr_add_profile_image">change profile image</string>