Finish up the filter edit dialog

filtering
Floens 10 years ago
parent 810fd66e4e
commit 97a0d1b4a8
  1. 12
      Clover/app/build.gradle
  2. 11
      Clover/app/src/main/java/org/floens/chan/chan/ChanLoader.java
  3. 115
      Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java
  4. 11
      Clover/app/src/main/java/org/floens/chan/core/model/Filter.java
  5. 8
      Clover/app/src/main/java/org/floens/chan/ui/controller/FiltersController.java
  6. 121
      Clover/app/src/main/java/org/floens/chan/ui/dialog/ColorPickerView.java
  7. 246
      Clover/app/src/main/java/org/floens/chan/ui/layout/FilterLayout.java
  8. 1
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  9. 1
      Clover/app/src/main/java/org/floens/chan/ui/theme/DarkTheme.java
  10. 2
      Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java
  11. BIN
      Clover/app/src/main/res/drawable-hdpi/ic_help_outline_black_24dp.png
  12. BIN
      Clover/app/src/main/res/drawable-hdpi/ic_help_outline_white_24dp.png
  13. BIN
      Clover/app/src/main/res/drawable-mdpi/ic_help_outline_black_24dp.png
  14. BIN
      Clover/app/src/main/res/drawable-mdpi/ic_help_outline_white_24dp.png
  15. BIN
      Clover/app/src/main/res/drawable-xhdpi/ic_help_outline_black_24dp.png
  16. BIN
      Clover/app/src/main/res/drawable-xhdpi/ic_help_outline_white_24dp.png
  17. BIN
      Clover/app/src/main/res/drawable-xxhdpi/ic_help_outline_black_24dp.png
  18. BIN
      Clover/app/src/main/res/drawable-xxhdpi/ic_help_outline_white_24dp.png
  19. BIN
      Clover/app/src/main/res/drawable-xxxhdpi/ic_help_outline_black_24dp.png
  20. BIN
      Clover/app/src/main/res/drawable-xxxhdpi/ic_help_outline_white_24dp.png
  21. 112
      Clover/app/src/main/res/layout/layout_filter.xml
  22. 37
      Clover/app/src/main/res/values/strings.xml

