From 97a0d1b4a8ad25510420c07541049b0098a8cdfc Mon Sep 17 00:00:00 2001 From: Floens Date: Sat, 25 Jul 2015 14:14:21 +0200 Subject: [PATCH] Finish up the filter edit dialog --- Clover/app/build.gradle | 12 +- .../java/org/floens/chan/chan/ChanLoader.java | 11 +- .../chan/core/manager/FilterEngine.java | 115 +++++++- .../org/floens/chan/core/model/Filter.java | 11 +- .../chan/ui/controller/FiltersController.java | 8 +- .../chan/ui/dialog/ColorPickerView.java | 121 +++++++++ .../floens/chan/ui/layout/FilterLayout.java | 246 +++++++++++++++--- .../floens/chan/ui/layout/ThreadLayout.java | 1 + .../org/floens/chan/ui/theme/DarkTheme.java | 1 + .../java/org/floens/chan/ui/theme/Theme.java | 2 + .../ic_help_outline_black_24dp.png | Bin 0 -> 590 bytes .../ic_help_outline_white_24dp.png | Bin 0 -> 591 bytes .../ic_help_outline_black_24dp.png | Bin 0 -> 371 bytes .../ic_help_outline_white_24dp.png | Bin 0 -> 387 bytes .../ic_help_outline_black_24dp.png | Bin 0 -> 784 bytes .../ic_help_outline_white_24dp.png | Bin 0 -> 801 bytes .../ic_help_outline_black_24dp.png | Bin 0 -> 1157 bytes .../ic_help_outline_white_24dp.png | Bin 0 -> 1174 bytes .../ic_help_outline_black_24dp.png | Bin 0 -> 1549 bytes .../ic_help_outline_white_24dp.png | Bin 0 -> 1578 bytes .../app/src/main/res/layout/layout_filter.xml | 112 +++++++- Clover/app/src/main/res/values/strings.xml | 37 ++- 22 files changed, 619 insertions(+), 58 deletions(-) create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/dialog/ColorPickerView.java create mode 100644 Clover/app/src/main/res/drawable-hdpi/ic_help_outline_black_24dp.png create mode 100644 Clover/app/src/main/res/drawable-hdpi/ic_help_outline_white_24dp.png create mode 100644 Clover/app/src/main/res/drawable-mdpi/ic_help_outline_black_24dp.png create mode 100644 Clover/app/src/main/res/drawable-mdpi/ic_help_outline_white_24dp.png create mode 100644 Clover/app/src/main/res/drawable-xhdpi/ic_help_outline_black_24dp.png create mode 100644 Clover/app/src/main/res/drawable-xhdpi/ic_help_outline_white_24dp.png create mode 100644 Clover/app/src/main/res/drawable-xxhdpi/ic_help_outline_black_24dp.png create mode 100644 Clover/app/src/main/res/drawable-xxhdpi/ic_help_outline_white_24dp.png create mode 100644 Clover/app/src/main/res/drawable-xxxhdpi/ic_help_outline_black_24dp.png create mode 100644 Clover/app/src/main/res/drawable-xxxhdpi/ic_help_outline_white_24dp.png diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index b1923d3f..05075bfa 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -71,12 +71,12 @@ android { } dependencies { - compile 'com.android.support:support-v13:22.2.0' - compile 'com.android.support:appcompat-v7:22.2.0' - compile 'com.android.support:recyclerview-v7:22.2.0' - compile 'com.android.support:cardview-v7:22.2.0' - compile 'com.android.support:support-annotations:22.2.0' - compile 'com.android.support:design:22.2.0' + compile 'com.android.support:support-v13:22.2.1' + compile 'com.android.support:appcompat-v7:22.2.1' + compile 'com.android.support:recyclerview-v7:22.2.1' + compile 'com.android.support:cardview-v7:22.2.1' + compile 'com.android.support:support-annotations:22.2.1' + compile 'com.android.support:design:22.2.1' compile 'org.jsoup:jsoup:1.8.2' compile 'com.j256.ormlite:ormlite-core:4.48' diff --git a/Clover/app/src/main/java/org/floens/chan/chan/ChanLoader.java b/Clover/app/src/main/java/org/floens/chan/chan/ChanLoader.java index 127dca1f..13250ff0 100644 --- a/Clover/app/src/main/java/org/floens/chan/chan/ChanLoader.java +++ b/Clover/app/src/main/java/org/floens/chan/chan/ChanLoader.java @@ -268,10 +268,13 @@ public class ChanLoader implements Response.ErrorListener, Response.Listener 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.repliesTo.contains(sourcePost.no)) { sourcePost.repliesFrom.add(replyToSource.no); @@ -279,6 +282,8 @@ public class ChanLoader implements Response.ErrorListener, Response.Listener. + */ +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)); + } +} 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 f39a28a1..3d7ceeac 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 @@ -19,13 +19,22 @@ package org.floens.chan.ui.layout; import android.content.Context; import android.content.DialogInterface; +import android.graphics.Typeface; 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.TextWatcher; +import android.text.style.BackgroundColorSpan; +import android.text.style.StyleSpan; +import android.text.style.TypefaceSpan; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.widget.CheckBox; +import android.widget.ImageView; import android.widget.LinearLayout; 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.model.Board; 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.view.FloatingMenu; 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.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.getAttrColor; 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 boardsSelector; + private boolean patternContainerErrorShowing = false; private TextView pattern; + private TextView patternPreview; + private TextView patternPreviewStatus; private CheckBox enabled; - private CheckBox hide; + private ImageView help; + private TextView actionText; + private LinearLayout colorContainer; + private View colorPreview; private BoardManager boardManager; + private FilterLayoutCallback callback; private Filter filter = new Filter(); private List appliedBoards = new ArrayList<>(); @@ -79,9 +97,49 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener, typeText = (TextView) findViewById(R.id.type); boardsSelector = (TextView) findViewById(R.id.boards); + actionText = (TextView) findViewById(R.id.action); 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); - 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.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); @@ -90,12 +148,15 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener, 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); - 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) { this.filter.apply(filter); appliedBoards.clear(); + if (filter.allBoards) { appliedBoards.addAll(boardManager.getSavedBoards()); } 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() { - filter.pattern = pattern.getText().toString(); - filter.hide = hide.isChecked(); filter.enabled = enabled.isChecked(); filter.boards = ""; @@ -129,7 +200,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener, @Override public void onClick(View v) { if (v == typeText) { - List menuItems = new ArrayList<>(2); + List menuItems = new ArrayList<>(6); for (FilterEngine.FilterType filterType : FilterEngine.FilterType.values()) { 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()); menu.setAnchor(v, Gravity.LEFT, -dp(5), -dp(5)); 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.show(); } else if (v == boardsSelector) { @@ -151,32 +234,103 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener, .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - setCheckedBoards(boardSelectLayout.getCheckedBoards(), boardSelectLayout.getAllChecked()); - update(); + appliedBoards.clear(); + appliedBoards.addAll(boardSelectLayout.getCheckedBoards()); + filter.allBoards = boardSelectLayout.getAllChecked(); + updateBoardsSummary(); + } + }) + .show(); + } else if (v == actionText) { + List 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(); + dialog.getWindow().setLayout(dp(300), dp(300)); } } - @Override - public void onFloatingMenuItemClicked(FloatingMenu menu, FloatingMenuItem item) { - FilterEngine.FilterType type = (FilterEngine.FilterType) item.getId(); - filter.type = type.id; - update(); - } + private void updateFilterValidity() { + FilterEngine.FilterType filterType = FilterEngine.FilterType.forId(filter.type); - @Override - public void onFloatingMenuDismissed(FloatingMenu menu) { - } + boolean valid; + if (filterType.isRegex) { + valid = FilterEngine.getInstance().compile(filter.pattern) != null; + } else { + valid = !TextUtils.isEmpty(filter.pattern); + } - private void update() { - pattern.setText(filter.pattern); - hide.setChecked(filter.hide); - enabled.setChecked(filter.enabled); + if (valid != patternContainerErrorShowing) { + patternContainerErrorShowing = valid; + pattern.setError(valid ? null : getString(R.string.filter_invalid_pattern)); + } - typeText.setText(filterTypeName(FilterEngine.FilterType.forId(filter.type))); + if (callback != null) { + callback.setSaveButtonEnabled(valid); + } + } + private void updateBoardsSummary() { String text = getString(R.string.filter_boards) + " ("; if (filter.allBoards) { text += getString(R.string.filter_boards_all); @@ -187,10 +341,30 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener, boardsSelector.setText(text); } - private void setCheckedBoards(List checkedBoards, boolean allChecked) { - appliedBoards.clear(); - appliedBoards.addAll(checkedBoards); - filter.allBoards = allChecked; + private void updateCheckboxes() { + enabled.setChecked(filter.enabled); + } + + 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) { @@ -210,4 +384,18 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener, } 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 59daf19a..0217cced 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 @@ -419,6 +419,7 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T private void showReplyButton(boolean show) { if (show != showingReplyButton) { showingReplyButton = show; + replyButton.animate() .setInterpolator(new DecelerateInterpolator(2f)) .setStartDelay(show ? 100 : 0) diff --git a/Clover/app/src/main/java/org/floens/chan/ui/theme/DarkTheme.java b/Clover/app/src/main/java/org/floens/chan/ui/theme/DarkTheme.java index 9598cfcd..9ece2259 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/theme/DarkTheme.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/theme/DarkTheme.java @@ -18,5 +18,6 @@ public class DarkTheme extends Theme { doneDrawable = new ThemeDrawable(R.drawable.ic_done_white_24dp, 1f); historyDrawable = new ThemeDrawable(R.drawable.ic_history_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); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java b/Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java index ffc4eb3a..b6c2b406 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java @@ -63,6 +63,7 @@ public class Theme { public ThemeDrawable doneDrawable; public ThemeDrawable historyDrawable; public ThemeDrawable listAddDrawable; + public ThemeDrawable helpDrawable; public Theme(String displayName, String name, int resValue, ThemeHelper.PrimaryColor primaryColor) { this.displayName = displayName; @@ -83,6 +84,7 @@ public class Theme { doneDrawable = new ThemeDrawable(R.drawable.ic_done_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); + helpDrawable = new ThemeDrawable(R.drawable.ic_help_outline_black_24dp, 0.54f); } private void resolveSpanColors() { diff --git a/Clover/app/src/main/res/drawable-hdpi/ic_help_outline_black_24dp.png b/Clover/app/src/main/res/drawable-hdpi/ic_help_outline_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..314154a08e1c733ad16563e84b36c4e048d22e9b GIT binary patch literal 590 zcmV-U04Z@^lW4qdy4 zfV4~LUX3E=MkusWOqLW5hYL?~??asZJk#?#CkOAzjXXpy^E7A?(5Fv8i#_H@aW7M> zbHgCwbn(eCrYZpg%ekfkVCV25{J<|ZC{e)0r9g>I&ca#)$3Et>HlDFal3_`zw6){d zHcI~*4oDMcl0)sF~ zj9Ay*q!D;J#vJ8=$Z#Ra8be%tZx%tV$o-BQd9u{#$^8weC4H|_80Pg0Pl7s?qz0-c z_ivya&Lx?}(2#HgLbsCSLFMKC4?VJ3wAy2a2_=D1XmFjO^fPIkt(LPG;ie~`!N3$*asZ{zrN7x^kIyTHIrmi~YVhWJuwGZ~rtn6g|-zRHLk z*HSthV^D$<{U$R+s4$STL&7>JL0vl+h|$o7BQnIva-v;zVmTaZ(*;X75%py*wc`^> zEFAV3=s9PLGDSQ*ij>)=9oFjBF{+|#k2_Z^Fs2-9bR&lZtdikgQp{4PNrxUiIyBj3 ch6x^?|M4eD;3?sA(EtDd07*qoM6N<$f)qFkng9R* literal 0 HcmV?d00001 diff --git a/Clover/app/src/main/res/drawable-hdpi/ic_help_outline_white_24dp.png b/Clover/app/src/main/res/drawable-hdpi/ic_help_outline_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..4bc2a96383743b65301a022be3b3ef2830dfd6bb GIT binary patch literal 591 zcmV-V0h*!MpsQ~Sg4{SYqXXB8_GKcaA()N83hG1=Iv<$Xvk+ldG7&DYeq!z7*WvzT*{|I zr+_nKqLhp~2b`oq6~GUnDcW@F)tg^^=i|^H0?J9yv<^56&79u=!0+Y;GdUNat@Lkb z2QBIZq~Hat1$Yy@shTpWst3Le^Xs5%G!CtRJK?07p2F--4<0~~aUnd+u3oOQ=f-b0thRg=&_$UrVYM^C2G@(Z=-kS6XwnXBPk~TOW z0?N4pPbQKswld&QE)&YB16I?n+6LglgeWDGt^oC{%l{JaU|l}pq&7SP-Wkb$j!ppF z+qYm=QPHdg2U-B?fysCv%i6NWlht9L?2>I=Nuy&cdH*758MUPD+OwXXXV;D_4t~F( dp^SVu&_7{&-GpOX15yA0002ovPDHLkV1i+n1t0(b literal 0 HcmV?d00001 diff --git a/Clover/app/src/main/res/drawable-mdpi/ic_help_outline_black_24dp.png b/Clover/app/src/main/res/drawable-mdpi/ic_help_outline_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..9eb8f36ca3882289780c7efd3e5eba40b20d7d13 GIT binary patch literal 371 zcmV-(0gV2MP)=g+~WEBF5RXau%OvLn%!?MGnKuiXFB2#keWjdWPH16RVtidFB0 z51=v?_N7aq1KbNM;EC`Xa4cL%y|8BJsqh0Z5^kiq&`4m_XWyxz7Y8v8EXn3=hDZcpQp{Rb|RlB^n9 RXfOZ(002ovPDHLkV1g{ssH6Y@ literal 0 HcmV?d00001 diff --git a/Clover/app/src/main/res/drawable-mdpi/ic_help_outline_white_24dp.png b/Clover/app/src/main/res/drawable-mdpi/ic_help_outline_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..1e5dde767ee8b57508a85adf9ddf1146f27fbec7 GIT binary patch literal 387 zcmV-}0et?6P)Nkl0)yW11~I`Va-HF7^ELO(P1{^sx~yWFD{|1*`t3{MLwq7Ve}`v4^WDxkN|Zf-m?Q z1&JRmnL<4C(G%8Vnl}2Hl9(WI=n2O$OHJEI*MWviX2p?fBcIjcH8$Ltm0OSbq;{OU hH861F%(gV%*nc%)=VV)+QW!x}FM|%0xx4~a*vq+v4 zapDY8@=RT-X+$idWk!&e`fO(+8oXp z#YT<`y=H?p=C$+((g13lO-^xf`I#y?0F~iLubTz4muc$Vr$IS4*9&BKY;mirf7GD>@g4IADE%wrChLcwe{wf*62Ji|l{o zab5%l%=$;(lcgeNpG(bmL{YY{>9Q>CV5Fbq*D?80DwYVClBHV`D8p1I3D>Wmib(>Z z{BA$XZ5nh*gb=IxZ`MmU;`|~ztPtu|TvG>yClRiqAHXaIRVle6*YN=p`j`1JMk#Kj z=z>f~2W0tG)~*@Ej5*!ph>Z0CS?anrgEF>BpJEIf6MFaB9y5q>pyhXz+q_Y6Dzvoz zYkIKYphw_wPLZTxtV+m>Dcd_(im`!*2Dj`|qClEH`bbltL=Bl*ZB7pznWcee8Q09v zg(23pzvgK15rcGXgb%qgZhpxMSspA(o-#G+G;ncgP^ZQclSFv(2>cHhDN11ksP}OI O0000UxlvJ=kh90sVRSXVS@JCQ0i_@Oh-)egQaC{v|MnFVffiakX1 zv)I8|J`m9MO6Z;}Twx zMAaFR+-J=qCrBs&Nyb=W%{&RUM~nx!zVa7QwLpvsR&m`Us&=@9Yl)re0K*h<&8jty z;(AI-9*i=_A`TA!l4DpN4Dt-uVTEHGWy}&O)sf&bjlMI-X4NrB5mRET!f+Aum7S_1 z!6Sk^WV7lRp@KQDVC6y!%4)33G|;eX6A zH6Vp4$&V@~t~NNtGUg6lYCt!}ksnn|TrDw;>pltj(2a5AM;SAq7C4RTDp55dg(=C8 z1x#9@808ygR)NT13i9I?W?Z4Tf_Y3tfyiR=^5Ycdx{SSEVkYFr9?UXHg}}k| zC=k7@V0O!o2>)Tmv_WJsZ|PDq&R~{^X#^^8IJP$M6PDBwD%*2v*1GNKN!i#NDt z)DBVRajh~*Of3*+iVCi~Y*Kq9m}ku^#_3T2ddc#dHFrsI82F7edM6lkFA`iLOT`C(Wi}KJOA*GFmIV=NI#1RX(q{2poD`%i2`{h f*iDyy`vv>}3M)dB_9^(R00000NkvXXu0mjfHaK6Q literal 0 HcmV?d00001 diff --git a/Clover/app/src/main/res/drawable-xxhdpi/ic_help_outline_black_24dp.png b/Clover/app/src/main/res/drawable-xxhdpi/ic_help_outline_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..dc5cdeebee37688af845f670bae946e6eb439fd8 GIT binary patch literal 1157 zcmV;01bX|4P)p94b`U zW|gkgRv31cv0*`5naC*HZDG{Px>?&0rEJ|aww~QSocAO-IeAlhkJLR=7eJZPZgsh!C~Z(?%ELJVF^` zkPro`X2!5^ib3|1%>jm3MfqZy2nC4qt8d9=5_s6~ILkwn7I%o_sM=Rfn)7(%BSIgW zzLKIi2>UkcclP37)#tiUb10Yiy6Z#+9stqS^^*YN9? zIkb?IY|v#q{H~!y{6`=A2ot7>e%4VUuHYACMV?P`sFPnQ31g$n&iaE!q<8pJcAT-_ z&dCvuI9Z*MS12BSz4F_k5#1g(L`FA$5qa&>Nmg@mSiodjWb|S3rO5ax=PfWx7M>j4 z*^kMx$k>NTv&eXW$sv)kP9+(&$?*#_S#lVYO8NGINf}9zbDWHNwGMF;5v%-*WbV`+ zG8z+UUASr$tK7vbA#yH~(Sk^8!_}l%Wf-$|kuy(5OCqfvH=SaQE%sv8AhH(8C?(Qr zaq%fi#MfjJ6In^XqJu&XGx&Gf>|8}AVcChrDX4+FC=tIA1J{BI-?B#S<#3i71lKKd zJ=8mSpxyEzB*#PT-u9QY3A_x!we8)17~f_~Joi1XAXxk`j@VrtMwhNlP{TRV&ouScOMcVXw^IxpD<}PUWw=c&C~*SU$}OjIW@w>k zTDZ=pbt!2kIYQCJ>139Ttfj0o!)ao?K$RS)hl|X!NRkaUNV3R0BXsi~+j&Wql$87r X#)Bu-P|j^v00000NkvXXu0mjf>%1k| literal 0 HcmV?d00001 diff --git a/Clover/app/src/main/res/drawable-xxhdpi/ic_help_outline_white_24dp.png b/Clover/app/src/main/res/drawable-xxhdpi/ic_help_outline_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..e9585a04413371c3e5cd1fa505a32dd82f99b664 GIT binary patch literal 1174 zcmV;H1Zn$;P)yRG19otp z3EX-pQU0jp3Gy`+1}X61pc z43q9YA?1n-r0XK8JW<9S(w$RoI7+&U6v_h|XrhNPQl$8s9=_odd0++Yr2A6Av6k03 zU6cpbbBkH#o8<=UMN{$!&Z_>8|Wv#gT~r!mu1Dicm(5=7)e z6Xq=&H4mFeVjAT_2@{wT%7lJQt31$yX_g0m#QY`?T)_0n9}!ZR?ef4FW{W(q4f9+c z*oB#*NPeipjLQ=#OjsT$!X)K^0^*p1@=)g26Pt;=mkSAi8 z%ksk@rcQa{Hl|IUsK*S*52KiB<%JWNIX24^HJB0kA%Q7Z9{3igpX7rIOk93QVM59g zrTl`^5XJIA7?YGAQkal(LJfc5^g9*uMHrKm9}<{y<$#U6#Ho)mWk3ZcEOtV#4;Gitn$qc4fL9h>}Vak*d4$6pCJj2YfN5OCnr)~;c1~|wU1zf=?L8XG@2&#JEKqZ$Ge7i-99dvQP;QYw=LU z&*X28D^zN+QOg7J(aT;f7537_G(n}f%^oc^Dmg(PNivzDm*Z6E-JytsoZ&J9j1VVD ok~kv_&`u+t^P!e4TlSv%A3Ll`2waD!UH||907*qoM6N<$f>w4B{r~^~ literal 0 HcmV?d00001 diff --git a/Clover/app/src/main/res/drawable-xxxhdpi/ic_help_outline_black_24dp.png b/Clover/app/src/main/res/drawable-xxxhdpi/ic_help_outline_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..2d3937d0b932ee3ce36b6358d5720a1035899845 GIT binary patch literal 1549 zcmV+o2J-odP)RJEPY4ARyv1ao0+8rB!Go zX*8h(Q&ShV!RVsKM$?5hwLuGrmQ)d{g2e}lxBL5X?rY9mSj_L+o^#K4X3jnL+;a~a zrKP2%rKRPwioLXQl`aN(%{b${W{@tfaE86CBA+QK%6eMp!Eg}WG*dx@DI$ZFwDS_f zWsK5BIT=i<7`60aBoV#rVj7bo!akm2Xow*i2;;LF`Y|-dL#pvj5uF$*h)WdWgH4QJ zq$r;A1t}Gx)i#O0X{V8OEMNvXOgN0PEk9G$GubC`TB%d1=4Zr6aJ1N0VDZ9CD z*@f#QNP!aWE1P`AQIWb`H<}o>c+?f4*&O~+H1?E6VtA3m0iG+a+Rtn>Dp90A&Pnn} zMx4{US9D<(n&c}6&tEJ?hb8nV22uhILW*1WEKzic(Wbbavq@U(ghQtm-S$j4l$w)& zY#AcnP>Hn|8)@MtLyR%TAUA2Dni$rq7?Z70LlTM@k?FGFUmh4jBAZ@-Y1GmsYu_h3&p79&@0N<;Zlr$#pqv*O2o}}NX4im#9Fn7 z0FJE`jWwcnMwEWVW>a$kw((k&YeDU>D7_rV+QoW5hDr`EMfytt=~G^rO8&<*RC2|C za1Mu}jGC$*MQx+_r8c9oO_co{wPU8TF9_qvdhxPY;^KC@MF5omzne;*LhWPG-8DEN zQo4qfo2JrRu=1s;^siXCA!b4+#OuXQtPGh-mw}64bpR`MrqXwD-~;i;m0&@vAG5HM zE!MVH=89Yr*jgp_d31`IuKp5hk4)uPU~8|bN;^8u7t1tj9j5a2?q4Ar(MLX>E@17T z?W0e)YO1mhed1z6ek;~0P313RtIJen0lH+0H@p#Yu(rrl;|{h4O;u)~M}!|SOwoXy z0=sEf5KFEwIu!C7a@QvlI}x!lU~Ak|B}no;s ze+Xr83PZ$QKEj!(976uy#NSM~;~7WNbrXwebRphtL#BU$5Q5HW^oprU1zr^)w;0b7 z#5wsO>%@+D!`+8h--+~pAc#AM#BQ~3i@CVitN4>BZk-n!T&|bX{dluk{Qiq^@3H9` z<=Dy>4VP*eKtJQ&JdsNr2f9s_cH@C~ecyz8^`>id;YhQXw>=P>y~VgDHVjVSNQKx| z@iK_sn647RtvO-JpffZvXY9G2l& z2EF1WPaHqxGa|m*2XJAR=>KV45%t}XTlU*=Wg0_bE?VMS2bPN3Pl(`>{M^|?%$E-2 z(r5p9*It;1;xcW%bf81jy-x@asxd_DNqGnM%U0QdM?cAvaa5(W12yuf0T=PCkYV|4 zk+mu7z&iQ8lV=nF=sYhmMs-R$P{UhI=OpCXR1BmBblWcmk63?tldU-A*+vXqa_PX3 z!7pT@L4s}!8T7CO9hNGN@ZTd3O=i)rIIY`4f@I{=syxCx6OCpoPdyKFm|VP=&JjkG z>6f9|EP61MojAs9+?d00i&OCTm`MsGYz}STppLoNnMb{N*EQd|d8A0ToK!_TqJx7} zvWNmA1PKzQfJIbti1R$Qt$%_{QmRHiowda?s_;P}mr|1cMGEju6%Vu~f1eHbEKEIv zT9SW4Eg>dFlpXXYm3%kbi7=@GEax0AT&F(5S(XuCiU_lYliYQbdKV{HO^7Ke!3yeW z;WBp^V3c>fW0V2z&`C3OloKZ{EiElAO`HD#_bn|+QS!*b00000NkvXXu0mjf`J&hy literal 0 HcmV?d00001 diff --git a/Clover/app/src/main/res/drawable-xxxhdpi/ic_help_outline_white_24dp.png b/Clover/app/src/main/res/drawable-xxxhdpi/ic_help_outline_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..0f88a23638966bc4aea528f101104931d93c2c65 GIT binary patch literal 1578 zcmV+_2G#kAP)Fi&TWs2kHZ+o5)TS=jf?^?Ud6)_gM3D2|?Y_Tr{!i!p0WZ$$B^4AD z6ciN1hH4IQoE|=Bh$$9WW|;-17~(U!InDv9buXffr%5qF7{hdNm{Q#vDCPosZ#zZ;R4z8Ql^+tP9NC}utE8vhfK?~DrUS&noM2F z4@by!her9LoM%Zf$TW*AGQ}V%nkknrnph-Li*mCPhaXqUh}F{4=5*(?td5#9wvz`W?=OURF*)9)M zF@rTik;38$T%GbnH?G?prAYsWB95_uD4JI;7Z8$#tyf<%>qFPZfkAtVa|R)^Q$dmJ0b|H`XNuVG3)DVnHc?$JL<# z)L>002n$$A#eiD=g=>Iy3P35=oPw~7l~CUJEz`J0`K1Dpz`Cm-EMp~&MOGrSSOStKEU-i5{iU3vAPt5<5>O536(5i{hKmH z#79_d3c>-bapi=gxb`SAe#Y9bAXMY3R}T09>z|5@hj3LY7{gevCcp2CGiUfxup&Qo=ih_+yW2M=v z2yiF@_TieORMF6ZYlL!zhC`vTiBVjwii#vZ;ObLoI20OZab2ZE(a}t%R}~Ttg~V%Q zdPce7AGp#qDHI$Eg%;AdK2ffyWd_$GPbmZ(3V}l`;krSMaz_i97HL)>9127WOJr(N ze&`}onhwQ`*GQA;wDQFU2FRw5a>ayAoF$ttSf~6^#W2~7uval)AERXR9h(&owlPjN zX;M@v4^-1bc4OSH_^^X%Zj1)Si*1bHWrlayp$OQ?yUgO{JMLHf zsA2#=7ii-_1>hmt8N|;QY}U$P16>69iL<;xBfF`glmrRNs9`sayvaxWOpw#8(@LU+ z8CJ>-nzZt$+JT_kB|ia1QtN{QA2WxU80@-fT{lxi(e#AAHO zEuxyGmq#hmy@4c8a)!%. --> . + + + + + + + + + + + + . + + + android:layout_height="wrap_content" + android:orientation="horizontal"> - + android:drawablePadding="8dp" + android:paddingBottom="4dp" + android:paddingLeft="4dp" + android:paddingTop="4dp" /> - + android:orientation="horizontal"> + + + + + + + + + + + + diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml index c0e4ae3e..ec777fe5 100644 --- a/Clover/app/src/main/res/values/strings.xml +++ b/Clover/app/src/main/res/values/strings.xml @@ -118,10 +118,45 @@ along with this program. If not, see . Sort A-Z Enabled + Filter + Action Pattern + Test pattern + Exact text + Pattern Boards all - Hide + Hide post + Highlight + Invalid pattern + Test your filter + Does not match + Matches + Pick color + Filter help + + "Filters act on a given pattern and a place to search the pattern.<br> +If the pattern matches then the post can be hidden or highlighted.<br> + +<h4>For tripcodes, names and IDs:</h4> +<p> + It will match the given pattern exact.<br> + <tt>!Ep8pui8Vw2</tt> will match the tripcode <i>!Ep8pui8Vw2</i> but not <i>Ep8pu</i>. +</p> + +<h4>For comments, subjects and filenames:</h4> +<p> + These filters are pattern based, and have three modes:<br> + <br> + 1. The pattern <tt>foo bar</tt> will match text that has any of the words in it. It will match <i>foo</i> or <i>bar</i>, but not <i>foobar</i>. + Placing a * allows any character to be filled in: <tt>f*o</tt> will match both <i>foo</i>, <i>foooo</i> but not <i>foobar</i><br> + <br> + 2. Quoting your pattern with <tt>\"</tt> like <tt>\"foo bar\"</tt> will match the text exact. + <i>foo bar</i> matches but <i>foo</i> does not.<br> + <br> + 3. Regular expressions. <tt>/^>implying/</tt> for example. +</p>" + Tripcode Name