diff --git a/app/src/main/java/com/mogo/launcher/lancet/ViewPressedStateLancet.java b/app/src/main/java/com/mogo/launcher/lancet/ViewPressedStateLancet.java new file mode 100644 index 0000000000..3ebd92187f --- /dev/null +++ b/app/src/main/java/com/mogo/launcher/lancet/ViewPressedStateLancet.java @@ -0,0 +1,191 @@ +package com.mogo.launcher.lancet; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.StateListDrawable; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.core.view.ViewCompat; +import com.knightboost.lancet.api.Origin; +import com.knightboost.lancet.api.Scope; +import com.knightboost.lancet.api.annotations.Group; +import com.knightboost.lancet.api.annotations.ImplementedInterface; +import com.knightboost.lancet.api.annotations.Insert; +import com.knightboost.lancet.api.annotations.ReplaceInvoke; +import com.knightboost.lancet.api.annotations.TargetClass; +import com.knightboost.lancet.api.annotations.TargetMethod; +import com.knightboost.lancet.api.annotations.Weaver; +import com.mogo.launcher.R; + +@Weaver +@Group("view_pressed_state") +public class ViewPressedStateLancet { + + @TargetClass(value = "android.view.View", scope = Scope.ALL) + @TargetMethod(methodName = "setOnClickListener") + @ReplaceInvoke + public static void setOnClickListener(View view, View.OnClickListener listener) { + Object tag = view.getTag(R.id.click_pressed_attr_id); + checkSetBgIfNeed(view, tag == null ? null : (AttributeSet) tag); + view.setOnClickListener(listener); + } + + @TargetClass(value = "android.view.View", scope = Scope.ALL) + @TargetMethod(methodName = "setOnLongClickListener") + @ReplaceInvoke + public static void setOnLongClickListener(View view, View.OnLongClickListener listener) { + Object tag = view.getTag(R.id.click_pressed_attr_id); + checkSetBgIfNeed(view, tag == null ? null : (AttributeSet) tag); + view.setOnLongClickListener(listener); + } + + @ImplementedInterface(value = "android.view.LayoutInflater$Factory", scope = Scope.LEAF) + @Insert(mayCreateSuper = true) + @TargetMethod(methodName = "onCreateView") + public View onCreateView(String name, Context context, AttributeSet attrs) { + View view = (View) Origin.call(); + checkSetBgIfNeed(view, attrs); + return view; + } + + @ImplementedInterface(value = "android.view.LayoutInflater$Factory2", scope = Scope.LEAF) + @Insert(mayCreateSuper = true) + @TargetMethod(methodName = "onCreateView") + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { + View view = (View) Origin.call(); + checkSetBgIfNeed(view, attrs); + return view; + } + + private static void checkSetBgIfNeed(View view, AttributeSet attr) { + try { + TypedArray t = null; + float alpha; + boolean enabled; + try { + if (view == null) { + return; + } + Context context = view.getContext(); + if (context == null) { + return; + } + if (attr == null) { + return; + } + t = context.obtainStyledAttributes(attr, R.styleable.ClickPressedStyle); + enabled = t.getBoolean(R.styleable.ClickPressedStyle_pressed_enabled, true); + if (!enabled) { + return; + } + alpha = t.getFloat(R.styleable.ClickPressedStyle_pressed_alpha, 0.6f); + if (alpha > 1.0f) { + throw new AssertionError("view custom attr: alpha > 1.0f, must be in [0.0, 1.0]"); + } + view.setTag(R.id.click_pressed_attr_id, attr); + } finally { + if (t != null) { + t.recycle(); + } + } + if (view instanceof ImageView) { + ImageView image = (ImageView) view; + Drawable replaced = checkAndReplaceDrawable(image.getBackground(), alpha); + if (replaced != null) { + ViewCompat.setBackground(image, replaced); + return; + } + replaced = checkAndReplaceDrawable(image.getDrawable(), alpha); + if (replaced != null) { + image.setImageDrawable(replaced); + return; + } + } + if (view instanceof TextView) { + TextView text = (TextView) view; + Drawable replaced = checkAndReplaceDrawable(text.getBackground(), alpha); + if (replaced != null) { + ViewCompat.setBackground(text, replaced); + return; + } + ColorStateList textColor = text.getTextColors(); + if (textColor != null) { + int pressed = textColor.getColorForState(new int[]{ android.R.attr.state_pressed }, Integer.MIN_VALUE); + if (pressed != Integer.MIN_VALUE) { + return; + } + int defaultColor = textColor.getDefaultColor(); + int pressedColor = Color.argb((int)(Color.alpha(defaultColor) * alpha), Color.red(defaultColor), Color.green(defaultColor), Color.blue(defaultColor)); + ColorStateList newColor = new ColorStateList(new int[][] { new int[] { -android.R.attr.state_pressed }, new int[] { android.R.attr.state_pressed }}, new int[] { defaultColor, pressedColor }); + text.setTextColor(newColor); + } + } + } catch (Throwable t) { + t.printStackTrace(); + } + } + + private static Drawable checkAndReplaceDrawable(Drawable old, float alpha) { + if (old == null) { + return null; + } + if (old instanceof StateListDrawable) { + StateListDrawable drawable = (StateListDrawable) old; + int[] states = drawable.getState(); + boolean hasPressed = false; + int index = 0; + int targetIndex = -1; + for (int state : states) { + if (!hasPressed && state == android.R.attr.state_pressed) { + hasPressed = true; + } + if (targetIndex == -1 && state != -android.R.attr.state_enabled) { + targetIndex = index; + } + if (hasPressed && targetIndex != -1) { + break; + } + index ++; + } + if (hasPressed) { + return null; + } + if (targetIndex != -1) { + Drawable origin= null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + origin = drawable.getStateDrawable(targetIndex); + } else { + origin = drawable.getCurrent(); + } + if (origin != null) { + Drawable.ConstantState constantState = origin.getConstantState(); + if (constantState != null) { + Drawable pressed = DrawableCompat.wrap(constantState.newDrawable().mutate()); + pressed.setAlpha((int)(255 * alpha)); + drawable.addState(new int[] {android.R.attr.state_pressed}, pressed); + return drawable; + } + } + return null; + } + return null; + } + Drawable.ConstantState constantState = old.getConstantState(); + if (constantState != null) { + StateListDrawable result = new StateListDrawable(); + result.addState(new int[] { -android.R.attr.state_pressed }, old); + Drawable pressed = DrawableCompat.wrap(constantState.newDrawable().mutate()); + pressed.setAlpha((int)(255 * alpha)); + result.addState(new int[] { android.R.attr.state_pressed }, pressed); + return result; + } + return null; + } +} diff --git a/core/mogo-core-res/src/main/res/values/ids.xml b/core/mogo-core-res/src/main/res/values/ids.xml new file mode 100644 index 0000000000..fc995c989e --- /dev/null +++ b/core/mogo-core-res/src/main/res/values/ids.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core/mogo-core-res/src/main/res/values/styles.xml b/core/mogo-core-res/src/main/res/values/styles.xml index d983a865a0..e6496d2029 100644 --- a/core/mogo-core-res/src/main/res/values/styles.xml +++ b/core/mogo-core-res/src/main/res/values/styles.xml @@ -13,4 +13,8 @@ + + + + \ No newline at end of file diff --git a/gradle/bytex/bytex_lancetx.gradle b/gradle/bytex/bytex_lancetx.gradle index d4cbbd960e..9f9ed05765 100644 --- a/gradle/bytex/bytex_lancetx.gradle +++ b/gradle/bytex/bytex_lancetx.gradle @@ -30,6 +30,9 @@ LancetX { main_block_check { enable rootProject.isJunkDetectEnable() } + view_pressed_state { + enable true + } } }