@ -71,12 +71,12 @@ android {
} }
dependencies { dependencies {
compile 'com.android.support:support-v13:22.2.0' compile 'com.android.support:support-v13:22.2.1'
compile 'com.android.support:appcompat-v7:22.2.0' compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:recyclerview-v7:22.2.0' compile 'com.android.support:recyclerview-v7:22.2.1'
compile 'com.android.support:cardview-v7:22.2.0' compile 'com.android.support:cardview-v7:22.2.1'
compile 'com.android.support:support-annotations:22.2.0' compile 'com.android.support:support-annotations:22.2.1'
compile 'com.android.support:design:22.2.0' compile 'com.android.support:design:22.2.1'
compile 'org.jsoup:jsoup:1.8.2' compile 'org.jsoup:jsoup:1.8.2'
compile 'com.j256.ormlite:ormlite-core:4.48' compile 'com.j256.ormlite:ormlite-core:4.48'

@ -268,10 +268,13 @@ public class ChanLoader implements Response.ErrorListener, Response.Listener<Cha
} }
} }
for (Post sourcePost : thread.posts) { long start = Time.get();
sourcePost.repliesFrom.clear(); List<Post> posts = thread.posts;
for (int i = 0; i < posts.size(); i++) {
Post sourcePost = posts.get(i);
for (Post replyToSource : thread.posts) { for (int j = i; j < posts.size(); j++) {
Post replyToSource = posts.get(j);
if (replyToSource != sourcePost) { if (replyToSource != sourcePost) {
if (replyToSource.repliesTo.contains(sourcePost.no)) { if (replyToSource.repliesTo.contains(sourcePost.no)) {
sourcePost.repliesFrom.add(replyToSource.no); sourcePost.repliesFrom.add(replyToSource.no);
@ -279,6 +282,8 @@ public class ChanLoader implements Response.ErrorListener, Response.Listener<Cha
} }
} }
} }
Logger.test("processResponse took " + Time.get(start) + "ms");
} }
private void setTimer(int postCount) { private void setTimer(int postCount) {

@ -17,24 +17,34 @@
*/ */
package org.floens.chan.core.manager; package org.floens.chan.core.manager;
import android.text.TextUtils;
import org.floens.chan.core.model.Filter; import org.floens.chan.core.model.Filter;
import org.floens.chan.utils.Logger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class FilterEngine { public class FilterEngine {
private static final String TAG = "FilterEngine";
public enum FilterType { public enum FilterType {
TRIPCODE(0), TRIPCODE(0, false),
NAME(1), NAME(1, false),
COMMENT(2), COMMENT(2, true),
ID(3), ID(3, false),
SUBJECT(4), SUBJECT(4, true),
FILENAME(5); FILENAME(5, true);
public final int id; public final int id;
public final boolean isRegex;
FilterType(int id) { FilterType(int id, boolean isRegex) {
this.id = id; this.id = id;
this.isRegex = isRegex;
} }
public static FilterType forId(int id) { public static FilterType forId(int id) {
@ -47,6 +57,26 @@ public class FilterEngine {
} }
} }
public enum FilterAction {
HIDE(0),
COLOR(1);
public final int id;
FilterAction(int id) {
this.id = id;
}
public static FilterAction forId(int id) {
for (FilterAction type : values()) {
if (type.id == id) {
return type;
}
}
return null;
}
}
private static final FilterEngine instance = new FilterEngine(); private static final FilterEngine instance = new FilterEngine();
public static FilterEngine getInstance() { public static FilterEngine getInstance() {
@ -61,4 +91,75 @@ public class FilterEngine {
public void add(Filter filter) { public void add(Filter filter) {
} }
private static final Pattern isRegexPattern = Pattern.compile("^/(.*)/(i?)$");
private static final Pattern filterFilthyPattern = Pattern.compile("(\\.|\\^|\\$|\\*|\\+|\\?|\\(|\\)|\\[|\\]|\\{|\\\\|\\||\\-)");
private static final Pattern wildcardPattern = Pattern.compile("\\\\\\*"); // an escaped \ and an escaped *, to replace an escaped * from escapeRegex
public Pattern compile(String rawPattern) {
Pattern pattern;
if (TextUtils.isEmpty(rawPattern)) {
return null;
}
Matcher isRegex = isRegexPattern.matcher(rawPattern);
if (isRegex.matches()) {
// This is a /Pattern/
String flagsGroup = isRegex.group(2);
int flags = 0;
if (flagsGroup.contains("i")) {
flags |= Pattern.CASE_INSENSITIVE;
}
try {
pattern = Pattern.compile(isRegex.group(1), flags);
} catch (PatternSyntaxException e) {
return null;
}
} else if (rawPattern.charAt(0) == '"' && rawPattern.charAt(rawPattern.length() - 1) == '"') {
// "matches an exact sentence"
pattern = Pattern.compile(escapeRegex(rawPattern).substring(1, rawPattern.length() - 1));
} else {
String[] words = rawPattern.split(" ");
String text = "";
for (int i = 0, wordsLength = words.length; i < wordsLength; i++) {
String word = words[i];
// Find a word (bounded by \b), replacing any * with \S*
text += "(\\b" + (wildcardPattern.matcher(escapeRegex(word)).replaceAll("\\\\S*")) + "\\b)";
// Allow multiple words by joining them with |
if (i < words.length - 1) {
text += "|";
}
}
pattern = Pattern.compile(text, Pattern.CASE_INSENSITIVE);
}
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);
}
if (compiled != null) {
return compiled.matcher(text).find();
} else {
Logger.e(TAG, "Invalid pattern");
return false;
}
} else {
return text.equals(filter.pattern);
}
}
private String escapeRegex(String filthy) {
return filterFilthyPattern.matcher(filthy).replaceAll("\\\\$1"); // Escape regex special characters with a \
}
} }

@ -17,6 +17,8 @@
*/ */
package org.floens.chan.core.model; package org.floens.chan.core.model;
import java.util.regex.Pattern;
public class Filter { public class Filter {
public int id; public int id;
@ -30,7 +32,11 @@ public class Filter {
public String boards; public String boards;
public boolean hide = true; public int action;
public int color;
public Pattern compiledPattern;
public void apply(Filter filter) { public void apply(Filter filter) {
enabled = filter.enabled; enabled = filter.enabled;
@ -38,6 +44,7 @@ public class Filter {
pattern = filter.pattern; pattern = filter.pattern;
allBoards = filter.allBoards; allBoards = filter.allBoards;
boards = filter.boards; boards = filter.boards;
hide = filter.hide; action = filter.action;
color = filter.color;
} }
} }

@ -104,7 +104,7 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too
private void showFilterDialog(final Filter filter) { private void showFilterDialog(final Filter filter) {
final FilterLayout filterLayout = (FilterLayout) LayoutInflater.from(context).inflate(R.layout.layout_filter, null); final FilterLayout filterLayout = (FilterLayout) LayoutInflater.from(context).inflate(R.layout.layout_filter, null);
new AlertDialog.Builder(context) final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setView(filterLayout) .setView(filterLayout)
.setPositiveButton(R.string.save, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.save, new DialogInterface.OnClickListener() {
@Override @Override
@ -118,6 +118,12 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too
}) })
.show(); .show();
filterLayout.setCallback(new FilterLayout.FilterLayoutCallback() {
@Override
public void setSaveButtonEnabled(boolean enabled) {
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled);
}
});
filterLayout.setFilter(filter); filterLayout.setFilter(filter);
} }

@ -0,0 +1,121 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.dialog;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.support.annotation.NonNull;
import android.view.MotionEvent;
import android.view.View;
import static org.floens.chan.utils.AndroidUtils.dp;
public class ColorPickerView extends View {
private static final int[] COLORS = new int[]{
0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00,
0xFFFFFF00, 0xFFFF0000
};
private Paint paint;
private Paint centerPaint;
private int centerRadius;
public ColorPickerView(Context context) {
super(context);
centerRadius = dp(32);
Shader s = new SweepGradient(0, 0, COLORS, null);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setShader(s);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(dp(32));
centerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
centerPaint.setStrokeWidth(dp(5));
}
public void setColor(int color) {
centerPaint.setColor(color);
}
public int getColor() {
return centerPaint.getColor();
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
float x = event.getX() - getWidth() / 2f;
float y = event.getY() - getHeight() / 2f;
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
float angle = (float) Math.atan2(y, x);
// need to turn angle [-PI ... PI] into unit [0....1]
float unit = (float) (angle / (2.0 * Math.PI));
if (unit < 0.0) {
unit += 1.0;
}
centerPaint.setColor(interpColor(COLORS, unit));
invalidate();
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
float r = Math.min(getWidth() / 2f, getHeight() / 2f) - paint.getStrokeWidth() * 0.5f;
canvas.translate(getWidth() / 2f, getHeight() / 2f);
canvas.drawOval(new RectF(-r, -r, r, r), paint);
canvas.drawCircle(0, 0, centerRadius, centerPaint);
}
private int interpColor(int colors[], float unit) {
if (unit <= 0) {
return colors[0];
}
if (unit >= 1) {
return colors[colors.length - 1];
}
float p = unit * (colors.length - 1);
int i = (int) p;
p -= i;
// now p is just the fractional part [0...1) and i is the index
int c0 = colors[i];
int c1 = colors[i + 1];
int a = ave(Color.alpha(c0), Color.alpha(c1), p);
int r = ave(Color.red(c0), Color.red(c1), p);
int g = ave(Color.green(c0), Color.green(c1), p);
int b = ave(Color.blue(c0), Color.blue(c1), p);
return Color.argb(a, r, g, b);
}
private int ave(int s, int d, float p) {
return s + Math.round(p * (d - s));
}
}

@ -19,13 +19,22 @@ package org.floens.chan.ui.layout;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.graphics.Typeface;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.BackgroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
@ -35,6 +44,7 @@ import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.manager.FilterEngine; import org.floens.chan.core.manager.FilterEngine;
import org.floens.chan.core.model.Board; import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Filter; import org.floens.chan.core.model.Filter;
import org.floens.chan.ui.dialog.ColorPickerView;
import org.floens.chan.ui.drawable.DropdownArrowDrawable; import org.floens.chan.ui.drawable.DropdownArrowDrawable;
import org.floens.chan.ui.view.FloatingMenu; import org.floens.chan.ui.view.FloatingMenu;
import org.floens.chan.ui.view.FloatingMenuItem; import org.floens.chan.ui.view.FloatingMenuItem;
@ -42,19 +52,27 @@ import org.floens.chan.ui.view.FloatingMenuItem;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.floens.chan.ui.theme.ThemeHelper.theme;
import static org.floens.chan.utils.AndroidUtils.dp; import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getAttrColor; import static org.floens.chan.utils.AndroidUtils.getAttrColor;
import static org.floens.chan.utils.AndroidUtils.getString; import static org.floens.chan.utils.AndroidUtils.getString;
public class FilterLayout extends LinearLayout implements View.OnClickListener, FloatingMenu.FloatingMenuCallback { public class FilterLayout extends LinearLayout implements View.OnClickListener {
private TextView typeText; private TextView typeText;
private TextView boardsSelector; private TextView boardsSelector;
private boolean patternContainerErrorShowing = false;
private TextView pattern; private TextView pattern;
private TextView patternPreview;
private TextView patternPreviewStatus;
private CheckBox enabled; private CheckBox enabled;
private CheckBox hide; private ImageView help;
private TextView actionText;
private LinearLayout colorContainer;
private View colorPreview;
private BoardManager boardManager; private BoardManager boardManager;
private FilterLayoutCallback callback;
private Filter filter = new Filter(); private Filter filter = new Filter();
private List<Board> appliedBoards = new ArrayList<>(); private List<Board> appliedBoards = new ArrayList<>();
@ -79,9 +97,49 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener,
typeText = (TextView) findViewById(R.id.type); typeText = (TextView) findViewById(R.id.type);
boardsSelector = (TextView) findViewById(R.id.boards); boardsSelector = (TextView) findViewById(R.id.boards);
actionText = (TextView) findViewById(R.id.action);
pattern = (TextView) findViewById(R.id.pattern); pattern = (TextView) findViewById(R.id.pattern);
pattern.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
filter.compiledPattern = null;
filter.pattern = s.toString();
updateFilterValidity();
updatePatternPreview();
}
@Override
public void afterTextChanged(Editable s) {
}
});
patternPreview = (TextView) findViewById(R.id.pattern_preview);
patternPreview.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
updatePatternPreview();
}
@Override
public void afterTextChanged(Editable s) {
}
});
patternPreviewStatus = (TextView) findViewById(R.id.pattern_preview_status);
enabled = (CheckBox) findViewById(R.id.enabled); enabled = (CheckBox) findViewById(R.id.enabled);
hide = (CheckBox) findViewById(R.id.hide); help = (ImageView) findViewById(R.id.help);
theme().helpDrawable.apply(help);
help.setOnClickListener(this);
colorContainer = (LinearLayout) findViewById(R.id.color_container);
colorContainer.setOnClickListener(this);
colorPreview = findViewById(R.id.color_preview);
typeText.setOnClickListener(this); typeText.setOnClickListener(this);
typeText.setCompoundDrawablesWithIntrinsicBounds(null, null, new DropdownArrowDrawable(dp(12), dp(12), true, typeText.setCompoundDrawablesWithIntrinsicBounds(null, null, new DropdownArrowDrawable(dp(12), dp(12), true,
getAttrColor(getContext(), R.attr.dropdown_dark_color), getAttrColor(getContext(), R.attr.dropdown_dark_pressed_color)), null); getAttrColor(getContext(), R.attr.dropdown_dark_color), getAttrColor(getContext(), R.attr.dropdown_dark_pressed_color)), null);
@ -90,12 +148,15 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener,
boardsSelector.setCompoundDrawablesWithIntrinsicBounds(null, null, new DropdownArrowDrawable(dp(12), dp(12), true, boardsSelector.setCompoundDrawablesWithIntrinsicBounds(null, null, new DropdownArrowDrawable(dp(12), dp(12), true,
getAttrColor(getContext(), R.attr.dropdown_dark_color), getAttrColor(getContext(), R.attr.dropdown_dark_pressed_color)), null); getAttrColor(getContext(), R.attr.dropdown_dark_color), getAttrColor(getContext(), R.attr.dropdown_dark_pressed_color)), null);
update(); actionText.setOnClickListener(this);
actionText.setCompoundDrawablesWithIntrinsicBounds(null, null, new DropdownArrowDrawable(dp(12), dp(12), true,
getAttrColor(getContext(), R.attr.dropdown_dark_color), getAttrColor(getContext(), R.attr.dropdown_dark_pressed_color)), null);
} }
public void setFilter(Filter filter) { public void setFilter(Filter filter) {
this.filter.apply(filter); this.filter.apply(filter);
appliedBoards.clear(); appliedBoards.clear();
if (filter.allBoards) { if (filter.allBoards) {
appliedBoards.addAll(boardManager.getSavedBoards()); appliedBoards.addAll(boardManager.getSavedBoards());
} else if (!TextUtils.isEmpty(filter.boards)) { } else if (!TextUtils.isEmpty(filter.boards)) {
@ -106,12 +167,22 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener,
} }
} }
} }
update();
pattern.setText(filter.pattern);
updateFilterValidity();
updateCheckboxes();
updateFilterType();
updateFilterAction();
updateBoardsSummary();
updatePatternPreview();
}
public void setCallback(FilterLayoutCallback callback) {
this.callback = callback;
} }
public Filter getFilter() { public Filter getFilter() {
filter.pattern = pattern.getText().toString();
filter.hide = hide.isChecked();
filter.enabled = enabled.isChecked(); filter.enabled = enabled.isChecked();
filter.boards = ""; filter.boards = "";
@ -129,7 +200,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener,
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (v == typeText) { if (v == typeText) {
List<FloatingMenuItem> menuItems = new ArrayList<>(2); List<FloatingMenuItem> menuItems = new ArrayList<>(6);
for (FilterEngine.FilterType filterType : FilterEngine.FilterType.values()) { for (FilterEngine.FilterType filterType : FilterEngine.FilterType.values()) {
menuItems.add(new FloatingMenuItem(filterType, filterTypeName(filterType))); menuItems.add(new FloatingMenuItem(filterType, filterTypeName(filterType)));
@ -138,7 +209,19 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener,
FloatingMenu menu = new FloatingMenu(v.getContext()); FloatingMenu menu = new FloatingMenu(v.getContext());
menu.setAnchor(v, Gravity.LEFT, -dp(5), -dp(5)); menu.setAnchor(v, Gravity.LEFT, -dp(5), -dp(5));
menu.setPopupWidth(dp(150)); menu.setPopupWidth(dp(150));
menu.setCallback(this); menu.setCallback(new FloatingMenu.FloatingMenuCallback() {
@Override
public void onFloatingMenuItemClicked(FloatingMenu menu, FloatingMenuItem item) {
FilterEngine.FilterType type = (FilterEngine.FilterType) item.getId();
filter.type = type.id;
updateFilterType();
updatePatternPreview();
}
@Override
public void onFloatingMenuDismissed(FloatingMenu menu) {
}
});
menu.setItems(menuItems); menu.setItems(menuItems);
menu.show(); menu.show();
} else if (v == boardsSelector) { } else if (v == boardsSelector) {
@ -151,32 +234,103 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener,
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
setCheckedBoards(boardSelectLayout.getCheckedBoards(), boardSelectLayout.getAllChecked()); appliedBoards.clear();
update(); appliedBoards.addAll(boardSelectLayout.getCheckedBoards());
filter.allBoards = boardSelectLayout.getAllChecked();
updateBoardsSummary();
}
})
.show();
} else if (v == actionText) {
List<FloatingMenuItem> menuItems = new ArrayList<>(6);
for (FilterEngine.FilterAction action : FilterEngine.FilterAction.values()) {
menuItems.add(new FloatingMenuItem(action, actionName(action)));
}
FloatingMenu menu = new FloatingMenu(v.getContext());
menu.setAnchor(v, Gravity.LEFT, -dp(5), -dp(5));
menu.setPopupWidth(dp(150));
menu.setCallback(new FloatingMenu.FloatingMenuCallback() {
@Override
public void onFloatingMenuItemClicked(FloatingMenu menu, FloatingMenuItem item) {
FilterEngine.FilterAction action = (FilterEngine.FilterAction) item.getId();
filter.action = action.id;
updateFilterAction();
}
@Override
public void onFloatingMenuDismissed(FloatingMenu menu) {
}
});
menu.setItems(menuItems);
menu.show();
} else if (v == help) {
SpannableStringBuilder message = (SpannableStringBuilder) Html.fromHtml(getString(R.string.filter_help));
TypefaceSpan[] typefaceSpans = message.getSpans(0, message.length(), TypefaceSpan.class);
for (TypefaceSpan span : typefaceSpans) {
if (span.getFamily().equals("monospace")) {
int start = message.getSpanStart(span);
int end = message.getSpanEnd(span);
message.setSpan(new BackgroundColorSpan(0x22000000), start, end, 0);
}
}
StyleSpan[] styleSpans = message.getSpans(0, message.length(), StyleSpan.class);
for (StyleSpan span : styleSpans) {
if (span.getStyle() == Typeface.ITALIC) {
int start = message.getSpanStart(span);
int end = message.getSpanEnd(span);
message.setSpan(new BackgroundColorSpan(0x22000000), start, end, 0);
}
}
new AlertDialog.Builder(getContext())
.setTitle(R.string.filter_help_title)
.setMessage(message)
.setPositiveButton(R.string.ok, null)
.show();
} else if (v == colorContainer) {
final ColorPickerView colorPickerView = new ColorPickerView(getContext());
colorPickerView.setColor(filter.color);
AlertDialog dialog = new AlertDialog.Builder(getContext())
.setTitle(R.string.filter_color_pick)
.setView(colorPickerView)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
filter.color = colorPickerView.getColor();
updateFilterAction();
} }
}) })
.show(); .show();
dialog.getWindow().setLayout(dp(300), dp(300));
} }
} }
@Override private void updateFilterValidity() {
public void onFloatingMenuItemClicked(FloatingMenu menu, FloatingMenuItem item) { FilterEngine.FilterType filterType = FilterEngine.FilterType.forId(filter.type);
FilterEngine.FilterType type = (FilterEngine.FilterType) item.getId();
filter.type = type.id;
update();
}
@Override boolean valid;
public void onFloatingMenuDismissed(FloatingMenu menu) { if (filterType.isRegex) {
} valid = FilterEngine.getInstance().compile(filter.pattern) != null;
} else {
valid = !TextUtils.isEmpty(filter.pattern);
}
private void update() { if (valid != patternContainerErrorShowing) {
pattern.setText(filter.pattern); patternContainerErrorShowing = valid;
hide.setChecked(filter.hide); pattern.setError(valid ? null : getString(R.string.filter_invalid_pattern));
enabled.setChecked(filter.enabled); }
typeText.setText(filterTypeName(FilterEngine.FilterType.forId(filter.type))); if (callback != null) {
callback.setSaveButtonEnabled(valid);
}
}
private void updateBoardsSummary() {
String text = getString(R.string.filter_boards) + " ("; String text = getString(R.string.filter_boards) + " (";
if (filter.allBoards) { if (filter.allBoards) {
text += getString(R.string.filter_boards_all); text += getString(R.string.filter_boards_all);
@ -187,10 +341,30 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener,
boardsSelector.setText(text); boardsSelector.setText(text);
} }
private void setCheckedBoards(List<Board> checkedBoards, boolean allChecked) { private void updateCheckboxes() {
appliedBoards.clear(); enabled.setChecked(filter.enabled);
appliedBoards.addAll(checkedBoards); }
filter.allBoards = allChecked;
private void updateFilterAction() {
FilterEngine.FilterAction action = FilterEngine.FilterAction.forId(filter.action);
actionText.setText(actionName(action));
colorContainer.setVisibility(action == FilterEngine.FilterAction.COLOR ? VISIBLE : GONE);
if (filter.color == 0) {
filter.color = 0xffff0000;
}
colorPreview.setBackgroundColor(filter.color);
}
private void updateFilterType() {
FilterEngine.FilterType filterType = FilterEngine.FilterType.forId(filter.type);
typeText.setText(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);
patternPreviewStatus.setText(matches ? R.string.filter_matches : R.string.filter_no_matches);
} }
private String filterTypeName(FilterEngine.FilterType type) { private String filterTypeName(FilterEngine.FilterType type) {
@ -210,4 +384,18 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener,
} }
return null; 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);
}
} }

