From 207b92dae0e6a3078e3875e033ead4b44a0ee723 Mon Sep 17 00:00:00 2001 From: Hicores Date: Tue, 12 Sep 2023 23:49:03 +0800 Subject: [PATCH] refactor: update stickerPanel and ReflectUtils Signed-off-by: Hicores --- .../main/java/cc/hicore/QApp/QAppUtils.java | 18 +- .../java/cc/hicore/ReflectUtil/MClass.java | 133 --- .../java/cc/hicore/ReflectUtil/MField.java | 147 --- .../java/cc/hicore/ReflectUtil/MMethod.java | 8 +- .../java/cc/hicore/ReflectUtil/XClass.java | 73 ++ .../java/cc/hicore/ReflectUtil/XField.java | 202 +++++ .../java/cc/hicore/ReflectUtil/XMethod.java | 168 ++++ app/src/main/java/cc/hicore/Utils/Async.java | 172 ++++ .../main/java/cc/hicore/Utils/FileUtils.java | 8 +- .../main/java/cc/hicore/Utils/FunConf.java | 58 ++ .../main/java/cc/hicore/Utils/ImageUtils.java | 57 ++ .../main/java/cc/hicore/hook/Repeater.java | 22 +- .../java/cc/hicore/hook/RepeaterHelper.java | 13 +- .../java/cc/hicore/hook/RepeaterPlus.java | 11 +- .../cc/hicore/hook/UnlockLeftSlipLimit.java | 5 +- .../hook/stickerPanel/EmoOnlineLoader.java | 23 + .../cc/hicore/hook/stickerPanel/EmoPanel.java | 2 + .../Hooker/StickerPanelEntryHooker.java | 71 +- .../cc/hicore/hook/stickerPanel/ICreator.java | 201 +++-- .../hook/stickerPanel/LocalDataHelper.java | 111 ++- .../MainItemImpl/InputFromLocalImpl.java | 235 +++-- .../MainItemImpl/LocalStickerImpl.java | 265 +++--- .../MainItemImpl/OnlinePreviewItemImpl.java | 4 + .../MainItemImpl/PanelSetImpl.java | 146 +++ .../MainItemImpl/RecentStickerImpl.java | 85 +- .../hook/stickerPanel/MainPanelAdapter.java | 72 -- .../hicore/hook/stickerPanel/PanelUtils.java | 45 +- .../hook/stickerPanel/QQGifDecoder.java | 138 +++ .../stickerPanel/RecentStickerHelper.java | 63 +- .../message/bridge/Chat_facade_bridge.java | 162 ++-- .../cc/hicore/message/chat/SessionHooker.java | 3 +- .../cc/hicore/message/chat/SessionUtils.java | 12 +- .../cc/hicore/message/common/MsgBuilder.java | 157 ++-- .../cc/hicore/message/common/MsgUtils.java | 2 +- .../java/cc/hicore/ui/SimpleDragSortView.java | 95 ++ .../cc/hicore/ui/handygridview/Child.java | 99 ++ .../cc/hicore/ui/handygridview/Children.java | 61 ++ .../ui/handygridview/HandyGridView.java | 844 ++++++++++++++++++ .../ui/handygridview/listener/IDrawer.java | 15 + .../listener/OnItemCapturedListener.java | 19 + .../handygridview/scrollrunner/ICarrier.java | 16 + .../scrollrunner/OnItemMovedListener.java | 18 + .../scrollrunner/OnceRunnable.java | 30 + .../scrollrunner/ScrollRunner.java | 93 ++ .../hicore/ui/handygridview/utils/Pools.java | 125 +++ .../ui/handygridview/utils/ReflectUtil.java | 32 + .../ui/handygridview/utils/SdkVerUtils.java | 21 + .../main/java/cc/ioctl/util/LayoutHelper.java | 8 +- .../java/io/github/qauxv/util/Toasts.java | 3 + .../res/drawable/sticker_panel_input_icon.png | Bin 0 -> 4879 bytes .../res/drawable/sticker_panel_like_icon.png | Bin 0 -> 9411 bytes .../drawable/sticker_panel_recent_icon.png | Bin 0 -> 9737 bytes .../res/drawable/sticker_panel_round_bg.xml | 6 + .../sticker_panen_set_button_icon.png | Bin 0 -> 10613 bytes .../sticker_panel_impl_input_from_local.xml | 19 +- .../res/layout/sticker_panel_plus_main.xml | 16 +- .../layout/sticker_panel_plus_pack_item.xml | 4 +- app/src/main/res/layout/sticker_panel_set.xml | 82 ++ app/src/main/res/layout/sticker_pre_save.xml | 16 +- .../main/res/layout/sticker_share_root.xml | 16 + app/src/main/res/values-night/colors.xml | 9 + app/src/main/res/values/colors.xml | 9 + 62 files changed, 3498 insertions(+), 1050 deletions(-) delete mode 100644 app/src/main/java/cc/hicore/ReflectUtil/MClass.java create mode 100644 app/src/main/java/cc/hicore/ReflectUtil/XClass.java create mode 100644 app/src/main/java/cc/hicore/ReflectUtil/XField.java create mode 100644 app/src/main/java/cc/hicore/ReflectUtil/XMethod.java create mode 100644 app/src/main/java/cc/hicore/Utils/Async.java create mode 100644 app/src/main/java/cc/hicore/Utils/FunConf.java create mode 100644 app/src/main/java/cc/hicore/Utils/ImageUtils.java create mode 100644 app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/OnlinePreviewItemImpl.java create mode 100644 app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/PanelSetImpl.java delete mode 100644 app/src/main/java/cc/hicore/hook/stickerPanel/MainPanelAdapter.java create mode 100644 app/src/main/java/cc/hicore/hook/stickerPanel/QQGifDecoder.java create mode 100644 app/src/main/java/cc/hicore/ui/SimpleDragSortView.java create mode 100644 app/src/main/java/cc/hicore/ui/handygridview/Child.java create mode 100644 app/src/main/java/cc/hicore/ui/handygridview/Children.java create mode 100644 app/src/main/java/cc/hicore/ui/handygridview/HandyGridView.java create mode 100644 app/src/main/java/cc/hicore/ui/handygridview/listener/IDrawer.java create mode 100644 app/src/main/java/cc/hicore/ui/handygridview/listener/OnItemCapturedListener.java create mode 100644 app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/ICarrier.java create mode 100644 app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/OnItemMovedListener.java create mode 100644 app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/OnceRunnable.java create mode 100644 app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/ScrollRunner.java create mode 100644 app/src/main/java/cc/hicore/ui/handygridview/utils/Pools.java create mode 100644 app/src/main/java/cc/hicore/ui/handygridview/utils/ReflectUtil.java create mode 100644 app/src/main/java/cc/hicore/ui/handygridview/utils/SdkVerUtils.java create mode 100644 app/src/main/res/drawable/sticker_panel_input_icon.png create mode 100644 app/src/main/res/drawable/sticker_panel_like_icon.png create mode 100644 app/src/main/res/drawable/sticker_panel_recent_icon.png create mode 100644 app/src/main/res/drawable/sticker_panel_round_bg.xml create mode 100644 app/src/main/res/drawable/sticker_panen_set_button_icon.png create mode 100644 app/src/main/res/layout/sticker_panel_set.xml create mode 100644 app/src/main/res/layout/sticker_share_root.xml diff --git a/app/src/main/java/cc/hicore/QApp/QAppUtils.java b/app/src/main/java/cc/hicore/QApp/QAppUtils.java index 238e10d5db..59ed380cc0 100644 --- a/app/src/main/java/cc/hicore/QApp/QAppUtils.java +++ b/app/src/main/java/cc/hicore/QApp/QAppUtils.java @@ -21,22 +21,22 @@ package cc.hicore.QApp; -import cc.hicore.ReflectUtil.MClass; -import cc.hicore.ReflectUtil.MMethod; +import cc.hicore.ReflectUtil.XClass; +import cc.hicore.ReflectUtil.XMethod; import io.github.qauxv.util.Initiator; public class QAppUtils { public static long getServiceTime(){ try { - return MMethod.CallStaticMethod(MClass.loadClass("com.tencent.mobileqq.msf.core.NetConnInfoCenter"),"getServerTimeMillis",long.class); + return XMethod.clz("com.tencent.mobileqq.msf.core.NetConnInfoCenter").name("getServerTimeMillis").ret(long.class).invoke(); } catch (Exception e) { return 0; } } public static String UserUinToPeerID(String UserUin){ try { - Object convertHelper = MClass.NewInstance(MClass.loadClass("com.tencent.qqnt.kernel.api.impl.UixConvertAdapterApiImpl")); - return MMethod.CallMethod(convertHelper,"getUidFromUin",String.class,new Class[]{long.class},Long.parseLong(UserUin)); + Object convertHelper = XClass.newInstance(Initiator.loadClass("com.tencent.qqnt.kernel.api.impl.UixConvertAdapterApiImpl")); + return XMethod.obj(convertHelper).name("getUidFromUin").ret(String.class).param(long.class).invoke(Long.parseLong(UserUin)); }catch (Exception e){ return ""; } @@ -52,15 +52,13 @@ public static boolean isQQnt(){ public static String getCurrentUin(){ try { Object AppRuntime = getAppRuntime(); - return MMethod.CallMethodNoParam(AppRuntime, "getCurrentAccountUin", String.class); + return XMethod.obj(AppRuntime).name("getCurrentAccountUin").ret(String.class).invoke(); } catch (Exception e) { return ""; } } public static Object getAppRuntime() throws Exception { - Object sApplication = MMethod.CallStaticMethod(MClass.loadClass("com.tencent.common.app.BaseApplicationImpl"), - "getApplication", MClass.loadClass("com.tencent.common.app.BaseApplicationImpl")); - - return MMethod.CallMethodNoParam(sApplication, "getRuntime", MClass.loadClass("mqq.app.AppRuntime")); + Object sApplication = XMethod.clz("com.tencent.common.app.BaseApplicationImpl").name("getApplication").ret(Initiator.load("com.tencent.common.app.BaseApplicationImpl")).invoke(); + return XMethod.obj(sApplication).name("getRuntime").ret(Initiator.loadClass("mqq.app.AppRuntime")).invoke(); } } diff --git a/app/src/main/java/cc/hicore/ReflectUtil/MClass.java b/app/src/main/java/cc/hicore/ReflectUtil/MClass.java deleted file mode 100644 index bae043a59b..0000000000 --- a/app/src/main/java/cc/hicore/ReflectUtil/MClass.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * QAuxiliary - An Xposed module for QQ/TIM - * Copyright (C) 2019-2023 QAuxiliary developers - * https://github.com/cinit/QAuxiliary - * - * This software is non-free but opensource software: you can redistribute it - * and/or modify it under the terms of the qwq233 Universal License - * as published on https://github.com/qwq233/license; either - * version 2 of the License, or any later version and our EULA as published - * by QAuxiliary contributors. - * - * This software 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 qwq233 Universal License for more details. - * - * See - * - * . - */ - -package cc.hicore.ReflectUtil; - -import io.github.qauxv.util.Initiator; -import java.lang.reflect.Constructor; -import java.util.HashMap; - -public class MClass { - - private static final HashMap> clzMap = new HashMap<>(); - - public static Class loadClass(String ClassName) { - Class clz = clzMap.get(ClassName); - if (clz != null) { - return clz; - } - try { - clz = Initiator.loadClass(ClassName); - clzMap.put(ClassName, clz); - return clz; - } catch (Throwable e) { - return null; - } - } - - public static Constructor findCons(Class clz, Class[] paramTypes) { - Loop: - for (Constructor con : clz.getDeclaredConstructors()) { - Class[] CheckParam = con.getParameterTypes(); - if (CheckParam.length != paramTypes.length) { - continue; - } - for (int i = 0; i < paramTypes.length; i++) { - if (!CheckClass(CheckParam[i], paramTypes[i])) { - continue Loop; - } - } - con.setAccessible(true); - return con; - } - return null; - } - - public static T NewInstance(Class clz, Class[] paramTypes, Object... params) throws Exception { - Loop: - for (Constructor con : clz.getDeclaredConstructors()) { - Class[] CheckParam = con.getParameterTypes(); - if (CheckParam.length != paramTypes.length) { - continue; - } - for (int i = 0; i < paramTypes.length; i++) { - if (!CheckClass(CheckParam[i], paramTypes[i])) { - continue Loop; - } - } - con.setAccessible(true); - return (T) con.newInstance(params); - } - throw new RuntimeException("No Instance for " + clz); - } - - public static T NewInstance(Class clz, Object... params) throws Exception { - Class[] paramTypes = new Class[params.length]; - for (int i = 0; i < params.length; i++) { - paramTypes[i] = params[i].getClass(); - } - return NewInstance(clz, paramTypes, params); - } - - public static boolean CheckClass(Class clz, Class convert) { - if (clz.equals(convert)) { - return true; - } - if (clz.equals(hasType(convert))) { - return true; - } - return clz.isAssignableFrom(convert); - } - - private static Class hasType(Class clz) { - try { - - if (clz.equals(Boolean.class)) { - return boolean.class; - } - if (clz.equals(Integer.class)) { - return int.class; - } - if (clz.equals(Long.class)) { - return long.class; - } - if (clz.equals(Byte.class)) { - return byte.class; - } - if (clz.equals(Short.class)) { - return short.class; - } - if (clz.equals(Float.class)) { - return float.class; - } - if (clz.equals(Double.class)) { - return double.class; - } - if (clz.equals(Character.class)) { - return char.class; - } - return null; - } catch (Exception e) { - return null; - } - } - -} diff --git a/app/src/main/java/cc/hicore/ReflectUtil/MField.java b/app/src/main/java/cc/hicore/ReflectUtil/MField.java index f84a5dddf2..13209f41c4 100644 --- a/app/src/main/java/cc/hicore/ReflectUtil/MField.java +++ b/app/src/main/java/cc/hicore/ReflectUtil/MField.java @@ -26,41 +26,6 @@ public class MField { - private static final HashMap FieldCache = new HashMap<>(); - - public static void SetField(Object CheckObj, String FieldName, Object Value) throws Exception { - SetField(CheckObj, CheckObj.getClass(), FieldName, Value.getClass(), Value); - } - - public static void SetField(Object CheckObj, String FieldName, Class CheckClass, Object Value) throws Exception { - SetField(CheckObj, CheckObj.getClass(), FieldName, CheckClass, Value); - } - - public static T GetField(Object CheckObj, String FieldName) throws Exception { - Class clz = CheckObj.getClass(); - String SignText = clz.getName() + ":" + FieldName; - if (FieldCache.containsKey(SignText)) { - Field f = FieldCache.get(SignText); - return (T) f.get(CheckObj); - } - Class Check = clz; - while (Check != null) { - for (Field f : Check.getDeclaredFields()) { - if (f.getName().equals(FieldName)) { - f.setAccessible(true); - FieldCache.put(SignText, f); - return (T) f.get(CheckObj); - } - } - Check = Check.getSuperclass(); - } - throw new RuntimeException("Can't find field " + FieldName + " in class " + clz.getName()); - } - - public static T GetField(Object CheckObj, String FieldName, Class FieldType) throws Exception { - return GetField(CheckObj, CheckObj.getClass(), FieldName, FieldType); - } - public static T GetStaticField(Class clz, String FieldName) { try { Class checkClz = clz; @@ -77,116 +42,4 @@ public static T GetStaticField(Class clz, String FieldName) { } throw new RuntimeException("Can't find field " + FieldName + " in class " + clz); } - - public static void SetField(Object CheckObj, Class CheckClass, String FieldName, Class FieldClass, Object Value) throws Exception { - String SignText = CheckClass.getName() + ":" + FieldName + "(" + FieldClass.getName() + ")"; - if (FieldCache.containsKey(SignText)) { - Field f = FieldCache.get(SignText); - f.set(CheckObj, Value); - return; - } - - Class Check = CheckClass; - while (Check != null) { - for (Field f : Check.getDeclaredFields()) { - if (f.getName().equals(FieldName)) { - if (MClass.CheckClass(f.getType(), FieldClass)) { - f.setAccessible(true); - FieldCache.put(SignText, f); - f.set(CheckObj, Value); - return; - } - } - } - Check = Check.getSuperclass(); - } - throw new RuntimeException("Can't find field " + FieldName + "(" + FieldClass.getName() + ") in class " + CheckClass.getName()); - } - - public static T GetField(Object CheckObj, Class CheckClass, String FieldName, Class FieldClass) throws Exception { - String SignText = CheckClass.getName() + ":" + FieldName + "(" + FieldClass.getName() + ")"; - if (FieldCache.containsKey(SignText)) { - Field f = FieldCache.get(SignText); - return (T) f.get(CheckObj); - } - - Class Check = CheckClass; - while (Check != null) { - for (Field f : Check.getDeclaredFields()) { - if (f.getName().equals(FieldName)) { - if (MClass.CheckClass(f.getType(), FieldClass)) { - f.setAccessible(true); - FieldCache.put(SignText, f); - return (T) f.get(CheckObj); - } - } - } - Check = Check.getSuperclass(); - } - throw new RuntimeException("Can't find field " + FieldName + "(" + FieldClass.getName() + ") in class " + CheckClass.getName()); - } - - public static T GetFirstField(Object CheckObj, Class CheckClass, Class FieldClass) throws Exception { - String SignText = CheckClass.getName() + ":!NoName!" + "(" + FieldClass.getName() + ")"; - if (FieldCache.containsKey(SignText)) { - Field f = FieldCache.get(SignText); - return (T) f.get(CheckObj); - } - - Class Check = CheckClass; - while (Check != null) { - for (Field f : Check.getDeclaredFields()) { - if (FieldClass == f.getType()) { - f.setAccessible(true); - FieldCache.put(SignText, f); - return (T) f.get(CheckObj); - } - } - Check = Check.getSuperclass(); - } - throw new RuntimeException("Can't find field " + "(" + FieldClass.getName() + ") in class " + CheckClass.getName()); - } - - public static T GetRoundField(Object CheckObj, Class CheckClass, Class FieldClass, int Round) throws Exception { - int pos = 0; - String SignText = CheckClass.getName() + ":!NoName!" + "(" + FieldClass.getName() + ")" + Round; - if (FieldCache.containsKey(SignText)) { - Field f = FieldCache.get(SignText); - return (T) f.get(CheckObj); - } - - Class Check = CheckClass; - while (Check != null) { - for (Field f : Check.getDeclaredFields()) { - if (MClass.CheckClass(f.getType(), FieldClass)) { - if (pos != Round) { - pos++; - continue; - } - f.setAccessible(true); - FieldCache.put(SignText, f); - return (T) f.get(CheckObj); - } - } - Check = Check.getSuperclass(); - } - throw new RuntimeException("Can't find field " + "(" + FieldClass.getName() + ") in class " + CheckClass.getName()); - } - - public static T GetFirstField(Object CheckObj, Class FieldClass) throws Exception { - return GetFirstField(CheckObj, CheckObj.getClass(), FieldClass); - } - - public static Field FindField(Class ObjClass, String FieldName, Class FieldType) { - Class FindClass = ObjClass; - while (FindClass != null) { - for (Field f : FindClass.getDeclaredFields()) { - if (f.getName().equals(FieldName) && f.getType().equals(FieldType)) { - return f; - } - } - FindClass = FindClass.getSuperclass(); - } - return null; - } } diff --git a/app/src/main/java/cc/hicore/ReflectUtil/MMethod.java b/app/src/main/java/cc/hicore/ReflectUtil/MMethod.java index 3fa7191d78..77ff0c4b82 100644 --- a/app/src/main/java/cc/hicore/ReflectUtil/MMethod.java +++ b/app/src/main/java/cc/hicore/ReflectUtil/MMethod.java @@ -118,10 +118,6 @@ public static T CallMethod(Object obj, Class clz, String MethodName, Clas private static final HashMap MethodCache = new HashMap<>(); - public static Method FindMethod(String FindClass, String MethodName, Class ReturnType, Class[] ParamTypes) { - return FindMethod(MClass.loadClass(FindClass), MethodName, ReturnType, ParamTypes); - } - public static Method FindMethod(Class FindClass, String MethodName, Class ReturnType, Class[] ParamTypes) { if (FindClass == null) { return null; @@ -146,7 +142,7 @@ public static Method FindMethod(Class FindClass, String MethodName, Class if (params.length == ParamTypes.length) { for (int i = 0; i < params.length; i++) { - if (!MClass.CheckClass(params[i], ParamTypes[i])) { + if (!XClass.CheckClass(params[i], ParamTypes[i])) { continue Loop; } } @@ -185,7 +181,7 @@ public static Method FindMethod(Class FindClass, String MethodName, Class[ if (params.length == ParamTypes.length) { for (int i = 0; i < params.length; i++) { - if (!MClass.CheckClass(params[i], ParamTypes[i])) { + if (!XClass.CheckClass(params[i], ParamTypes[i])) { continue Loop; } } diff --git a/app/src/main/java/cc/hicore/ReflectUtil/XClass.java b/app/src/main/java/cc/hicore/ReflectUtil/XClass.java new file mode 100644 index 0000000000..8c3a5a7e61 --- /dev/null +++ b/app/src/main/java/cc/hicore/ReflectUtil/XClass.java @@ -0,0 +1,73 @@ +package cc.hicore.ReflectUtil; + +import io.github.qauxv.util.Initiator; +import java.lang.reflect.Constructor; + +public class XClass { + public static Class load(String name) throws ClassNotFoundException { + Class findResult = null; + try { + findResult = Initiator.load(name); + if (findResult == null) { + throw new ClassNotFoundException(name); + } + } catch (ClassNotFoundException ignored) { } + if (findResult == null){ + return Class.forName(name); + } + return findResult; + } + public static Class loadEx(String name){ + try { + return load(name); + }catch (Exception e){ + return null; + } + } + public static Constructor getInit(Class clz, Class... paramTypes) throws NoSuchMethodException { + Loop: + for (Constructor con : clz.getDeclaredConstructors()) { + Class[] CheckParam = con.getParameterTypes(); + if (CheckParam.length != paramTypes.length) continue; + for (int i = 0; i < paramTypes.length; i++) { + if (!CheckClass(CheckParam[i], paramTypes[i])) { + continue Loop; + } + } + con.setAccessible(true); + return con; + } + throw new NoSuchMethodException("No Instance for " + clz); + } + public static T newInstance(Class clz, Object... params) throws Exception { + Class[] paramTypes = new Class[params.length]; + for (int i = 0; i < params.length; i++) { + paramTypes[i] = params[i].getClass(); + } + return newInstance(clz, paramTypes, params); + } + public static T newInstance(Class clz, Class[] paramTypes, Object... params) throws Exception { + return (T) getInit(clz, paramTypes).newInstance(params); + } + protected static boolean CheckClass(Class clz, Class convert) { + if (clz.equals(convert)) return true; + if (clz.isAssignableFrom(hasType(convert))) return true; + return clz.isAssignableFrom(convert); + } + private static Class hasType(Class clz) { + try { + + if (clz.equals(Boolean.class)) return boolean.class; + if (clz.equals(Integer.class)) return int.class; + if (clz.equals(Long.class)) return long.class; + if (clz.equals(Byte.class)) return byte.class; + if (clz.equals(Short.class)) return short.class; + if (clz.equals(Float.class)) return float.class; + if (clz.equals(Double.class)) return double.class; + if (clz.equals(Character.class)) return char.class; + return clz; + } catch (Exception e) { + return null; + } + } +} diff --git a/app/src/main/java/cc/hicore/ReflectUtil/XField.java b/app/src/main/java/cc/hicore/ReflectUtil/XField.java new file mode 100644 index 0000000000..85deec0990 --- /dev/null +++ b/app/src/main/java/cc/hicore/ReflectUtil/XField.java @@ -0,0 +1,202 @@ +package cc.hicore.ReflectUtil; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Objects; + +public class XField { + public interface XFieldFilter { + boolean onField(Field field, TYPE value); + } + public static void getStaticFields(Class clz, XFieldFilter filter){ + Field[] fs = clz.getDeclaredFields(); + for (Field f : fs){ + f.setAccessible(true); + if (Modifier.isStatic(f.getModifiers())){ + try { + Object value = f.get(null); + filter.onField(f,value); + } catch (Exception ignored) { } + } + } + } + public static void getObjFields(Object obj, XFieldFilter filter){ + Class clz = obj.getClass(); + Field[] fs = clz.getDeclaredFields(); + for (Field f : fs){ + f.setAccessible(true); + try { + Object value = f.get(obj); + filter.onField(f,value); + } catch (Exception ignored) { } + } + } + public static void getObjFields2(Object obj,Classclz, XFieldFilter filter){ + Field[] fs = clz.getDeclaredFields(); + for (Field f : fs){ + f.setAccessible(true); + try { + Object value = f.get(obj); + filter.onField(f,value); + } catch (Exception ignored) { } + } + } + public static XFieldBuilder clz(String name){ + XFieldBuilder newBuilder = new XFieldBuilder(); + return newBuilder.clazz(XClass.loadEx(name)); + } + public static XFieldBuilder clz(Class clz){ + XFieldBuilder newBuilder = new XFieldBuilder(); + return newBuilder.clazz(clz); + } + public static XFieldBuilder obj(Object obj){ + XFieldBuilder newBuilder = new XFieldBuilder(); + return newBuilder.obj(obj); + } + public static class XFieldBuilder{ + private String name; + private Class type; + private boolean isStrictMode; + private Object obj; + private Class objClass; + private XFieldFilter filter; + private boolean onlyStatic; + private XFieldBuilder(){ + + } + public XFieldBuilder name(String name){ + this.name = name; + return this; + } + public XFieldBuilder type(Class type){ + this.type = type; + return this; + } + public XFieldBuilder clazz(Class clazz){ + this.objClass = clazz; + return this; + } + public XFieldBuilder filter(XFieldFilter filter){ + this.filter = filter; + return this; + } + public XFieldBuilder obj(Object obj){ + this.obj = obj; + return this; + } + public XFieldBuilder onlyStatic(boolean b){ + this.onlyStatic = b; + return this; + } + public XFieldBuilder strict(boolean b){ + this.isStrictMode = b; + return this; + } + public T get(){ + return doFind(); + } + public void set(T value) throws IllegalAccessException { + Class findClazz = this.objClass; + if (findClazz == null){ + findClazz = this.obj == null ? null : this.obj.getClass(); + } + if (obj == null)onlyStatic = true; + Objects.requireNonNull(findClazz,"find clz can't be null.(name = " + name + ", type = " + type + ", strict = " + isStrictMode + ")"); + Class saveSourceClass = findClazz; + while (findClazz != null){ + Field[] fields = findClazz.getDeclaredFields(); + for (Field f : fields){ + //比对基本信息 + Class f_type = f.getType(); + String f_name = f.getName(); + if (onlyStatic && !Modifier.isStatic(f.getModifiers()))continue; + if (name != null && !f_name.equals(name))continue; + if (type != null){ + if (isStrictMode){ + if (!f_type.equals(type))continue; + }else { + if (!type.isAssignableFrom(f_type)){ + if (!f_type.equals(type))continue; + } + } + } + f.setAccessible(true); + //获取信息 + Object fieldValue = null; + try { + if (Modifier.isStatic(f.getModifiers())){ + fieldValue = f.get(null); + }else { + if (obj != null){ + fieldValue = f.get(obj); + } + } + }catch (Exception ignored){ } + + //调用筛选器 + if (filter != null){ + if (!filter.onField(f, fieldValue))continue; + } + + if (Modifier.isStatic(f.getModifiers())){ + f.set(null,value); + }else { + f.set(obj,value); + } + return; + } + findClazz = findClazz.getSuperclass(); + } + throw new RuntimeException("No such field(name="+name+",type="+type+",class="+saveSourceClass+",strict="+isStrictMode+") found."); + } + public T doFind(){ + Class findClazz = this.objClass; + if (findClazz == null){ + findClazz = this.obj == null ? null : this.obj.getClass(); + } + if (obj == null)onlyStatic = true; + Objects.requireNonNull(findClazz,"find clz can't be null.(name = " + name + ",type = " + type + ",strict = " + isStrictMode + ")"); + Class saveSourceClass = findClazz; + while (findClazz != null){ + Field[] fields = findClazz.getDeclaredFields(); + for (Field f : fields){ + //比对基本信息 + Class f_type = f.getType(); + String f_name = f.getName(); + if (onlyStatic && !Modifier.isStatic(f.getModifiers()))continue; + if (name != null && !f_name.equals(name))continue; + if (type != null){ + if (isStrictMode){ + if (!f_type.equals(type))continue; + }else { + if (!type.isAssignableFrom(f_type)){ + if (!f_type.equals(type))continue; + } + } + } + f.setAccessible(true); + //获取信息 + Object fieldValue = null; + try { + if (Modifier.isStatic(f.getModifiers())){ + fieldValue = f.get(null); + }else { + if (obj != null){ + fieldValue = f.get(obj); + } + } + }catch (Exception ignored){ } + + //调用筛选器 + if (filter != null){ + if (!filter.onField(f,fieldValue))continue; + } + return (T) fieldValue; + } + findClazz = findClazz.getSuperclass(); + } + throw new RuntimeException("No such field(name="+name+",type="+type+",class="+saveSourceClass+",strict="+isStrictMode+") found."); + } + + } +} diff --git a/app/src/main/java/cc/hicore/ReflectUtil/XMethod.java b/app/src/main/java/cc/hicore/ReflectUtil/XMethod.java new file mode 100644 index 0000000000..eaa5282144 --- /dev/null +++ b/app/src/main/java/cc/hicore/ReflectUtil/XMethod.java @@ -0,0 +1,168 @@ +package cc.hicore.ReflectUtil; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; + +public class XMethod { + public interface XMethodFilter { + boolean onMethod(Method m); + } + public static XMethodBuilder clz(String name){ + XMethodBuilder newBuilder = new XMethodBuilder(); + newBuilder.clazz(XClass.loadEx(name)); + return newBuilder; + } + public static XMethodBuilder clz(Class clz){ + XMethodBuilder newBuilder = new XMethodBuilder(); + newBuilder.clazz(clz); + return newBuilder; + } + public static XMethodBuilder obj(Object obj){ + XMethodBuilder newBuilder = new XMethodBuilder(); + newBuilder.obj(obj); + return newBuilder; + } + public static class XMethodBuilder { + + private Class clz; + private Object obj; + private String name; + private boolean noLoop; + private boolean ignoreParam; + private boolean noAbstract; + private int paramCount = -1; + private Class retType; + private final ArrayList> paramTypes = new ArrayList<>(); + private XMethodFilter filter; + private XMethodBuilder(){ } + public T invoke(Object... params) throws Exception { + //fillParam + if (paramTypes.size() == 0){ + for (Object param : params){ + Objects.requireNonNull(param,"Can't invoke a method with null param when paramTypes is not set."); + paramTypes.add(param.getClass()); + } + } + //getMethod + Method m = get(); + //invoke + if (Modifier.isStatic(m.getModifiers())){ + m.setAccessible(true); + return (T) m.invoke(null,params); + }else { + Objects.requireNonNull(obj,"Can't invoke a virtual method in a null object."); + m.setAccessible(true); + return (T) m.invoke(obj,params); + } + } + public Method get() throws NoSuchMethodException { + Class findClz = clz; + if (findClz == null){ + if (obj != null) findClz = obj.getClass(); + } + Objects.requireNonNull(findClz,"find method class can't be null (name = " + name + ", ret = " + retType + ", params = " + paramTypes); + + Class findClzSave = findClz; + while (findClz != null){ + Method[] methods = findClz.getDeclaredMethods(); + + MethodLoop: + for (Method m : methods){ + String m_name = m.getName(); + Class m_retType = m.getReturnType(); + Class[] m_paramTypes = m.getParameterTypes(); + if (noAbstract && Modifier.isAbstract(m.getModifiers()))continue; + if (name != null && !m_name.equals(name))continue; + if (retType != null && !m_retType.equals(retType))continue; + if (!ignoreParam){ + if (paramCount > -1 && m_paramTypes.length!= paramCount)continue; + if (m_paramTypes.length != paramTypes.size())continue; + for (int i=0;i retType){ + this.retType = retType; + return this; + } + public XMethodBuilder paramCount(int count){ + this.paramCount = count; + return this; + } + public XMethodBuilder param(Class... params){ + paramTypes.addAll(Arrays.asList(params)); + return this; + } + public XMethodBuilder ignoreParam(){ + this.ignoreParam = true; + return this; + } + private void clazz(Class clz){ + this.clz = clz; + } + private void obj(Object obj){ + this.obj = obj; + } + public XMethodBuilder filter(XMethodFilter filter){ + this.filter = filter; + return this; + } + } + public static Method[] getAllMethod(String clzName,String methodName){ + Class clz = XClass.loadEx(clzName); + ArrayList methods = new ArrayList<>(); + while (clz != null){ + Method[] ms = clz.getDeclaredMethods(); + for (Method m : ms){ + if (m.getName().equals(methodName)){ + methods.add(m); + } + } + clz = clz.getSuperclass(); + } + return methods.toArray(new Method[0]); + } + public static Filter_Param ParamFilter(int count,int pos,Class clz){ + return new Filter_Param(count,pos,clz); + } + private static class Filter_Param implements XMethodFilter{ + private final int count; + private final int pos; + private final Class clz; + public Filter_Param(int count,int pos,Class clz){ + this.count = count; + this.pos = pos; + this.clz = clz; + } + @Override + public boolean onMethod(Method m) { + return m.getParameterCount() == count && m.getParameterCount() > pos && m.getParameterTypes()[pos].equals(clz); + } + } +} diff --git a/app/src/main/java/cc/hicore/Utils/Async.java b/app/src/main/java/cc/hicore/Utils/Async.java new file mode 100644 index 0000000000..f555c7bc87 --- /dev/null +++ b/app/src/main/java/cc/hicore/Utils/Async.java @@ -0,0 +1,172 @@ +package cc.hicore.Utils; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import com.lxj.xpopup.XPopup; +import com.lxj.xpopup.impl.LoadingPopupView; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class Async { + public static void runOnUi(Runnable run){ + new Handler(Looper.getMainLooper()).post(()->{ + try { + run.run(); + }catch (Throwable t){ + XLog.e("AsyncRunOnUI",t); + } + }); + }public static void runOnUi(Runnable run,long time){ + new Handler(Looper.getMainLooper()).postDelayed(()->{ + try { + run.run(); + }catch (Throwable t){ + XLog.e("AsyncRunOnUI",t); + } + },time); + } + public static void runAsyncLoading(Context mContext,String title,Runnable runnable){ + runOnUi(()->{ + LoadingPopupView popupView = new XPopup.Builder(mContext) + .dismissOnTouchOutside(false) + .dismissOnBackPressed(false) + .asLoading(title); + popupView.show(); + runAsync(param->{ + runnable.run(); + return null; + }).doLast(popupView::dismiss).exec(); + }); + } + + public interface AsyncRunnable { + Object onRun(Object param) throws Throwable; + } + public interface AsyncRunnableNoReturn { + void onRun() throws Throwable; + } + public static class AsyncRunnableInner implements Runnable { + private Object param; + private AsyncRunnableInner nextTask; + private AsyncRunnable mainTask; + private GlobalContainer container; + private boolean isUI; + @Override + public void run() { + try { + Object ret = mainTask.onRun(param); + if (nextTask != null){ + nextTask.param = ret; + postAsyncRunnable(nextTask); + }else { + postDoLast(); + } + } catch (Throwable e) { + if (container.exceptionReceiver != null){ + try { + container.exceptionReceiver.onRun(e); + } catch (Throwable ignored) { } + }else { + XLog.e("RunAsyncNotHandlerException",e); + } + postDoLast(); + } + } + private void postDoLast(){ + if (container.doLastUI != null){ + runOnUi(() -> { + try { + container.doLastUI.onRun(); + } catch (Throwable ignored) { } + }); + container.threadPool.shutdown(); + } + } + } + private static class GlobalContainer{ + private AsyncRunnable exceptionReceiver; + private AsyncRunnableNoReturn doLastUI; + private final ExecutorService threadPool = Executors.newCachedThreadPool(); + } + private static void postAsyncRunnable(AsyncRunnableInner runnable){ + if (runnable.isUI){ + runOnUi(runnable); + }else { + runnable.container.threadPool.execute(runnable); + } + } + + public static AsyncRun runAsync(AsyncRunnable execTask, Object param){ + AsyncRunnableInner runnable = new AsyncRunnableInner(); + runnable.mainTask = execTask; + runnable.param = param; + + + AsyncRun newRunBuilder = new AsyncRun(); + newRunBuilder.currentRun = runnable; + newRunBuilder.firstRun = runnable; + runnable.container = new GlobalContainer(); + return newRunBuilder; + } + public static AsyncRun runAsync(AsyncRunnable execTask){ + return runAsync(execTask, null); + } + public static AsyncRun runAsyncUI(AsyncRunnable mTask, Object param){ + AsyncRunnableInner runnable = new AsyncRunnableInner(); + runnable.mainTask = mTask; + runnable.param = param; + + + AsyncRun newRunBuilder = new AsyncRun(); + newRunBuilder.currentRun = runnable; + newRunBuilder.firstRun = runnable; + runnable.container = new GlobalContainer(); + runnable.isUI = true; + return newRunBuilder; + } + public static AsyncRun runAsyncUI(AsyncRunnable mTask){ + return runAsyncUI(mTask, null); + } + public static class AsyncRun{ + private AsyncRunnableInner firstRun; + private AsyncRunnableInner currentRun; + public AsyncRun next(AsyncRunnable mtask){ + AsyncRunnableInner runnable = new AsyncRunnableInner(); + runnable.mainTask = mtask; + + AsyncRun newBuilder = new AsyncRun(); + newBuilder.firstRun = firstRun; + runnable.container = newBuilder.firstRun.container; + + newBuilder.currentRun = runnable; + currentRun.nextTask = runnable; + return newBuilder; + } + public AsyncRun nextUI(AsyncRunnable mtask){ + AsyncRunnableInner runnable = new AsyncRunnableInner(); + runnable.mainTask = mtask; + + AsyncRun newBuilder = new AsyncRun(); + newBuilder.firstRun = firstRun; + runnable.container = newBuilder.firstRun.container; + + + newBuilder.currentRun = runnable; + currentRun.nextTask = runnable; + runnable.isUI = true; + return newBuilder; + } + public AsyncRun onException(AsyncRunnable mtask){ + firstRun.container.exceptionReceiver = mtask; + return this; + } + public AsyncRun doLast(AsyncRunnableNoReturn run){ + firstRun.container.doLastUI = run; + return this; + } + public void exec(){ + postAsyncRunnable(firstRun); + } + } +} diff --git a/app/src/main/java/cc/hicore/Utils/FileUtils.java b/app/src/main/java/cc/hicore/Utils/FileUtils.java index 14f78519e8..b7e5662b83 100644 --- a/app/src/main/java/cc/hicore/Utils/FileUtils.java +++ b/app/src/main/java/cc/hicore/Utils/FileUtils.java @@ -31,7 +31,7 @@ import java.nio.charset.StandardCharsets; public class FileUtils { - public static void WriteToFile(String File, String FileContent) { + public static void writeToFile(String File, String FileContent) { try { File parent = new File(File).getParentFile(); if (!parent.exists()) parent.mkdirs(); @@ -57,7 +57,7 @@ public static void deleteFile(File file) { } file.delete(); } - public static String ReadFileString(File f) { + public static String readFileString(File f) { try { FileInputStream fInp = new FileInputStream(f); String Content = new String(readAllBytes(fInp), StandardCharsets.UTF_8); @@ -67,8 +67,8 @@ public static String ReadFileString(File f) { return null; } } - public static String ReadFileString(String f) { - return ReadFileString(new File(f)); + public static String readFileString(String f) { + return readFileString(new File(f)); } public static void copy(String source, String dest) { diff --git a/app/src/main/java/cc/hicore/Utils/FunConf.java b/app/src/main/java/cc/hicore/Utils/FunConf.java new file mode 100644 index 0000000000..e1c0038737 --- /dev/null +++ b/app/src/main/java/cc/hicore/Utils/FunConf.java @@ -0,0 +1,58 @@ +/* + * QAuxiliary - An Xposed module for QQ/TIM + * Copyright (C) 2019-2023 QAuxiliary developers + * https://github.com/cinit/QAuxiliary + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version and our eula as published + * by QAuxiliary contributors. + * + * This software 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * . + */ + +package cc.hicore.Utils; + +import io.github.qauxv.config.ConfigManager; +import java.util.Map; + +public class FunConf { + public static void setString(String setName,String key,String value){ + ConfigManager manager = ConfigManager.getDefaultConfig(); + manager.putString(setName + ":" + key,value); + } + public static String getString(String setName,String key,String defValue){ + ConfigManager manager = ConfigManager.getDefaultConfig(); + return manager.getString(setName + ":" + key,defValue); + } + public static boolean getBoolean(String setName,String key,boolean defValue){ + ConfigManager manager = ConfigManager.getDefaultConfig(); + return manager.getBoolean(setName + ":" + key,defValue); + } + public static void setBoolean(String setName,String key,boolean value){ + ConfigManager manager = ConfigManager.getDefaultConfig(); + manager.putBoolean(setName + ":" + key,value); + } + public static int getInt(String setName,String key,int defValue){ + ConfigManager manager = ConfigManager.getDefaultConfig(); + return manager.getInt(setName + ":" + key,defValue); + } + public static void setInt(String setName,String key,int value){ + ConfigManager manager = ConfigManager.getDefaultConfig(); + manager.putInt(setName + ":" + key,value); + } + public static void removeConfig(String setName){ + ConfigManager manager = ConfigManager.getDefaultConfig(); + Map map = manager.getAll(); + map.keySet().removeIf(key -> key.startsWith(setName + ":")); + } +} diff --git a/app/src/main/java/cc/hicore/Utils/ImageUtils.java b/app/src/main/java/cc/hicore/Utils/ImageUtils.java new file mode 100644 index 0000000000..15c8c220b6 --- /dev/null +++ b/app/src/main/java/cc/hicore/Utils/ImageUtils.java @@ -0,0 +1,57 @@ +package cc.hicore.Utils; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import cc.hicore.Env; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public class ImageUtils { + public static String getResizePicPath(String sourcePath,int max){ + String hash = DataUtils.getStrMD5(sourcePath); + String destPath = Env.app_save_path + "Cache/resize/" + hash; + if (new File(destPath).exists())return destPath; + new File(Env.app_save_path + "Cache/resize/").mkdirs(); + resizeImage(sourcePath,max,max,destPath); + return destPath; + } + public static void resizeImage(String imagePath, int maxWidth, int maxHeight, String outputFilePath) { + // 加载原始图片并获取其宽高信息 + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(imagePath, options); + int imageWidth = options.outWidth; + int imageHeight = options.outHeight; + + // 计算缩放比例 + float scaleFactor = Math.min((float) maxWidth / imageWidth, (float) maxHeight / imageHeight); + + // 根据缩放比例创建 Matrix 对象 + Matrix matrix = new Matrix(); + matrix.postScale(scaleFactor, scaleFactor); + + // 使用 Matrix 对象进行缩放操作 + Bitmap scaledBitmap = BitmapFactory.decodeFile(imagePath); + Bitmap resizedBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, imageWidth, imageHeight, matrix, true); + + // 输出至文件 + FileOutputStream outputStream = null; + try { + File outputFile = new File(outputFilePath); + outputStream = new FileOutputStream(outputFile); + resizedBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (outputStream != null) { + outputStream.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/app/src/main/java/cc/hicore/hook/Repeater.java b/app/src/main/java/cc/hicore/hook/Repeater.java index 4225b7bcf5..b96011c83d 100644 --- a/app/src/main/java/cc/hicore/hook/Repeater.java +++ b/app/src/main/java/cc/hicore/hook/Repeater.java @@ -21,12 +21,14 @@ package cc.hicore.hook; -import cc.hicore.ReflectUtil.MClass; import cc.hicore.ReflectUtil.MField; import cc.hicore.ReflectUtil.MMethod; +import cc.hicore.ReflectUtil.XField; +import cc.hicore.ReflectUtil.XMethod; import cc.hicore.message.bridge.Chat_facade_bridge; import cc.hicore.message.common.MsgBuilder; import cc.hicore.message.common.MsgUtils; +import io.github.qauxv.util.Initiator; import io.github.qauxv.util.Toasts; import java.util.ArrayList; import org.json.JSONObject; @@ -39,21 +41,19 @@ public static void Repeat(Object Session, Object chatMsg) throws Exception { case "MessageForText": case "MessageForLongTextMsg": case "MessageForFoldMsg": { - ArrayList AtList1 = MField.GetField(chatMsg, "atInfoTempList", ArrayList.class); - ArrayList AtList2 = MField.GetField(chatMsg, "atInfoList", ArrayList.class); - String mStr = MField.GetField(chatMsg, "extStr", String.class); + ArrayList AtList1 = XField.obj(chatMsg).name("atInfoTempList").type(ArrayList.class).get(); + ArrayList AtList2 = XField.obj(chatMsg).name("atInfoList").type(ArrayList.class).get(); + String mStr = XField.obj(chatMsg).name("extStr").type(String.class).get(); JSONObject mJson = new JSONObject(mStr); mStr = mJson.optString("troop_at_info_list"); - ArrayList AtList3 = MMethod - .CallMethod(null, MClass.loadClass("com.tencent.mobileqq.data.MessageForText"), "getTroopMemberInfoFromExtrJson", ArrayList.class, - new Class[]{String.class}, mStr); + ArrayList AtList3 = XMethod.clz("com.tencent.mobileqq.data.MessageForText").name("getTroopMemberInfoFromExtrJson").ret(ArrayList.class).param(String.class).invoke(mStr); if (AtList1 == null) { AtList1 = AtList2; } if (AtList1 == null) { AtList1 = AtList3; } - String nowMsg = MField.GetField(chatMsg, "msg", String.class); + String nowMsg = XField.obj(chatMsg).name("msg").type(String.class).get(); Chat_facade_bridge.sendText(Session, nowMsg, AtList1); break; } @@ -79,16 +79,16 @@ public static void Repeat(Object Session, Object chatMsg) throws Exception { break; } case "MessageForArkApp": { - Chat_facade_bridge.sendArkApp(Session, MField.GetField(chatMsg, "ark_app_message")); + Chat_facade_bridge.sendArkApp(Session,XField.obj(chatMsg).name("ark_app_message").get()); break; } case "MessageForStructing": case "MessageForTroopPobing": { - Chat_facade_bridge.sendStruct(Session, MField.GetField(chatMsg, "structingMsg")); + Chat_facade_bridge.sendStruct(Session, XField.obj(chatMsg).name("structingMsg").get()); break; } case "MessageForAniSticker": { - int servID = MsgUtils.DecodeAntEmoCode(MField.GetField(chatMsg, "sevrId", int.class)); + int servID = MsgUtils.DecodeAntEmoCode(XField.obj(chatMsg).name("sevrId").type(int.class).get()); Chat_facade_bridge.sendAnimation(Session, servID); break; } diff --git a/app/src/main/java/cc/hicore/hook/RepeaterHelper.java b/app/src/main/java/cc/hicore/hook/RepeaterHelper.java index 3b783e7bf3..e18d8d4f70 100644 --- a/app/src/main/java/cc/hicore/hook/RepeaterHelper.java +++ b/app/src/main/java/cc/hicore/hook/RepeaterHelper.java @@ -35,7 +35,8 @@ import android.widget.RelativeLayout; import cc.hicore.QApp.QAppUtils; import cc.hicore.ReflectUtil.MField; -import cc.hicore.ReflectUtil.MMethod; +import cc.hicore.ReflectUtil.XField; +import cc.hicore.ReflectUtil.XMethod; import cc.hicore.dialog.RepeaterPlusIconSettingDialog; import cc.ioctl.util.LayoutHelper; import de.robv.android.xposed.XC_MethodHook; @@ -74,12 +75,12 @@ public class RepeaterHelper { public static void createRepeatIcon(RelativeLayout baseChatItem, Object ChatMsg, Object session) throws Exception { boolean isSendFromLocal; - int istroop = MField.GetField(ChatMsg, "istroop", int.class); + int istroop = XField.obj(ChatMsg).name("istroop").type(int.class).get(); if (istroop == 1 || istroop == 0) { - String UserUin = MField.GetField(ChatMsg, "senderuin", String.class); + String UserUin = XField.obj(ChatMsg).name("senderuin").type(String.class).get(); isSendFromLocal = UserUin.equals(QAppUtils.getCurrentUin()); } else { - isSendFromLocal = MMethod.CallMethodNoParam(ChatMsg, "isSendFromLocal", boolean.class); + isSendFromLocal = XMethod.obj(ChatMsg).name("isSendFromLocal").ret( boolean.class).invoke(); } Context context = baseChatItem.getContext(); @@ -169,9 +170,9 @@ public static void createRepeatIcon(RelativeLayout baseChatItem, Object ChatMsg, } private static boolean checkIsAvailStruct(Object msg) throws Exception { if (msg.getClass().getSimpleName().equals("MessageForStructing")) { - Object struct = MField.GetField(msg, "structingMsg"); + Object struct = XField.obj(msg).name( "structingMsg").get(); if (struct != null) { - int id = MField.GetField(struct, "mMsgServiceID"); + int id = XField.obj(struct).name("mMsgServiceID").get(); return id == 5; } return false; diff --git a/app/src/main/java/cc/hicore/hook/RepeaterPlus.java b/app/src/main/java/cc/hicore/hook/RepeaterPlus.java index eb9f89fe13..39d20c8dc0 100644 --- a/app/src/main/java/cc/hicore/hook/RepeaterPlus.java +++ b/app/src/main/java/cc/hicore/hook/RepeaterPlus.java @@ -38,7 +38,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import cc.hicore.ReflectUtil.MField; -import cc.hicore.ReflectUtil.MMethod; +import cc.hicore.ReflectUtil.XField; +import cc.hicore.ReflectUtil.XMethod; import cc.hicore.dialog.RepeaterPlusIconSettingDialog; import cc.hicore.message.bridge.Nt_kernel_bridge; import cc.hicore.message.chat.SessionHooker; @@ -285,11 +286,11 @@ protected void afterHookedMethod(MethodHookParam param) { } Objects.requireNonNull(kChatAdapter1, "ChatAdapter1.class is null"); if (!RepeaterPlusIconSettingDialog.getIsShowInMenu()) { - HookUtils.hookAfterIfEnabled(this, MMethod.FindMethod(kChatAdapter1, "getView", View.class, new Class[]{ + HookUtils.hookAfterIfEnabled(this, XMethod.obj(kChatAdapter1).name( "getView").ret( View.class).param( int.class, View.class, ViewGroup.class - }), param -> { + ).get(), param -> { Object mGetView = param.getResult(); RelativeLayout baseChatItem = null; if (mGetView instanceof RelativeLayout) { @@ -301,7 +302,7 @@ protected void afterHookedMethod(MethodHookParam param) { if (context.getClass().getName().contains("MultiForwardActivity")) { return; } - List MessageRecoreList = MField.GetFirstField(param.thisObject, List.class); + List MessageRecoreList = XField.obj(param.thisObject).type(List.class).get(); if (MessageRecoreList == null) { return; } @@ -325,7 +326,7 @@ protected void afterHookedMethod(MethodHookParam param) { finalBaseChatItem.getViewTreeObserver().addOnGlobalLayoutListener(listenerContainer.get()); }); - HookUtils.hookBeforeIfEnabled(this, MMethod.FindMethod("com.tencent.mobileqq.data.ChatMessage", "isFollowMessage", boolean.class, new Class[0]), + HookUtils.hookBeforeIfEnabled(this, XMethod.clz("com.tencent.mobileqq.data.ChatMessage").name("isFollowMessage").ret(boolean.class).get(), param -> param.setResult(false)); } else { diff --git a/app/src/main/java/cc/hicore/hook/UnlockLeftSlipLimit.java b/app/src/main/java/cc/hicore/hook/UnlockLeftSlipLimit.java index 2ee695e46e..c457d4836b 100644 --- a/app/src/main/java/cc/hicore/hook/UnlockLeftSlipLimit.java +++ b/app/src/main/java/cc/hicore/hook/UnlockLeftSlipLimit.java @@ -22,7 +22,7 @@ package cc.hicore.hook; import androidx.annotation.NonNull; -import cc.hicore.ReflectUtil.MMethod; +import cc.hicore.ReflectUtil.XMethod; import cc.ioctl.util.HookUtils; import cc.ioctl.util.HostInfo; import de.robv.android.xposed.XC_MethodHook; @@ -68,8 +68,7 @@ protected void beforeHookedMethod(MethodHookParam param) { }); return true; } - Method m = MMethod.FindMethod(DexKit.requireMethodFromCache(NLeftSwipeReplyHelper_reply.INSTANCE).getDeclaringClass(), methodName, boolean.class, - new Class[0]); + Method m = XMethod.clz(DexKit.requireMethodFromCache(NLeftSwipeReplyHelper_reply.INSTANCE).getDeclaringClass()).name(methodName).ret(boolean.class).get(); HookUtils.hookBeforeIfEnabled(this, m, param -> param.setResult(true)); return true; } diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/EmoOnlineLoader.java b/app/src/main/java/cc/hicore/hook/stickerPanel/EmoOnlineLoader.java index ee7a19beb4..92a4e77842 100644 --- a/app/src/main/java/cc/hicore/hook/stickerPanel/EmoOnlineLoader.java +++ b/app/src/main/java/cc/hicore/hook/stickerPanel/EmoOnlineLoader.java @@ -10,6 +10,8 @@ import java.util.concurrent.Executors; public class EmoOnlineLoader { + static ExecutorService savePool = Executors.newFixedThreadPool(16); + static ExecutorService savePoolSingle = Executors.newSingleThreadExecutor(); public static ExecutorService syncThread = Executors.newFixedThreadPool(16); public static void submit(EmoPanel.EmoInfo info, Runnable run) { @@ -32,4 +34,25 @@ public static void submit(EmoPanel.EmoInfo info, Runnable run) { }); } + + public static void submit2(EmoPanel.EmoInfo info, Runnable run) { + savePool.submit(() -> { + try { + String CacheDir = Env.app_save_path+ "/Cache/img_" + info.MD5; + if (info.MD5.equals(DataUtils.getFileMD5(new File(CacheDir)))) { + info.Path = CacheDir; + new Handler(Looper.getMainLooper()).post(run); + return; + } + new File(CacheDir).delete(); + + HttpUtils.DownloadToFile(info.URL, CacheDir); + info.Path = CacheDir; + new Handler(Looper.getMainLooper()).post(run); + } catch (Throwable th) { + new Handler(Looper.getMainLooper()).post(run); + } + + }); + } } diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/EmoPanel.java b/app/src/main/java/cc/hicore/hook/stickerPanel/EmoPanel.java index 530ad3a8f4..ab6b6944ef 100644 --- a/app/src/main/java/cc/hicore/hook/stickerPanel/EmoPanel.java +++ b/app/src/main/java/cc/hicore/hook/stickerPanel/EmoPanel.java @@ -4,9 +4,11 @@ public class EmoPanel { public static class EmoInfo { public String Path; + public String OCR; public int type; public String MD5; public String URL; + public String thumb; } diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/Hooker/StickerPanelEntryHooker.java b/app/src/main/java/cc/hicore/hook/stickerPanel/Hooker/StickerPanelEntryHooker.java index b8d9c77843..0c6f65f35e 100644 --- a/app/src/main/java/cc/hicore/hook/stickerPanel/Hooker/StickerPanelEntryHooker.java +++ b/app/src/main/java/cc/hicore/hook/stickerPanel/Hooker/StickerPanelEntryHooker.java @@ -21,14 +21,16 @@ package cc.hicore.hook.stickerPanel.Hooker; +import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; import androidx.annotation.NonNull; import cc.hicore.QApp.QAppUtils; -import cc.hicore.ReflectUtil.MMethod; import cc.hicore.ReflectUtil.MRes; +import cc.hicore.ReflectUtil.XMethod; +import cc.hicore.Utils.FunConf; import cc.hicore.Utils.XLog; import cc.hicore.hook.stickerPanel.ICreator; import cc.hicore.hook.stickerPanel.PanelUtils; @@ -52,6 +54,7 @@ import io.github.qauxv.util.CustomMenu; import io.github.qauxv.util.Initiator; import io.github.qauxv.util.SyncUtils; +import io.github.qauxv.util.Toasts; import io.github.qauxv.util.dexkit.AbstractQQCustomMenuItem; import io.github.qauxv.util.dexkit.ChatPanel_InitPanel_QQNT; import io.github.qauxv.util.dexkit.DexKit; @@ -105,23 +108,25 @@ protected boolean initOnce() throws Exception { } }); - HookUtils.hookAfterIfEnabled(this,DexKit.loadMethodFromCache(Guild_Emo_Btn_Create_QQNT.INSTANCE),param -> { - ViewGroup vg = (ViewGroup) param.getResult(); - for (int i = 0; i < vg.getChildCount(); i++) { - View v = vg.getChildAt(i); - if (v instanceof ImageView) { - v.setOnLongClickListener(v1 -> { - ICreator.createPanel(v1.getContext()); - return true; - }); + HookUtils.hookAfterIfEnabled( + this, + DexKit.loadMethodFromCache(Guild_Emo_Btn_Create_QQNT.INSTANCE), + param -> { + ViewGroup vg = (ViewGroup) param.getResult(); + for (int i = 0; i < vg.getChildCount(); i++) { + View v = vg.getChildAt(i); + if (v instanceof ImageView) { + v.setOnLongClickListener(v1 -> { + ICreator.createPanel(v1.getContext()); + return true; + }); + } + } } - } - }); - - HookUtils.hookAfterIfEnabled(this, MMethod.FindMethod(Initiator.loadClass("com.tencent.qqnt.aio.shortcutbar.PanelIconLinearLayout"), - null, - ImageView.class, - new Class[]{Initiator.load("com.tencent.qqnt.aio.shortcutbar.a")}), + ); + HookUtils.hookAfterIfEnabled( + this, + XMethod.clz("com.tencent.qqnt.aio.shortcutbar.PanelIconLinearLayout").ret(ImageView.class).ignoreParam().get(), param -> { ImageView imageView = (ImageView) param.getResult(); if ("表情".contentEquals(imageView.getContentDescription())){ @@ -130,7 +135,8 @@ protected boolean initOnce() throws Exception { return true; }); } - }); + } + ); //Hook for longClick msgItem { @@ -211,6 +217,35 @@ protected boolean initOnce() throws Exception { } + //Hook for change title + + Method sendMsgMethod = XMethod + .clz("com.tencent.qqnt.kernel.nativeinterface.IKernelMsgService$CppProxy") + .name("sendMsg") + .ignoreParam().get(); + + + HookUtils.hookBeforeIfEnabled( + this, + sendMsgMethod, + param -> { + if (isEnabled()){ + ArrayList elements = (ArrayList) param.args[2]; + if (FunConf.getBoolean("global", "sticker_panel_set_ch_change_title", false)){ + String text = FunConf.getString("global", "sticker_panel_set_ed_change_title", ""); + if (!TextUtils.isEmpty(text)){ + for (MsgElement element : elements){ + if (element.getPicElement() != null){ + PicElement picElement = element.getPicElement(); + picElement.setSummary(text); + } + } + } + } + } + } + ); + return true; } diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/ICreator.java b/app/src/main/java/cc/hicore/hook/stickerPanel/ICreator.java index d59d2d94fd..9e01878f47 100644 --- a/app/src/main/java/cc/hicore/hook/stickerPanel/ICreator.java +++ b/app/src/main/java/cc/hicore/hook/stickerPanel/ICreator.java @@ -2,21 +2,22 @@ import android.annotation.SuppressLint; import android.content.Context; +import android.graphics.Color; import android.os.Handler; import android.os.Looper; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; -import android.widget.AbsListView; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.ListView; +import android.widget.ScrollView; import android.widget.TextView; import androidx.annotation.NonNull; import cc.hicore.Env; -import cc.hicore.Utils.ContextUtils; +import cc.hicore.Utils.Async; import cc.hicore.hook.stickerPanel.MainItemImpl.InputFromLocalImpl; import cc.hicore.hook.stickerPanel.MainItemImpl.LocalStickerImpl; +import cc.hicore.hook.stickerPanel.MainItemImpl.PanelSetImpl; import cc.hicore.hook.stickerPanel.MainItemImpl.RecentStickerImpl; import cc.ioctl.util.HostInfo; import cc.ioctl.util.LayoutHelper; @@ -26,30 +27,33 @@ import com.lxj.xpopup.core.BottomPopupView; import com.lxj.xpopup.util.XPopupUtils; import io.github.qauxv.R; -import io.github.qauxv.lifecycle.Parasitics; +import io.github.qauxv.ui.CommonContextWrapper; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; @SuppressLint("ResourceType") -public class ICreator extends BottomPopupView implements AbsListView.OnScrollListener { +public class ICreator extends BottomPopupView{ private static BasePopupView popupView; - MainPanelAdapter adapter = new MainPanelAdapter(); LinearLayout topSelectBar; - int recentUsePos = 0; + private ScrollView itemContainer; + private IMainPanelItem currentTab; - int IdOfInputPic; - private final List newTabView = new ArrayList<>(); - private ListView listView; + ArrayList mItems = new ArrayList<>(); + + private static long savedSelectID; + private static long lastSelectTime; + private static int savedScrollTo; + + private ViewGroup recentUse; public ICreator(@NonNull Context context) { super(context); } public static void createPanel(Context context) { - - Parasitics.injectModuleResources(context.getResources()); - Context fixContext = ContextUtils.getFixContext(context); - XPopup.Builder NewPop = new XPopup.Builder(fixContext).isDestroyOnDismiss(true); + Context fixContext = CommonContextWrapper.createAppCompatContext(context); + XPopup.Builder NewPop = new XPopup.Builder(fixContext).moveUpToKeyboard(false).isDestroyOnDismiss(true); popupView = NewPop.asCustom(new ICreator(fixContext)); popupView.show(); } @@ -64,55 +68,77 @@ private void initTopSelectBar() { topSelectBar = findViewById(R.id.Sticker_Pack_Select_Bar); } + long scrollTime = 0; private void initListView() { - listView = findViewById(R.id.Sticker_Panel_Main_List_View); - listView.setAdapter(adapter); - listView.setVerticalScrollBarEnabled(false); - listView.setOnScrollListener(this); + itemContainer = findViewById(R.id.sticker_panel_pack_container); + + itemContainer.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { + if (System.currentTimeMillis() - scrollTime > 20){ + currentTab.notifyViewUpdate0(); + scrollTime = System.currentTimeMillis(); + } + }); } private void initStickerPacks() { List paths = LocalDataHelper.readPaths(); for (LocalDataHelper.LocalPath path : paths) { - int localPathPos = adapter.addItemData(new LocalStickerImpl(path, LocalDataHelper.getPicItems(path.storePath), getContext())); + IMainPanelItem newItem = new LocalStickerImpl(path, getContext()); + AtomicReference sItemView = new AtomicReference<>(); ViewGroup sticker_pack_item = (ViewGroup) createPicImage(path.coverName, path.Name, v -> { - listView.setSelection(localPathPos); - listView.smoothScrollToPositionFromTop(localPathPos, -5); - + itemContainer.scrollTo(0, 0); + switchToItem(sItemView.get()); }, path); - sticker_pack_item.setTag(localPathPos); + sticker_pack_item.setTag(newItem); topSelectBar.addView(sticker_pack_item); + sItemView.set(sticker_pack_item); } } private void initDefItemsBefore() { - ViewGroup recentUse = (ViewGroup) createPicImage(R.drawable.sticker_recent, "最近使用", v -> { - listView.setSelection(recentUsePos); - listView.smoothScrollToPositionFromTop(recentUsePos, -5); - }); - recentUsePos = adapter.addItemData(new RecentStickerImpl(getContext())); - recentUse.setTag(recentUsePos); + IMainPanelItem newItem = new RecentStickerImpl(getContext()); + AtomicReference sItemView = new AtomicReference<>(); + ViewGroup recentUse = (ViewGroup) createPicImage(R.drawable.sticker_panel_recent_icon, "最近使用", v -> switchToItem(sItemView.get())); + sItemView.set(recentUse); + recentUse.setTag(newItem); topSelectBar.addView(recentUse); + recentUse.setTag(newItem); + + this.recentUse = recentUse; } + private void switchToItem(ViewGroup item){ + for (ViewGroup i : mItems) { + IMainPanelItem mItem = (IMainPanelItem) i.getTag(); + mItem.onViewDestroy(); + i.findViewById(887533).setVisibility(GONE); + } - private void initDefItemsLast() { + currentTab = (IMainPanelItem) item.getTag(); + itemContainer.removeAllViews(); + itemContainer.addView(currentTab.getView()); + item.findViewById(887533).setVisibility(VISIBLE); + + + Async.runOnUi(currentTab::notifyViewUpdate0); - ViewGroup inputView = (ViewGroup) createPicImage(R.drawable.input, "导入表情", v -> { - listView.setSelection(IdOfInputPic); - listView.smoothScrollToPositionFromTop(IdOfInputPic, -5); - }); - IdOfInputPic = adapter.addItemData(new InputFromLocalImpl()); - topSelectBar.addView(inputView); - //topSelectBar.addView(createPicImage(R.drawable.sticker_pack_set_icon,"设置分组",v -> Utils.ShowToast("Click"))); } - public void notifyTabViewSelect(ViewGroup vg) { - for (ViewGroup v : newTabView) { - v.findViewById(887533).setVisibility(GONE); - } - vg.findViewById(887533).setVisibility(VISIBLE); + private void initDefItemsLast() { + IMainPanelItem inputPic = new InputFromLocalImpl(getContext()); + AtomicReference sticker_panel_input_view = new AtomicReference<>(); + ViewGroup inputView = (ViewGroup) createPicImage(R.drawable.sticker_panel_input_icon, "导入图片", v -> switchToItem(sticker_panel_input_view.get())); + sticker_panel_input_view.set(inputView); + inputView.setTag(inputPic); + topSelectBar.addView(inputView); + + IMainPanelItem setItem = new PanelSetImpl(getContext()); + AtomicReference sticker_panel_set_view = new AtomicReference<>(); + ViewGroup setView = (ViewGroup) createPicImage(R.drawable.sticker_panen_set_button_icon, "设置", v -> switchToItem(sticker_panel_set_view.get())); + sticker_panel_set_view.set(setView); + setView.setTag(setItem); + topSelectBar.addView(setView); } //创建贴纸包面板的滑动按钮 @@ -135,18 +161,14 @@ private View createPicImage(String imgPath, String title, OnClickListener clickL TextView titleView = new TextView(getContext()); titleView.setText(title); - titleView.setTextColor(0xff888888); + titleView.setTextColor(getResources().getColor(R.color.global_font_color, null)); titleView.setGravity(Gravity.CENTER_HORIZONTAL); titleView.setTextSize(10); titleView.setSingleLine(); panel.addView(titleView); - if (imgPath.startsWith("http://") || imgPath.startsWith("https://")) { - Glide.with(HostInfo.getApplication()).load(imgPath).into(img); - } else { - Glide.with(HostInfo.getApplication()).load(Env.app_save_path + "本地表情包/" + path.storePath + "/" + imgPath).into(img); - } + Glide.with(HostInfo.getApplication()).load(Env.app_save_path + "本地表情包/" + path.storePath + "/" + imgPath).into(img); LinearLayout.LayoutParams panel_param = new LinearLayout.LayoutParams(LayoutHelper.dip2px(getContext(), 50), ViewGroup.LayoutParams.WRAP_CONTENT); @@ -155,7 +177,7 @@ private View createPicImage(String imgPath, String title, OnClickListener clickL panel.setLayoutParams(panel_param); View greenTip = new View(getContext()); - greenTip.setBackgroundColor(0xff339933); + greenTip.setBackgroundColor(Color.GREEN); panel_param = new LinearLayout.LayoutParams(LayoutHelper.dip2px(getContext(), 30), 50); panel_param.leftMargin = LayoutHelper.dip2px(getContext(), 5); panel_param.rightMargin = LayoutHelper.dip2px(getContext(), 5); @@ -164,7 +186,7 @@ private View createPicImage(String imgPath, String title, OnClickListener clickL greenTip.setVisibility(GONE); panel.addView(greenTip); - newTabView.add(panel); + mItems.add(panel); return panel; } @@ -189,7 +211,7 @@ private View createPicImage(int resID, String title, OnClickListener clickListen TextView titleView = new TextView(getContext()); titleView.setText(title); titleView.setGravity(Gravity.CENTER_HORIZONTAL); - titleView.setTextColor(0xff888888); + titleView.setTextColor(getContext().getColor(R.color.global_font_color)); titleView.setTextSize(10); titleView.setSingleLine(); panel.addView(titleView); @@ -202,7 +224,7 @@ private View createPicImage(int resID, String title, OnClickListener clickListen panel.setLayoutParams(panel_param); View greenTip = new View(getContext()); - greenTip.setBackgroundColor(0xff339933); + greenTip.setBackgroundColor(Color.GREEN); panel_param = new LinearLayout.LayoutParams(LayoutHelper.dip2px(getContext(), 30), 50); panel_param.leftMargin = LayoutHelper.dip2px(getContext(), 5); panel_param.rightMargin = LayoutHelper.dip2px(getContext(), 5); @@ -211,8 +233,7 @@ private View createPicImage(int resID, String title, OnClickListener clickListen greenTip.setVisibility(GONE); panel.addView(greenTip); - newTabView.add(panel); - + mItems.add(panel); return panel; } @@ -235,9 +256,24 @@ protected void onCreate() { initDefItemsLast(); + if (System.currentTimeMillis() - lastSelectTime > 60 * 1000){ + switchToItem(recentUse); + }else if (savedSelectID != 0){ + for (ViewGroup item : mItems){ + IMainPanelItem iMainPanelItem = (IMainPanelItem) item.getTag(); + if (iMainPanelItem.getID() == savedSelectID){ + switchToItem(item); + + Async.runOnUi(iMainPanelItem::notifyViewUpdate0,100); + Async.runOnUi(()-> itemContainer.scrollTo(0,savedScrollTo)); + break; + } + } + }else { + switchToItem(recentUse); + } - adapter.notifyDataSetChanged(); - }, 200); + }, 50); } @@ -253,48 +289,39 @@ protected int getPopupHeight() { } @Override - protected void onDismiss() { - super.onDismiss(); - adapter.destroyAllViews(); - Glide.get(HostInfo.getApplication()).clearMemory(); + protected void beforeDismiss() { + if (currentTab != null){ + savedScrollTo = itemContainer.getScrollY(); + savedSelectID = currentTab.getID(); + lastSelectTime = System.currentTimeMillis(); + } + super.beforeDismiss(); } @Override - protected int getImplLayoutId() { - return R.layout.sticker_panel_plus_main; - } + protected void onDismiss() { - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { + super.onDismiss(); + for (ViewGroup item : mItems){ + IMainPanelItem iMainPanelItem = (IMainPanelItem) item.getTag(); + iMainPanelItem.onViewDestroy(); + } + Glide.get(HostInfo.getApplication()).clearMemory(); } @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - ViewGroup vg = findViewByItemNumber(firstVisibleItem); - if (vg != null) { - notifyTabViewSelect(vg); - } + protected int getImplLayoutId() { + return io.github.qauxv.R.layout.sticker_panel_plus_main; + } - int first = view.getFirstVisiblePosition(); - int last = view.getLastVisiblePosition(); - adapter.notifyViewUpdate(first, last); + public interface IMainPanelItem { + View getView(); - } + void onViewDestroy(); - @Override - public void onDestroy() { - super.onDestroy(); - adapter.destroyAllViews(); - } + long getID(); - private ViewGroup findViewByItemNumber(int number) { - for (int i = 0; i < newTabView.size(); i++) { - Object tag = newTabView.get(i).getTag(); - if (tag instanceof Integer && ((Integer) tag) == number) { - return newTabView.get(i); - } - } - return null; + void notifyViewUpdate0(); } } diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/LocalDataHelper.java b/app/src/main/java/cc/hicore/hook/stickerPanel/LocalDataHelper.java index b3cfc949a2..16bfc644d0 100644 --- a/app/src/main/java/cc/hicore/hook/stickerPanel/LocalDataHelper.java +++ b/app/src/main/java/cc/hicore/hook/stickerPanel/LocalDataHelper.java @@ -13,7 +13,7 @@ public class LocalDataHelper { public static List readPaths() { try { String pathSetDir = Env.app_save_path + "本地表情包/set.json"; - JSONObject pathJson = new JSONObject(FileUtils.ReadFileString(pathSetDir)); + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); List paths = new ArrayList<>(); JSONArray pathList = pathJson.getJSONArray("paths"); for (int i = 0; i < pathList.length(); i++) { @@ -24,7 +24,9 @@ public static List readPaths() { localPath.Name = path.getString("Name"); localPath.storePath = path.getString("storePath"); paths.add(localPath); - } catch (Exception ignored) { } + } catch (Exception e) { + + } } return paths; } catch (Exception e) { @@ -36,7 +38,7 @@ public static List readPaths() { public synchronized static List getPicItems(String pathName) { try { String pathSetDir = Env.app_save_path + "本地表情包/" + pathName + "/info.json"; - JSONObject pathJson = new JSONObject(FileUtils.ReadFileString(pathSetDir)); + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); List items = new ArrayList<>(); JSONArray pathList = pathJson.getJSONArray("items"); for (int i = 0; i < pathList.length(); i++) { @@ -46,13 +48,14 @@ public synchronized static List getPicItems(String pathName) { localPath.MD5 = path.getString("MD5"); localPath.fileName = path.optString("fileName"); localPath.addTime = path.optLong("addTime"); - localPath.type = path.optInt("type",1); localPath.url = path.optString("url"); localPath.thumbName = path.optString("thumbName"); localPath.thumbUrl = path.optString("thumbUrl"); localPath.ocr = path.optString("ocr"); items.add(localPath); - } catch (Exception ignored) { } + } catch (Exception e) { + + } } return items; @@ -66,13 +69,13 @@ public synchronized static boolean addPath(LocalPath addInfo) { try { String pathSetDir = Env.app_save_path + "本地表情包/set.json"; if (!new File(pathSetDir).exists()) { - FileUtils.WriteToFile(pathSetDir, "{\"paths\":[]}"); + FileUtils.writeToFile(pathSetDir, "{\"paths\":[]}"); } - JSONObject pathJson = new JSONObject(FileUtils.ReadFileString(pathSetDir)); + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); JSONArray pathList = pathJson.getJSONArray("paths"); for (int i = 0; i < pathList.length(); i++) { JSONObject path = pathList.getJSONObject(i); - if (path.getString("Name").equals(addInfo.Name)) { + if (path.getString("storePath").equals(addInfo.storePath)) { return false; } } @@ -83,7 +86,7 @@ public synchronized static boolean addPath(LocalPath addInfo) { pathList.put(newPath); new File(Env.app_save_path + "本地表情包/" + addInfo.storePath).mkdirs(); - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); return true; } catch (Exception e) { e.printStackTrace(); @@ -97,9 +100,9 @@ public synchronized static boolean addPicItem(String pathName, LocalPicItems add if (!new File(pathSetDir).exists()) { JSONObject pathJson = new JSONObject(); pathJson.put("items", new JSONArray()); - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); } - JSONObject pathJson = new JSONObject(FileUtils.ReadFileString(pathSetDir)); + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); JSONArray pathList = pathJson.getJSONArray("items"); for (int i = 0; i < pathList.length(); i++) { JSONObject path = pathList.getJSONObject(i); @@ -111,7 +114,6 @@ public synchronized static boolean addPicItem(String pathName, LocalPicItems add newPath.put("MD5", addInfo.MD5); newPath.put("fileName", addInfo.fileName); newPath.put("addTime", addInfo.addTime); - newPath.put("type", addInfo.type); newPath.put("url", addInfo.url); newPath.put("thumbName", addInfo.thumbName); newPath.put("thumbUrl", addInfo.thumbUrl); @@ -119,7 +121,7 @@ public synchronized static boolean addPicItem(String pathName, LocalPicItems add pathList.put(newPath); - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); return true; } catch (Exception e) { e.printStackTrace(); @@ -130,26 +132,45 @@ public synchronized static boolean addPicItem(String pathName, LocalPicItems add public synchronized static void deletePath(LocalPath pathInfo) { try { String pathSetDir = Env.app_save_path + "本地表情包/set.json"; - JSONObject pathJson = new JSONObject(FileUtils.ReadFileString(pathSetDir)); + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); JSONArray pathList = pathJson.getJSONArray("paths"); for (int i = 0; i < pathList.length(); i++) { JSONObject path = pathList.getJSONObject(i); - if (path.getString("Name").equals(pathInfo.Name)) { + if (path.getString("storePath").equals(pathInfo.storePath)) { pathList.remove(i); break; } } - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); FileUtils.deleteFile(new File(Env.app_save_path + "本地表情包/" + pathInfo.storePath)); } catch (Exception e) { e.printStackTrace(); } } + public synchronized static void deletePathName(LocalPath pathInfo) { + try { + String pathSetDir = Env.app_save_path + "本地表情包/set.json"; + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); + JSONArray pathList = pathJson.getJSONArray("paths"); + for (int i = 0; i < pathList.length(); i++) { + JSONObject path = pathList.getJSONObject(i); + if (path.getString("storePath").equals(pathInfo.storePath)) { + pathList.remove(i); + break; + } + } + FileUtils.writeToFile(pathSetDir, pathJson.toString()); + } catch (Exception e) { + e.printStackTrace(); + } + } public synchronized static void deletePicItem(LocalPath pathInfo, LocalPicItems item) { try { + FileUtils.deleteFile(new File(getLocalItemPath(pathInfo, item))); + String pathSetDir = Env.app_save_path + "本地表情包/" + pathInfo.storePath + "/info.json"; - JSONObject pathJson = new JSONObject(FileUtils.ReadFileString(pathSetDir)); + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); JSONArray pathList = pathJson.getJSONArray("items"); for (int i = 0; i < pathList.length(); i++) { JSONObject path = pathList.getJSONObject(i); @@ -158,30 +179,58 @@ public synchronized static void deletePicItem(LocalPath pathInfo, LocalPicItems break; } } - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); + } catch (Exception e) { + e.printStackTrace(); + } + } + public synchronized static void deletePicLog(LocalPath pathInfo, LocalPicItems item) { + try { + String pathSetDir = Env.app_save_path + "本地表情包/" + pathInfo.storePath + "/info.json"; + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); + JSONArray pathList = pathJson.getJSONArray("items"); + for (int i = 0; i < pathList.length(); i++) { + JSONObject path = pathList.getJSONObject(i); + if (path.getString("MD5").equals(item.MD5)) { + pathList.remove(i); + break; + } + } + FileUtils.writeToFile(pathSetDir, pathJson.toString()); } catch (Exception e) { e.printStackTrace(); } } - public synchronized static void setPathCover(LocalPath pathInfo, LocalPicItems coverItem) { try { String pathSetDir = Env.app_save_path + "本地表情包/set.json"; - JSONObject pathJson = new JSONObject(FileUtils.ReadFileString(pathSetDir)); + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); JSONArray pathList = pathJson.getJSONArray("paths"); for (int i = 0; i < pathList.length(); i++) { JSONObject path = pathList.getJSONObject(i); - if (path.getString("Name").equals(pathInfo.Name)) { - if (coverItem.type == 1) { - path.put("coverName", coverItem.fileName); - } else if (coverItem.type == 2) { - path.put("coverName", coverItem.url); - } - + if (path.getString("storePath").equals(pathInfo.storePath)) { + path.put("coverName", coverItem.fileName); + break; + } + } + FileUtils.writeToFile(pathSetDir, pathJson.toString()); + } catch (Exception e) { + e.printStackTrace(); + } + } + public synchronized static void setPathName(LocalPath pathInfo,String newPathName){ + try { + String pathSetDir = Env.app_save_path + "本地表情包/set.json"; + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); + JSONArray pathList = pathJson.getJSONArray("paths"); + for (int i = 0; i < pathList.length(); i++) { + JSONObject path = pathList.getJSONObject(i); + if (path.getString("storePath").equals(pathInfo.storePath)) { + path.put("Name", newPathName); break; } } - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); } catch (Exception e) { e.printStackTrace(); } @@ -190,21 +239,20 @@ public synchronized static void setPathCover(LocalPath pathInfo, LocalPicItems c public synchronized static void updatePicItemInfo(LocalPath pathInfo, LocalPicItems newItemInfo) { try { String pathSetDir = Env.app_save_path + "本地表情包/" + pathInfo.storePath + "/info.json"; - JSONObject pathJson = new JSONObject(FileUtils.ReadFileString(pathSetDir)); + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); JSONArray pathList = pathJson.getJSONArray("items"); for (int i = 0; i < pathList.length(); i++) { JSONObject path = pathList.getJSONObject(i); if (path.getString("MD5").equals(newItemInfo.MD5)) { path.put("fileName", newItemInfo.fileName); path.put("addTime", newItemInfo.addTime); - path.put("type", newItemInfo.type); path.put("url", newItemInfo.url); path.put("thumbName", newItemInfo.thumbName); path.put("thumbUrl", newItemInfo.thumbUrl); break; } } - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); } catch (Exception e) { e.printStackTrace(); } @@ -238,7 +286,6 @@ public static class LocalPicItems { public long addTime; - public int type; public String ocr; } } diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/InputFromLocalImpl.java b/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/InputFromLocalImpl.java index 991908b747..bb72f6b64e 100644 --- a/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/InputFromLocalImpl.java +++ b/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/InputFromLocalImpl.java @@ -1,6 +1,7 @@ package cc.hicore.hook.stickerPanel.MainItemImpl; import android.app.AlertDialog; +import android.app.ProgressDialog; import android.content.Context; import android.graphics.BitmapFactory; import android.text.TextUtils; @@ -8,119 +9,183 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; -import cc.hicore.Utils.ContextUtils; import cc.hicore.Utils.DataUtils; import cc.hicore.Utils.FileUtils; import cc.hicore.Utils.RandomUtils; import cc.hicore.hook.stickerPanel.ICreator; import cc.hicore.hook.stickerPanel.LocalDataHelper; -import cc.hicore.hook.stickerPanel.MainPanelAdapter; -import cc.ioctl.util.ui.FaultyDialog; import com.lxj.xpopup.XPopup; -import com.lxj.xpopup.impl.LoadingPopupView; import io.github.qauxv.R; -import io.github.qauxv.ui.CommonContextWrapper; import io.github.qauxv.util.SyncUtils; import java.io.File; +import java.util.ArrayList; import java.util.List; - -public class InputFromLocalImpl implements MainPanelAdapter.IMainPanelItem { - +import org.jetbrains.annotations.Async; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public class InputFromLocalImpl implements ICreator.IMainPanelItem { + private Context mContext; + public InputFromLocalImpl(Context mContext){ + this.mContext = mContext; + } @Override - public View getView(ViewGroup parent) { - ViewGroup vgInput = (ViewGroup) View.inflate(parent.getContext(), R.layout.sticker_panel_impl_input_from_local, null); + public View getView() { + ViewGroup vgInput = (ViewGroup) View.inflate(mContext, R.layout.sticker_panel_impl_input_from_local, null); EditText edPath = vgInput.findViewById(R.id.input_path); + edPath.setFocusable(true); Button btnPath = vgInput.findViewById(R.id.btn_confirm_input); - btnPath.setOnClickListener(v -> { + btnPath.setOnClickListener(v->{ String path = edPath.getText().toString(); File[] f = new File(path).listFiles(); - if (f == null) { - FaultyDialog.show(parent.getContext(), "错误", "路径无效"); - } else { - EditText ed = new EditText(parent.getContext()); - new AlertDialog.Builder(CommonContextWrapper.createAppCompatContext(parent.getContext())) - .setTitle("输入分组名称") - .setView(ed) - .setPositiveButton("确定导入", (dialog, which) -> inputWorker(parent.getContext(), path, ed.getText().toString())) - .setNeutralButton("取消", null) + if (f == null){ + new AlertDialog.Builder(mContext) + .setTitle("错误") + .setMessage("路径无效") + .setPositiveButton("确定", null) .show(); + }else { + inputWorker(mContext, path); + EditText ed = new EditText(mContext); + } }); return vgInput; } - - private static void inputWorker(Context context, String path, String name) { - if (TextUtils.isEmpty(name)) { - FaultyDialog.show(context, "错误", "名称不能为空"); - return; + private static void inputWorker(Context context,String path){ + List paths = LocalDataHelper.readPaths(); + ArrayList names = new ArrayList<>(); + for (LocalDataHelper.LocalPath p : paths){ + names.add(p.Name); } - LoadingPopupView progress = new XPopup.Builder(ContextUtils.getFixContext(CommonContextWrapper.createAppCompatContext(context))) - .dismissOnBackPressed(false) - .dismissOnTouchOutside(false) - .asLoading("正在导入..."); - progress.show(); + new AlertDialog.Builder(context) + .setTitle("选择需要导入到的分组") + .setItems(names.toArray(new String[0]), (dialog, which) -> { + String id = paths.get(which).storePath; + ProgressDialog progressDialog = new ProgressDialog(context); + progressDialog.setTitle("正在导入..."); + progressDialog.setCancelable(false); + progressDialog.show(); + SyncUtils.async(() -> { + File[] f = new File(path).listFiles(); + for (File file : f) { + if (file.isDirectory()) continue; + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file.getAbsolutePath(),options); + if (options.outWidth > 0 && options.outHeight > 0){ + LocalDataHelper.LocalPicItems localItem = new LocalDataHelper.LocalPicItems(); + localItem.url = ""; + localItem.MD5 = DataUtils.getFileMD5(file); + localItem.addTime = System.currentTimeMillis(); + localItem.fileName = localItem.MD5; + + FileUtils.copy(file.getAbsolutePath(),LocalDataHelper.getLocalItemPath(paths.get(which),localItem)); + LocalDataHelper.addPicItem(id, localItem); + } + + } + SyncUtils.runOnUiThread(progressDialog::dismiss); + }); + }) + .setNegativeButton("创建全新的表情包", (dialog, which) -> { + EditText editText = new EditText(context); + new AlertDialog.Builder(context) + .setTitle("输入显示的名字") + .setView(editText) + .setNegativeButton("确定导入", (dialogaaa, whichaaa) -> { + ProgressDialog progressDialog = new ProgressDialog(context); + progressDialog.setTitle("正在导入..."); + progressDialog.setCancelable(false); + progressDialog.show(); + SyncUtils.async(() -> { + File[] f = new File(path).listFiles(); + if (f == null){ + SyncUtils.runOnUiThread(()->{ + progressDialog.dismiss(); + new AlertDialog.Builder(context) + .setTitle("错误") + .setMessage("路径无效") + .setPositiveButton("确定", null) + .show(); + }); + return; + } + String ID = RandomUtils.getRandomString(8); + LocalDataHelper.LocalPath newPath = new LocalDataHelper.LocalPath(); + newPath.storePath = ID; + newPath.coverName = ""; + newPath.Name = editText.getText().toString(); + LocalDataHelper.addPath(newPath); + + int size = f.length; + int finish = 0; + int available = 0; + for (File file : f) { + if (file.isFile()){ + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file.getAbsolutePath(),options); + if (options.outWidth > 0 && options.outHeight > 0){ + LocalDataHelper.LocalPicItems localItem = new LocalDataHelper.LocalPicItems(); + localItem.url = ""; + localItem.MD5 = DataUtils.getFileMD5(file); + localItem.addTime = System.currentTimeMillis(); + localItem.fileName = localItem.MD5; + localItem.thumbUrl = ""; + + + FileUtils.copy(file.getAbsolutePath(),LocalDataHelper.getLocalItemPath(newPath,localItem)); + + LocalDataHelper.addPicItem(ID,localItem); + available++; + } + } + finish++; + int finalFinish = finish; + int finalAvailable = available; + SyncUtils.runOnUiThread(()->{ + progressDialog.setMessage("已完成"+ finalFinish +"/"+size+"个文件,有效文件"+ finalAvailable +"个"); + }); + } + + List list = LocalDataHelper.getPicItems(ID); + if (list.size() > 0){ + LocalDataHelper.setPathCover(newPath,list.get(0)); + } + SyncUtils.runOnUiThread(()->{ + progressDialog.dismiss(); + new AlertDialog.Builder(context) + .setTitle("导入完成") + .setMessage("导入完成") + .setPositiveButton("确定", null) + .show(); + + ICreator.dismissAll(); + }); + }); + }).show(); + + }).show(); - SyncUtils.async(() -> { - File[] f = new File(path).listFiles(); - if (f == null) { - SyncUtils.runOnUiThread(() -> { - progress.dismiss(); - FaultyDialog.show(context, "错误", "路径无效"); - }); - return; - } - String ID = RandomUtils.getRandomString(8); - LocalDataHelper.LocalPath newPath = new LocalDataHelper.LocalPath(); - newPath.storePath = ID; - newPath.coverName = ""; - newPath.Name = name; - LocalDataHelper.addPath(newPath); - - int size = f.length; - int finish = 0; - int available = 0; - for (File file : f) { - if (file.isFile()) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(file.getAbsolutePath(), options); - if (options.outWidth > 0 && options.outHeight > 0) { - LocalDataHelper.LocalPicItems localItem = new LocalDataHelper.LocalPicItems(); - localItem.url = ""; - localItem.type = 1; - localItem.MD5 = DataUtils.getFileMD5(file); - localItem.addTime = System.currentTimeMillis(); - localItem.fileName = localItem.MD5; - localItem.thumbUrl = ""; - - FileUtils.copy(file.getAbsolutePath(), LocalDataHelper.getLocalItemPath(newPath, localItem)); - - LocalDataHelper.addPicItem(ID, localItem); - available++; - } - } - finish++; - int finalFinish = finish; - int finalAvailable = available; - SyncUtils.runOnUiThread(() -> progress.setTitle("已完成" + finalFinish + "/" + size + "个文件,有效文件" + finalAvailable + "个")); - } - List list = LocalDataHelper.getPicItems(ID); - if (list.size() > 0) { - LocalDataHelper.setPathCover(newPath, list.get(0)); - } - SyncUtils.runOnUiThread(() -> { - progress.dismiss(); - FaultyDialog.show(context, "导入完成", "导入完成"); - ICreator.dismissAll(); - }); - }); + + } + private static boolean isImageFile(String filePath) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(filePath, options); + if (options.outWidth == -1) { + return false; + } + return true; } @Override - public void onViewDestroy(ViewGroup parent) { + public void onViewDestroy() { } diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/LocalStickerImpl.java b/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/LocalStickerImpl.java index 4c83a6a0f4..265beb8384 100644 --- a/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/LocalStickerImpl.java +++ b/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/LocalStickerImpl.java @@ -2,67 +2,65 @@ import android.app.AlertDialog; import android.content.Context; -import android.text.TextUtils; -import android.util.Log; import android.view.View; import android.view.ViewGroup; +import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import cc.hicore.Env; -import cc.hicore.Utils.ContextUtils; -import cc.hicore.Utils.HttpUtils; +import cc.hicore.Utils.Async; +import cc.hicore.Utils.FunConf; +import cc.hicore.Utils.ImageUtils; +import cc.hicore.Utils.XLog; import cc.hicore.hook.stickerPanel.Hooker.StickerPanelEntryHooker; import cc.hicore.hook.stickerPanel.ICreator; import cc.hicore.hook.stickerPanel.LocalDataHelper; -import cc.hicore.hook.stickerPanel.MainPanelAdapter; import cc.hicore.hook.stickerPanel.RecentStickerHelper; import cc.hicore.message.chat.SessionUtils; import cc.hicore.message.common.MsgSender; +import cc.hicore.ui.SimpleDragSortView; import cc.ioctl.util.HostInfo; import cc.ioctl.util.LayoutHelper; import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.lxj.xpopup.XPopup; -import com.lxj.xpopup.impl.LoadingPopupView; -import com.tencent.qqnt.kernel.nativeinterface.Contact; -import de.robv.android.xposed.XposedBridge; import io.github.qauxv.R; -import io.github.qauxv.ui.CommonContextWrapper; -import io.github.qauxv.util.SyncUtils; import io.github.qauxv.util.Toasts; import java.io.File; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicInteger; -public class LocalStickerImpl implements MainPanelAdapter.IMainPanelItem { +public class LocalStickerImpl implements ICreator.IMainPanelItem { ViewGroup cacheView; Context mContext; LinearLayout panelContainer; HashSet cacheImageView = new HashSet<>(); TextView tv_title; - LocalDataHelper.LocalPath mPathInfo; + LocalDataHelper.LocalPath mPackInfo; List mPicItems; - public LocalStickerImpl(LocalDataHelper.LocalPath pathInfo, List picItems, Context mContext) { - mPathInfo = pathInfo; - mPicItems = picItems; + View setButton; + + int showControlType; + boolean dontAutoClose; + boolean isCreated; + + public LocalStickerImpl(LocalDataHelper.LocalPath pathInfo, Context mContext) { + mPackInfo = pathInfo; this.mContext = mContext; cacheView = (ViewGroup) View.inflate(mContext, R.layout.sticker_panel_plus_pack_item, null); tv_title = cacheView.findViewById(R.id.Sticker_Panel_Item_Name); panelContainer = cacheView.findViewById(R.id.Sticker_Item_Container); - tv_title.setText(mPathInfo.Name); + tv_title.setText(mPackInfo.Name); - View setButton = cacheView.findViewById(R.id.Sticker_Panel_Set_Item); + setButton = cacheView.findViewById(R.id.Sticker_Panel_Set_Item); setButton.setOnClickListener(v -> onSetButtonClick()); - + } + private void createMainView(){ try { + mPicItems = LocalDataHelper.getPicItems(mPackInfo.storePath); LinearLayout itemLine = null; for (int i = 0; i < mPicItems.size(); i++) { LocalDataHelper.LocalPicItems item = mPicItems.get(i); @@ -72,102 +70,94 @@ public LocalStickerImpl(LocalDataHelper.LocalPath pathInfo, List { if (which == 0) { - new AlertDialog.Builder(CommonContextWrapper.createAppCompatContext(mContext)) + new AlertDialog.Builder(mContext) .setTitle("提示") .setMessage("是否删除该表情包(" + tv_title.getText() + "),该表情包内的本地表情将被删除并不可恢复") .setNeutralButton("确定删除", (dialog1, which1) -> { - LocalDataHelper.deletePath(mPathInfo); + LocalDataHelper.deletePath(mPackInfo); ICreator.dismissAll(); }) .setNegativeButton("取消", (dialog12, which12) -> { }).show(); - } else if (which == 1) { - updateAllResToLocal(); - } - }).show(); - } - - private void updateAllResToLocal() { - LoadingPopupView progress = new XPopup.Builder(ContextUtils.getFixContext(CommonContextWrapper.createAppCompatContext(mContext))) - .dismissOnBackPressed(false) - .dismissOnTouchOutside(false) - .asLoading("正在更新表情包,请稍等..."); - - progress.show(); - new Thread(() -> { - try { - ExecutorService threadPool = Executors.newFixedThreadPool(8); - AtomicInteger finishCount = new AtomicInteger(); - CountDownLatch latch = new CountDownLatch(mPicItems.size()); - for (LocalDataHelper.LocalPicItems item : mPicItems) { - threadPool.execute(() -> { - try { - if (item.url.startsWith("http")) { - String localStorePath = LocalDataHelper.getLocalItemPath(mPathInfo, item); - if (!TextUtils.isEmpty(localStorePath)) { - if (cc.hicore.Utils.HttpUtils.DownloadToFile(item.url, localStorePath)){ - item.type = 1; - item.fileName = item.MD5; - - - if (!TextUtils.isEmpty(item.thumbUrl)) { - String localThumbPath = LocalDataHelper.getLocalThumbPath(mPathInfo, item); - HttpUtils.DownloadToFile(item.thumbUrl, localThumbPath); - item.thumbName = item.MD5 + "_thumb"; + }else if (which == 1){ + EditText editText = new EditText(mContext); + editText.setHint("输入表情包的名字"); + editText.setText(mPackInfo.Name); + new AlertDialog.Builder(mContext) + .setTitle("修改表情包名字") + .setView(editText) + .setNegativeButton("确定修改", (dialog1, which1) -> { + String text = editText.getText().toString(); + if (text.isEmpty()){ + Toasts.show("输入的名字不能为空"); + return; + } + mPackInfo.Name = text; + LocalDataHelper.setPathName(mPackInfo, text); + ICreator.dismissAll(); + }) + .show(); + }else if (which == 2){ + Async.runAsyncLoading(mContext,"正在处理图片中...",()->{ + ArrayList fileList = new ArrayList<>(); + int width_item = LayoutHelper.getScreenWidth(mContext) / 6; + for (LocalDataHelper.LocalPicItems item : mPicItems) { + fileList.add(ImageUtils.getResizePicPath(LocalDataHelper.getLocalItemPath(mPackInfo, item),width_item)); + } + ArrayList sourceInfo = new ArrayList<>(); + for (LocalDataHelper.LocalPicItems item : mPicItems) { + sourceInfo.add(item.MD5); + } + Async.runOnUi(()-> SimpleDragSortView.createDrag(mContext,fileList,sourceInfo,()->{ + for (LocalDataHelper.LocalPicItems item : mPicItems){ + LocalDataHelper.deletePicLog(mPackInfo,item); + } + for (int i = 0; i < sourceInfo.size(); i++) { + for (LocalDataHelper.LocalPicItems item : mPicItems){ + if (item.MD5.equals(sourceInfo.get(i))){ + LocalDataHelper.addPicItem(mPackInfo.storePath,item); } - LocalDataHelper.updatePicItemInfo(mPathInfo, item); } - - } - } - } catch (Exception e) { - XposedBridge.log(Log.getStackTraceString(e)); - } finally { - latch.countDown(); - SyncUtils.runOnUiThread(() -> progress.setTitle("正在更新表情包,请稍等...(" + finishCount.getAndIncrement() + "/" + mPicItems.size() + ")")); - } - }); - } - latch.await(); - SyncUtils.runOnUiThread(progress::dismiss); - Toasts.info(mContext,"已更新完成"); - SyncUtils.runOnUiThread(ICreator::dismissAll); + ICreator.dismissAll(); + })); - } catch (Exception ignored) { - } - }).start(); + }); + + } + }).show(); } @Override - public View getView(ViewGroup parent) { - onViewDestroy(null); + public View getView() { + if (!isCreated) { + createMainView(); + } + showControlType = FunConf.getInt("global", "sticker_panel_set_rb_show_anim", 1); + dontAutoClose = FunConf.getBoolean("global", "sticker_panel_set_dont_close_panel", false); + onViewDestroy(); return cacheView; } private View getItemContainer(Context context, String coverView, int count, LocalDataHelper.LocalPicItems item) { - int sizeLength = LayoutHelper.getScreenWidth(context) / 6; - int item_distance = (LayoutHelper.getScreenWidth(context) - sizeLength * 5) / 4; + int width_item = LayoutHelper.getScreenWidth(context) / 6; + int item_distance = (LayoutHelper.getScreenWidth(context) - width_item * 5) / 4; ImageView img = new ImageView(context); ViewInfo info = new ViewInfo(); @@ -176,48 +166,46 @@ private View getItemContainer(Context context, String coverView, int count, Loca cacheImageView.add(info); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(sizeLength, sizeLength); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width_item, width_item); if (count > 0) params.leftMargin = item_distance; img.setLayoutParams(params); img.setTag(coverView); img.setOnClickListener(v -> { - if (coverView.startsWith("http://") || coverView.startsWith("https://")) { - HttpUtils.ProgressDownload(coverView, Env.app_save_path + "Cache/" + coverView.substring(coverView.lastIndexOf("/")), () -> { - MsgSender.send_pic_by_contact(SessionUtils.AIOParam2Contact(StickerPanelEntryHooker.AIOParam), Env.app_save_path + "Cache/" + coverView.substring(coverView.lastIndexOf("/"))); - RecentStickerHelper.addPicItemToRecentRecord(mPathInfo, item); - }, mContext); + MsgSender.send_pic_by_contact(SessionUtils.AIOParam2Contact(StickerPanelEntryHooker.AIOParam), + LocalDataHelper.getLocalItemPath(mPackInfo, item)); + RecentStickerHelper.addPicItemToRecentRecord(mPackInfo, item); + if (!dontAutoClose){ ICreator.dismissAll(); - - } else { - Contact contact = SessionUtils.AIOParam2Contact(StickerPanelEntryHooker.AIOParam); - if (contact != null){ - MsgSender.send_pic_by_contact(contact, - LocalDataHelper.getLocalItemPath(mPathInfo, item)); - RecentStickerHelper.addPicItemToRecentRecord(mPathInfo, item); - ICreator.dismissAll(); - } - } + }); img.setOnLongClickListener(v -> { ImageView preView = new ImageView(context); preView.setScaleType(ImageView.ScaleType.FIT_CENTER); - preView.setLayoutParams(new ViewGroup.LayoutParams(LayoutHelper.getScreenWidth(v.getContext()) / 2, LayoutHelper.getScreenWidth(v.getContext()) / 2)); - if (coverView.startsWith("http://") || coverView.startsWith("https://")) { - Glide.with(HostInfo.getApplication()).load(coverView).override(LayoutHelper.getScreenWidth(v.getContext()) / 2, LayoutHelper.getScreenWidth(v.getContext()) / 2).into(preView); - } else { - Glide.with(HostInfo.getApplication()).load(coverView).fitCenter().diskCacheStrategy(DiskCacheStrategy.RESOURCE).override(LayoutHelper.getScreenWidth(v.getContext()) / 2, LayoutHelper.getScreenWidth(v.getContext()) / 2).into(preView); - } - new AlertDialog.Builder(CommonContextWrapper.createAppCompatContext(mContext)) + preView.setLayoutParams(new ViewGroup.LayoutParams(LayoutHelper.getScreenWidth(HostInfo.getApplication()) / 2, LayoutHelper.getScreenWidth(HostInfo.getApplication()) / 2)); + Glide.with(HostInfo.getApplication()).load(coverView).fitCenter().into(preView); + new AlertDialog.Builder(mContext) .setTitle("选择你对该表情的操作") .setView(preView) - .setOnDismissListener(dialog -> Glide.with(HostInfo.getApplication()).clear(preView)).setNegativeButton("删除该表情", (dialog, which) -> { - LocalDataHelper.deletePicItem(mPathInfo, item); - ICreator.dismissAll(); + .setOnDismissListener(dialog -> { + Glide.with(HostInfo.getApplication()).clear(preView); + }).setNegativeButton("删除该表情", (dialog, which) -> { + LocalDataHelper.deletePicItem(mPackInfo, item); + + mPicItems.remove(item); + + cacheImageView.clear(); + panelContainer.removeAllViews(); + + onViewDestroy(); + createMainView(); + Async.runOnUi(this::notifyViewUpdate0); + + }).setNeutralButton("设置为标题预览", (dialog, which) -> { - LocalDataHelper.setPathCover(mPathInfo, item); + LocalDataHelper.setPathCover(mPackInfo, item); ICreator.dismissAll(); }).show(); return true; @@ -228,34 +216,53 @@ private View getItemContainer(Context context, String coverView, int count, Loca } @Override - public void onViewDestroy(ViewGroup parent) { + public void onViewDestroy() { for (ViewInfo img : cacheImageView) { img.view.setImageBitmap(null); img.status = 0; Glide.with(HostInfo.getApplication()).clear(img.view); } + } @Override public long getID() { - return 0; + return mPackInfo.storePath.hashCode(); } @Override public void notifyViewUpdate0() { for (ViewInfo v : cacheImageView) { + XLog.d("NotifyUpdate","update->"+LayoutHelper.isSmallWindowNeedPlay(v.view)); if (LayoutHelper.isSmallWindowNeedPlay(v.view)) { if (v.status != 1) { v.status = 1; - int item_size = LayoutHelper.getScreenWidth(mContext) / 6; + String coverView = (String) v.view.getTag(); - if (coverView.startsWith("http://") || coverView.startsWith("https://")) { - Glide.with(HostInfo.getApplication()).load(coverView).override(item_size, item_size).into(v.view); - } else { - if(new File(coverView + "_thumb").exists()){ - Glide.with(HostInfo.getApplication()).load(coverView + "_thumb").fitCenter().diskCacheStrategy(DiskCacheStrategy.RESOURCE).override(item_size, item_size).into(v.view); - }else { - Glide.with(HostInfo.getApplication()).load(coverView).fitCenter().diskCacheStrategy(DiskCacheStrategy.RESOURCE).override(item_size, item_size).into(v.view); + if(new File(coverView + "_thumb").exists()){ + if (showControlType == 0){ + Glide.with(HostInfo.getApplication()).load(coverView + "_thumb").skipMemoryCache(true).fitCenter().into(v.view); + }else if (showControlType == 1){ + if (new File(coverView + "_thumb").length() > 2 * 1024 * 1024){ + Glide.with(HostInfo.getApplication()).load(coverView + "_thumb").dontAnimate().skipMemoryCache(true).fitCenter().into(v.view); + }else { + Glide.with(HostInfo.getApplication()).load(coverView + "_thumb").skipMemoryCache(true).fitCenter().into(v.view); + } + }else if (showControlType == 2){ + Glide.with(HostInfo.getApplication()).load(coverView + "_thumb").dontAnimate().skipMemoryCache(true).fitCenter().into(v.view); + } + + }else { + if (showControlType == 0){ + Glide.with(HostInfo.getApplication()).load(coverView).skipMemoryCache(true).fitCenter().into(v.view); + }else if (showControlType == 1){ + if (new File(coverView).length() > 2 * 1024 * 1024){ + Glide.with(HostInfo.getApplication()).load(coverView).dontAnimate().skipMemoryCache(true).fitCenter().into(v.view); + }else { + Glide.with(HostInfo.getApplication()).load(coverView).skipMemoryCache(true).fitCenter().into(v.view); + } + }else if (showControlType == 2){ + Glide.with(HostInfo.getApplication()).load(coverView).dontAnimate().skipMemoryCache(true).fitCenter().into(v.view); } } } @@ -265,8 +272,6 @@ public void notifyViewUpdate0() { Glide.with(HostInfo.getApplication()).clear(v.view); v.status = 0; } - - } } } diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/OnlinePreviewItemImpl.java b/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/OnlinePreviewItemImpl.java new file mode 100644 index 0000000000..b1edc008a5 --- /dev/null +++ b/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/OnlinePreviewItemImpl.java @@ -0,0 +1,4 @@ +package cc.hicore.hook.stickerPanel.MainItemImpl; + +public class OnlinePreviewItemImpl { +} diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/PanelSetImpl.java b/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/PanelSetImpl.java new file mode 100644 index 0000000000..996d3879b4 --- /dev/null +++ b/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/PanelSetImpl.java @@ -0,0 +1,146 @@ +package cc.hicore.hook.stickerPanel.MainItemImpl; + +import android.content.Context; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.RadioButton; +import cc.hicore.Env; +import cc.hicore.Utils.Async; +import cc.hicore.Utils.FunConf; +import cc.hicore.Utils.ImageUtils; +import cc.hicore.hook.stickerPanel.ICreator; +import cc.hicore.hook.stickerPanel.LocalDataHelper; +import cc.hicore.ui.SimpleDragSortView; +import cc.ioctl.util.LayoutHelper; +import io.github.qauxv.R; +import java.util.ArrayList; +import java.util.List; + +public class PanelSetImpl implements ICreator.IMainPanelItem { + private Context mContext; + private ViewGroup cacheView; + public PanelSetImpl(Context context) { + mContext = context; + cacheView = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.sticker_panel_set,null); + + EditText edit_show_title = cacheView.findViewById(R.id.sticker_panel_set_ed_change_title); + edit_show_title.setEnabled(FunConf.getBoolean("global", "sticker_panel_set_ch_change_title", false)); + edit_show_title.setText(FunConf.getString("global", "sticker_panel_set_ed_change_title", "")); + edit_show_title.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) { + FunConf.setString("global", "sticker_panel_set_ed_change_title", s.toString()); + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + + CheckBox open_show_title = cacheView.findViewById(R.id.sticker_panel_set_ch_change_title); + open_show_title.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (buttonView.isPressed()){ + edit_show_title.setEnabled(isChecked); + FunConf.setBoolean("global", "sticker_panel_set_ch_change_title", isChecked); + } + }); + open_show_title.setChecked(FunConf.getBoolean("global", "sticker_panel_set_ch_change_title", false)); + + + + + CheckBox dont_auto_close = cacheView.findViewById(R.id.sticker_panel_set_dont_close_panel); + dont_auto_close.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (buttonView.isPressed()){ + FunConf.setBoolean("global", "sticker_panel_set_dont_close_panel", isChecked); + } + }); + dont_auto_close.setChecked(FunConf.getBoolean("global", "sticker_panel_set_dont_close_panel", false)); + + RadioButton show_anim_always = cacheView.findViewById(R.id.sticker_panel_set_rb_show_anim_always); + show_anim_always.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (buttonView.isPressed()){ + FunConf.setInt("global", "sticker_panel_set_rb_show_anim", 0); + } + }); + show_anim_always.setChecked(FunConf.getInt("global", "sticker_panel_set_rb_show_anim", 1) == 0); + + RadioButton show_anim_need = cacheView.findViewById(R.id.sticker_panel_set_rb_show_anim_need); + show_anim_need.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (buttonView.isPressed()){ + FunConf.setInt("global", "sticker_panel_set_rb_show_anim", 1); + } + }); + show_anim_need.setChecked(FunConf.getInt("global", "sticker_panel_set_rb_show_anim", 1) == 1); + + RadioButton show_anim_never = cacheView.findViewById(R.id.sticker_panel_set_rb_show_never); + show_anim_never.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (buttonView.isPressed()){ + FunConf.setInt("global", "sticker_panel_set_rb_show_anim", 2); + } + }); + show_anim_never.setChecked(FunConf.getInt("global", "sticker_panel_set_rb_show_anim", 1) == 2); + + Button sort_group = cacheView.findViewById(R.id.sticker_panel_set_btn_sort_group); + sort_group.setOnClickListener(v->{ + Async.runAsyncLoading(mContext,"正在处理图片",()->{ + List paths = LocalDataHelper.readPaths(); + ArrayList picPaths = new ArrayList<>(); + ArrayList sources = new ArrayList<>(); + for (LocalDataHelper.LocalPath path : paths) { + sources.add(path.storePath); + int width_item = LayoutHelper.getScreenWidth(mContext) / 6; + picPaths.add(ImageUtils.getResizePicPath(Env.app_save_path + "本地表情包/" + path.storePath + "/" + path.coverName,width_item)); + } + Async.runOnUi(()->{ + SimpleDragSortView.createDrag(mContext,picPaths,sources,()->{ + for (LocalDataHelper.LocalPath path : paths){ + LocalDataHelper.deletePathName(path); + } + for (int i = 0; i < sources.size(); i++) { + for (LocalDataHelper.LocalPath path : paths){ + if (path.storePath.equals(sources.get(i))){ + LocalDataHelper.addPath(path); + } + } + } + ICreator.dismissAll(); + }); + + }); + }); + + }); + } + @Override + public View getView() { + return cacheView; + } + + @Override + public void onViewDestroy() { + + } + + @Override + public long getID() { + return 10010; + } + + @Override + public void notifyViewUpdate0() { + + } +} diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/RecentStickerImpl.java b/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/RecentStickerImpl.java index 42c7b0e139..a75be8f517 100644 --- a/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/RecentStickerImpl.java +++ b/app/src/main/java/cc/hicore/hook/stickerPanel/MainItemImpl/RecentStickerImpl.java @@ -1,19 +1,15 @@ package cc.hicore.hook.stickerPanel.MainItemImpl; import android.content.Context; -import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import cc.hicore.Env; -import cc.hicore.Utils.ContextUtils; -import cc.hicore.Utils.HttpUtils; +import cc.hicore.Utils.FunConf; import cc.hicore.hook.stickerPanel.Hooker.StickerPanelEntryHooker; import cc.hicore.hook.stickerPanel.ICreator; import cc.hicore.hook.stickerPanel.LocalDataHelper; -import cc.hicore.hook.stickerPanel.MainPanelAdapter; import cc.hicore.hook.stickerPanel.RecentStickerHelper; import cc.hicore.message.chat.SessionUtils; import cc.hicore.message.common.MsgSender; @@ -21,14 +17,12 @@ import cc.ioctl.util.LayoutHelper; import com.bumptech.glide.Glide; import com.lxj.xpopup.XPopup; -import com.tencent.qqnt.kernel.nativeinterface.Contact; -import de.robv.android.xposed.XposedBridge; import io.github.qauxv.R; import java.io.File; import java.util.HashSet; import java.util.List; -public class RecentStickerImpl implements MainPanelAdapter.IMainPanelItem { +public class RecentStickerImpl implements ICreator.IMainPanelItem { ViewGroup cacheView; Context mContext; LinearLayout panelContainer; @@ -37,6 +31,8 @@ public class RecentStickerImpl implements MainPanelAdapter.IMainPanelItem { List items; + boolean dontAutoClose; + public RecentStickerImpl(Context mContext) { this.mContext = mContext; items = RecentStickerHelper.getAllRecentRecord(); @@ -47,9 +43,14 @@ public RecentStickerImpl(Context mContext) { tv_title.setText("最近使用"); View setButton = cacheView.findViewById(R.id.Sticker_Panel_Set_Item); - setButton.setOnClickListener(v-> new XPopup.Builder(ContextUtils.getFixContext(mContext)) - .asConfirm("提示", "是否要清除最近的表情记录?", RecentStickerHelper::cleanAllRecentRecord) - .show()); + setButton.setOnClickListener(v->{ + new XPopup.Builder(mContext) + .asConfirm("提示", "是否要清除最近发送列表", () -> { + RecentStickerHelper.cleanAllRecentRecord(); + ICreator.dismissAll(); + }) + .show(); + }); try { LinearLayout itemLine = null; @@ -58,82 +59,54 @@ public RecentStickerImpl(Context mContext) { if (i % 5 == 0) { itemLine = new LinearLayout(mContext); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - params.bottomMargin = LayoutHelper.dip2px(mContext, 16); + params.bottomMargin = (int) LayoutHelper.dip2px(mContext, 16); panelContainer.addView(itemLine, params); } - if (item.type == 2) { - itemLine.addView(getItemContainer(mContext, item.url, i % 5, item)); - } else if (item.type == 1) { - itemLine.addView(getItemContainer(mContext, LocalDataHelper.getLocalItemPath(item), i % 5, item)); - } + itemLine.addView(getItemContainer(mContext, LocalDataHelper.getLocalItemPath(item), i % 5, item)); } } catch (Exception e) { - XposedBridge.log(Log.getStackTraceString(e)); + } } @Override - public View getView(ViewGroup parent) { - onViewDestroy(null); + public View getView() { notifyDataSetChanged(); - + dontAutoClose = FunConf.getBoolean("global", "sticker_panel_set_dont_close_panel", false); return cacheView; } private void notifyDataSetChanged() { for (ImageView img : cacheImageView) { String coverView = (String) img.getTag(); - if (coverView.startsWith("http://") || coverView.startsWith("https://")) { + if (new File(coverView+"_thumb").exists()){ + Glide.with(HostInfo.getApplication()).load(coverView+"_thumb").into(img); + }else { Glide.with(HostInfo.getApplication()).load(coverView).into(img); - } else { - if (new File(coverView+"_thumb").exists()){ - Glide.with(HostInfo.getApplication()).load(coverView+"_thumb").into(img); - }else { - Glide.with(HostInfo.getApplication()).load(coverView).into(img); - } - } } } private View getItemContainer(Context context, String coverView, int count, RecentStickerHelper.RecentItemInfo item) { - int item_size = LayoutHelper.getScreenWidth(context) / 6; - int item_distance = (LayoutHelper.getScreenWidth(context) - item_size * 5) / 4; + int width_item = LayoutHelper.getScreenWidth(context) / 6; + int item_distance = (LayoutHelper.getScreenWidth(context) - width_item * 5) / 4; ImageView img = new ImageView(context); cacheImageView.add(img); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(item_size, item_size); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width_item, width_item); if (count > 0) params.leftMargin = item_distance; img.setLayoutParams(params); img.setTag(coverView); img.setOnClickListener(v -> { - if (coverView.startsWith("http://") || coverView.startsWith("https://")) { - HttpUtils.ProgressDownload(coverView, Env.app_save_path + "Cache/" + coverView.substring(coverView.lastIndexOf("/")), () -> { - Contact contact = SessionUtils.AIOParam2Contact(StickerPanelEntryHooker.AIOParam); - if (contact != null){ - MsgSender.send_pic_by_contact(contact, - Env.app_save_path + "Cache/" + coverView.substring(coverView.lastIndexOf("/"))); - - RecentStickerHelper.addPicItemToRecentRecord(item); - } - - }, mContext); + MsgSender.send_pic_by_contact(SessionUtils.AIOParam2Contact(StickerPanelEntryHooker.AIOParam), coverView); + RecentStickerHelper.addPicItemToRecentRecord(item); + if (!dontAutoClose){ ICreator.dismissAll(); - - } else { - - Contact contact = SessionUtils.AIOParam2Contact(StickerPanelEntryHooker.AIOParam); - if (contact != null){ - MsgSender.send_pic_by_contact(contact, - coverView); - RecentStickerHelper.addPicItemToRecentRecord(item); - ICreator.dismissAll(); - } - } + }); return img; @@ -141,7 +114,7 @@ private View getItemContainer(Context context, String coverView, int count, Rece } @Override - public void onViewDestroy(ViewGroup parent) { + public void onViewDestroy() { for (ImageView img : cacheImageView) { img.setImageBitmap(null); Glide.with(HostInfo.getApplication()).clear(img); @@ -150,7 +123,7 @@ public void onViewDestroy(ViewGroup parent) { @Override public long getID() { - return 0; + return 1234; } @Override diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/MainPanelAdapter.java b/app/src/main/java/cc/hicore/hook/stickerPanel/MainPanelAdapter.java deleted file mode 100644 index ea4787b65d..0000000000 --- a/app/src/main/java/cc/hicore/hook/stickerPanel/MainPanelAdapter.java +++ /dev/null @@ -1,72 +0,0 @@ -package cc.hicore.hook.stickerPanel; - -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import java.util.ArrayList; - -public class MainPanelAdapter extends BaseAdapter { - ArrayList viewData = new ArrayList<>(); - volatile long timeStart; - - @Override - public int getCount() { - return viewData.size(); - } - - public int addItemData(IMainPanelItem item) { - viewData.add(item); - return viewData.size() - 1; - } - - @Override - public Object getItem(int position) { - return viewData.get(position); - } - - @Override - public long getItemId(int position) { - return viewData.get(position).getID(); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView != null) { - Object tag = convertView.getTag(); - if (tag instanceof IMainPanelItem) { - IMainPanelItem item = (IMainPanelItem) tag; - item.onViewDestroy(parent); - } - } - View retView = viewData.get(position).getView(parent); - retView.setTag(getItem(position)); - return retView; - } - - public void destroyAllViews() { - for (IMainPanelItem item : viewData) { - item.onViewDestroy(null); - } - } - - public void notifyViewUpdate(int first, int last) { - if (System.currentTimeMillis() - timeStart > 150) { - timeStart = System.currentTimeMillis(); - for (int i = first; i < last + 1; i++) { - IMainPanelItem item = viewData.get(i); - item.notifyViewUpdate0(); - } - } - - } - public interface IMainPanelItem { - View getView(ViewGroup parent); - - void onViewDestroy(ViewGroup parent); - - long getID(); - - void notifyViewUpdate0(); - } - -} diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/PanelUtils.java b/app/src/main/java/cc/hicore/hook/stickerPanel/PanelUtils.java index 02a5b8c94e..a5640faeca 100644 --- a/app/src/main/java/cc/hicore/hook/stickerPanel/PanelUtils.java +++ b/app/src/main/java/cc/hicore/hook/stickerPanel/PanelUtils.java @@ -16,7 +16,6 @@ import cc.ioctl.util.HostInfo; import com.bumptech.glide.Glide; import io.github.qauxv.R; -import io.github.qauxv.lifecycle.Parasitics; import io.github.qauxv.util.Toasts; import java.io.File; import java.util.ArrayList; @@ -30,7 +29,6 @@ public class PanelUtils { //显示确认保存的对话框 public static void PreSavePicToList(String URL, String MD5, Context context) { choicePath = null; - Parasitics.injectModuleResources(context.getResources()); LayoutInflater inflater = LayoutInflater.from(context); LinearLayout mRoot = (LinearLayout) inflater.inflate(R.layout.sticker_pre_save, null); ImageView preView = mRoot.findViewById(R.id.emo_pre_container); @@ -41,10 +39,12 @@ public static void PreSavePicToList(String URL, String MD5, Context context) { NewInfo.MD5 = MD5.toUpperCase(Locale.ROOT); if (URL.startsWith("http")) { - EmoOnlineLoader.submit(NewInfo, () -> Glide.with(HostInfo.getApplication()) - .load(new File(NewInfo.Path)) - .fitCenter() - .into(preView)); + EmoOnlineLoader.submit(NewInfo, () -> { + Glide.with(HostInfo.getApplication()) + .load(new File(NewInfo.Path)) + .fitCenter() + .into(preView); + }); } else { NewInfo.Path = URL; Glide.with(HostInfo.getApplication()) @@ -59,8 +59,8 @@ public static void PreSavePicToList(String URL, String MD5, Context context) { for (LocalDataHelper.LocalPath path : paths) { RadioButton button = new RadioButton(context); button.setText(path.Name); - button.setTextColor(context.getColor(R.color.font_plugin)); button.setTextSize(16); + button.setTextColor(context.getResources().getColor(R.color.global_font_color, null)); button.setOnCheckedChangeListener((v, ischeck) -> { if (v.isPressed() && ischeck) { choicePath = path; @@ -73,12 +73,12 @@ public static void PreSavePicToList(String URL, String MD5, Context context) { btnCreate.setOnClickListener(v -> { EditText edNew = new EditText(context); new AlertDialog.Builder(context) - .setTitle("创建分组") + .setTitle("创建新目录") .setView(edNew) - .setPositiveButton("确定创建", (dialog, which) -> { + .setNeutralButton("确定创建", (dialog, which) -> { String newName = edNew.getText().toString(); if (TextUtils.isEmpty(newName)) { - Toasts.info(v.getContext(),"名称不能为空"); + Toasts.show("名字不能为空"); return; } LocalDataHelper.LocalPath path = new LocalDataHelper.LocalPath(); @@ -93,7 +93,7 @@ public static void PreSavePicToList(String URL, String MD5, Context context) { RadioButton button = new RadioButton(context); button.setText(pathItem.Name); button.setTextSize(16); - button.setTextColor(context.getColor(R.color.font_plugin)); + button.setTextColor(context.getResources().getColor(R.color.global_font_color, null)); button.setOnCheckedChangeListener((vaa, ischeck) -> { if (vaa.isPressed() && ischeck) { choicePath = pathItem; @@ -102,42 +102,41 @@ public static void PreSavePicToList(String URL, String MD5, Context context) { group.addView(button); } }) - .setNeutralButton("取消", null) .show(); }); new AlertDialog.Builder(context) - .setTitle("选择保存分组") + .setTitle("是否保存") .setView(mRoot) - .setPositiveButton("保存", (dialog, which) -> { + .setNegativeButton("保存", (dialog, which) -> { if (choicePath == null) { - Toasts.info(context,"没有选择分组"); + Toasts.show("没有选择任何的保存列表"); } else if (TextUtils.isEmpty(NewInfo.Path)) { - Toasts.info(context,"图片尚未加载完毕,保存失败"); + Toasts.show("图片尚未加载完毕,保存失败"); } else { FileUtils.copy(NewInfo.Path, Env.app_save_path + "本地表情包/" + choicePath.storePath + "/" + MD5); LocalDataHelper.LocalPicItems item = new LocalDataHelper.LocalPicItems(); - item.type = 1; item.MD5 = MD5; item.fileName = MD5; item.addTime = System.currentTimeMillis(); LocalDataHelper.addPicItem(choicePath.storePath, item); - Toasts.info(context,"已保存到:" + Env.app_save_path + "本地表情包/" + choicePath.storePath + "/" + MD5); + Toasts.show("已保存到:" + Env.app_save_path + "本地表情包/" + choicePath.storePath + "/" + MD5); } - }).setNeutralButton("取消", null) - .setOnDismissListener(dialog -> Glide.with(HostInfo.getApplication()).clear(preView)) - .show(); + }).setOnDismissListener(dialog -> { + Glide.with(HostInfo.getApplication()).clear(preView); + }).show(); } //如果要保存的是多张图片则弹出MD5选择,选择后才弹出确认图片保存框 public static void PreSaveMultiPicList(ArrayList url, ArrayList MD5, Context context) { new AlertDialog.Builder(context) .setTitle("选择需要保存的图片") - .setItems(MD5.toArray(new String[0]), (dialog, which) -> PreSavePicToList(url.get(which), MD5.get(which), context)) - .setOnDismissListener(dialog -> { + .setItems(MD5.toArray(new String[0]), (dialog, which) -> { + PreSavePicToList(url.get(which), MD5.get(which), context); + }).setOnDismissListener(dialog -> { }).show(); } diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/QQGifDecoder.java b/app/src/main/java/cc/hicore/hook/stickerPanel/QQGifDecoder.java new file mode 100644 index 0000000000..20e616331b --- /dev/null +++ b/app/src/main/java/cc/hicore/hook/stickerPanel/QQGifDecoder.java @@ -0,0 +1,138 @@ +/* + * QAuxiliary - An Xposed module for QQ/TIM + * Copyright (C) 2019-2022 qwq233@qwq2333.top + * https://github.com/cinit/QAuxiliary + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version and our eula as published + * by QAuxiliary contributors. + * + * This software 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * . + */ + +package cc.hicore.hook.stickerPanel; + +import android.os.Environment; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.Locale; + +public class QQGifDecoder { + private static final byte[] GIFMask = {0, 1, 0, 1}; + + private static int conVertToInt(byte[] b) { + int i = 0; + int pos = 0; + while (pos < b.length && pos < 4) { + i += b[pos] << (pos * 8); + pos++; + } + return i; + } + + public static String decodeGifForLocalPath(int dwTabID, byte[] sbufID) { + try { + String path = Environment.getExternalStorageDirectory() + "/Android/data/com.tencent.mobileqq/Tencent/MobileQQ/.emotionsm/" + dwTabID + "/" + + bytesToHex(sbufID); + String cachePath = Environment.getExternalStorageDirectory() + "/Android/data/com.tencent.mobileqq/cache/" + bytesToHex(sbufID); + decodeGif(path, cachePath); + return cachePath; + } catch (Exception e) { + return ""; + } + } + + public static synchronized void decodeGif(String source, String dest) { + try { + new File(dest).delete(); + FileOutputStream out = new FileOutputStream(dest); + FileInputStream fInp = new FileInputStream(source); + byte[] headerMask = new byte[6]; + int read = fInp.read(headerMask); + if (read != 6) { + out.close(); + fInp.close(); + return; + } + decodeByteMask(headerMask, 0); + out.write(headerMask); + + /**************************/ + headerMask = new byte[2]; + fInp.read(headerMask); + decodeByteMask(headerMask, 6); + out.write(headerMask); + /**************************/ + headerMask = new byte[3]; + fInp.read(headerMask); + decodeByteMask(headerMask, 6 + 2); + out.write(headerMask); + int decodeLength = getNeedDecodeLength(1 << ((conVertToInt(headerMask) & 7) + 1)); + /**************************/ + byte[] needDecode = new byte[decodeLength]; + fInp.read(needDecode); + decodeByteMask(needDecode, 6 + 2 + 3); + out.write(needDecode); + /**************************/ + byte[] rest = new byte[1024]; + while ((read = fInp.read(rest)) != -1) { + out.write(rest, 0, read); + } + + out.close(); + fInp.close(); + + + } catch (Exception e) { + + } + } + + //必须取偶数 + private static void decodeByteMask(byte[] data, int pos) { + for (int i = 0; i < data.length; i++) { + data[i] ^= GIFMask[(i + pos) % 4]; + } + } + + private static int getNeedDecodeLength(int checkInt) { + int mask = getMaskL(checkInt); + if (checkInt != 1 << mask) return 0; + return mask * 3; + } + + private static int getMaskL(int checkInt) { + if (checkInt <= 2) return 1; + if (checkInt <= 4) return 2; + if (checkInt <= 8) return 3; + if (checkInt <= 16) return 4; + if (checkInt <= 32) return 5; + if (checkInt <= 64) return 6; + if (checkInt <= 128) return 7; + if (checkInt <= 256) return 8; + return 9; + } + + private static String bytesToHex(byte[] bytes) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < bytes.length; i++) { + String hex = Integer.toHexString(bytes[i] & 0xFF); + if (hex.length() < 2) { + sb.append(0); + } + sb.append(hex); + } + return sb.toString().toLowerCase(Locale.ROOT); + } +} diff --git a/app/src/main/java/cc/hicore/hook/stickerPanel/RecentStickerHelper.java b/app/src/main/java/cc/hicore/hook/stickerPanel/RecentStickerHelper.java index c253afbceb..c7143e1348 100644 --- a/app/src/main/java/cc/hicore/hook/stickerPanel/RecentStickerHelper.java +++ b/app/src/main/java/cc/hicore/hook/stickerPanel/RecentStickerHelper.java @@ -11,8 +11,9 @@ public class RecentStickerHelper { public static List getAllRecentRecord() { try { + removeUnavailableItem(); String pathSetDir = Env.app_save_path + "本地表情包/recent.json"; - JSONObject pathJson = new JSONObject(FileUtils.ReadFileString(pathSetDir)); + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); List items = new ArrayList<>(); JSONArray pathList = pathJson.getJSONArray("items"); for (int i = 0; i < pathList.length(); i++) { @@ -21,7 +22,6 @@ public static List getAllRecentRecord() { localPath.MD5 = path.getString("MD5"); localPath.fileName = path.getString("fileName"); localPath.addTime = path.getLong("addTime"); - localPath.type = path.getInt("type"); localPath.url = path.getString("url"); localPath.pathName = path.getString("pathName"); localPath.thumbName = path.optString("thumbName"); @@ -36,13 +36,40 @@ public static List getAllRecentRecord() { } } + private static void removeUnavailableItem(){ + try { + String pathSetDir = Env.app_save_path + "本地表情包/recent.json"; + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); + JSONArray pathList = pathJson.getJSONArray("items"); + for (int i = 0; i < pathList.length(); i++) { + JSONObject path = pathList.getJSONObject(i); + RecentItemInfo localPath = new RecentItemInfo(); + localPath.MD5 = path.getString("MD5"); + localPath.fileName = path.getString("fileName"); + localPath.addTime = path.getLong("addTime"); + localPath.url = path.getString("url"); + localPath.pathName = path.getString("pathName"); + localPath.thumbName = path.optString("thumbName"); + localPath.thumbUrl = path.optString("thumbUrl"); + + String localFilePath = LocalDataHelper.getLocalItemPath(localPath); + if (!new File(localFilePath).exists()) { + removeContainOrLast(localPath); + } + + } + } catch (Exception e) { + e.printStackTrace(); + } + } + public static void cleanAllRecentRecord() { try { String pathSetDir = Env.app_save_path + "本地表情包/recent.json"; JSONObject pathJson = new JSONObject(); JSONArray pathList = new JSONArray(); pathJson.put("items", pathList); - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); } catch (Exception e) { e.printStackTrace(); } @@ -55,7 +82,7 @@ private static void createIfFileNotContain() { JSONObject pathJson = new JSONObject(); JSONArray pathList = new JSONArray(); pathJson.put("items", pathList); - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); } } catch (Exception e) { e.printStackTrace(); @@ -67,14 +94,13 @@ public static void addPicItemToRecentRecord(LocalDataHelper.LocalPath bandPath, createIfFileNotContain(); removeContainOrLast(item); String pathSetDir = Env.app_save_path + "本地表情包/recent.json"; - JSONObject pathJson = new JSONObject(FileUtils.ReadFileString(pathSetDir)); + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); JSONArray pathList = pathJson.getJSONArray("items"); JSONObject newItem = new JSONObject(); newItem.put("MD5", item.MD5); newItem.put("fileName", item.fileName); newItem.put("addTime", item.addTime); - newItem.put("type", item.type); newItem.put("url", item.url); newItem.put("pathName", bandPath.storePath); newItem.put("thumbName", item.thumbName); @@ -82,7 +108,7 @@ public static void addPicItemToRecentRecord(LocalDataHelper.LocalPath bandPath, pathList.put(newItem); pathJson.put("items", pathList); - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); } catch (Exception e) { e.printStackTrace(); @@ -94,14 +120,13 @@ public static void addPicItemToRecentRecord(RecentItemInfo itemInfo) { createIfFileNotContain(); removeContainOrLast(itemInfo); String pathSetDir = Env.app_save_path + "本地表情包/recent.json"; - JSONObject pathJson = new JSONObject(FileUtils.ReadFileString(pathSetDir)); + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); JSONArray pathList = pathJson.getJSONArray("items"); JSONObject newItem = new JSONObject(); newItem.put("MD5", itemInfo.MD5); newItem.put("fileName", itemInfo.fileName); newItem.put("addTime", itemInfo.addTime); - newItem.put("type", itemInfo.type); newItem.put("url", itemInfo.url); newItem.put("pathName", itemInfo.pathName); newItem.put("thumbName", itemInfo.thumbName); @@ -111,7 +136,7 @@ public static void addPicItemToRecentRecord(RecentItemInfo itemInfo) { pathList.put(newItem); pathJson.put("items", pathList); - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); } catch (Exception e) { e.printStackTrace(); @@ -121,22 +146,22 @@ public static void addPicItemToRecentRecord(RecentItemInfo itemInfo) { private static void removeContainOrLast(LocalDataHelper.LocalPicItems item) { try { String pathSetDir = Env.app_save_path + "本地表情包/recent.json"; - JSONObject pathJson = new JSONObject(FileUtils.ReadFileString(pathSetDir)); + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); JSONArray pathList = pathJson.getJSONArray("items"); for (int i = 0; i < pathList.length(); i++) { JSONObject path = pathList.getJSONObject(i); if (path.getString("MD5").equals(item.MD5)) { pathList.remove(i); pathJson.put("items", pathList); - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); return; } } - if (pathList.length() >= 30) { + if (pathList.length() >= 100) { pathList.remove(0); pathJson.put("items", pathList); - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); } } catch (Exception e) { e.printStackTrace(); @@ -146,21 +171,21 @@ private static void removeContainOrLast(LocalDataHelper.LocalPicItems item) { private static void removeContainOrLast(RecentItemInfo item) { try { String pathSetDir = Env.app_save_path + "本地表情包/recent.json"; - JSONObject pathJson = new JSONObject(FileUtils.ReadFileString(pathSetDir)); + JSONObject pathJson = new JSONObject(FileUtils.readFileString(pathSetDir)); JSONArray pathList = pathJson.getJSONArray("items"); for (int i = 0; i < pathList.length(); i++) { JSONObject path = pathList.getJSONObject(i); if (path.getString("MD5").equals(item.MD5)) { pathList.remove(i); pathJson.put("items", pathList); - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); return; } } - if (pathList.length() >= 30) { + if (pathList.length() >= 100) { pathList.remove(0); pathJson.put("items", pathList); - FileUtils.WriteToFile(pathSetDir, pathJson.toString()); + FileUtils.writeToFile(pathSetDir, pathJson.toString()); } } catch (Exception e) { @@ -168,6 +193,7 @@ private static void removeContainOrLast(RecentItemInfo item) { } } + public static class RecentItemInfo { public String MD5; public String fileName; @@ -177,7 +203,6 @@ public static class RecentItemInfo { public long addTime; - public int type; public String pathName; } } diff --git a/app/src/main/java/cc/hicore/message/bridge/Chat_facade_bridge.java b/app/src/main/java/cc/hicore/message/bridge/Chat_facade_bridge.java index 382bdf0d35..32b038451f 100644 --- a/app/src/main/java/cc/hicore/message/bridge/Chat_facade_bridge.java +++ b/app/src/main/java/cc/hicore/message/bridge/Chat_facade_bridge.java @@ -23,8 +23,7 @@ import android.content.Context; import android.os.Environment; -import cc.hicore.ReflectUtil.MClass; -import cc.hicore.ReflectUtil.MMethod; +import cc.hicore.ReflectUtil.XMethod; import cc.ioctl.util.HostInfo; import cc.hicore.QApp.QAppUtils; import cc.hicore.Utils.FileUtils; @@ -40,14 +39,13 @@ public class Chat_facade_bridge { public static void sendText(Object _Session, String text, ArrayList atList) { try { - Method CallMethod = MMethod.FindMethod("com.tencent.mobileqq.activity.ChatActivityFacade", null, void.class, new Class[]{ - MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), + XMethod.clz("com.tencent.mobileqq.activity.ChatActivityFacade").ret( void.class).param( + Initiator.loadClass("com.tencent.mobileqq.app.QQAppInterface"), Context.class, - MClass.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), + Initiator.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), String.class, ArrayList.class - }); - CallMethod.invoke(null, QAppUtils.getAppRuntime(), HostInfo.getApplication(), _Session, text, atList); + ).invoke(QAppUtils.getAppRuntime(), HostInfo.getApplication(), _Session, text, atList); } catch (Exception e) { Log.e(e); } @@ -64,15 +62,12 @@ public static void sendPic(Object _Session, File pic) { public static void sendPic(Object _Session, Object picRecord) { try { - Method hookMethod = MMethod.FindMethod("com.tencent.mobileqq.activity.ChatActivityFacade", null, void.class, new Class[]{ - MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), - MClass.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), - MClass.loadClass("com.tencent.mobileqq.data.MessageForPic"), + XMethod.clz("com.tencent.mobileqq.activity.ChatActivityFacade").ret(void.class).param( + Initiator.loadClass("com.tencent.mobileqq.app.QQAppInterface"), + Initiator.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), + Initiator.loadClass("com.tencent.mobileqq.data.MessageForPic"), int.class - }); - hookMethod.invoke(null, - QAppUtils.getAppRuntime(), _Session, picRecord, 0 - ); + ).invoke(QAppUtils.getAppRuntime(), _Session, picRecord, 0); } catch (Exception e) { Log.e(e); } @@ -80,13 +75,11 @@ public static void sendPic(Object _Session, Object picRecord) { public static void sendStruct(Object _Session, Object structMsg) { try { - Method CallMethod = MMethod.FindMethod("com.tencent.mobileqq.activity.ChatActivityFacade", null, - void.class, new Class[]{ - MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), - MClass.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), - MClass.loadClass("com.tencent.mobileqq.structmsg.AbsStructMsg") - }); - CallMethod.invoke(null, QAppUtils.getAppRuntime(), _Session, structMsg); + XMethod.clz("com.tencent.mobileqq.activity.ChatActivityFacade").ret(void.class).param( + Initiator.loadClass("com.tencent.mobileqq.app.QQAppInterface"), + Initiator.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), + Initiator.loadClass("com.tencent.mobileqq.structmsg.AbsStructMsg") + ).invoke(QAppUtils.getAppRuntime(), _Session, structMsg); } catch (Throwable th) { Log.e(th); } @@ -94,13 +87,11 @@ public static void sendStruct(Object _Session, Object structMsg) { public static void sendArkApp(Object _Session, Object arkAppMsg) { try { - Method CallMethod = MMethod.FindMethod("com.tencent.mobileqq.activity.ChatActivityFacade", null, - boolean.class, new Class[]{ - MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), - MClass.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), - MClass.loadClass("com.tencent.mobileqq.data.ArkAppMessage") - }); - CallMethod.invoke(null, QAppUtils.getAppRuntime(), _Session, arkAppMsg); + XMethod.clz("com.tencent.mobileqq.activity.ChatActivityFacade").ret(boolean.class).param( + Initiator.loadClass("com.tencent.mobileqq.app.QQAppInterface"), + Initiator.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), + Initiator.loadClass("com.tencent.mobileqq.data.ArkAppMessage") + ).invoke(QAppUtils.getAppRuntime(), _Session, arkAppMsg); } catch (Throwable th) { Log.e(th); } @@ -114,15 +105,14 @@ public static void sendVoice(Object _Session, String path) { FileUtils.copy(path, newPath); path = newPath; } - Method CallMethod = - !HostInfo.requireMinQQVersion(QQVersion.QQ_8_8_11) ? - MMethod.FindMethod("com.tencent.mobileqq.activity.ChatActivityFacade", "a", long.class, - new Class[]{MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), - MClass.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), String.class}) : - MMethod.FindMethod("com.tencent.mobileqq.activity.ChatActivityFacade", null, long.class, - new Class[]{MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), - MClass.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), String.class}); - CallMethod.invoke(null, QAppUtils.getAppRuntime(), _Session, path); + + XMethod.clz("com.tencent.mobileqq.activity.ChatActivityFacade").ret(long.class).param( + Initiator.loadClass("com.tencent.mobileqq.app.QQAppInterface"), + Initiator.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), + String.class + ).invoke(QAppUtils.getAppRuntime(), _Session, path); + + } catch (Exception e) { Log.e(e); } @@ -130,28 +120,18 @@ public static void sendVoice(Object _Session, String path) { public static void sendMix(Object _Session, Object mixRecord) { try { - + Object replyMsgSender; if (HostInfo.requireMinQQVersion(QQVersion.QQ_8_9_0)){ - Method mMethod = MMethod.FindMethod("com.tencent.mobileqq.replymsg.d", null, void.class, new Class[]{ - MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), - MClass.loadClass("com.tencent.mobileqq.data.MessageForMixedMsg"), - MClass.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), - int.class - }); - Object Call = MMethod.CallStaticMethodNoParam(MClass.loadClass("com.tencent.mobileqq.replymsg.d"), null, - MClass.loadClass("com.tencent.mobileqq.replymsg.d")); - mMethod.invoke(Call, QAppUtils.getAppRuntime(), mixRecord, _Session, 0); + replyMsgSender = XMethod.clz("com.tencent.mobileqq.replymsg.d").param(Initiator.loadClass("com.tencent.mobileqq.replymsg.d")).invoke(); }else { - Method mMethod = MMethod.FindMethod("com.tencent.mobileqq.replymsg.ReplyMsgSender", null, void.class, new Class[]{ - MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), - MClass.loadClass("com.tencent.mobileqq.data.MessageForMixedMsg"), - MClass.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), - int.class - }); - Object Call = MMethod.CallStaticMethodNoParam(MClass.loadClass("com.tencent.mobileqq.replymsg.ReplyMsgSender"), null, - MClass.loadClass("com.tencent.mobileqq.replymsg.ReplyMsgSender")); - mMethod.invoke(Call, QAppUtils.getAppRuntime(), mixRecord, _Session, 0); + replyMsgSender = XMethod.clz("com.tencent.mobileqq.replymsg.ReplyMsgSender").param(Initiator.loadClass("com.tencent.mobileqq.replymsg.ReplyMsgSender")).invoke(); } + XMethod.obj(replyMsgSender).ret(void.class).param( + Initiator.loadClass("com.tencent.mobileqq.app.QQAppInterface"), + Initiator.loadClass("com.tencent.mobileqq.data.MessageForMixedMsg"), + Initiator.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), + int.class + ).invoke(QAppUtils.getAppRuntime(), mixRecord, _Session, 0); } catch (Exception e) { Log.e(e); @@ -161,33 +141,22 @@ public static void sendMix(Object _Session, Object mixRecord) { public static void sendReply(Object _Session, Object replyRecord) { try { - + Object replyMsgSender; if (HostInfo.requireMinQQVersion(QQVersion.QQ_8_9_0)){ - Object Call = MMethod.CallStaticMethodNoParam(MClass.loadClass("com.tencent.mobileqq.replymsg.d"), null, - MClass.loadClass("com.tencent.mobileqq.replymsg.d")); - Method mMethod = MMethod.FindMethod("com.tencent.mobileqq.replymsg.d", null, void.class, new Class[]{ - MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), - MClass.loadClass("com.tencent.mobileqq.data.ChatMessage"), - Initiator._BaseSessionInfo(), - int.class, - int.class, - boolean.class - }); - mMethod.invoke(Call, QAppUtils.getAppRuntime(), replyRecord, _Session, 2, 0, false); + replyMsgSender = XMethod.clz("com.tencent.mobileqq.replymsg.d").param(Initiator.loadClass("com.tencent.mobileqq.replymsg.d")).invoke(); }else { - Object Call = MMethod.CallStaticMethodNoParam(MClass.loadClass("com.tencent.mobileqq.replymsg.ReplyMsgSender"), null, - MClass.loadClass("com.tencent.mobileqq.replymsg.ReplyMsgSender")); - Method mMethod = MMethod.FindMethod("com.tencent.mobileqq.replymsg.ReplyMsgSender", null, void.class, new Class[]{ - MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), - MClass.loadClass("com.tencent.mobileqq.data.ChatMessage"), - MClass.loadClass("com.tencent.mobileqq.activity.aio.BaseSessionInfo"), - int.class, - int.class, - boolean.class - }); - mMethod.invoke(Call, QAppUtils.getAppRuntime(), replyRecord, _Session, 2, 0, false); + replyMsgSender = XMethod.clz("com.tencent.mobileqq.replymsg.ReplyMsgSender").param(Initiator.loadClass("com.tencent.mobileqq.replymsg.ReplyMsgSender")).invoke(); } + XMethod.obj(replyMsgSender).ret(void.class).param( + Initiator.loadClass("com.tencent.mobileqq.app.QQAppInterface"), + Initiator.loadClass("com.tencent.mobileqq.data.ChatMessage"), + Initiator._BaseSessionInfo(), + int.class, + int.class, + boolean.class + ).invoke(QAppUtils.getAppRuntime(), replyRecord, _Session, 2, 0, false); + } catch (Exception e) { Log.e(e); } @@ -196,12 +165,12 @@ public static void sendAnimation(Object Session, int sevrID) { try { if (!HostInfo.requireMinQQVersion(QQVersion.QQ_8_8_20)) { - MMethod.CallMethod(null, MClass.loadClass("com.tencent.mobileqq.emoticonview.AniStickerSendMessageCallBack"), "sendAniSticker", - boolean.class, new Class[]{int.class, MClass.loadClass("com.tencent.mobileqq.activity.aio.BaseSessionInfo")}, sevrID, Session); + XMethod.clz("com.tencent.mobileqq.emoticonview.AniStickerSendMessageCallBack").name("sendAniSticker") + .ret(boolean.class).param(int.class, Initiator.loadClass("com.tencent.mobileqq.activity.aio.BaseSessionInfo")).invoke( sevrID, Session); } else { - MMethod.CallMethod(null, MClass.loadClass("com.tencent.mobileqq.emoticonview.AniStickerSendMessageCallBack"), "sendAniSticker", - boolean.class, new Class[]{int.class, Initiator._BaseSessionInfo(), int.class}, sevrID, - Session, 0); + + XMethod.clz("com.tencent.mobileqq.emoticonview.AniStickerSendMessageCallBack").name("sendAniSticker") + .ret(boolean.class).param(int.class, Initiator._BaseSessionInfo(), int.class).invoke( sevrID, Session, 0); } } catch (Exception e) { Log.e(e); @@ -210,24 +179,23 @@ public static void sendAnimation(Object Session, int sevrID) { public static void QQ_Forward_ShortVideo(Object _SessionInfo, Object ChatMessage) { try { - MMethod.CallStaticMethod(MClass.loadClass("com.tencent.mobileqq.activity.ChatActivityFacade"), null, void.class, new Class[]{ - MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), - MClass.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), - MClass.loadClass("com.tencent.mobileqq.data.MessageForShortVideo") - }, QAppUtils.getAppRuntime(), _SessionInfo, ChatMessage); + XMethod.clz("com.tencent.mobileqq.activity.ChatActivityFacade").ret(void.class).param( + Initiator.loadClass("com.tencent.mobileqq.app.QQAppInterface"), + Initiator.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), + Initiator.loadClass("com.tencent.mobileqq.data.MessageForShortVideo") + ).invoke(QAppUtils.getAppRuntime(), _SessionInfo, ChatMessage); } catch (Exception e) { Log.e(e); } } public static void AddAndSendMsg(Object MessageRecord) { try { - Object MessageFacade = MMethod.CallMethodNoParam(QAppUtils.getAppRuntime(), "getMessageFacade", - MClass.loadClass("com.tencent.imcore.message.QQMessageFacade")); - Method mMethod = MMethod.FindMethod("com.tencent.imcore.message.BaseQQMessageFacade", null, void.class, new Class[]{ - MClass.loadClass("com.tencent.mobileqq.data.MessageRecord"), - MClass.loadClass("com.tencent.mobileqq.app.BusinessObserver"),boolean.class - }); - mMethod.invoke(MessageFacade, MessageRecord, null,false); + Object MessageFacade = XMethod.obj(QAppUtils.getAppRuntime()).name("getMessageFacade").ret(Initiator.loadClass("com.tencent.imcore.message.QQMessageFacade")).invoke(); + XMethod.obj(MessageFacade).ret(void.class).param( + Initiator.loadClass("com.tencent.mobileqq.data.MessageRecord"), + Initiator.loadClass("com.tencent.mobileqq.app.BusinessObserver"), + boolean.class + ).invoke(MessageRecord, null,false); } catch (Exception e) { } diff --git a/app/src/main/java/cc/hicore/message/chat/SessionHooker.java b/app/src/main/java/cc/hicore/message/chat/SessionHooker.java index 28e26d5052..b3800c3dc0 100644 --- a/app/src/main/java/cc/hicore/message/chat/SessionHooker.java +++ b/app/src/main/java/cc/hicore/message/chat/SessionHooker.java @@ -24,6 +24,7 @@ import androidx.annotation.NonNull; import cc.hicore.QApp.QAppUtils; import cc.hicore.ReflectUtil.MField; +import cc.hicore.ReflectUtil.XField; import cc.hicore.hook.RepeaterPlus; import cc.hicore.hook.stickerPanel.Hooker.StickerPanelEntryHooker; import de.robv.android.xposed.XC_MethodHook; @@ -66,7 +67,7 @@ protected boolean initOnce() throws Exception { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Object pie = param.thisObject; - Object AIOParam = MField.GetFirstField(pie,Initiator.loadClass("com.tencent.aio.data.AIOParam")); + Object AIOParam = XField.obj(pie).type(Initiator.loadClass("com.tencent.aio.data.AIOParam")).get(); for (SessionHooker.IAIOParamUpdate decorator : getDecorators()) { decorator.onAIOParamUpdate(AIOParam); } diff --git a/app/src/main/java/cc/hicore/message/chat/SessionUtils.java b/app/src/main/java/cc/hicore/message/chat/SessionUtils.java index abd79e0fa8..1099141483 100644 --- a/app/src/main/java/cc/hicore/message/chat/SessionUtils.java +++ b/app/src/main/java/cc/hicore/message/chat/SessionUtils.java @@ -24,16 +24,18 @@ import android.text.TextUtils; import cc.hicore.QApp.QAppUtils; import cc.hicore.ReflectUtil.MField; +import cc.hicore.ReflectUtil.XField; import cc.hicore.Utils.XLog; import com.tencent.qqnt.kernel.nativeinterface.Contact; import io.github.qauxv.bridge.SessionInfoImpl; import io.github.qauxv.util.Initiator; +import java.lang.reflect.Field; public class SessionUtils { public static Contact AIOParam2Contact(Object AIOParam) { try { - Object AIOSession = MField.GetFirstField(AIOParam, Initiator.loadClass("com.tencent.aio.data.AIOSession")); - Object AIOContact = MField.GetFirstField(AIOSession,Initiator.loadClass("com.tencent.aio.data.AIOContact")); + Object AIOSession = XField.obj(AIOParam).type(Initiator.loadClass("com.tencent.aio.data.AIOSession")).get(); + Object AIOContact = XField.obj(AIOSession).type(Initiator.loadClass("com.tencent.aio.data.AIOContact")).get(); Contact contact = new Contact(); contact.setPeerUid(getCurrentPeerIDByAIOContact(AIOContact)); @@ -51,12 +53,12 @@ public static Contact AIOParam2Contact(Object AIOParam) { } public static String getCurrentPeerIDByAIOContact(Object AIOContact) throws Exception { - return MField.GetField(AIOContact,"f",String.class); + return XField.obj(AIOContact).name("f").type(String.class).get(); } public static int getCurrentChatTypeByAIOContact(Object AIOContact) throws Exception{ - return MField.GetField(AIOContact,"e",int.class); + return XField.obj(AIOContact).name("e").type(int.class).get(); } public static String getCurrentGuildIDByAIOContact(Object AIOContact) throws Exception{ - return MField.GetField(AIOContact,"g",String.class); + return XField.obj(AIOContact).name("g").type(String.class).get(); } } diff --git a/app/src/main/java/cc/hicore/message/common/MsgBuilder.java b/app/src/main/java/cc/hicore/message/common/MsgBuilder.java index db3f696cc3..0a2582c295 100644 --- a/app/src/main/java/cc/hicore/message/common/MsgBuilder.java +++ b/app/src/main/java/cc/hicore/message/common/MsgBuilder.java @@ -22,9 +22,10 @@ package cc.hicore.message.common; import cc.hicore.QApp.QAppUtils; -import cc.hicore.ReflectUtil.MClass; import cc.hicore.ReflectUtil.MField; -import cc.hicore.ReflectUtil.MMethod; +import cc.hicore.ReflectUtil.XClass; +import cc.hicore.ReflectUtil.XField; +import cc.hicore.ReflectUtil.XMethod; import cc.hicore.Utils.DataUtils; import cc.ioctl.util.HostInfo; import cc.hicore.Utils.XLog; @@ -32,6 +33,7 @@ import com.tencent.qqnt.kernel.nativeinterface.PttElement; import com.tencent.qqnt.kernel.nativeinterface.TextElement; import io.github.qauxv.bridge.AppRuntimeHelper; +import io.github.qauxv.util.Initiator; import io.github.qauxv.util.QQVersion; import java.io.File; import java.lang.reflect.Method; @@ -69,8 +71,8 @@ public static MsgElement nt_build_voice(String origin,int time){ } public static MsgElement nt_build_pic(String path){ try { - Object helper = MClass.NewInstance(MClass.loadClass("com.tencent.qqnt.msg.api.impl.MsgUtilApiImpl")); - return MMethod.CallMethod(helper,"createPicElement",MsgElement.class,new Class[]{String.class,boolean.class,int.class},path,true,0); + Object helper = XClass.newInstance(Initiator.loadClass("com.tencent.qqnt.msg.api.impl.MsgUtilApiImpl")); + return XMethod.obj(helper).name("createPicElement").ret(MsgElement.class).param(String.class,boolean.class,int.class).invoke(path,true,0); } catch (Exception e) { XLog.e("MsgBuilder.nt_build_pic",e); throw new RuntimeException(e); @@ -78,8 +80,8 @@ public static MsgElement nt_build_pic(String path){ } public static MsgElement nt_build_pic_guild(String path){ try { - Object helper = MClass.NewInstance(MClass.loadClass("com.tencent.qqnt.msg.api.impl.MsgUtilApiImpl")); - return MMethod.CallMethod(helper,"createPicElementForGuild",MsgElement.class,new Class[]{String.class,boolean.class,int.class},path,true,0); + Object helper = XClass.newInstance(Initiator.loadClass("com.tencent.qqnt.msg.api.impl.MsgUtilApiImpl")); + return XMethod.obj(helper).name("createPicElementForGuild").ret(MsgElement.class).param(String.class,boolean.class,int.class).invoke(path,true,0); } catch (Exception e) { XLog.e("MsgBuilder.nt_build_pic_guild",e); throw new RuntimeException(e); @@ -87,19 +89,17 @@ public static MsgElement nt_build_pic_guild(String path){ } public static Object build_pic(Object _SessionInfo,String path){ try { - Method CallMethod = MMethod.FindMethod("com.tencent.mobileqq.activity.ChatActivityFacade", null, MClass.loadClass("com.tencent.mobileqq.data.ChatMessage"), new Class[]{ - MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), - MClass.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), - String.class - }); - Object PICMsg = CallMethod.invoke(null, - AppRuntimeHelper.getQQAppInterface(), _SessionInfo, path - ); - MField.SetField(PICMsg, "md5", DataUtils.getFileMD5(new File(path))); - MField.SetField(PICMsg, "uuid", DataUtils.getFileMD5(new File(path)) + ".jpg"); - MField.SetField(PICMsg, "localUUID", UUID.randomUUID().toString()); - MMethod.CallMethodNoParam(PICMsg, "prewrite", void.class); - return PICMsg; + Object picObj = XMethod.clz("com.tencent.mobileqq.activity.ChatActivityFacade").ret(Initiator.loadClass("com.tencent.mobileqq.data.ChatMessage")) + .param( + Initiator.loadClass("com.tencent.mobileqq.app.QQAppInterface"), + Initiator.loadClass("com.tencent.mobileqq.activity.aio.SessionInfo"), + String.class + ).invoke(AppRuntimeHelper.getQQAppInterface(), _SessionInfo, path); + XField.obj(picObj).name("md5").set(DataUtils.getFileMD5(new File(path))); + XField.obj(picObj).name("uuid").set(DataUtils.getFileMD5(new File(path)) + ".jpg"); + XField.obj(picObj).name("localUUID").set(UUID.randomUUID().toString()); + XMethod.obj(picObj).name("prewrite").ret(void.class).invoke(); + return picObj; }catch (Exception e){ XLog.e("MsgBuilder.build_pic", e); return null; @@ -109,30 +109,27 @@ public static Object copy_new_flash_chat(Object source){ try { Method ArkChatObj; if (HostInfo.requireMinQQVersion(QQVersion.QQ_8_8_90)){ - ArkChatObj = MMethod.FindMethod("com.tencent.mobileqq.service.h.r", null, - MClass.loadClass("com.tencent.mobileqq.data.MessageForArkFlashChat"), - new Class[]{ - MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), + ArkChatObj = XMethod.clz("com.tencent.mobileqq.service.h.r") + .ret(Initiator.loadClass("com.tencent.mobileqq.data.MessageForArkFlashChat")) + .param( + Initiator.loadClass("com.tencent.mobileqq.app.QQAppInterface"), String.class, String.class, int.class, - MClass.loadClass("com.tencent.mobileqq.data.ArkFlashChatMessage") - } - ); + Initiator.loadClass("com.tencent.mobileqq.data.ArkFlashChatMessage") + ).get(); }else { - ArkChatObj = MMethod.FindMethod("com.tencent.mobileqq.service.message.MessageRecordFactory", null, - MClass.loadClass("com.tencent.mobileqq.data.MessageForArkFlashChat"), - new Class[]{ - MClass.loadClass("com.tencent.mobileqq.app.QQAppInterface"), + ArkChatObj = XMethod.clz("com.tencent.mobileqq.service.message.MessageRecordFactory") + .ret(Initiator.loadClass("com.tencent.mobileqq.data.MessageForArkFlashChat")) + .param( + Initiator.loadClass("com.tencent.mobileqq.app.QQAppInterface"), String.class, String.class, int.class, - MClass.loadClass("com.tencent.mobileqq.data.ArkFlashChatMessage") - } - ); + Initiator.loadClass("com.tencent.mobileqq.data.ArkFlashChatMessage") + ).get(); } - Object sArk = MField.GetField(source, "ark_app_message"); - int isTroop = MField.GetField(source, "istroop", int.class); - String FriendUin = MField.GetField(source, "frienduin", String.class); - Object NewChat = ArkChatObj.invoke(null, QAppUtils.getAppRuntime(), FriendUin, QAppUtils.getCurrentUin(), isTroop, sArk); - return NewChat; + Object sArk = XField.obj(source).name("ark_app_message").get(); + int isTroop = XField.obj(source).name("istroop").type(int.class).get(); + String FriendUin = XField.obj(source).name("frienduin").type(String.class).get(); + return ArkChatObj.invoke(null, QAppUtils.getAppRuntime(), FriendUin, QAppUtils.getCurrentUin(), isTroop, sArk); } catch (Exception e) { return null; } @@ -140,18 +137,18 @@ public static Object copy_new_flash_chat(Object source){ public static Object copy_market_face_msg(Object source){ try { Object mMessageRecord = build_common_message_record(-2007); - MMethod.CallMethod(mMessageRecord, mMessageRecord.getClass().getSuperclass().getSuperclass(), "initInner", void.class, - new Class[]{String.class, String.class, String.class, String.class, long.class, int.class, int.class, long.class}, - QAppUtils.getCurrentUin(), MField.GetField(source, "frienduin"), QAppUtils.getCurrentUin(), "[原创表情]", System.currentTimeMillis() / 1000, - -2007, - MField.GetField(source, "istroop"), System.currentTimeMillis() / 1000 - ); - MField.SetField(mMessageRecord, "msgData", MField.GetField(source, source.getClass(), "msgData", byte[].class)); - String strName = MField.GetField(source, "sendFaceName"); + XMethod.obj(mMessageRecord).name("initInner").ret(void.class).param(String.class, String.class, String.class, String.class, long.class, int.class, int.class, long.class) + .invoke(QAppUtils.getCurrentUin(), XField.obj(source).name("frienduin").get(), QAppUtils.getCurrentUin(), "[原创表情]", System.currentTimeMillis() / 1000, + -2007, + XField.obj(source).name("istroop").get(), System.currentTimeMillis() / 1000); + XField.obj(mMessageRecord).name("msgData").set(XField.obj(source).name("msgData").type(byte[].class).get()); + + String strName = XField.obj(source).name("sendFaceName").get(); + if (strName != null) { - MField.SetField(mMessageRecord, "sendFaceName", strName); + XField.obj(mMessageRecord).name("sendFaceName").set(strName); } - MMethod.CallMethodNoParam(mMessageRecord, "doParse", void.class); + XMethod.obj(mMessageRecord).name("doParse").ret(void.class).invoke(); return rebuild_message(mMessageRecord); }catch (Exception e){ XLog.e("MsgBuilder.copy_market_face_msg", e); @@ -160,22 +157,30 @@ public static Object copy_market_face_msg(Object source){ } public static Object copy_poke_msg(Object source){ try { - Object PokeEmo = MClass.NewInstance(MClass.loadClass("com.tencent.mobileqq.data.MessageForPokeEmo")); - MField.SetField(PokeEmo, "msgtype", -5018); - MField.SetField(PokeEmo, "pokeemoId", 13); - MField.SetField(PokeEmo, "pokeemoPressCount", MField.GetField(source, "pokeemoPressCount")); - MField.SetField(PokeEmo, "emoIndex", MField.GetField(source, "emoIndex")); - MField.SetField(PokeEmo, "summary", MField.GetField(source, "summary")); - MField.SetField(PokeEmo, "emoString", MField.GetField(source, "emoString")); - MField.SetField(PokeEmo, "emoCompat", MField.GetField(source, "emoCompat")); - MMethod.CallMethod(PokeEmo, "initMsg", void.class, new Class[0]); - String friendInfo = MField.GetField(source, "frienduin", String.class); - int istroop = MField.GetField(source, "istroop", int.class); - MMethod.CallStaticMethod(HostInfo.requireMinQQVersion(QQVersion.QQ_8_9_0) ? MClass.loadClass("com.tencent.mobileqq.service.h.r"): - MClass.loadClass("com.tencent.mobileqq.service.message.MessageRecordFactory"), + Object PokeEmo = XClass.newInstance(Initiator.loadClass("com.tencent.mobileqq.data.MessageForPokeEmo")); + XField.obj(PokeEmo).name("msgtype").set(-5018); + XField.obj(PokeEmo).name("pokeemoId").set(13); + XField.obj(PokeEmo).name("pokeemoPressCount").set(XField.obj(source).name("pokeemoPressCount").get()); + XField.obj(PokeEmo).name("emoIndex").set(XField.obj(source).name("emoIndex").get()); + XField.obj(PokeEmo).name("summary").set(XField.obj(source).name("summary").get()); + XField.obj(PokeEmo).name("emoString").set(XField.obj(source).name("emoString").get()); + XField.obj(PokeEmo).name("emoCompat").set(XField.obj(source).name("emoCompat").get()); + XMethod.obj(PokeEmo).name("initMsg").ret(void.class).invoke(); + - null, void.class, new Class[]{MClass.loadClass("com.tencent.common.app.AppInterface"), MClass.loadClass("com.tencent.mobileqq.data.MessageRecord"), String.class, String.class, int.class}, - QAppUtils.getAppRuntime(), PokeEmo, friendInfo, QAppUtils.getCurrentUin(), istroop); + String friendInfo = XField.obj(source).name("frienduin").type(String.class).get(); + int istroop = XField.obj(source).name("istroop").type(int.class).get(); + + XMethod.clz(HostInfo.requireMinQQVersion(QQVersion.QQ_8_9_0) ? Initiator.loadClass("com.tencent.mobileqq.service.h.r"): + Initiator.loadClass("com.tencent.mobileqq.service.message.MessageRecordFactory")) + .ret(void.class) + .param( + Initiator.loadClass("com.tencent.common.app.AppInterface"), + Initiator.loadClass("com.tencent.mobileqq.data.MessageRecord"), + String.class, + String.class, + int.class + ).invoke(QAppUtils.getAppRuntime(), PokeEmo, friendInfo, QAppUtils.getCurrentUin(), istroop); return PokeEmo; } catch (Exception e) { XLog.e("MsgBuilder.copy_poke_msg", e); @@ -185,9 +190,9 @@ public static Object copy_poke_msg(Object source){ public static Object rebuild_message(Object record){ try{ if (HostInfo.requireMinQQVersion(QQVersion.QQ_8_9_0)){ - return MMethod.CallStaticMethod(MClass.loadClass("com.tencent.mobileqq.service.h.r"),null,MClass.loadClass("com.tencent.mobileqq.data.MessageRecord"),record); + return XMethod.clz("com.tencent.mobileqq.service.h.r").ret(Initiator.loadClass("com.tencent.mobileqq.data.MessageRecord")).invoke(record); }else { - return MMethod.CallStaticMethod(MClass.loadClass("com.tencent.mobileqq.service.message.MessageRecordFactory"),null,MClass.loadClass("com.tencent.mobileqq.data.MessageRecord"),record); + return XMethod.clz("com.tencent.mobileqq.service.message.MessageRecordFactory").ret(Initiator.loadClass("com.tencent.mobileqq.data.MessageRecord")).invoke(record); } }catch (Exception e){ @@ -198,17 +203,21 @@ public static Object build_common_message_record(int type){ try{ Method CallMethod = null; if (HostInfo.requireMinQQVersion(QQVersion.QQ_8_9_0)){ - CallMethod = MMethod.FindMethod("com.tencent.mobileqq.service.h.r","d", MClass.loadClass("com.tencent.mobileqq.data.MessageRecord"),new Class[]{ - int.class - }); + CallMethod = XMethod.clz("com.tencent.mobileqq.service.h.r") + .name("d") + .ret(Initiator.loadClass("com.tencent.mobileqq.data.MessageRecord")) + .param(int.class).get(); } else if (HostInfo.requireMinQQVersion(QQVersion.QQ_8_8_93)){ - CallMethod = MMethod.FindMethod("com.tencent.mobileqq.service.message.MessageRecordFactory","d", MClass.loadClass("com.tencent.mobileqq.data.MessageRecord"),new Class[]{ - int.class - }); + + CallMethod = XMethod.clz("com.tencent.mobileqq.service.message.MessageRecordFactory") + .name("d") + .ret(Initiator.loadClass("com.tencent.mobileqq.data.MessageRecord")) + .param(int.class).get(); }else { - CallMethod = MMethod.FindMethod("com.tencent.mobileqq.service.message.MessageRecordFactory","a", MClass.loadClass("com.tencent.mobileqq.data.MessageRecord"),new Class[]{ - int.class - }); + CallMethod = XMethod.clz("com.tencent.mobileqq.service.message.MessageRecordFactory") + .name("a") + .ret(Initiator.loadClass("com.tencent.mobileqq.data.MessageRecord")) + .param(int.class).get(); } return CallMethod.invoke(null,type); }catch (Exception e){ diff --git a/app/src/main/java/cc/hicore/message/common/MsgUtils.java b/app/src/main/java/cc/hicore/message/common/MsgUtils.java index 7d9bb1494a..b2f2be292b 100644 --- a/app/src/main/java/cc/hicore/message/common/MsgUtils.java +++ b/app/src/main/java/cc/hicore/message/common/MsgUtils.java @@ -29,7 +29,7 @@ public class MsgUtils { public static int DecodeAntEmoCode(int EmoCode) { try { - String s = FileUtils.ReadFileString(HostInfo.getApplication().getFilesDir() + "/qq_emoticon_res/face_config.json"); + String s = FileUtils.readFileString(HostInfo.getApplication().getFilesDir() + "/qq_emoticon_res/face_config.json"); JSONObject j = new JSONObject(s); JSONArray arr = j.getJSONArray("sysface"); for (int i = 0; i < arr.length(); i++) { diff --git a/app/src/main/java/cc/hicore/ui/SimpleDragSortView.java b/app/src/main/java/cc/hicore/ui/SimpleDragSortView.java new file mode 100644 index 0000000000..dccc86ed76 --- /dev/null +++ b/app/src/main/java/cc/hicore/ui/SimpleDragSortView.java @@ -0,0 +1,95 @@ +package cc.hicore.ui; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; +import cc.hicore.ui.handygridview.HandyGridView; +import cc.hicore.ui.handygridview.scrollrunner.OnItemMovedListener; +import cc.ioctl.util.LayoutHelper; +import java.util.List; + +@SuppressLint("ResourceType") +public class SimpleDragSortView { + public static SimpleDragSortView createDrag(Context mContext,List localFilePath,List sourceInfo,Runnable onDone){ + SimpleDragSortView dragView = new SimpleDragSortView(mContext,localFilePath,sourceInfo); + Dialog mDialog = new Dialog(mContext,2); + mDialog.setContentView(dragView.getView()); + mDialog.setOnDismissListener(dialog -> onDone.run()); + mDialog.show(); + return dragView; + } + List localFilePath; + List sourceInfo; + private HandyGridView mGridView; + private Context mContext; + public SimpleDragSortView(Context context,List localFilePath,List sourceInfo) { + if (localFilePath.size() == 0)return; + this.localFilePath = localFilePath; + this.mContext = context; + this.sourceInfo = sourceInfo; + mGridView = new HandyGridView(context); + mGridView.setNumColumns(5); + mGridView.setStretchMode(HandyGridView.STRETCH_COLUMN_WIDTH); + mGridView.setVerticalSpacing(10); + mGridView.setPadding(10, 10, 10, 10); + mGridView.setAdapter(new GridViewAdapter(localFilePath,sourceInfo)); + + } + public View getView(){ + return mGridView; + } + + private static class GridViewAdapter extends BaseAdapter implements OnItemMovedListener { + private List picPath; + private List sourceInfo; + public GridViewAdapter(List picPath,List sourceInfo){ + this.picPath = picPath; + this.sourceInfo = sourceInfo; + } + + @Override + public int getCount() { + return picPath.size(); + } + + @Override + public String getItem(int position) { + return picPath.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + int width_item = LayoutHelper.getScreenWidth(parent.getContext()) / 5; + TextView textView = new TextView(parent.getContext()); + textView.setHeight(width_item); + textView.setWidth(width_item); + textView.setBackground(Drawable.createFromPath(picPath.get(position))); + return textView; + } + + @Override + public void onItemMoved(int from, int to) { + String path = picPath.remove(from); + picPath.add(to, path); + + String info = sourceInfo.remove(from); + sourceInfo.add(to, info); + } + + @Override + public boolean isFixed(int position) { + return false; + } + } + +} diff --git a/app/src/main/java/cc/hicore/ui/handygridview/Child.java b/app/src/main/java/cc/hicore/ui/handygridview/Child.java new file mode 100644 index 0000000000..2274e472c5 --- /dev/null +++ b/app/src/main/java/cc/hicore/ui/handygridview/Child.java @@ -0,0 +1,99 @@ +package cc.hicore.ui.handygridview; + +import android.content.Context; +import android.view.View; +import cc.hicore.ui.handygridview.scrollrunner.ICarrier; +import cc.hicore.ui.handygridview.scrollrunner.ScrollRunner; + + +public class Child implements ICarrier { + public int position; + public View view; + private ScrollRunner mRunner; + private int from, to; + private boolean hasNext = false; + private HandyGridView parent; + + public Child(View view) { + this.view = view; + mRunner = new ScrollRunner(this); + } + + public void cancel() { + mRunner.cancel(); + hasNext = false; + } + + public void setParent(HandyGridView parent) { + this.parent = parent; + } + + private void move(final int offsetX, final int offsetY) { + mRunner.start(offsetX, offsetY); + } + + public void moveTo(int from, int to) { + this.from = from; + this.to = to; + int[] start = parent.getLeftAndTopForPosition(from); + int[] end = parent.getLeftAndTopForPosition(to); + if (!mRunner.isRunning()) { + int offsetX = end[0] - start[0]; + int offsetY = end[1] - start[1]; + move(offsetX, offsetY); + } else { + hasNext = true; + } + } + + @Override + public void onDone() { + int[] start = new int[]{view.getLeft(), view.getTop()}; + from = parent.pointToPosition(start[0], start[1]); + + int[] end = parent.getLeftAndTopForPosition(to); + if (hasNext) { + if (from != to) { + int offsetX = end[0] - start[0]; + int offsetY = end[1] - start[1]; + move(offsetX, offsetY); + } + hasNext = false; + } + } + + @Override + public void onMove(int lastX, int lastY, int curX, int curY) { + int deltaX = curX - lastX; + int deltaY = curY - lastY; + view.offsetLeftAndRight(deltaX); + view.offsetTopAndBottom(deltaY); + } + + @Override + public boolean post(Runnable runnable) { + return view.post(runnable); + } + + @Override + public boolean removeCallbacks(Runnable action) { + return view.removeCallbacks(action); + } + + @Override + public Context getContext() { + return view.getContext(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj instanceof Child) { + Child child = (Child) obj; + if (this.view == child.view) { + return true; + } + } + return super.equals(obj); + } +} diff --git a/app/src/main/java/cc/hicore/ui/handygridview/Children.java b/app/src/main/java/cc/hicore/ui/handygridview/Children.java new file mode 100644 index 0000000000..a2b1983465 --- /dev/null +++ b/app/src/main/java/cc/hicore/ui/handygridview/Children.java @@ -0,0 +1,61 @@ +package cc.hicore.ui.handygridview; + +import android.view.View; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; + +public class Children { + private LinkedHashMap container = new LinkedHashMap<>(); + private LinkedList mChildren = new LinkedList<>(); + private HandyGridView parent; + + public Children(HandyGridView parent) { + this.parent = parent; + } + + public void add(int index, View view) { + Child child = container.get(view); + if (child == null) { + child = new Child(view); + child.setParent(parent); + container.put(view, child); + } + mChildren.add(index, child); + } + + public boolean remove(Child child) { + return mChildren.remove(child); + } + + public void remove(int index) { + mChildren.remove(index); + } + + public Child get(int index) { + return mChildren.get(index); + } + + public int indexOf(View v) { + Child child = container.get(v); + if (child == null) { + return -2; + } + return mChildren.indexOf(child); + } + + public int size() { + return mChildren.size(); + } + + public void clear() { + container.clear(); + Iterator it = mChildren.iterator(); + //子view从gridView移除时取消动画效果 + while (it.hasNext()) { + Child child = it.next(); + child.cancel(); + it.remove(); + } + } +} diff --git a/app/src/main/java/cc/hicore/ui/handygridview/HandyGridView.java b/app/src/main/java/cc/hicore/ui/handygridview/HandyGridView.java new file mode 100644 index 0000000000..ea0253b5ba --- /dev/null +++ b/app/src/main/java/cc/hicore/ui/handygridview/HandyGridView.java @@ -0,0 +1,844 @@ +package cc.hicore.ui.handygridview; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.GridView; +import android.widget.ListAdapter; +import cc.hicore.ui.handygridview.listener.IDrawer; +import cc.hicore.ui.handygridview.listener.OnItemCapturedListener; +import cc.hicore.ui.handygridview.scrollrunner.ICarrier; +import cc.hicore.ui.handygridview.scrollrunner.OnItemMovedListener; +import cc.hicore.ui.handygridview.scrollrunner.ScrollRunner; +import cc.hicore.ui.handygridview.utils.ReflectUtil; +import cc.hicore.ui.handygridview.utils.SdkVerUtils; + + +public class HandyGridView extends GridView implements AdapterView.OnItemLongClickListener, AdapterView.OnItemClickListener, ICarrier { + private int mScrollY; + private int mFirstTop, mFirstLeft; + private int mFirstVisibleFirstItem = -1; + private float mLastMotionX, mLastMotionY; + private Children mChildren; + private int mTouchSlop; + private OnItemLongClickListener mOnItemLongClickListener; + private OnScrollListener mOnScrollListener; + private ScrollRunner mScrollRunner; + private int mScrollSpeed = 750; + private boolean mClipToPadding = false; + private Rect mGridViewVisibleRect; + private MotionEvent mCurrMotionEvent; + + private ListAdapter mAdapter; + private OnItemMovedListener mItemMovedListener; + + private View mDraggedView; + private int mDraggedIndex = -1; + private Rect mDraggedRect = new Rect(); + private int mColumnWidth, mRowHeight, mColumnsNum; + private int mHorizontalSpacing; + private int mVerticalSpacing; + private int mDraggedPosition = INVALID_POSITION; + private OnItemClickListener mOnItemClickListener; + private boolean mShouldMove = false; + private boolean mUseSelector = false; + private boolean mAutoOptimize = true; + private MODE mode = MODE.TOUCH; + + private Drawable mSelector; + private Drawable mSpaceDrawable; + private IDrawer mDrawer; + private boolean mDrawOnTop = false; + + public HandyGridView(Context context) { + this(context, null); + } + + public HandyGridView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + private void init(Context context) { + mScrollRunner = new ScrollRunner(this, new AccelerateDecelerateInterpolator()); + setChildrenDrawingOrderEnabled(true); + super.setOnItemLongClickListener(this); + super.setOnItemClickListener(this); + setOverScrollMode(OVER_SCROLL_NEVER); + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mChildren = new Children(this); + super.setOnScrollListener(new OnScrollListener() { + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + if (mOnScrollListener != null) { + mOnScrollListener.onScrollStateChanged(view, scrollState); + } + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + if (visibleItemCount != 0) { + mFirstVisibleFirstItem = firstVisibleItem; + getBasicValues(firstVisibleItem); + if (mOnScrollListener != null) { + mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); + } + } + } + }); + } + + private void getBasicValues(int firstVisibleItem) { + View firstChild = getChildAt(0); + mFirstLeft = getListPaddingLeft(); + mFirstTop = firstChild.getTop(); + mColumnWidth = firstChild.getWidth(); + mRowHeight = firstChild.getHeight(); + int rowLine = firstVisibleItem / mColumnsNum; + mScrollY = mFirstTop - rowLine * (mVerticalSpacing + mRowHeight); + } + + @Override + public void setOnItemLongClickListener(OnItemLongClickListener listener) { + mOnItemLongClickListener = listener; + } + + @Override + public void setOnScrollListener(OnScrollListener l) { + mOnScrollListener = l; + } + + /** + * If you want to draw something in gridview,just set a drawer. + * + * @param drawer a drawer. + * @param drawOnTop true if you want draw on the top of Gridview. + * @see IDrawer#onDraw(Canvas, int, int) + */ + public void setDrawer(IDrawer drawer, boolean drawOnTop) { + this.mDrawer = drawer; + mDrawOnTop = drawOnTop; + } + + /** + * Tells the GridView whether to use Selector. + * Nonuse selector by default. + * + * @param enabled true if use selector. + */ + public void setSelectorEnabled(boolean enabled) { + if (enabled != mUseSelector) { + mUseSelector = enabled; + if (mUseSelector && mSelector != null) { + setSelector(mSelector); + } + if (!mUseSelector) { + setSelector(getSelector()); + } + } + + } + + public boolean isSelectorEnabled() { + return mUseSelector; + } + + @Override + public void setSelector(Drawable sel) { + if (mUseSelector) { + super.setSelector(sel); + } else { + mSelector = sel; + if (mSpaceDrawable == null) { + mSpaceDrawable = new ColorDrawable(); + } + super.setSelector(mSpaceDrawable); + } + } + + @Override + public void setHorizontalSpacing(int horizontalSpacing) { + super.setHorizontalSpacing(horizontalSpacing); + mHorizontalSpacing = horizontalSpacing; + } + + @Override + public void setNumColumns(int numColumns) { + super.setNumColumns(numColumns); + mColumnsNum = numColumns; + } + + @Override + public void setClipToPadding(boolean clipToPadding) { + super.setClipToPadding(clipToPadding); + mClipToPadding = clipToPadding; + } + + @Override + public void setVerticalSpacing(int verticalSpacing) { + super.setVerticalSpacing(verticalSpacing); + mVerticalSpacing = verticalSpacing; + } + + @Override + public void setAdapter(ListAdapter adapter) { + mAdapter = adapter; + if (adapter instanceof OnItemMovedListener) { + mItemMovedListener = (OnItemMovedListener) adapter; + } else { + //TODO should throw a exception here. + log("Your adapter should implements OnItemMovedListener for listening item's swap action."); + } + super.setAdapter(mAdapter); + } + + /** + * When set, will change TOUCH mode to LONG_PRESS mode if the content of gridview can not scroll. + * Set by default. + * + * @param autoOptimize + */ + public void setAutoOptimize(boolean autoOptimize) { + mAutoOptimize = autoOptimize; + } + + public void setMode(MODE mode) { + this.mode = mode; + } + + /** + * set the number of pixels gridview scrolled per second. + * + * @param scrollSpeed + */ + public void setScrollSpeed(int scrollSpeed) { + mScrollSpeed = scrollSpeed; + } + + public int getDragPosition() { + return mDraggedPosition; + } + + private void refreshChildren() { + int childCount = getChildCount(); + clearAllChildren(); + for (int i = 0; i < childCount; i++) { + addChild(i, super.getChildAt(i)); + } + } + + @Override + protected void layoutChildren() { + super.layoutChildren(); +// if (getChildCount() > 0) { +// getBasicValues(getFirstVisiblePosition()); +// } + if (mDraggedView != null) { + refreshChildren(); + View draggedView = super.getChildAt(mDraggedPosition - mFirstVisibleFirstItem); + dispatchItemReleased(); + mDraggedView = draggedView; + dispatchItemCaptured(); + correctDraggedViewLocation(0, 0); + } else { + refreshChildren(); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + mGridViewVisibleRect = null; + measureVisibleRect(); + } + + @Override + protected void detachViewsFromParent(int start, int count) { + super.detachViewsFromParent(start, count); + if (start == 0) { + for (int i = start; i < start + count; i++) { + removeChild(0); + } + } else { + start = mChildren.size() - 1; + for (int i = start; i > start - count; i--) { + removeChild(i); + } + } + } + + @Override + protected void attachViewToParent(View child, int index, ViewGroup.LayoutParams params) { + super.attachViewToParent(child, index, params); + addChild(index, child); + } + + @Override + protected void detachAllViewsFromParent() { + super.detachAllViewsFromParent(); + clearAllChildren(); + } + + @Override + public void onViewAdded(View child) { + super.onViewAdded(child); + int index = indexOfChild(child); + addChild(index, child); + } + + @Override + public void onViewRemoved(View child) { + super.onViewRemoved(child); + removeChild(child); + } + + private void clearAllChildren() { + mChildren.clear(); + } + + private void addChild(int index, View view) { + if (index < 0) { + index = mChildren.size(); + } + mChildren.add(index, view); + } + + private boolean removeChild(View view) { + boolean result = false; + int childSize = mChildren.size(); + for (int i = 0; i < childSize; i++) { + Child child = mChildren.get(i); + if (child.view == view) { + result = mChildren.remove(child); + break; + } + } + return result; + } + + private void removeChild(int index) { + mChildren.remove(index); + } + + public boolean isTouchMode() { + if (canScrollDown() || canScrollUp()) { + // if the content of gridview can not scroll,change mode to LONG_PRESS for better user experience. + if (mAutoOptimize) { + mode = MODE.LONG_PRESS; + } + } + return mode == MODE.TOUCH; + } + + public boolean isNoneMode() { + return mode == MODE.NONE; + } + + public boolean isLongPressMode() { + return mode == MODE.LONG_PRESS; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return handleTouchEvent(ev); + } + + private boolean handleTouchEvent(MotionEvent ev) { + int action = ev.getAction(); + mCurrMotionEvent = ev; + boolean handled = false; + switch (action) { + case MotionEvent.ACTION_DOWN: + mLastMotionX = ev.getRawX(); + mLastMotionY = ev.getRawY(); + mShouldMove = false; +// Debug.startMethodTracing("MoveOnGridView"); + if (isTouchMode()) { + recordDragViewIfNeeded(null, -1); + invalidate(); + if (mDraggedView != null) { + mDraggedView.setPressed(true); + } + } + break; + case MotionEvent.ACTION_MOVE: + int deltaX = (int) (ev.getRawX() - mLastMotionX); + int deltaY = (int) (ev.getRawY() - mLastMotionY); + if (mDraggedView != null) { + handled = true; + // intercept the MotionEvent only when user is not scrolling + if (!mShouldMove) { +// if (Math.abs(deltaX) > mTouchSlop || Math.abs(deltaY) > mTouchSlop) { +// if (mDraggedView.isPressed()) { +// mDraggedView.setPressed(false); +// } +// mShouldMove = true; +// } + if (mDraggedView.isPressed()) { + mDraggedView.setPressed(false); + } + mShouldMove = true; + } + if (mShouldMove) { + correctDraggedViewLocation(deltaX, deltaY); + swapItemIfNeed(ev); + scrollIfNeeded(); + } + mLastMotionX = ev.getRawX(); + mLastMotionY = ev.getRawY(); + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: +// Debug.stopMethodTracing(); + if (mDraggedView != null) { + releaseDraggedView(); + mScrollRunner.cancel(); + handled = true; + } +// int motionPosition = getMotionPosition(); + mDraggedView = null; + mCurrMotionEvent = null; +// super.onTouchEvent(ev); + break; + } + if (isTouchMode()) { + handled = true; + } + boolean result = handled ? handled : super.onTouchEvent(ev); + return result; + } + + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + boolean handled = false; + if (isLongPressMode() && !isFixedPosition(position)) { + recordDragViewIfNeeded(view, position); + handled = true; + } + if (mOnItemLongClickListener != null) { + boolean longClickReturn = mOnItemLongClickListener.onItemLongClick(parent, view, position, id); + if (!handled) { + handled = longClickReturn; + } + } + return handled; + } + + private int getMotionPosition() { + final int realX = (int) (mCurrMotionEvent.getRawX() - mGridViewVisibleRect.left); + final int realY = (int) (mCurrMotionEvent.getRawY() - mGridViewVisibleRect.top); + return pointToPosition(realX, realY); + } + + private void recordDragViewIfNeeded(View view, int position) { + measureVisibleRect(); + if (view == null && position == -1) { + int currDraggedPosition = getMotionPosition(); + if (currDraggedPosition != INVALID_POSITION) { + recordDragViewIfNeeded(getChildAt(currDraggedPosition - mFirstVisibleFirstItem), currDraggedPosition); + } + } else { + mDraggedPosition = position; + mDraggedView = view; + measureDraggedRect(); + mDraggedIndex = mDraggedPosition - mFirstVisibleFirstItem; + dispatchItemCaptured(); + correctDraggedViewLocation(0, 0); + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + if (!mDrawOnTop) { + drawSomeThing(canvas); + } + super.dispatchDraw(canvas); + if (mDrawOnTop) { + drawSomeThing(canvas); + } + } + + private void drawSomeThing(Canvas canvas) { + if (mDrawer != null) { + canvas.save(); + mDrawer.onDraw(canvas, getWidth(), getHeight()); + canvas.restore(); + } + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private void scrollIfNeeded() { + measureDraggedRect(); + measureVisibleRect(); + if (!isDraggedInGridView()) { + mScrollRunner.cancel(); + } else if (mDraggedRect.top <= mGridViewVisibleRect.top) { + boolean canScrollDown = canScrollDown(); + if (canScrollDown && !mScrollRunner.isRunning()) { + int deltaY = mClipToPadding ? mScrollY : mScrollY - getListPaddingTop(); + final int duration = Math.abs(deltaY) * 1000 / mScrollSpeed; + mScrollRunner.start(0, deltaY, duration); + } + } else if (mDraggedRect.bottom >= mGridViewVisibleRect.bottom) { + boolean canScrollUp = canScrollUp(); + if (canScrollUp && !mScrollRunner.isRunning()) { + int deltaY = mClipToPadding ? getTotalScrollY() + mScrollY : getTotalScrollY() + mScrollY + getListPaddingBottom(); + final int duration = Math.abs(deltaY) * 1000 / mScrollSpeed; + mScrollRunner.start(0, deltaY, duration); + } + } else { + mScrollRunner.cancel(); + } + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + public void scrollListBy(int deltaY) { + if (SdkVerUtils.isAbove19()) { + super.scrollListBy(deltaY); + } else { + ReflectUtil.invokeMethod(this, "trackMotionScroll", new Object[]{-deltaY, -deltaY}, new Class[]{int.class, int.class}); + } + } + + @Override + public void onMove(int lastX, int lastY, int curX, int curY) { + int deltaY = curY - lastY; + mDraggedView.offsetTopAndBottom(deltaY); + scrollListBy(deltaY); + swapItemIfNeed(mCurrMotionEvent); + } + + @Override + public void onDone() { + } + + /** + * detact whether the content of gridview can scroll down. + * + * @return + */ + public boolean canScrollDown() { + final int threshold = mClipToPadding ? 0 : getListPaddingTop(); + if (mScrollY < threshold) { + return true; + } + return false; + } + + /** + * detact whether the content of gridview can scroll up. + * + * @return + */ + public boolean canScrollUp() { + final int threshold = mClipToPadding ? -mScrollY : getListPaddingBottom(); + if (getTotalScrollY() > -threshold) { + return true; + } + return false; + } + + public int getTotalScrollY() { + if (mAdapter == null) return 0; + int row = (mAdapter.getCount() - 1) / mColumnsNum + 1; + int total = row * mRowHeight + (row - 1) * mVerticalSpacing; + return total - getHeight(); + } + + private boolean isDraggedInGridView() { + return mGridViewVisibleRect.intersects(mDraggedRect.left, mDraggedRect.top, mDraggedRect.right, mDraggedRect.bottom); + } + + private void measureDraggedRect() { + mDraggedView.getGlobalVisibleRect(mDraggedRect); + int location[] = new int[2]; + mDraggedView.getLocationOnScreen(location); + mDraggedRect.set(location[0], location[1], location[0] + mDraggedRect.width(), location[1] + mDraggedRect.height()); + } + + private void measureVisibleRect() { + if (mGridViewVisibleRect == null) { + mGridViewVisibleRect = new Rect(); + getGlobalVisibleRect(mGridViewVisibleRect); + int location[] = new int[2]; + getLocationOnScreen(location); + mGridViewVisibleRect.set(location[0], location[1], location[0] + mGridViewVisibleRect.width(), + location[1] + mGridViewVisibleRect.height()); + } + } + + private boolean isFixedPosition(int position) { + if (position != INVALID_POSITION && mAdapter instanceof OnItemMovedListener) { + mItemMovedListener = (OnItemMovedListener) mAdapter; + if (mItemMovedListener.isFixed(position)) { + return true; + } + return false; + } + return false; + } + + private void swapItemIfNeed(MotionEvent ev) { + if (ev == null || mDraggedView == null || isFixedPosition(mDraggedPosition)) return; + measureVisibleRect(); + measureDraggedRect(); + final int realX = (int) (ev.getRawX() - mGridViewVisibleRect.left); + final int realY = (int) (ev.getRawY() - mGridViewVisibleRect.top); + int currDraggedPosition = pointToPosition(realX, realY); + boolean intersect = isDraggedInGridView(); + int draggedViewPosition = INVALID_POSITION; + if (currDraggedPosition != INVALID_POSITION) { + if (intersect) { + draggedViewPosition = currDraggedPosition; + } else { + draggedViewPosition = INVALID_POSITION; + } + } + if (isFixedPosition(draggedViewPosition)) { + draggedViewPosition = INVALID_POSITION; + } + if (draggedViewPosition != INVALID_POSITION) { + int dragPosition = getDragPosition(); + if (draggedViewPosition != dragPosition) { + measureDraggedRect(); + if (draggedViewPosition < dragPosition) { + for (int i = dragPosition - 1; i >= draggedViewPosition; i--) { + swapItem(i, i + 1); + } + } else { + for (int i = dragPosition + 1; i <= draggedViewPosition; i++) { + swapItem(i, i - 1); + } + } + moveViewToPosition(draggedViewPosition, mDraggedView); + mDraggedPosition = draggedViewPosition; + } + } + } + + private void swapItem(int from, int to) { + int fromIndex = from - getFirstVisiblePosition(); + Child fromChild = mChildren.get(fromIndex); + final View fromView = fromChild.view; + if (fromChild == null || fromView == null) return; + fromChild.moveTo(from, to); + moveViewToPosition(to, fromView); + dispatchItemMoved(from, to); + detachViewFromParent(fromView); + super.attachViewToParent(fromView, to - getFirstVisiblePosition(), fromView.getLayoutParams()); + } + + private void dispatchItemMoved(int from, int to) { + if (mItemMovedListener != null) { + mItemMovedListener.onItemMoved(from, to); + } + } + + private void moveViewToPosition(int position, View view) { + boolean removeResult = removeChild(view); + addChild(getIndex(position), view); + } + + private int getIndex(int position) { + return position - mFirstVisibleFirstItem; + } + + public int[] getLeftAndTopForPosition(int position) { + int[] lt = new int[2]; + int m = position % mColumnsNum; + int n = position / mColumnsNum; + int left = mFirstLeft + m * (mColumnWidth + mHorizontalSpacing); + int top = mScrollY + n * (mRowHeight + mVerticalSpacing); + lt[0] = left; + lt[1] = top; + return lt; + } + + @Override + public View getChildAt(int index) { + int position = index; + final int childCount = getChildCount(); + if (mDraggedView != null) { + int dragIndex = mDraggedPosition - mFirstVisibleFirstItem; + if (dragIndex == 0) { + if (index == 0) { + position = 1; + } else if (position == 1) { + position = 0; + } else { +// position = position; + } + + } else if (dragIndex == childCount - 1) { + if (childCount % mColumnsNum != 1) { + if (index == childCount - 1) { + position = index - 1; + } else if (index == childCount - 2) { + position = childCount - 1; + } + } + } + } + if (position >= getChildCount()) { + position = getChildCount() - 1; + } + return super.getChildAt(position); + } + + @Override + public int getChildCount() { + return super.getChildCount(); + } + + @Override + public int pointToPosition(int x, int y) { + if (mColumnWidth + mHorizontalSpacing <= 0 || mRowHeight + mVerticalSpacing == 0) { + return INVALID_POSITION; + } + int m = (x - mFirstLeft) / (mColumnWidth + mHorizontalSpacing); + int n = (y - mFirstTop) / (mRowHeight + mVerticalSpacing); + int right = mFirstLeft + (m + 1) * (mColumnWidth + mHorizontalSpacing); + int bottom = mFirstTop + (n + 1) * (mRowHeight + mVerticalSpacing) + mRowHeight; + if (x > right || y > bottom || m >= mColumnsNum) { + return INVALID_POSITION; + } else { + int result = n * mColumnsNum + m + mFirstVisibleFirstItem; + result = result <= getLastVisiblePosition() ? result : INVALID_POSITION; + return result; + } + } + + private void log(String msg) { + Log.e("moveOnGridView", msg); + } + + public void setOnItemClickListener(OnItemClickListener listener) { + mOnItemClickListener = listener; + } + + /** + * Let our finger touch the center area of draggedView. + */ + private void correctDraggedViewLocation(int deltaX, int deltaY) { + if (mCurrMotionEvent == null) return; + float motionX = mCurrMotionEvent.getRawX(); + float motionY = mCurrMotionEvent.getRawY(); + measureVisibleRect(); + deltaX = (int) (motionX - mGridViewVisibleRect.left - (mDraggedView.getLeft() + mColumnWidth / 2)) + deltaX; + deltaY = (int) (motionY - mGridViewVisibleRect.top - (mDraggedView.getTop() + mRowHeight / 2)) + deltaY; + if (!isFixedPosition(mDraggedPosition)) { + mDraggedView.offsetLeftAndRight(deltaX); + mDraggedView.offsetTopAndBottom(deltaY); + } + + } + + private void dispatchItemCaptured() { + if (mItemCapturedListener != null && !isFixedPosition(mDraggedPosition)) { + mItemCapturedListener.onItemCaptured(mDraggedView, mDraggedPosition); + } + } + + private void releaseDraggedView() { + int[] destination = getLeftAndTopForPosition(mDraggedPosition); + int offsetX = destination[0] - mDraggedView.getLeft(); + int offsetY = destination[1] - mDraggedView.getTop(); + mDraggedView.offsetLeftAndRight(offsetX); + mDraggedView.offsetTopAndBottom(offsetY); + dispatchItemReleased(); + if (mDraggedView.isPressed()) { + mDraggedView.setPressed(false); + } + } + + private void dispatchItemReleased() { + if (mItemCapturedListener != null && !isFixedPosition(mDraggedPosition)) { + mItemCapturedListener.onItemReleased(mDraggedView, mDraggedPosition); + } + } + + private OnItemCapturedListener mItemCapturedListener; + + public void setOnItemCapturedListener(OnItemCapturedListener listener) { + mItemCapturedListener = listener; + } + + @Override + protected int getChildDrawingOrder(int childCount, int i) { + int order = i; + if (mDraggedView != null) { + mDraggedIndex = mDraggedPosition - mFirstVisibleFirstItem; + if (i == mDraggedIndex) { + order = childCount - 1; + } else if (i == childCount - 1) { + order = mDraggedIndex; + } + } + return order; + } + + public MODE getMode() { + return mode; + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (mOnItemClickListener != null) { + mOnItemClickListener.onItemClick(parent, view, position, id); + } + } + + public enum MODE { + TOUCH, LONG_PRESS, NONE; + + public static int indexOf(MODE mode) { + int index = -1; + for (MODE mode2 : MODE.values()) { + index++; + if (mode == mode2) { + break; + } + } + return index; + } + + public static MODE get(int index) { + int i = 0; + for (MODE mode : MODE.values()) { + if (i == index) { + return mode; + } + i++; + } + return null; + } + + @Override + public String toString() { + String name = name(); + switch (name) { + case "TOUCH": + return "编辑模式"; + case "LONG_PRESS": + return "长按拖拽模式"; + case "NONE": + return "普通模式"; + } + return super.toString(); + } + } +} diff --git a/app/src/main/java/cc/hicore/ui/handygridview/listener/IDrawer.java b/app/src/main/java/cc/hicore/ui/handygridview/listener/IDrawer.java new file mode 100644 index 0000000000..a93b1a23c3 --- /dev/null +++ b/app/src/main/java/cc/hicore/ui/handygridview/listener/IDrawer.java @@ -0,0 +1,15 @@ +package cc.hicore.ui.handygridview.listener; + +import android.graphics.Canvas; + + +public interface IDrawer { + /** + * You can draw something in gridview by this method. + * + * @param canvas + * @param width the gridview's width + * @param height the gridview's height + */ + void onDraw(Canvas canvas, int width, int height); +} diff --git a/app/src/main/java/cc/hicore/ui/handygridview/listener/OnItemCapturedListener.java b/app/src/main/java/cc/hicore/ui/handygridview/listener/OnItemCapturedListener.java new file mode 100644 index 0000000000..f394fd962e --- /dev/null +++ b/app/src/main/java/cc/hicore/ui/handygridview/listener/OnItemCapturedListener.java @@ -0,0 +1,19 @@ +package cc.hicore.ui.handygridview.listener; + +import android.view.View; + +public interface OnItemCapturedListener { + /** + * Called when user selected a view to drag. + * + * @param v + */ + void onItemCaptured(View v,int position); + + /** + * Called when user released the drag view. + * + * @param v + */ + void onItemReleased(View v,int position); +} diff --git a/app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/ICarrier.java b/app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/ICarrier.java new file mode 100644 index 0000000000..144ac57675 --- /dev/null +++ b/app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/ICarrier.java @@ -0,0 +1,16 @@ +package cc.hicore.ui.handygridview.scrollrunner; + + +import android.content.Context; + +public interface ICarrier { + Context getContext(); + + void onMove(int lastX, int lastY, int curX, int curY); + + void onDone(); + + boolean post(Runnable runnable); + + boolean removeCallbacks(Runnable action); +} diff --git a/app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/OnItemMovedListener.java b/app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/OnItemMovedListener.java new file mode 100644 index 0000000000..9a5aabec09 --- /dev/null +++ b/app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/OnItemMovedListener.java @@ -0,0 +1,18 @@ +package cc.hicore.ui.handygridview.scrollrunner; + +public interface OnItemMovedListener { + /** + * Called when user moved the item of gridview. + * you should swipe data in this method. + * @param from item's original position + * @param to item's destination poisition + */ + void onItemMoved(int from, int to); + + /** + * return true if the item of special position can not move. + * @param position + * @return + */ + boolean isFixed(int position); +} diff --git a/app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/OnceRunnable.java b/app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/OnceRunnable.java new file mode 100644 index 0000000000..3a46be1e29 --- /dev/null +++ b/app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/OnceRunnable.java @@ -0,0 +1,30 @@ +package cc.hicore.ui.handygridview.scrollrunner; + +import android.view.View; + +public abstract class OnceRunnable implements Runnable { + private boolean mScheduled; + + public final void run() { + onRun(); + mScheduled = false; + } + + public abstract void onRun(); + + public void postSelf(View carrier) { + postDelaySelf(carrier, 0); + } + + public void postDelaySelf(View carrier, int delay) { + if (!mScheduled) { + carrier.postDelayed(this, delay); + mScheduled = true; + } + } + + public void removeSelf(View carrier) { + mScheduled = false; + carrier.removeCallbacks(this); + } +} diff --git a/app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/ScrollRunner.java b/app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/ScrollRunner.java new file mode 100644 index 0000000000..e85d8c7042 --- /dev/null +++ b/app/src/main/java/cc/hicore/ui/handygridview/scrollrunner/ScrollRunner.java @@ -0,0 +1,93 @@ +package cc.hicore.ui.handygridview.scrollrunner; + + +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; +import android.widget.Scroller; + +public class ScrollRunner implements Runnable { + private Scroller mScroller; + private ICarrier mCarrier; + private int mDuration = 250; + private int lastX, lastY; + + public ScrollRunner(ICarrier carrier) { + this(carrier, new LinearInterpolator()); + } + + public ScrollRunner(ICarrier carrier, Interpolator interpolator) { + mCarrier = carrier; + mScroller = new Scroller(carrier.getContext(), interpolator); + } + + public void setCarrier(ICarrier carrier) { + mCarrier = carrier; + } + + public void start(int dx, int dy) { + start(dx, dy, mDuration); + } + + public void start(int dx, int dy, int duration) { + start(0, 0, dx, dy, duration); + } + + public void start(int startX, int startY, int dx, int dy) { + start(startX, startY, dx, dy, mDuration); + } + + public void start(int startX, int startY, int dx, int dy, int duration) { + this.mDuration = duration; + mScroller.startScroll(startX, startY, dx, dy, duration); + mCarrier.removeCallbacks(this); + mCarrier.post(this); + lastX = startX; + lastY = startY; + } + + public void cancel() { + if (!mScroller.isFinished()) { + mCarrier.removeCallbacks(this); + mScroller.forceFinished(true); + } + } + + public int getCurX() { + return mScroller.getCurrX(); + } + + public int getCurY() { + return mScroller.getCurrY(); + } + + public void abortAnimation() { + if (!mScroller.isFinished()) { + mScroller.abortAnimation(); + } + } + + public boolean isRunning() { + return !mScroller.isFinished(); + } + + @Override + public void run() { + if (mScroller.computeScrollOffset()) { + int currentX = mScroller.getCurrX(); + int currentY = mScroller.getCurrY(); + mCarrier.onMove(lastX, lastY, currentX, currentY); + lastX = currentX; + lastY = currentY; + if (currentX == mScroller.getFinalX() && currentY == mScroller.getFinalY()) { + mCarrier.removeCallbacks(this); + mCarrier.onDone(); + } else { + mCarrier.post(this); + } + } else { + mCarrier.removeCallbacks(this); + mCarrier.onDone(); + } + } + +} diff --git a/app/src/main/java/cc/hicore/ui/handygridview/utils/Pools.java b/app/src/main/java/cc/hicore/ui/handygridview/utils/Pools.java new file mode 100644 index 0000000000..90a9ec833d --- /dev/null +++ b/app/src/main/java/cc/hicore/ui/handygridview/utils/Pools.java @@ -0,0 +1,125 @@ +package cc.hicore.ui.handygridview.utils; + +public final class Pools { + + /** + * Interface for managing a pool of objects. + * + * @param The pooled type. + */ + public static interface Pool { + + /** + * @return An instance from the pool if such, null otherwise. + */ + public T acquire(); + + /** + * Release an instance to the pool. + * + * @param instance The instance to release. + * @return Whether the instance was put in the pool. + * + * @throws IllegalStateException If the instance is already in the pool. + */ + public boolean release(T instance); + } + + private Pools() { + /* do nothing - hiding constructor */ + } + + /** + * Simple (non-synchronized) pool of objects. + * + * @param The pooled type. + */ + public static class SimplePool implements Pool { + private final Object[] mPool; + + private int mPoolSize; + + /** + * Creates a new instance. + * + * @param maxPoolSize The max pool size. + * + * @throws IllegalArgumentException If the max pool size is less than zero. + */ + public SimplePool(int maxPoolSize) { + if (maxPoolSize <= 0) { + throw new IllegalArgumentException("The max pool size must be > 0"); + } + mPool = new Object[maxPoolSize]; + } + + @Override + @SuppressWarnings("unchecked") + public T acquire() { + if (mPoolSize > 0) { + final int lastPooledIndex = mPoolSize - 1; + T instance = (T) mPool[lastPooledIndex]; + mPool[lastPooledIndex] = null; + mPoolSize--; + return instance; + } + return null; + } + + @Override + public boolean release(T instance) { + if (isInPool(instance)) { + throw new IllegalStateException("Already in the pool!"); + } + if (mPoolSize < mPool.length) { + mPool[mPoolSize] = instance; + mPoolSize++; + return true; + } + return false; + } + + private boolean isInPool(T instance) { + for (int i = 0; i < mPoolSize; i++) { + if (mPool[i] == instance) { + return true; + } + } + return false; + } + } + + /** + * Synchronized) pool of objects. + * + * @param The pooled type. + */ + public static class SynchronizedPool extends SimplePool { + private final Object mLock = new Object(); + + /** + * Creates a new instance. + * + * @param maxPoolSize The max pool size. + * + * @throws IllegalArgumentException If the max pool size is less than zero. + */ + public SynchronizedPool(int maxPoolSize) { + super(maxPoolSize); + } + + @Override + public T acquire() { + synchronized (mLock) { + return super.acquire(); + } + } + + @Override + public boolean release(T element) { + synchronized (mLock) { + return super.release(element); + } + } + } +} diff --git a/app/src/main/java/cc/hicore/ui/handygridview/utils/ReflectUtil.java b/app/src/main/java/cc/hicore/ui/handygridview/utils/ReflectUtil.java new file mode 100644 index 0000000000..0a2d08676a --- /dev/null +++ b/app/src/main/java/cc/hicore/ui/handygridview/utils/ReflectUtil.java @@ -0,0 +1,32 @@ +package cc.hicore.ui.handygridview.utils; + +import android.text.TextUtils; +import java.lang.reflect.Method; + +public class ReflectUtil { + public static Object invokeMethod(Object targetObject, String methodName, Object[] params, Class[] paramTypes) { + Object returnObj = null; + if (targetObject == null || TextUtils.isEmpty(methodName)) { + return null; + } + Method method = null; + for (Class cls = targetObject.getClass(); cls != Object.class; cls = cls.getSuperclass()) { + try { + method = cls.getDeclaredMethod(methodName, paramTypes); + break; + } catch (Exception e) { +// e.printStackTrace(); +// return null; + } + } + if (method != null) { + method.setAccessible(true); + try { + returnObj = method.invoke(targetObject, params); + } catch (Exception e) { + e.printStackTrace(); + } + } + return returnObj; + } +} diff --git a/app/src/main/java/cc/hicore/ui/handygridview/utils/SdkVerUtils.java b/app/src/main/java/cc/hicore/ui/handygridview/utils/SdkVerUtils.java new file mode 100644 index 0000000000..203bc0f3d0 --- /dev/null +++ b/app/src/main/java/cc/hicore/ui/handygridview/utils/SdkVerUtils.java @@ -0,0 +1,21 @@ +package cc.hicore.ui.handygridview.utils; + +import android.os.Build; + +/** + * Created by Administrator on 2017/11/26. + */ + +public class SdkVerUtils { + public static boolean isAboveVersion(int version) { + int sdkVersion = Build.VERSION.SDK_INT; + if (sdkVersion >= version) { + return true; + } + return false; + } + + public static boolean isAbove19() { + return isAboveVersion(Build.VERSION_CODES.KITKAT); + } +} diff --git a/app/src/main/java/cc/ioctl/util/LayoutHelper.java b/app/src/main/java/cc/ioctl/util/LayoutHelper.java index 4624e596c7..6910fd2c2d 100644 --- a/app/src/main/java/cc/ioctl/util/LayoutHelper.java +++ b/app/src/main/java/cc/ioctl/util/LayoutHelper.java @@ -24,6 +24,7 @@ import android.app.Activity; import android.content.Context; +import android.content.ContextWrapper; import android.content.res.TypedArray; import android.graphics.Point; import android.graphics.Rect; @@ -195,16 +196,15 @@ public static boolean isSmallWindowNeedPlay(View v) { if (visibleRect) { Point point = new Point(); Context baseContext = v.getContext(); - if (baseContext instanceof ContextUtils.FixContext) { - ContextUtils.FixContext fix = (ContextUtils.FixContext) v.getContext(); - baseContext = fix.getBaseContext(); + if (!(baseContext instanceof Activity) && (baseContext instanceof ContextWrapper)) { + baseContext = ((ContextWrapper)baseContext).getBaseContext(); } if (baseContext instanceof Activity) { ((Activity) baseContext).getWindowManager().getDefaultDisplay().getSize(point); - return rect.top >= 0 && rect.top <= point.y && rect.left >= 0 && rect.left <= point.x; + return rect.top >= 0 && (rect.top - 100) <= point.y && rect.left >= 0 && rect.left <= point.x; } } return false; diff --git a/app/src/main/java/io/github/qauxv/util/Toasts.java b/app/src/main/java/io/github/qauxv/util/Toasts.java index 1b83779d38..b78b9782f7 100644 --- a/app/src/main/java/io/github/qauxv/util/Toasts.java +++ b/app/src/main/java/io/github/qauxv/util/Toasts.java @@ -171,4 +171,7 @@ public static void show(Context ctx, @NonNull CharSequence text, int duration) { public static void show(Context ctx, @NonNull CharSequence text) { showToast(ctx, TYPE_PLAIN, text, LENGTH_SHORT); } + public static void show(@NonNull CharSequence text) { + showToast(null, TYPE_PLAIN, text, LENGTH_SHORT); + } } diff --git a/app/src/main/res/drawable/sticker_panel_input_icon.png b/app/src/main/res/drawable/sticker_panel_input_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1c10464fbf04f3601324bcbf1f65d786727d1b0f GIT binary patch literal 4879 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 BpP&E$ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/sticker_panel_like_icon.png b/app/src/main/res/drawable/sticker_panel_like_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d0d48891b829bb05c1fbed9b5687734507e43114 GIT binary patch literal 9411 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 + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/sticker_panen_set_button_icon.png b/app/src/main/res/drawable/sticker_panen_set_button_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e020c055f27ac7db84cfdeaa1d9005dacc525c65 GIT binary patch literal 10613 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 + android:text="输入表情包路径或ID导入"/> -