From 1e205d268399fb55e6d31cf74fef36d2d98734c4 Mon Sep 17 00:00:00 2001 From: Dev4Mod Date: Fri, 24 May 2024 11:57:52 -0300 Subject: [PATCH] Improve CSS theme performance --- .../features/customization/CustomView.java | 430 +++++++++--------- changelog.txt | 4 +- 2 files changed, 228 insertions(+), 206 deletions(-) diff --git a/app/src/main/java/com/wmods/wppenhacer/xposed/features/customization/CustomView.java b/app/src/main/java/com/wmods/wppenhacer/xposed/features/customization/CustomView.java index 3a140835..8588fa4b 100644 --- a/app/src/main/java/com/wmods/wppenhacer/xposed/features/customization/CustomView.java +++ b/app/src/main/java/com/wmods/wppenhacer/xposed/features/customization/CustomView.java @@ -2,17 +2,17 @@ import static com.wmods.wppenhacer.utils.ColorReplacement.replaceColors; +import android.app.Activity; import android.content.res.ColorStateList; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; @@ -33,12 +33,14 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import cz.vutbr.web.css.CSSFactory; import cz.vutbr.web.css.CombinedSelector; import cz.vutbr.web.css.Declaration; import cz.vutbr.web.css.RuleSet; +import cz.vutbr.web.css.StyleSheet; import cz.vutbr.web.css.TermColor; import cz.vutbr.web.css.TermFloatValue; import cz.vutbr.web.css.TermLength; @@ -51,7 +53,7 @@ public class CustomView extends Feature { private DrawableCache cacheImages; - private HashMap> IdObjects; + private static CompletableFuture mFuture; public CustomView(@NonNull ClassLoader loader, @NonNull XSharedPreferences preferences) { super(loader, preferences); @@ -63,11 +65,35 @@ public void doHook() throws Throwable { var filter_itens = prefs.getString("css_theme", null); if (TextUtils.isEmpty(filter_itens)) return; var sheet = CSSFactory.parseString(filter_itens, new URL("https://base.url/")); - IdObjects = new HashMap<>(); + + XposedHelpers.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + var activity = (Activity) param.thisObject; + var content = (ViewGroup) activity.findViewById(android.R.id.content); + content.getViewTreeObserver().addOnGlobalLayoutListener(() -> { + if (mFuture != null && !mFuture.isDone()) return; + mFuture = CompletableFuture.runAsync(() -> content.post(() -> registerCssRules(activity, content, sheet))); + }); + } + }); + } + + private void registerCssRules(Activity activity, ViewGroup currenView, StyleSheet sheet) { for (var selector : sheet) { var ruleSet = (RuleSet) selector; for (var selectorItem : ruleSet.getSelectors()) { - var name = selectorItem.get(0).getIDName().trim(); + var item = selectorItem.get(0); + String className; + String name; + if ((className = item.getClassName()) != null) { + className = className.replaceAll("_", ".").trim(); + var clazz = XposedHelpers.findClass(className, loader); + if (clazz == null || !clazz.isInstance(activity)) continue; + name = selectorItem.get(1).getIDName().trim(); + } else { + name = selectorItem.get(0).getIDName().trim(); + } int id = 0; if (name.contains("android_")) { try { @@ -78,230 +104,226 @@ public void doHook() throws Throwable { id = Utils.getID(name, "id"); } if (id <= 0) continue; - var rule = IdObjects.computeIfAbsent(id, k -> new ArrayList<>()); - rule.add(new RuleItem(selectorItem, ruleSet)); - } - } - XposedHelpers.findAndHookMethod(FrameLayout.class, "onMeasure", int.class, int.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - var currentView = (ViewGroup) param.thisObject; - if (currentView.getId() == android.R.id.content && (currentView.getTag() == null || currentView.getTag() != "attachCSS")) { - currentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - currentView.getViewTreeObserver().removeOnGlobalLayoutListener(this); - registerCssRules(currentView); - currentView.getViewTreeObserver().addOnGlobalLayoutListener(this); - } - }); - currentView.setTag("attachCSS"); - } + var view = currenView.findViewById(id); + if (view == null || !view.isShown() || view.getVisibility() != View.VISIBLE || !view.isAttachedToWindow()) + continue; + var ruleItem = new RuleItem(selectorItem, ruleSet); + setCssRule(view, ruleItem); } - }); - - } - - private void registerCssRules(ViewGroup currenView) { - for (var id : IdObjects.keySet()) { - var findView = currenView.findViewById(id); - if (findView == null) continue; - var rulesItens = IdObjects.get(id); - if (rulesItens == null) continue; - setCssRule(findView, rulesItens); } } - private void setCssRule(View currentView, ArrayList ruleItens) { - for (var ruleItem : ruleItens) { - var resultViews = new ArrayList(); - captureSelector(currentView, ruleItem.selector, 0, resultViews); - if (ruleItem.rule.getSelectors().length == 0 || ruleItem.rule.isEmpty() || resultViews.isEmpty()) - return; - for (var view : resultViews) { - for (var declaration : ruleItem.rule) { - var property = declaration.getProperty(); - switch (property) { - case "background-color" -> { - if (declaration.size() != 2) continue; - var color = (TermColor) declaration.get(0); - var colorNew = (TermColor) declaration.get(1); - var colorValue = IColors.toString(color.getValue().getRGB()); - var colorNewValue = IColors.toString(colorNew.getValue().getRGB()); - HashMap colors = new HashMap<>(); - colors.put(colorValue, colorNewValue); - replaceColors(view, colors); + private void setCssRule(View currentView, RuleItem ruleItem) { + var resultViews = new ArrayList(); + captureSelector(currentView, ruleItem.selector, 0, resultViews); + if (ruleItem.rule.getSelectors().length == 0 || ruleItem.rule.isEmpty() || resultViews.isEmpty()) + return; + for (var view : resultViews) { + for (var declaration : ruleItem.rule) { + var property = declaration.getProperty(); + switch (property) { + case "background-color" -> { + if (declaration.size() != 2) continue; + var color = (TermColor) declaration.get(0); + var colorNew = (TermColor) declaration.get(1); + var colorValue = IColors.toString(color.getValue().getRGB()); + var colorNewValue = IColors.toString(colorNew.getValue().getRGB()); + HashMap colors = new HashMap<>(); + colors.put(colorValue, colorNewValue); + replaceColors(view, colors); + if (view instanceof ImageView imageView) { + var drawable = imageView.getDrawable(); + if (drawable == null) continue; + drawable.setTint(colorNew.getValue().getRGB()); + } + } + case "display" -> { + var value = declaration.get(0).toString(); + switch (value) { + case "none" -> view.setVisibility(View.GONE); + case "block" -> view.setVisibility(View.VISIBLE); + case "invisible" -> view.setVisibility(View.INVISIBLE); + } + } + case "font-size" -> { + if (!(view instanceof TextView textView)) continue; + var value = (TermLength) declaration.get(0); + textView.setTextSize(getRealValue(value, 0)); + } + case "color" -> { + if (!(view instanceof TextView textView)) continue; + var value = (TermColor) declaration.get(0); + textView.setTextColor(value.getValue().getRGB()); + } + case "alpha" -> { + var value = (TermFloatValue) declaration.get(0); + view.setAlpha(value.getValue()); + } + case "background-image" -> { + var uri = (TermURI) declaration.get(0); + if (!new File(uri.getValue()).exists()) continue; + var draw = cacheImages.getDrawable(uri.getValue()); + if (view instanceof ImageView imageView) { + if (draw == imageView.getDrawable()) continue; + imageView.setImageDrawable(draw); + } else { + if (draw == view.getBackground()) continue; + view.setBackground(draw); + } + } + case "background-size" -> { + if (declaration.get(0) instanceof TermLength width) { + var height = (TermLength) declaration.get(1); if (view instanceof ImageView imageView) { + if (width.isPercentage() || height.isPercentage()) { + if (width.getValue() == 100 || height.getValue() == 100) { + imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); + continue; + } + } var drawable = imageView.getDrawable(); if (drawable == null) continue; - drawable.setTint(colorNew.getValue().getRGB()); + Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); + if (getRealValue(width, imageView.getWidth()) == bitmap.getWidth() && getRealValue(height, imageView.getHeight()) == bitmap.getHeight()) + continue; + Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, getRealValue(width, imageView.getWidth()), getRealValue(height, imageView.getHeight()), false); + drawable = new BitmapDrawable(view.getContext().getResources(), resizedBitmap); + imageView.setImageDrawable(drawable); + } else { + var drawable = view.getBackground(); + if (drawable == null) continue; + Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); + if (getRealValue(width, 0) == bitmap.getWidth() && getRealValue(height, 0) == bitmap.getHeight()) + continue; + Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, getRealValue(width, 0), getRealValue(height, 0), false); + drawable = new BitmapDrawable(view.getContext().getResources(), resizedBitmap); + view.setBackground(drawable); } - } - case "display" -> { - var value = declaration.get(0).toString(); - switch (value) { - case "none" -> view.setVisibility(View.GONE); - case "block" -> view.setVisibility(View.VISIBLE); - case "invisible" -> view.setVisibility(View.INVISIBLE); + } else { + var value = declaration.get(0).toString().trim(); + if (value.equals("cover")) { + if (view instanceof ImageView imageView) { + imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + } } } - case "font-size" -> { - if (!(view instanceof TextView textView)) return; - var value = (TermLength) declaration.get(0); - textView.setTextSize(getRealValue(value, 0)); - } - case "color" -> { - if (!(view instanceof TextView textView)) return; - var value = (TermColor) declaration.get(0); - textView.setTextColor(value.getValue().getRGB()); - } - case "alpha" -> { - var value = (TermFloatValue) declaration.get(0); - view.setAlpha(value.getValue()); + } + case "background" -> { + + if (declaration.get(0) instanceof TermColor color) { + view.setBackground(new ColorDrawable(color.getValue().getRGB())); + continue; } - case "background-image" -> { - var uri = (TermURI) declaration.get(0); + + if (declaration.get(0) instanceof TermURI uri) { if (!new File(uri.getValue()).exists()) continue; var draw = cacheImages.getDrawable(uri.getValue()); - if (view instanceof ImageView imageView) { - imageView.setImageDrawable(draw); - } else { - view.setBackground(draw); - } + view.setBackground(draw); + continue; } - case "background-size" -> { - if (declaration.get(0) instanceof TermLength width) { - var height = (TermLength) declaration.get(1); - if (view instanceof ImageView imageView) { - if (width.isPercentage() || height.isPercentage()) { - if (width.getValue() == 100 || height.getValue() == 100) { - imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); - continue; - } - } - var drawable = imageView.getDrawable(); - if (drawable == null) continue; - Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); - Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, getRealValue(width, imageView.getWidth()), getRealValue(height, imageView.getHeight()), false); - drawable = new BitmapDrawable(view.getContext().getResources(), resizedBitmap); - imageView.setImageDrawable(drawable); - } else { - var drawable = view.getBackground(); - if (drawable == null) continue; - Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); - Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, getRealValue(width, 0), getRealValue(height, 0), false); - drawable = new BitmapDrawable(view.getContext().getResources(), resizedBitmap); - view.setBackground(drawable); - } - } else { - var value = declaration.get(0).toString().trim(); - if (value.equals("cover")) { - if (view instanceof ImageView imageView) { - imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); - } - } - } + + var value = declaration.get(0).toString().trim(); + if (value.equals("none")) { + view.setBackground(null); } - case "background" -> { - if (declaration.get(0) instanceof TermColor uri) { - view.setBackground(new ColorDrawable(uri.getValue().getRGB())); - continue; - } - var value = declaration.get(0).toString().trim(); - switch (value) { - case "none" -> view.setBackground(null); - case "transparent" -> view.setBackgroundColor(0); - } + } + case "foreground" -> { + if (declaration.get(0) instanceof TermColor color) { + view.setBackground(new ColorDrawable(color.getValue().getRGB())); + continue; } - case "foreground" -> { - var value = declaration.get(0).toString().trim(); - switch (value) { - case "none" -> view.setForeground(null); - case "transparent" -> view.setForeground(new ColorDrawable(0)); - } + + if (declaration.get(0) instanceof TermURI uri) { + if (!new File(uri.getValue()).exists()) continue; + var draw = cacheImages.getDrawable(uri.getValue()); + view.setBackground(draw); + continue; } - case "width" -> { - var value = (TermLength) declaration.get(0); - view.getLayoutParams().width = getRealValue(value, 0); - view.requestLayout(); + var value = declaration.get(0).toString().trim(); + if (value.equals("none")) { + view.setForeground(null); } - case "height" -> { - var value = (TermLength) declaration.get(0); - view.getLayoutParams().height = getRealValue(value, 0) - ; + } + case "width" -> { + var value = (TermLength) declaration.get(0); + view.getLayoutParams().width = getRealValue(value, 0); + view.requestLayout(); + } + case "height" -> { + var value = (TermLength) declaration.get(0); + view.getLayoutParams().height = getRealValue(value, 0) + ; + } + case "left" -> { + var value = (TermLength) declaration.get(0); + var layoutParams = view.getLayoutParams(); + if (layoutParams instanceof RelativeLayout.LayoutParams rParams) { + rParams.addRule(RelativeLayout.ALIGN_LEFT, getRealValue(value, 0)); + } else if (layoutParams instanceof ViewGroup.MarginLayoutParams fParams) { + fParams.leftMargin = getRealValue(value, 0); } - case "left" -> { - var value = (TermLength) declaration.get(0); - var layoutParams = view.getLayoutParams(); - if (layoutParams instanceof RelativeLayout.LayoutParams rParams) { - rParams.addRule(RelativeLayout.ALIGN_LEFT, getRealValue(value, 0)); - } else if (layoutParams instanceof FrameLayout.LayoutParams fParams) { - fParams.leftMargin = getRealValue(value, 0); - } + } + case "right" -> { + var value = (TermLength) declaration.get(0); + var layoutParams = view.getLayoutParams(); + if (layoutParams instanceof RelativeLayout.LayoutParams rParams) { + rParams.addRule(RelativeLayout.ALIGN_RIGHT, getRealValue(value, 0)); + } else if (layoutParams instanceof ViewGroup.MarginLayoutParams fParams) { + fParams.rightMargin = getRealValue(value, 0); } - case "right" -> { - var value = (TermLength) declaration.get(0); - var layoutParams = view.getLayoutParams(); - if (layoutParams instanceof RelativeLayout.LayoutParams rParams) { - rParams.addRule(RelativeLayout.ALIGN_RIGHT, getRealValue(value, 0)); - } else if (layoutParams instanceof FrameLayout.LayoutParams fParams) { - fParams.rightMargin = getRealValue(value, 0); - } + } + case "top" -> { + var value = (TermLength) declaration.get(0); + var layoutParams = view.getLayoutParams(); + if (layoutParams instanceof RelativeLayout.LayoutParams rParams) { + rParams.addRule(RelativeLayout.ALIGN_TOP, getRealValue(value, 0)); + } else if (layoutParams instanceof ViewGroup.MarginLayoutParams fParams) { + fParams.topMargin = getRealValue(value, 0); } - case "top" -> { - var value = (TermLength) declaration.get(0); - var layoutParams = view.getLayoutParams(); - if (layoutParams instanceof RelativeLayout.LayoutParams rParams) { - rParams.addRule(RelativeLayout.ALIGN_TOP, getRealValue(value, 0)); - } else if (layoutParams instanceof FrameLayout.LayoutParams fParams) { - fParams.topMargin = getRealValue(value, 0); - } + } + case "bottom" -> { + var value = (TermLength) declaration.get(0); + var layoutParams = view.getLayoutParams(); + if (layoutParams instanceof RelativeLayout.LayoutParams rParams) { + rParams.addRule(RelativeLayout.ALIGN_BOTTOM, getRealValue(value, 0)); + } else if (layoutParams instanceof ViewGroup.MarginLayoutParams fParams) { + fParams.bottomMargin = getRealValue(value, 0); } - case "bottom" -> { - var value = (TermLength) declaration.get(0); - var layoutParams = view.getLayoutParams(); - if (layoutParams instanceof RelativeLayout.LayoutParams rParams) { - rParams.addRule(RelativeLayout.ALIGN_BOTTOM, getRealValue(value, 0)); - } else if (layoutParams instanceof FrameLayout.LayoutParams fParams) { - fParams.bottomMargin = getRealValue(value, 0); - } + } + case "color-filter" -> { + if (!(view instanceof ImageView imageView)) continue; + var mode = declaration.get(0).toString().trim(); + if (mode.equals("none")) { + imageView.clearColorFilter(); + continue; } - case "color-filter" -> { - if (!(view instanceof ImageView imageView)) continue; - var mode = declaration.get(0).toString().trim(); - if (mode.equals("none")) { - imageView.clearColorFilter(); - continue; - } - var color = (TermColor) declaration.get(1); - try { - var pMode = PorterDuff.Mode.valueOf(mode); - imageView.setColorFilter(color.getValue().getRGB(), pMode); - } catch (Exception ignored) { - } + var color = (TermColor) declaration.get(1); + try { + var pMode = PorterDuff.Mode.valueOf(mode); + imageView.setColorFilter(color.getValue().getRGB(), pMode); + } catch (Exception ignored) { } - case "color-tint" -> { - if (!(view instanceof ImageView imageView)) continue; - if (declaration.get(0) instanceof TermColor color) { - if (declaration.size() == 1) { - imageView.setImageTintList(ColorStateList.valueOf(color.getValue().getRGB())); - } else if (declaration.size() == 3) { - ColorStateList themeColorStateList = getColorStateList(declaration); - imageView.setImageTintList(themeColorStateList); - } - } else { - var value = declaration.get(0).toString().trim(); - switch (value) { - case "none" -> imageView.setImageTintList(null); - case "transparent" -> - imageView.setImageTintList(ColorStateList.valueOf(0)); - } + } + case "color-tint" -> { + if (!(view instanceof ImageView imageView)) continue; + if (declaration.get(0) instanceof TermColor color) { + if (declaration.size() == 1) { + imageView.setImageTintList(ColorStateList.valueOf(color.getValue().getRGB())); + } else if (declaration.size() == 3) { + ColorStateList themeColorStateList = getColorStateList(declaration); + imageView.setImageTintList(themeColorStateList); + } + } else { + var value = declaration.get(0).toString().trim(); + switch (value) { + case "none" -> imageView.setImageTintList(null); + case "transparent" -> + imageView.setImageTintList(ColorStateList.valueOf(0)); } } } } } - currentView.invalidate(); } } @@ -341,7 +363,9 @@ private int getRealValue(TermLength value, int size) { private void captureSelector(View currentView, CombinedSelector selector, int position, ArrayList resultViews) { if (selector.size() == position) return; var selectorItem = selector.get(position); - if (selectorItem.getIDName() != null) { + if (selectorItem.getClassName() != null) { + captureSelector(currentView, selector, position + 1, resultViews); + } else if (selectorItem.getIDName() != null) { var name = selectorItem.getIDName().trim(); int id = 0; if (name.contains("android_")) { @@ -438,8 +462,8 @@ public static class RuleItem { public RuleSet rule; public RuleItem(CombinedSelector selectorItem, RuleSet ruleSet) { - selector = selectorItem; - rule = ruleSet; + this.selector = selectorItem; + this.rule = ruleSet; } } diff --git a/changelog.txt b/changelog.txt index 339fc860..2d1ff3cd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1 @@ -* Improve CSS Theme: added "color","color-filter","color-tint" -* Add function Anti Disappearing Messages -* Update translations \ No newline at end of file +* Improve CSS theme performance \ No newline at end of file