@ -419,6 +419,7 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
private void showReplyButton(boolean show) { private void showReplyButton(boolean show) {
if (show != showingReplyButton) { if (show != showingReplyButton) {
showingReplyButton = show; showingReplyButton = show;
replyButton.animate() replyButton.animate()
.setInterpolator(new DecelerateInterpolator(2f)) .setInterpolator(new DecelerateInterpolator(2f))
.setStartDelay(show ? 100 : 0) .setStartDelay(show ? 100 : 0)

@ -18,5 +18,6 @@ public class DarkTheme extends Theme {
doneDrawable = new ThemeDrawable(R.drawable.ic_done_white_24dp, 1f); doneDrawable = new ThemeDrawable(R.drawable.ic_done_white_24dp, 1f);
historyDrawable = new ThemeDrawable(R.drawable.ic_history_white_24dp, 1f); historyDrawable = new ThemeDrawable(R.drawable.ic_history_white_24dp, 1f);
listAddDrawable = new ThemeDrawable(R.drawable.ic_playlist_add_white_24dp, 1f); listAddDrawable = new ThemeDrawable(R.drawable.ic_playlist_add_white_24dp, 1f);
helpDrawable = new ThemeDrawable(R.drawable.ic_help_outline_white_24dp, 1f);
} }
} }

@ -63,6 +63,7 @@ public class Theme {
public ThemeDrawable doneDrawable; public ThemeDrawable doneDrawable;
public ThemeDrawable historyDrawable; public ThemeDrawable historyDrawable;
public ThemeDrawable listAddDrawable; public ThemeDrawable listAddDrawable;
public ThemeDrawable helpDrawable;
public Theme(String displayName, String name, int resValue, ThemeHelper.PrimaryColor primaryColor) { public Theme(String displayName, String name, int resValue, ThemeHelper.PrimaryColor primaryColor) {
this.displayName = displayName; this.displayName = displayName;
@ -83,6 +84,7 @@ public class Theme {
doneDrawable = new ThemeDrawable(R.drawable.ic_done_black_24dp, 0.54f); doneDrawable = new ThemeDrawable(R.drawable.ic_done_black_24dp, 0.54f);
historyDrawable = new ThemeDrawable(R.drawable.ic_history_black_24dp, 0.54f); historyDrawable = new ThemeDrawable(R.drawable.ic_history_black_24dp, 0.54f);
listAddDrawable = new ThemeDrawable(R.drawable.ic_playlist_add_black_24dp, 0.54f); listAddDrawable = new ThemeDrawable(R.drawable.ic_playlist_add_black_24dp, 0.54f);
helpDrawable = new ThemeDrawable(R.drawable.ic_help_outline_black_24dp, 0.54f);
} }
private void resolveSpanColors() { private void resolveSpanColors() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<org.floens.chan.ui.layout.FilterLayout xmlns:android="http://schemas.android.com/apk/res/android" <org.floens.chan.ui.layout.FilterLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
@ -24,6 +25,44 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"
android:textColor="?attr/text_color_secondary">
<CheckBox
android:id="@+id/enabled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/filter_enabled" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<ImageView
android:id="@+id/help"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/item_background"
android:paddingBottom="4dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:paddingTop="4dp" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="4dp"
android:paddingTop="8dp"
android:text="@string/filter_filter"
android:textColor="?attr/text_color_secondary"
android:textSize="16sp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
@ -44,30 +83,85 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</LinearLayout> </LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="4dp"
android:paddingTop="8dp"
android:text="@string/filter_action"
android:textColor="?attr/text_color_secondary"
android:textSize="16sp" />
<LinearLayout <LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox <TextView
android:id="@+id/enabled" android:id="@+id/action"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/filter_enabled" /> android:drawablePadding="8dp"
android:paddingBottom="4dp"
android:paddingLeft="4dp"
android:paddingTop="4dp" />
<CheckBox <LinearLayout
android:id="@+id/hide" android:id="@+id/color_container"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/filter_hide" /> android:orientation="horizontal">
<View
android:id="@+id/color_preview"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginBottom="4dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="4dp" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="4dp"
android:paddingTop="8dp"
android:text="@string/filter_pattern"
android:textColor="?attr/text_color_secondary"
android:textSize="16sp" />
<EditText <EditText
android:id="@+id/pattern" android:id="@+id/pattern"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/filter_pattern" android:textSize="14sp"
tools:ignore="TextFields" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="4dp"
android:paddingTop="8dp"
android:text="@string/filter_match_test"
android:textColor="?attr/text_color_secondary"
android:textSize="16sp" />
<TextView
android:id="@+id/pattern_preview_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="4dp"
android:paddingTop="8dp" />
<EditText
android:id="@+id/pattern_preview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/filter_preview"
android:textSize="14sp" /> android:textSize="14sp" />
</org.floens.chan.ui.layout.FilterLayout> </org.floens.chan.ui.layout.FilterLayout>

@ -118,10 +118,45 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<string name="board_edit_sort_a_z">Sort A-Z</string> <string name="board_edit_sort_a_z">Sort A-Z</string>
<string name="filter_enabled">Enabled</string> <string name="filter_enabled">Enabled</string>
<string name="filter_filter">Filter</string>
<string name="filter_action">Action</string>
<string name="filter_pattern">Pattern</string> <string name="filter_pattern">Pattern</string>
<string name="filter_match_test">Test pattern</string>
<string name="filter_pattern_hint_exact">Exact text</string>
<string name="filter_pattern_hint_regex">Pattern</string>
<string name="filter_boards">Boards</string> <string name="filter_boards">Boards</string>
<string name="filter_boards_all">all</string> <string name="filter_boards_all">all</string>
<string name="filter_hide">Hide</string> <string name="filter_hide">Hide post</string>
<string name="filter_color">Highlight</string>
<string name="filter_invalid_pattern">Invalid pattern</string>
<string name="filter_preview">Test your filter</string>
<string name="filter_no_matches">Does not match</string>
<string name="filter_matches">Matches</string>
<string name="filter_color_pick">Pick color</string>
<string name="filter_help_title">Filter help</string>
<string name="filter_help">
"Filters act on a given pattern and a place to search the pattern.&lt;br>
If the pattern matches then the post can be hidden or highlighted.&lt;br>
&lt;h4>For tripcodes, names and IDs:&lt;/h4>
&lt;p>
It will match the given pattern exact.&lt;br>
&lt;tt>!Ep8pui8Vw2&lt;/tt> will match the tripcode &lt;i>!Ep8pui8Vw2&lt;/i> but not &lt;i>Ep8pu&lt;/i>.
&lt;/p>
&lt;h4>For comments, subjects and filenames:&lt;/h4>
&lt;p>
These filters are pattern based, and have three modes:&lt;br>
&lt;br>
1. The pattern &lt;tt>foo bar&lt;/tt> will match text that has any of the words in it. It will match &lt;i>foo&lt;/i> or &lt;i>bar&lt;/i>, but not &lt;i>foobar&lt;/i>.
Placing a * allows any character to be filled in: &lt;tt>f*o&lt;/tt> will match both &lt;i>foo&lt;/i>, &lt;i>foooo&lt;/i> but not &lt;i>foobar&lt;/i>&lt;br>
&lt;br>
2. Quoting your pattern with &lt;tt>\"&lt;/tt> like &lt;tt>\"foo bar\"&lt;/tt> will match the text exact.
&lt;i>foo bar&lt;/i> matches but &lt;i>foo&lt;/i> does not.&lt;br>
&lt;br>
3. Regular expressions. &lt;tt>/^>implying/&lt;/tt> for example.
&lt;/p>"
</string>
<string name="filter_tripcode">Tripcode</string> <string name="filter_tripcode">Tripcode</string>
<string name="filter_name">Name</string> <string name="filter_name">Name</string>

Loading…
Cancel
Save