diff --git a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHelper.java b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHelper.java index 6e57372d..a9880573 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHelper.java +++ b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHelper.java @@ -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 pinDao; public Dao loadableDao; @@ -50,6 +51,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { public Dao boardsDao; public Dao threadHideDao; public Dao historyDao; + public Dao 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() { diff --git a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseManager.java b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseManager.java index 85e780dc..5ee58946 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseManager.java +++ b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseManager.java @@ -69,8 +69,6 @@ public class DatabaseManager { private final Object historyLock = new Object(); private final HashMap historyByLoadable = new HashMap<>(); - private final List 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 getFilters() { - return filters; + List list = null; + try { + list = helper.filterDao.queryForAll(); + } catch (SQLException e) { + Logger.e(TAG, "Error getting filters from db", e); + } + + return list; } /** diff --git a/Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java b/Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java index 9d08b2d3..7b1a8835 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java +++ b/Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java @@ -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) { + return enums[id]; + } + + private static FilterType[] enums = new FilterType[6]; + + static { for (FilterType type : values()) { - if (type.id == id) { - return type; - } + 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 static final FilterEngine instance = new FilterEngine(); + private final DatabaseManager databaseManager; - public static FilterEngine getInstance() { - return instance; + private List filters; + private final List enabledFilters = new ArrayList<>(); + + private FilterEngine() { + databaseManager = Chan.getDatabaseManager(); + filters = databaseManager.getFilters(); + updateEnabledFilters(); } - private List filters = new ArrayList<>(); + /** + * 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 FilterEngine() { + /** + * Remove a filter, thread-safe. + * + * @param filter filter to remove + */ + public void remove(Filter filter) { + databaseManager.removeFilter(filter); + filters = databaseManager.getFilters(); + updateEnabledFilters(); + } + /** + * Get all enabled filters, thread safe if locked on {@link #getEnabledFiltersLock()}. + * + * @return List of enabled filters + */ + public List getEnabledFilters() { + return enabledFilters; } - public void add(Filter filter) { + /** + * 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; + } + + 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 getBoardsForFilter(Filter filter) { + if (filter.allBoards) { + return Chan.getBoardManager().getSavedBoards(); + } else if (!TextUtils.isEmpty(filter.boards)) { + List appliedBoards = new ArrayList<>(); + for (String value : filter.boards.split(",")) { + Board boardByValue = Chan.getBoardManager().getBoardByValue(value); + if (boardByValue != null) { + appliedBoards.add(boardByValue); + } } + return appliedBoards; + } else { + return Collections.emptyList(); + } + } - if (compiled != null) { - return compiled.matcher(text).find(); - } else { - Logger.e(TAG, "Invalid pattern"); - return false; + public void saveBoardsToFilter(List 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 += ","; } - } else { - return text.equals(filter.pattern); } } private String escapeRegex(String filthy) { return filterFilthyPattern.matcher(filthy).replaceAll("\\\\$1"); // Escape regex special characters with a \ } + + private void updateEnabledFilters() { + List enabled = new ArrayList<>(); + for (Filter filter : filters) { + if (filter.enabled) { + enabled.add(filter); + } + } + + synchronized (enabledFilters) { + enabledFilters.clear(); + enabledFilters.addAll(enabled); + } + } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Filter.java b/Clover/app/src/main/java/org/floens/chan/core/model/Filter.java index ab80b169..ef03f110 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/Filter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/Filter.java @@ -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; diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Post.java b/Clover/app/src/main/java/org/floens/chan/core/model/Post.java index 5f95b9e8..0e828fbe 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/Post.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/Post.java @@ -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(). */ diff --git a/Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java b/Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java index 779e0657..233d7b78 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java +++ b/Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java @@ -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 cached; private Post op; + private FilterEngine filterEngine; private ChanReaderRequest(String url, Listener listener, ErrorListener errorListener) { super(url, listener, errorListener); + filterEngine = FilterEngine.getInstance(); } public static ChanReaderRequest newInstance(Loadable loadable, List cached, Listener listener, ErrorListener errorListener) { @@ -318,13 +322,35 @@ public class ChanReaderRequest extends JsonReaderRequest 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.
+ // 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 posts; diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java index 868e6813..3f2aeb72 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java @@ -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); diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java index a85ab6d1..3f6f8f48 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java @@ -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); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostAdapter.java b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostAdapter.java index fa8fd843..c31f3e0c 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostAdapter.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostAdapter.java @@ -128,7 +128,7 @@ public class PostAdapter extends RecyclerView.Adapter { } } - public void setThread(ChanThread thread, PostFilter filter) { + public void setThread(ChanThread thread, PostsFilter filter) { showError(null); sourceList.clear(); sourceList.addAll(thread.posts); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostFilter.java b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostsFilter.java similarity index 94% rename from Clover/app/src/main/java/org/floens/chan/ui/adapter/PostFilter.java rename to Clover/app/src/main/java/org/floens/chan/ui/adapter/PostsFilter.java index 19de204f..5cd37918 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostFilter.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostsFilter.java @@ -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 IMAGE_COMPARATOR = new Comparator() { @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 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); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java index 6b066c1f..48232f93 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java @@ -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); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java index 18c48f57..003fba9f 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java @@ -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); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java index 84ccbca4..687fb49b 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java @@ -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")); } } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java index 646edd3a..65cedc97 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java @@ -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 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 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); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/FiltersController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/FiltersController.java index 349ce213..c5ccec43 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/FiltersController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/FiltersController.java @@ -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,7 +238,9 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too if (!TextUtils.isEmpty(searchQuery)) { String query = searchQuery.toLowerCase(Locale.ENGLISH); for (Filter filter : sourceList) { - displayList.add(filter); + if (filter.pattern.toLowerCase().contains(query)) { + displayList.add(filter); + } } } else { displayList.addAll(sourceList); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java index a4ae464c..8657ff52 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java @@ -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; - } - } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java index 02869bea..2a4e3677 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java @@ -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(); } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/RefreshUIMessage.java b/Clover/app/src/main/java/org/floens/chan/ui/helper/RefreshUIMessage.java new file mode 100644 index 00000000..3c3a6653 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/helper/RefreshUIMessage.java @@ -0,0 +1,9 @@ +package org.floens.chan.ui.helper; + +public class RefreshUIMessage { + public String reason; + + public RefreshUIMessage(String reason) { + this.reason = reason; + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/FilterLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/FilterLayout.java index 3d7ceeac..278c74ba 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/FilterLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/FilterLayout.java @@ -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 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 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); } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java index aa15c8cc..6dada9dd 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java @@ -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(); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java index 8df00947..9bf47eb9 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java @@ -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); diff --git a/Clover/app/src/main/res/layout/cell_filter.xml b/Clover/app/src/main/res/layout/cell_filter.xml index 03f70217..0faa6d9d 100644 --- a/Clover/app/src/main/res/layout/cell_filter.xml +++ b/Clover/app/src/main/res/layout/cell_filter.xml @@ -42,7 +42,6 @@ along with this program. If not, see . android:paddingRight="16dp" android:paddingTop="8dp" android:singleLine="true" - android:textColor="?text_color_primary" android:textSize="14sp" /> . --> - + - + android:layout_height="wrap_content"> - + - + - + - + + + + + + + - + diff --git a/Clover/app/src/main/res/layout/cell_post_card.xml b/Clover/app/src/main/res/layout/cell_post_card.xml index 9a9ae3e6..854ad3c1 100644 --- a/Clover/app/src/main/res/layout/cell_post_card.xml +++ b/Clover/app/src/main/res/layout/cell_post_card.xml @@ -55,6 +55,12 @@ along with this program. If not, see . + + . %d images + + %d board + %d boards + + %1$dR %2$dI Reload @@ -117,6 +122,7 @@ along with this program. If not, see . The board with code %1$s is not known. Sort A-Z + all boards Enabled Filter Action diff --git a/docs/database.txt b/docs/database.txt index cf5d2edc..33de22d8 100644 --- a/docs/database.txt +++ b/docs/database.txt @@ -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 )