Add filtering logic and db and made color highlighting work

filtering
Floens 10 years ago
parent 5a564ddc32
commit 8a472097db
  1. 14
      Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHelper.java
  2. 25
      Clover/app/src/main/java/org/floens/chan/core/database/DatabaseManager.java
  3. 190
      Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java
  4. 26
      Clover/app/src/main/java/org/floens/chan/core/model/Filter.java
  5. 2
      Clover/app/src/main/java/org/floens/chan/core/model/Post.java
  6. 28
      Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java
  7. 10
      Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java
  8. 4
      Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java
  9. 2
      Clover/app/src/main/java/org/floens/chan/ui/adapter/PostAdapter.java
  10. 11
      Clover/app/src/main/java/org/floens/chan/ui/adapter/PostsFilter.java
  11. 9
      Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java
  12. 14
      Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java
  13. 2
      Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java
  14. 10
      Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java
  15. 65
      Clover/app/src/main/java/org/floens/chan/ui/controller/FiltersController.java
  16. 8
      Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java
  17. 3
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java
  18. 9
      Clover/app/src/main/java/org/floens/chan/ui/helper/RefreshUIMessage.java
  19. 61
      Clover/app/src/main/java/org/floens/chan/ui/layout/FilterLayout.java
  20. 4
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  21. 4
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java
  22. 1
      Clover/app/src/main/res/layout/cell_filter.xml
  23. 15
      Clover/app/src/main/res/layout/cell_post.xml
  24. 6
      Clover/app/src/main/res/layout/cell_post_card.xml
  25. 6
      Clover/app/src/main/res/values/strings.xml
  26. 4
      docs/database.txt

@ -26,6 +26,7 @@ import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Filter;
import org.floens.chan.core.model.History;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Pin;
@ -42,7 +43,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
private static final String TAG = "DatabaseHelper";
private static final String DATABASE_NAME = "ChanDB";
private static final int DATABASE_VERSION = 18;
private static final int DATABASE_VERSION = 19;
public Dao<Pin, Integer> pinDao;
public Dao<Loadable, Integer> loadableDao;
@ -50,6 +51,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public Dao<Board, Integer> boardsDao;
public Dao<ThreadHide, Integer> threadHideDao;
public Dao<History, Integer> historyDao;
public Dao<Filter, Integer> filterDao;
private final Context context;
@ -65,6 +67,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
boardsDao = getDao(Board.class);
threadHideDao = getDao(ThreadHide.class);
historyDao = getDao(History.class);
filterDao = getDao(Filter.class);
} catch (SQLException e) {
Logger.e(TAG, "Error creating Daos", e);
}
@ -79,6 +82,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
TableUtils.createTable(connectionSource, Board.class);
TableUtils.createTable(connectionSource, ThreadHide.class);
TableUtils.createTable(connectionSource, History.class);
TableUtils.createTable(connectionSource, Filter.class);
} catch (SQLException e) {
Logger.e(TAG, "Error creating db", e);
throw new RuntimeException(e);
@ -175,6 +179,14 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
Logger.e(TAG, "Error upgrading to version 18", e);
}
}
if (oldVersion < 19) {
try {
filterDao.executeRawNoArgs("CREATE TABLE `filter` (`action` INTEGER NOT NULL , `allBoards` SMALLINT NOT NULL , `boards` VARCHAR NOT NULL , `color` INTEGER NOT NULL , `enabled` SMALLINT NOT NULL , `id` INTEGER PRIMARY KEY AUTOINCREMENT , `pattern` VARCHAR NOT NULL , `type` INTEGER NOT NULL );");
} catch (SQLException e) {
Logger.e(TAG, "Error upgrading to version 19", e);
}
}
}
public void reset() {

@ -69,8 +69,6 @@ public class DatabaseManager {
private final Object historyLock = new Object();
private final HashMap<Loadable, History> historyByLoadable = new HashMap<>();
private final List<Filter> filters = new ArrayList<>();
public DatabaseManager(Context context) {
helper = new DatabaseHelper(context);
initialize();
@ -284,16 +282,31 @@ public class DatabaseManager {
return list;
}
public void addFilter(Filter filter) {
filters.add(filter);
public void addOrUpdateFilter(Filter filter) {
try {
helper.filterDao.createOrUpdate(filter);
} catch (SQLException e) {
Logger.e(TAG, "Error adding filter to db", e);
}
}
public void removeFilter(Filter filter) {
filters.remove(filter);
try {
helper.filterDao.delete(filter);
} catch (SQLException e) {
Logger.e(TAG, "Error removing filter from db", e);
}
}
public List<Filter> getFilters() {
return filters;
List<Filter> list = null;
try {
list = helper.filterDao.queryForAll();
} catch (SQLException e) {
Logger.e(TAG, "Error getting filters from db", e);
}
return list;
}
/**

@ -19,10 +19,15 @@ package org.floens.chan.core.manager;
import android.text.TextUtils;
import org.floens.chan.Chan;
import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Filter;
import org.floens.chan.core.model.Post;
import org.floens.chan.utils.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -31,6 +36,12 @@ import java.util.regex.PatternSyntaxException;
public class FilterEngine {
private static final String TAG = "FilterEngine";
private static final FilterEngine instance = new FilterEngine();
public static FilterEngine getInstance() {
return instance;
}
public enum FilterType {
TRIPCODE(0, false),
NAME(1, false),
@ -48,12 +59,15 @@ public class FilterEngine {
}
public static FilterType forId(int id) {
for (FilterType type : values()) {
if (type.id == id) {
return type;
return enums[id];
}
private static FilterType[] enums = new FilterType[6];
static {
for (FilterType type : values()) {
enums[type.id] = type;
}
return null;
}
}
@ -68,28 +82,128 @@ public class FilterEngine {
}
public static FilterAction forId(int id) {
return enums[id];
}
private static FilterAction[] enums = new FilterAction[2];
static {
for (FilterAction type : values()) {
if (type.id == id) {
return type;
enums[type.id] = type;
}
}
return null;
}
private final DatabaseManager databaseManager;
private List<Filter> filters;
private final List<Filter> enabledFilters = new ArrayList<>();
private FilterEngine() {
databaseManager = Chan.getDatabaseManager();
filters = databaseManager.getFilters();
updateEnabledFilters();
}
private static final FilterEngine instance = new FilterEngine();
/**
* Add or update a filter, thread-safe.
* The filter will be updated in the db if the {@link Filter#id} was non-null.
*
* @param filter filter too add or update.
*/
public void addOrUpdate(Filter filter) {
databaseManager.addOrUpdateFilter(filter);
filters = databaseManager.getFilters();
updateEnabledFilters();
}
public static FilterEngine getInstance() {
return instance;
/**
* Remove a filter, thread-safe.
*
* @param filter filter to remove
*/
public void remove(Filter filter) {
databaseManager.removeFilter(filter);
filters = databaseManager.getFilters();
updateEnabledFilters();
}
private List<Filter> filters = new ArrayList<>();
/**
* Get all enabled filters, thread safe if locked on {@link #getEnabledFiltersLock()}.
*
* @return List of enabled filters
*/
public List<Filter> getEnabledFilters() {
return enabledFilters;
}
public FilterEngine() {
/**
* Lock for usage of {@link #getEnabledFilters()}
*
* @return Object to call synchronized on
*/
public Object getEnabledFiltersLock() {
return enabledFilters;
}
public boolean matches(Filter filter, Post post) {
String text = null;
FilterType type = FilterType.forId(filter.type);
switch (type) {
case TRIPCODE:
text = post.tripcode;
break;
case NAME:
text = post.name;
break;
case COMMENT:
text = post.comment.toString();
break;
case ID:
text = post.id;
break;
case SUBJECT:
text = post.subject;
break;
case FILENAME:
text = post.filename;
break;
}
public void add(Filter filter) {
return matches(filter, text, false);
}
public boolean matches(Filter filter, String text, boolean forceCompile) {
FilterType type = FilterType.forId(filter.type);
if (type.isRegex) {
Matcher matcher = null;
synchronized (filter.compiledMatcherLock) {
if (!forceCompile) {
matcher = filter.compiledMatcher;
}
if (matcher == null) {
Pattern compiledPattern = compile(filter.pattern);
matcher = filter.compiledMatcher = compiledPattern.matcher("");
Logger.d(TAG, "Resulting pattern: " + filter.compiledMatcher);
}
}
if (matcher != null) {
matcher.reset(text);
try {
return matcher.find();
} catch (IllegalArgumentException e) {
Logger.w(TAG, "matcher.find() exception", e);
return false;
}
} else {
Logger.e(TAG, "Invalid pattern");
return false;
}
} else {
return text.equals(filter.pattern);
}
}
private static final Pattern isRegexPattern = Pattern.compile("^/(.*)/(i?)$");
@ -139,27 +253,49 @@ public class FilterEngine {
return pattern;
}
public boolean matches(Filter filter, String text) {
FilterType type = FilterType.forId(filter.type);
if (type.isRegex) {
Pattern compiled = filter.compiledPattern;
if (compiled == null) {
compiled = filter.compiledPattern = compile(filter.pattern);
Logger.test("Resulting pattern: " + filter.compiledPattern);
public List<Board> getBoardsForFilter(Filter filter) {
if (filter.allBoards) {
return Chan.getBoardManager().getSavedBoards();
} else if (!TextUtils.isEmpty(filter.boards)) {
List<Board> appliedBoards = new ArrayList<>();
for (String value : filter.boards.split(",")) {
Board boardByValue = Chan.getBoardManager().getBoardByValue(value);
if (boardByValue != null) {
appliedBoards.add(boardByValue);
}
if (compiled != null) {
return compiled.matcher(text).find();
} else {
Logger.e(TAG, "Invalid pattern");
return false;
}
return appliedBoards;
} else {
return text.equals(filter.pattern);
return Collections.emptyList();
}
}
public void saveBoardsToFilter(List<Board> appliedBoards, Filter filter) {
filter.boards = "";
for (int i = 0; i < appliedBoards.size(); i++) {
Board board = appliedBoards.get(i);
filter.boards += board.value;
if (i < appliedBoards.size() - 1) {
filter.boards += ",";
}
}
}
private String escapeRegex(String filthy) {
return filterFilthyPattern.matcher(filthy).replaceAll("\\\\$1"); // Escape regex special characters with a \
}
private void updateEnabledFilters() {
List<Filter> enabled = new ArrayList<>();
for (Filter filter : filters) {
if (filter.enabled) {
enabled.add(filter);
}
}
synchronized (enabledFilters) {
enabledFilters.clear();
enabledFilters.addAll(enabled);
}
}
}

@ -17,26 +17,46 @@
*/
package org.floens.chan.core.model;
import java.util.regex.Pattern;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import org.floens.chan.core.manager.FilterEngine;
import java.util.regex.Matcher;
@DatabaseTable
public class Filter {
@DatabaseField(generatedId = true)
public int id;
@DatabaseField(canBeNull = false)
public boolean enabled = true;
public int type;
@DatabaseField(canBeNull = false)
public int type = FilterEngine.FilterType.COMMENT.id;
@DatabaseField(canBeNull = false)
public String pattern;
@DatabaseField(canBeNull = false)
public boolean allBoards = true;
@DatabaseField(canBeNull = false)
public String boards;
@DatabaseField(canBeNull = false)
public int action;
@DatabaseField(canBeNull = false)
public int color;
public Pattern compiledPattern;
public final Object compiledMatcherLock = new Object();
/**
* Cached version of {@link #pattern} compiled by {@link org.floens.chan.core.manager.FilterEngine#compile(String)}.
* Thread safe when synchronized on {@link #compiledMatcherLock}
*/
public Matcher compiledMatcher;
public void apply(Filter filter) {
enabled = filter.enabled;

@ -66,6 +66,8 @@ public class Post {
public String rawComment;
public String countryUrl;
public boolean spoiler = false;
public int filterHighlightedColor = 0;
public boolean filterStub = false;
/**
* This post replies to the these ids. Is an unmodifiable list after finish().
*/

@ -24,6 +24,8 @@ import com.android.volley.Response.Listener;
import org.floens.chan.Chan;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.manager.FilterEngine;
import org.floens.chan.core.model.Filter;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Post;
import org.floens.chan.utils.Logger;
@ -37,9 +39,11 @@ public class ChanReaderRequest extends JsonReaderRequest<ChanReaderRequest.ChanR
private Loadable loadable;
private List<Post> cached;
private Post op;
private FilterEngine filterEngine;
private ChanReaderRequest(String url, Listener<ChanReaderResponse> listener, ErrorListener errorListener) {
super(url, listener, errorListener);
filterEngine = FilterEngine.getInstance();
}
public static ChanReaderRequest newInstance(Loadable loadable, List<Post> cached, Listener<ChanReaderResponse> listener, ErrorListener errorListener) {
@ -318,13 +322,35 @@ public class ChanReaderRequest extends JsonReaderRequest<ChanReaderRequest.ChanR
Logger.e(TAG, "Incorrect data about post received for post " + post.no);
return null;
} else {
processPostFilter(post);
return post;
}
}
}
private void processPostFilter(Post post) {
synchronized (filterEngine.getEnabledFiltersLock()) {
List<Filter> filters = filterEngine.getEnabledFilters();
int filterSize = filters.size();
for (int i = 0; i < filterSize; i++) {
Filter filter = filters.get(i);
if (filterEngine.matches(filter, post)) {
FilterEngine.FilterAction action = FilterEngine.FilterAction.forId(filter.action);
switch (action) {
case COLOR:
post.filterHighlightedColor = filter.color;
break;
case HIDE:
post.filterStub = true;
break;
}
}
}
}
}
public static class ChanReaderResponse {
// Op Post that is created new each time.<br>
// Op Post that is created new each time.
// Used to later copy members like image count to the real op on the main thread.
public Post op;
public List<Post> posts;

@ -40,7 +40,7 @@ import org.floens.chan.core.model.SavedReply;
import org.floens.chan.core.net.LoaderPool;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.adapter.PostAdapter;
import org.floens.chan.ui.adapter.PostFilter;
import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.cell.PostCellInterface;
import org.floens.chan.ui.cell.ThreadStatusCell;
import org.floens.chan.ui.helper.PostHelper;
@ -79,7 +79,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
private ChanLoader chanLoader;
private boolean searchOpen = false;
private String searchQuery;
private PostFilter.Order order = PostFilter.Order.BUMP;
private PostsFilter.Order order = PostsFilter.Order.BUMP;
private boolean historyAdded = false;
private int notificationPostCount = -1;
@ -181,7 +181,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
}
}
public void setOrder(PostFilter.Order order) {
public void setOrder(PostsFilter.Order order) {
if (this.order != order) {
this.order = order;
if (chanLoader != null) {
@ -585,7 +585,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
}
private void showPosts() {
threadPresenterCallback.showPosts(chanLoader.getThread(), new PostFilter(order, searchQuery));
threadPresenterCallback.showPosts(chanLoader.getThread(), new PostsFilter(order, searchQuery));
}
private void addHistory() {
@ -602,7 +602,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
}
public interface ThreadPresenterCallback {
void showPosts(ChanThread thread, PostFilter filter);
void showPosts(ChanThread thread, PostsFilter filter);
void postClicked(Post post);

@ -23,7 +23,7 @@ import android.os.Environment;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.ui.adapter.PostFilter;
import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.cell.PostCellInterface;
import org.floens.chan.utils.AndroidUtils;
@ -112,7 +112,7 @@ public class ChanSettings {
videoOpenExternal = new BooleanSetting(p, "preference_video_external", false);
videoErrorIgnore = new BooleanSetting(p, "preference_video_error_ignore", false);
boardViewMode = new StringSetting(p, "preference_board_view_mode", PostCellInterface.PostViewMode.LIST.name); // "list" or "grid"
boardOrder = new StringSetting(p, "preference_board_order", PostFilter.Order.BUMP.name);
boardOrder = new StringSetting(p, "preference_board_order", PostsFilter.Order.BUMP.name);
postDefaultName = new StringSetting(p, "preference_default_name", "");
postPinThread = new BooleanSetting(p, "preference_pin_on_post", false);

@ -128,7 +128,7 @@ public class PostAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
}
}
public void setThread(ChanThread thread, PostFilter filter) {
public void setThread(ChanThread thread, PostsFilter filter) {
showError(null);
sourceList.clear();
sourceList.addAll(thread.posts);

@ -20,8 +20,9 @@ package org.floens.chan.ui.adapter;
import android.text.TextUtils;
import org.floens.chan.Chan;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.manager.FilterEngine;
import org.floens.chan.core.model.Post;
import java.util.ArrayList;
import java.util.Collections;
@ -30,7 +31,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Locale;
public class PostFilter {
public class PostsFilter {
public static final Comparator<Post> IMAGE_COMPARATOR = new Comparator<Post>() {
@Override
public int compare(Post lhs, Post rhs) {
@ -60,14 +61,16 @@ public class PostFilter {
};
private final DatabaseManager databaseManager;
private final FilterEngine filterEngine;
private Order order;
private String query;
public PostFilter(Order order, String query) {
public PostsFilter(Order order, String query) {
this.order = order;
this.query = query;
databaseManager = Chan.getDatabaseManager();
filterEngine = FilterEngine.getInstance();
}
/**
@ -80,7 +83,7 @@ public class PostFilter {
List<Post> posts = new ArrayList<>(original);
// Process order
if (order != PostFilter.Order.BUMP) {
if (order != PostsFilter.Order.BUMP) {
switch (order) {
case IMAGE:
Collections.sort(posts, IMAGE_COMPARATOR);

@ -55,6 +55,7 @@ public class CardPostCell extends CardView implements PostCellInterface, View.On
private FastTextView comment;
private TextView replies;
private ImageView options;
private View colorLeft;
public CardPostCell(Context context) {
super(context);
@ -79,6 +80,7 @@ public class CardPostCell extends CardView implements PostCellInterface, View.On
replies = (TextView) findViewById(R.id.replies);
options = (ImageView) findViewById(R.id.options);
setRoundItemBackground(options);
colorLeft = findViewById(R.id.filter_match_color);
int textSizeSp = Integer.parseInt(ChanSettings.fontSize.get());
title.setTextSize(textSizeSp);
@ -183,6 +185,13 @@ public class CardPostCell extends CardView implements PostCellInterface, View.On
thumbnailView.setUrl(null, 0, 0);
}
if (post.filterHighlightedColor != 0) {
colorLeft.setVisibility(View.VISIBLE);
colorLeft.setBackgroundColor(post.filterHighlightedColor);
} else {
colorLeft.setVisibility(View.GONE);
}
if (!TextUtils.isEmpty(post.subjectSpan)) {
title.setVisibility(View.VISIBLE);
title.setText(post.subjectSpan);

@ -40,6 +40,7 @@ import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
@ -67,7 +68,7 @@ import static org.floens.chan.utils.AndroidUtils.getRes;
import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground;
import static org.floens.chan.utils.AndroidUtils.sp;
public class PostCell extends RelativeLayout implements PostCellInterface, PostLinkable.Callback {
public class PostCell extends LinearLayout implements PostCellInterface, PostLinkable.Callback {
private static final int COMMENT_MAX_LENGTH_BOARD = 500;
private ThumbnailView thumbnailView;
@ -77,6 +78,7 @@ public class PostCell extends RelativeLayout implements PostCellInterface, PostL
private TextView replies;
private ImageView options;
private View divider;
private View colorLeft;
private boolean commentClickable = false;
private CharSequence iconsSpannable;
@ -129,6 +131,7 @@ public class PostCell extends RelativeLayout implements PostCellInterface, PostL
replies = (TextView) findViewById(R.id.replies);
options = (ImageView) findViewById(R.id.options);
divider = findViewById(R.id.divider);
colorLeft = findViewById(R.id.filter_match_color);
int textSizeSp = Integer.parseInt(ChanSettings.fontSize.get());
paddingPx = dp(textSizeSp - 6);
@ -150,7 +153,7 @@ public class PostCell extends RelativeLayout implements PostCellInterface, PostL
setRoundItemBackground(replies);
setRoundItemBackground(options);
RelativeLayout.LayoutParams dividerParams = (LayoutParams) divider.getLayoutParams();
RelativeLayout.LayoutParams dividerParams = (RelativeLayout.LayoutParams) divider.getLayoutParams();
dividerParams.leftMargin = paddingPx;
dividerParams.rightMargin = paddingPx;
divider.setLayoutParams(dividerParams);
@ -281,6 +284,13 @@ public class PostCell extends RelativeLayout implements PostCellInterface, PostL
setBackgroundResource(R.drawable.item_background);
}
if (post.filterHighlightedColor != 0) {
colorLeft.setVisibility(View.VISIBLE);
colorLeft.setBackgroundColor(post.filterHighlightedColor);
} else {
colorLeft.setVisibility(View.GONE);
}
if (post.hasImage) {
thumbnailView.setVisibility(View.VISIBLE);
thumbnailView.setUrl(post.thumbnailUrl, thumbnailView.getLayoutParams().width, thumbnailView.getLayoutParams().height);

@ -126,7 +126,7 @@ public class ThreadStatusCell extends LinearLayout implements View.OnClickListen
images.setSpan(new StyleSpan(Typeface.ITALIC), 0, images.length(), 0);
}
text.setText(TextUtils.concat(statusText, replies, " / ", images, " / ", String.valueOf(op.uniqueIps)));
text.setText(TextUtils.concat(statusText, replies, " / ", images, " / ", String.valueOf(op.uniqueIps) + "P"));
}
}
}

@ -34,7 +34,7 @@ import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Pin;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.adapter.PostFilter;
import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.cell.PostCellInterface;
import org.floens.chan.ui.layout.ThreadLayout;
import org.floens.chan.ui.toolbar.ToolbarMenu;
@ -54,7 +54,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
private static final int ORDER_ID = 104;
private PostCellInterface.PostViewMode postViewMode;
private PostFilter.Order order;
private PostsFilter.Order order;
private List<FloatingMenuItem> boardItems;
private FloatingMenuItem viewModeMenuItem;
@ -70,7 +70,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
super.onCreate();
postViewMode = PostCellInterface.PostViewMode.find(ChanSettings.boardViewMode.get());
order = PostFilter.Order.find(ChanSettings.boardOrder.get());
order = PostsFilter.Order.find(ChanSettings.boardOrder.get());
threadLayout.setPostViewMode(postViewMode);
threadLayout.getPresenter().setOrder(order);
@ -136,7 +136,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
break;
case ORDER_ID:
List<FloatingMenuItem> items = new ArrayList<>();
for (PostFilter.Order order : PostFilter.Order.values()) {
for (PostsFilter.Order order : PostsFilter.Order.values()) {
int nameId = 0;
switch (order) {
case BUMP:
@ -168,7 +168,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
menu.setCallback(new FloatingMenu.FloatingMenuCallback() {
@Override
public void onFloatingMenuItemClicked(FloatingMenu menu, FloatingMenuItem item) {
PostFilter.Order order = (PostFilter.Order) item.getId();
PostsFilter.Order order = (PostsFilter.Order) item.getId();
ChanSettings.boardOrder.set(order.name);
BrowseController.this.order = order;
threadLayout.getPresenter().setOrder(order);

@ -30,11 +30,11 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.manager.FilterEngine;
import org.floens.chan.core.model.Filter;
import org.floens.chan.ui.helper.RefreshUIMessage;
import org.floens.chan.ui.layout.FilterLayout;
import org.floens.chan.ui.toolbar.ToolbarMenu;
import org.floens.chan.ui.toolbar.ToolbarMenuItem;
@ -44,13 +44,17 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import de.greenrobot.event.EventBus;
import static org.floens.chan.ui.theme.ThemeHelper.theme;
import static org.floens.chan.utils.AndroidUtils.getAttrColor;
import static org.floens.chan.utils.AndroidUtils.getString;
public class FiltersController extends Controller implements ToolbarMenuItem.ToolbarMenuItemCallback, RootNavigationController.ToolbarSearchCallback, View.OnClickListener {
private static final int SEARCH_ID = 1;
private static final int CLEAR_ID = 101;
private DatabaseManager databaseManager;
private FilterEngine filterEngine;
private RecyclerView recyclerView;
private FloatingActionButton add;
private FilterAdapter adapter;
@ -59,11 +63,39 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too
super(context);
}
public static String filterTypeName(FilterEngine.FilterType type) {
switch (type) {
case TRIPCODE:
return getString(R.string.filter_tripcode);
case NAME:
return getString(R.string.filter_name);
case COMMENT:
return getString(R.string.filter_comment);
case ID:
return getString(R.string.filter_id);
case SUBJECT:
return getString(R.string.filter_subject);
case FILENAME:
return getString(R.string.filter_filename);
}
return null;
}
public static String actionName(FilterEngine.FilterAction action) {
switch (action) {
case HIDE:
return getString(R.string.filter_hide);
case COLOR:
return getString(R.string.filter_color);
}
return null;
}
@Override
public void onCreate() {
super.onCreate();
databaseManager = Chan.getDatabaseManager();
filterEngine = FilterEngine.getInstance();
navigationItem.title = string(R.string.filters_screen);
navigationItem.menu = new ToolbarMenu(context);
@ -111,8 +143,8 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too
public void onClick(DialogInterface dialog, int which) {
Filter newFilter = filterLayout.getFilter();
newFilter.id = filter.id;
Chan.getDatabaseManager().addFilter(newFilter);
filterEngine.addOrUpdate(newFilter);
EventBus.getDefault().post(new RefreshUIMessage("filters"));
adapter.load();
}
})
@ -128,7 +160,7 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too
}
private void deleteFilter(Filter filter) {
databaseManager.removeFilter(filter);
filterEngine.remove(filter);
adapter.load();
//TODO: undo
}
@ -163,7 +195,20 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too
public void onBindViewHolder(FilterCell holder, int position) {
Filter filter = displayList.get(position);
holder.text.setText(filter.pattern);
holder.subtext.setText(String.valueOf(filter.type));
holder.text.setTextColor(getAttrColor(context, filter.enabled ? R.attr.text_color_primary : R.attr.text_color_secondary));
String subText = filterTypeName(FilterEngine.FilterType.forId(filter.type));
subText += " - ";
if (filter.allBoards) {
subText += context.getString(R.string.filter_summary_all_boards);
} else {
int size = filterEngine.getBoardsForFilter(filter).size();
subText += context.getResources().getQuantityString(R.plurals.board, size, size);
}
subText += " - " + FiltersController.actionName(FilterEngine.FilterAction.forId(filter.action));
holder.subtext.setText(subText);
}
@Override
@ -183,7 +228,7 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too
private void load() {
sourceList.clear();
sourceList.addAll(databaseManager.getFilters());
sourceList.addAll(filterEngine.getEnabledFilters());
filter();
}
@ -193,8 +238,10 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too
if (!TextUtils.isEmpty(searchQuery)) {
String query = searchQuery.toLowerCase(Locale.ENGLISH);
for (Filter filter : sourceList) {
if (filter.pattern.toLowerCase().contains(query)) {
displayList.add(filter);
}
}
} else {
displayList.addAll(sourceList);
}

@ -26,6 +26,7 @@ import android.widget.Toast;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.helper.RefreshUIMessage;
import org.floens.chan.ui.settings.BooleanSettingView;
import org.floens.chan.ui.settings.LinkSettingView;
import org.floens.chan.ui.settings.ListSettingView;
@ -268,11 +269,4 @@ public class MainSettingsController extends SettingsController implements Toolba
return string(id);
}
public static class RefreshUIMessage {
public String reason;
public RefreshUIMessage(String reason) {
this.reason = reason;
}
}
}

@ -26,6 +26,7 @@ import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.ui.helper.RefreshUIMessage;
import org.floens.chan.ui.layout.ThreadLayout;
import org.floens.chan.ui.view.ThumbnailView;
@ -88,7 +89,7 @@ public abstract class ThreadController extends Controller implements ThreadLayou
threadLayout.getPresenter().onForegroundChanged(message.inForeground);
}
public void onEvent(MainSettingsController.RefreshUIMessage message) {
public void onEvent(RefreshUIMessage message) {
threadLayout.getPresenter().requestData();
}

@ -0,0 +1,9 @@
package org.floens.chan.ui.helper;
public class RefreshUIMessage {
public String reason;
public RefreshUIMessage(String reason) {
this.reason = reason;
}
}

@ -44,6 +44,7 @@ import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.manager.FilterEngine;
import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Filter;
import org.floens.chan.ui.controller.FiltersController;
import org.floens.chan.ui.dialog.ColorPickerView;
import org.floens.chan.ui.drawable.DropdownArrowDrawable;
import org.floens.chan.ui.view.FloatingMenu;
@ -106,7 +107,6 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
filter.compiledPattern = null;
filter.pattern = s.toString();
updateFilterValidity();
updatePatternPreview();
@ -156,17 +156,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener {
public void setFilter(Filter filter) {
this.filter.apply(filter);
appliedBoards.clear();
if (filter.allBoards) {
appliedBoards.addAll(boardManager.getSavedBoards());
} else if (!TextUtils.isEmpty(filter.boards)) {
for (String value : filter.boards.split(",")) {
Board boardByValue = boardManager.getBoardByValue(value);
if (boardByValue != null) {
appliedBoards.add(boardByValue);
}
}
}
appliedBoards.addAll(FilterEngine.getInstance().getBoardsForFilter(filter));
pattern.setText(filter.pattern);
@ -185,14 +175,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener {
public Filter getFilter() {
filter.enabled = enabled.isChecked();
filter.boards = "";
for (int i = 0; i < appliedBoards.size(); i++) {
Board board = appliedBoards.get(i);
filter.boards += board.value;
if (i < appliedBoards.size() - 1) {
filter.boards += ",";
}
}
FilterEngine.getInstance().saveBoardsToFilter(appliedBoards, filter);
return filter;
}
@ -203,7 +186,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener {
List<FloatingMenuItem> menuItems = new ArrayList<>(6);
for (FilterEngine.FilterType filterType : FilterEngine.FilterType.values()) {
menuItems.add(new FloatingMenuItem(filterType, filterTypeName(filterType)));
menuItems.add(new FloatingMenuItem(filterType, FiltersController.filterTypeName(filterType)));
}
FloatingMenu menu = new FloatingMenu(v.getContext());
@ -245,7 +228,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener {
List<FloatingMenuItem> menuItems = new ArrayList<>(6);
for (FilterEngine.FilterAction action : FilterEngine.FilterAction.values()) {
menuItems.add(new FloatingMenuItem(action, actionName(action)));
menuItems.add(new FloatingMenuItem(action, FiltersController.actionName(action)));
}
FloatingMenu menu = new FloatingMenu(v.getContext());
@ -347,7 +330,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener {
private void updateFilterAction() {
FilterEngine.FilterAction action = FilterEngine.FilterAction.forId(filter.action);
actionText.setText(actionName(action));
actionText.setText(FiltersController.actionName(action));
colorContainer.setVisibility(action == FilterEngine.FilterAction.COLOR ? VISIBLE : GONE);
if (filter.color == 0) {
filter.color = 0xffff0000;
@ -357,44 +340,16 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener {
private void updateFilterType() {
FilterEngine.FilterType filterType = FilterEngine.FilterType.forId(filter.type);
typeText.setText(filterTypeName(filterType));
typeText.setText(FiltersController.filterTypeName(filterType));
pattern.setHint(filterType.isRegex ? R.string.filter_pattern_hint_regex : R.string.filter_pattern_hint_exact);
}
private void updatePatternPreview() {
String text = patternPreview.getText().toString();
boolean matches = text.length() > 0 && FilterEngine.getInstance().matches(filter, text);
boolean matches = text.length() > 0 && FilterEngine.getInstance().matches(filter, text, true);
patternPreviewStatus.setText(matches ? R.string.filter_matches : R.string.filter_no_matches);
}
private String filterTypeName(FilterEngine.FilterType type) {
switch (type) {
case TRIPCODE:
return getString(R.string.filter_tripcode);
case NAME:
return getString(R.string.filter_name);
case COMMENT:
return getString(R.string.filter_comment);
case ID:
return getString(R.string.filter_id);
case SUBJECT:
return getString(R.string.filter_subject);
case FILENAME:
return getString(R.string.filter_filename);
}
return null;
}
private String actionName(FilterEngine.FilterAction action) {
switch (action) {
case HIDE:
return getString(R.string.filter_hide);
case COLOR:
return getString(R.string.filter_color);
}
return null;
}
public interface FilterLayoutCallback {
void setSaveButtonEnabled(boolean enabled);
}

@ -55,7 +55,7 @@ import org.floens.chan.core.model.PostLinkable;
import org.floens.chan.core.model.ThreadHide;
import org.floens.chan.core.presenter.ThreadPresenter;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.adapter.PostFilter;
import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.cell.PostCellInterface;
import org.floens.chan.ui.helper.PostPopupHelper;
import org.floens.chan.ui.view.LoadView;
@ -182,7 +182,7 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
}
@Override
public void showPosts(ChanThread thread, PostFilter filter) {
public void showPosts(ChanThread thread, PostsFilter filter) {
threadListLayout.showPosts(thread, filter, visible != Visible.THREAD);
switchVisible(Visible.THREAD);
callback.onShowPosts();

@ -33,7 +33,7 @@ import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.presenter.ReplyPresenter;
import org.floens.chan.ui.adapter.PostAdapter;
import org.floens.chan.ui.adapter.PostFilter;
import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.cell.PostCell;
import org.floens.chan.ui.cell.PostCellInterface;
import org.floens.chan.ui.cell.ThreadStatusCell;
@ -164,7 +164,7 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL
}
}
public void showPosts(ChanThread thread, PostFilter filter, boolean initial) {
public void showPosts(ChanThread thread, PostsFilter filter, boolean initial) {
showingThread = thread;
if (initial) {
reply.bindLoadable(showingThread.loadable);

@ -42,7 +42,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:paddingRight="16dp"
android:paddingTop="8dp"
android:singleLine="true"
android:textColor="?text_color_primary"
android:textSize="14sp" />
<TextView

@ -17,14 +17,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<org.floens.chan.ui.cell.PostCell xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/post_cell"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="RtlHardcoded,RtlSymmetry">
<View
android:id="@+id/filter_match_color"
android:layout_width="5dp"
android:layout_height="match_parent"
android:visibility="gone" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<org.floens.chan.ui.view.ThumbnailView
android:id="@+id/thumbnail_view"
android:layout_width="@dimen/cell_post_thumbnail_size"
android:layout_height="@dimen/cell_post_thumbnail_size"
android:layout_alignWithParentIfMissing="true"
android:gravity="top" />
<TextView
@ -88,4 +101,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:src="?post_options_drawable"
tools:ignore="ContentDescription" />
</RelativeLayout>
</org.floens.chan.ui.cell.PostCell>

@ -55,6 +55,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</FrameLayout>
<View
android:id="@+id/filter_match_color"
android:layout_width="match_parent"
android:layout_height="10dp"
android:visibility="gone" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"

@ -48,6 +48,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<item quantity="other">%d images</item>
</plurals>
<plurals name="board">
<item quantity="one">%d board</item>
<item quantity="other">%d boards</item>
</plurals>
<string name="card_stats">%1$dR %2$dI</string>
<string name="action_reload">Reload</string>
@ -117,6 +122,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<string name="board_add_unknown">The board with code %1$s is not known.</string>
<string name="board_edit_sort_a_z">Sort A-Z</string>
<string name="filter_summary_all_boards">all boards</string>
<string name="filter_enabled">Enabled</string>
<string name="filter_filter">Filter</string>
<string name="filter_action">Action</string>

@ -52,3 +52,7 @@ ALTER TABLE board ADD COLUMN description TEXT;
Changes in version 18:
CREATE TABLE `history` (`date` BIGINT , `id` INTEGER PRIMARY KEY AUTOINCREMENT , `loadable_id` INTEGER NOT NULL , `thumbnailUrl` VARCHAR )
Changes in version 19:
CREATE TABLE `filter` (`action` INTEGER NOT NULL , `allBoards` SMALLINT NOT NULL , `boards` VARCHAR NOT NULL , `color` INTEGER NOT NULL , `enabled` SMALLINT NOT NULL , `id` INTEGER PRIMARY KEY AUTOINCREMENT , `pattern` VARCHAR NOT NULL , `type` INTEGER NOT NULL )

Loading…
Cancel
Save