├── .gitmodules ├── image ├── .gitignore ├── chat01.png ├── main01.png ├── main02.png ├── main03.png ├── main04.png └── main05.png ├── ColorPicker ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── drawable-hdpi │ │ │ └── cpv_alpha.png │ │ ├── drawable-xhdpi │ │ │ └── cpv_alpha.png │ │ ├── drawable-xxhdpi │ │ │ └── cpv_alpha.png │ │ ├── values │ │ │ ├── ids.xml │ │ │ ├── dimen.xml │ │ │ ├── styles.xml │ │ │ ├── strings.xml │ │ │ └── attrs.xml │ │ ├── drawable │ │ │ ├── cpv_ic_arrow_right_black_24dp.xml │ │ │ ├── cpv_preset_checked.xml │ │ │ ├── cpv_btn_background_pressed.xml │ │ │ └── cpv_btn_background.xml │ │ └── layout │ │ │ ├── cpv_preference_circle.xml │ │ │ ├── cpv_preference_square.xml │ │ │ ├── cpv_preference_circle_large.xml │ │ │ ├── cpv_preference_square_large.xml │ │ │ ├── cpv_color_item_circle.xml │ │ │ ├── cpv_color_item_square.xml │ │ │ ├── cpv_dialog_presets.xml │ │ │ └── cpv_dialog_color_picker.xml │ │ └── java │ │ └── com │ │ └── jaredrummler │ │ └── android │ │ └── colorpicker │ │ ├── ColorShape.java │ │ ├── DrawingUtils.java │ │ ├── NestedGridView.java │ │ ├── ColorPickerDialogListener.java │ │ ├── AlphaPatternDrawable.java │ │ └── ColorPaletteAdapter.java ├── build.gradle ├── proguard-rules.pro └── gradle.properties ├── app ├── .gitignore ├── src │ └── main │ │ ├── assets │ │ ├── xposed_init │ │ ├── icon │ │ │ ├── ic_add.png │ │ │ ├── ic_chat.png │ │ │ ├── ic_money.png │ │ │ ├── ic_scan.png │ │ │ ├── tab_icon0.png │ │ │ ├── tab_icon1.png │ │ │ ├── tab_icon2.png │ │ │ ├── tab_icon3.png │ │ │ ├── bubble_left.9.png │ │ │ ├── ic_person_add.png │ │ │ └── bubble_right.9.png │ │ └── config │ │ │ └── view │ │ │ ├── floatbutton_readme.txt │ │ │ └── floatbutton.txt │ │ ├── res │ │ ├── drawable-xxhdpi │ │ │ ├── ic_add.png │ │ │ ├── ic_clear.png │ │ │ ├── ic_done.png │ │ │ └── ic_arrow_back.png │ │ ├── mipmap-xhdpi │ │ │ └── mdwechat_icon.png │ │ ├── values │ │ │ ├── styles.xml │ │ │ ├── dimen.xml │ │ │ ├── arrays.xml │ │ │ └── colors.xml │ │ ├── values-en │ │ │ └── strings.xml │ │ └── layout │ │ │ ├── item_wechat_config.xml │ │ │ ├── activity_mark_down.xml │ │ │ ├── activity_settings.xml │ │ │ └── view_floatbutton.xml │ │ ├── java │ │ └── com │ │ │ └── blanke │ │ │ └── mdwechat │ │ │ ├── auto_search │ │ │ ├── bean │ │ │ │ ├── LogEvent.kt │ │ │ │ └── OutputJson.kt │ │ │ ├── WechatGlobal.kt │ │ │ ├── Logs.kt │ │ │ ├── Fields.kt │ │ │ └── Methods.kt │ │ │ ├── settings │ │ │ ├── bean │ │ │ │ └── WechatConfig.kt │ │ │ ├── api │ │ │ │ └── APIManager.kt │ │ │ ├── view │ │ │ │ └── DownloadWechatDialog.kt │ │ │ └── SettingsActivity.kt │ │ │ ├── hookers │ │ │ ├── base │ │ │ │ ├── Hooker.kt │ │ │ │ └── HookerProvider.kt │ │ │ ├── DebugHooker.kt │ │ │ ├── SchemeHooker.kt │ │ │ ├── DiscoverHooker.kt │ │ │ ├── AvatarHooker.kt │ │ │ ├── main │ │ │ │ └── HomeActionBarHook.kt │ │ │ ├── LogHooker.kt │ │ │ ├── ActionBarHooker.kt │ │ │ ├── SettingsHooker.kt │ │ │ └── StatusBarHooker.kt │ │ │ ├── bean │ │ │ ├── ViewTreeItem.kt │ │ │ └── FloatButtonConfig.kt │ │ │ ├── util │ │ │ ├── VXPUtils.kt │ │ │ ├── ColorUtils.java │ │ │ ├── ConvertUtils.java │ │ │ ├── ViewTreeUtils.kt │ │ │ ├── KtUtils.kt │ │ │ ├── NightModeUtils.kt │ │ │ ├── ViewUtils.kt │ │ │ ├── DrawableUtils.java │ │ │ ├── ImageHelper.java │ │ │ ├── FileUtils.kt │ │ │ └── LogUtil.kt │ │ │ ├── Objects.kt │ │ │ ├── Version.kt │ │ │ ├── CC.java │ │ │ ├── WechatGlobal.kt │ │ │ ├── widget │ │ │ └── CircleImageView.kt │ │ │ ├── Common.kt │ │ │ ├── markdown │ │ │ └── MarkDownActivity.kt │ │ │ ├── WechatHook.kt │ │ │ ├── config │ │ │ ├── AppCustomConfig.kt │ │ │ └── WxVersionConfig.kt │ │ │ ├── Fields.kt │ │ │ ├── Methods.kt │ │ │ └── WeChatHelper.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── fab-lib ├── .gitignore ├── gradle.properties ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── values │ │ │ ├── ids.xml │ │ │ ├── dimens.xml │ │ │ └── attrs.xml │ │ └── anim │ │ │ ├── fab_scale_up.xml │ │ │ ├── fab_scale_down.xml │ │ │ ├── fab_slide_in_from_left.xml │ │ │ ├── fab_slide_in_from_right.xml │ │ │ ├── fab_slide_out_to_left.xml │ │ │ └── fab_slide_out_to_right.xml │ │ └── java │ │ └── com │ │ └── github │ │ └── clans │ │ └── fab │ │ └── Util.java ├── build.gradle └── proguard-rules.pro ├── tablayout-lib ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── flyco │ │ │ └── tablayout │ │ │ ├── listener │ │ │ ├── OnTabSelectListener.java │ │ │ └── CustomTabEntity.java │ │ │ ├── utils │ │ │ ├── FragmentChangeManager.java │ │ │ └── UnreadMsgUtils.java │ │ │ └── widget │ │ │ └── MsgView.java │ │ └── res │ │ ├── drawable │ │ └── shape_unread.xml │ │ └── layout │ │ ├── layout_tab_left.xml │ │ ├── layout_tab_right.xml │ │ ├── layout_tab_bottom.xml │ │ ├── layout_tab.xml │ │ ├── layout_tab_top.xml │ │ └── layout_tab_segment.xml ├── build.gradle └── proguard-rules.pro ├── settings.gradle ├── .github └── ISSUE_TEMPLATE │ ├── -------.md │ └── bug---.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── data ├── help │ ├── join_group.md │ ├── bubble.md │ ├── icon.md │ └── float_button.md └── config │ └── wechat │ ├── data.json │ ├── 6.6.6 │ └── 6.6.6.config │ ├── 6.7.2 │ └── 6.7.2.config │ ├── 6.7.3 │ └── 6.7.3.config │ ├── 6.7.3-play │ └── 6.7.3.config │ ├── 7.0.0 │ └── 7.0.0.config │ ├── 7.0.3 │ └── 7.0.3.config │ ├── 7.0.4 │ └── 7.0.4.config │ ├── 6.7.2-play │ └── 6.7.2.config │ └── 6.6.7-play │ └── 6.6.7.config ├── .gitignore ├── gradle.properties ├── README.md └── gradlew.bat /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /image/.gitignore: -------------------------------------------------------------------------------- 1 | *.png 2 | -------------------------------------------------------------------------------- /ColorPicker/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.idea -------------------------------------------------------------------------------- /fab-lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /tablayout-lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | com.blanke.mdwechat.WechatHook -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app',':fab-lib',':tablayout-lib',':ColorPicker' 2 | -------------------------------------------------------------------------------- /image/chat01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/image/chat01.png -------------------------------------------------------------------------------- /image/main01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/image/main01.png -------------------------------------------------------------------------------- /image/main02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/image/main02.png -------------------------------------------------------------------------------- /image/main03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/image/main03.png -------------------------------------------------------------------------------- /image/main04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/image/main04.png -------------------------------------------------------------------------------- /image/main05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/image/main05.png -------------------------------------------------------------------------------- /ColorPicker/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-------.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 功能建议或其他 3 | about: 提交 bug 请不要选择该模板 4 | 5 | --- 6 | 7 | 8 | -------------------------------------------------------------------------------- /fab-lib/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Floating Action Button Library 2 | POM_ARTIFACT_ID=fab 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /tablayout-lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/assets/icon/ic_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/assets/icon/ic_add.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/assets/icon/ic_chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/assets/icon/ic_chat.png -------------------------------------------------------------------------------- /app/src/main/assets/icon/ic_money.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/assets/icon/ic_money.png -------------------------------------------------------------------------------- /app/src/main/assets/icon/ic_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/assets/icon/ic_scan.png -------------------------------------------------------------------------------- /data/help/join_group.md: -------------------------------------------------------------------------------- 1 | - MDWechat 交流群(QQ群:674478488) 2 | - 微信群(添加微信 CSYJZF,回复 MDWechat) 3 | - MDWechat 内测群(QQ群:661865449) 4 | -------------------------------------------------------------------------------- /app/src/main/assets/icon/tab_icon0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/assets/icon/tab_icon0.png -------------------------------------------------------------------------------- /app/src/main/assets/icon/tab_icon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/assets/icon/tab_icon1.png -------------------------------------------------------------------------------- /app/src/main/assets/icon/tab_icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/assets/icon/tab_icon2.png -------------------------------------------------------------------------------- /app/src/main/assets/icon/tab_icon3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/assets/icon/tab_icon3.png -------------------------------------------------------------------------------- /fab-lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/assets/icon/bubble_left.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/assets/icon/bubble_left.9.png -------------------------------------------------------------------------------- /app/src/main/assets/icon/ic_person_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/assets/icon/ic_person_add.png -------------------------------------------------------------------------------- /app/src/main/assets/icon/bubble_right.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/assets/icon/bubble_right.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/res/drawable-xxhdpi/ic_add.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/res/drawable-xxhdpi/ic_clear.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/res/drawable-xxhdpi/ic_done.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/mdwechat_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/res/mipmap-xhdpi/mdwechat_icon.png -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/auto_search/bean/LogEvent.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.auto_search.bean 2 | 3 | class LogEvent(val msg: String) -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_arrow_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png -------------------------------------------------------------------------------- /fab-lib/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ColorPicker/src/main/res/drawable-hdpi/cpv_alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/ColorPicker/src/main/res/drawable-hdpi/cpv_alpha.png -------------------------------------------------------------------------------- /ColorPicker/src/main/res/drawable-xhdpi/cpv_alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/ColorPicker/src/main/res/drawable-xhdpi/cpv_alpha.png -------------------------------------------------------------------------------- /ColorPicker/src/main/res/drawable-xxhdpi/cpv_alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blankeer/MDWechat/HEAD/ColorPicker/src/main/res/drawable-xxhdpi/cpv_alpha.png -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/settings/bean/WechatConfig.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.settings.bean 2 | 3 | class WechatConfig(val name: String, val desc: String, val url: String) -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/hookers/base/Hooker.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.hookers.base 2 | 3 | data class Hooker(val hook: () -> Unit) { 4 | @Volatile var hasHooked = false 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | /.idea 11 | /app/release 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/bean/ViewTreeItem.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.bean 2 | 3 | class ViewTreeItem( 4 | val clazz: String, 5 | val children: Array = arrayOf() 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/auto_search/bean/OutputJson.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.auto_search.bean 2 | 3 | class OutputJson(val classes: Map, val methods: Map, val fields: Map) -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/util/VXPUtils.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.util 2 | 3 | 4 | object VXPUtils { 5 | fun isVXPEnv(): Boolean { 6 | return System.getProperty("vxp") != null 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12sp 4 | 2dp 5 | 48dp 6 | -------------------------------------------------------------------------------- /data/help/bubble.md: -------------------------------------------------------------------------------- 1 | # 气泡教程 2 | 3 | ## 路径 4 | - 左气泡:`/sdcard/mdwechat/icon/bubble_left.9.png` 5 | - 右气泡:`/sdcard/mdwechat/icon/bubble_right.9.png` 6 | 7 | ## 说明 8 | - 替换掉上述文件即可修改。 9 | - 需要.9格式。 10 | - 不会制作的可以使用[碘酒编辑器]、[9 Patch Editor]等 -------------------------------------------------------------------------------- /tablayout-lib/src/main/java/com/flyco/tablayout/listener/OnTabSelectListener.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.listener; 2 | 3 | public interface OnTabSelectListener { 4 | void onTabSelect(int position); 5 | void onTabReselect(int position); 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/hookers/base/HookerProvider.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.hookers.base 2 | 3 | interface HookerProvider { 4 | fun provideStaticHookers(): List? = null 5 | fun provideEventHooker(event: String): Hooker? = null 6 | } -------------------------------------------------------------------------------- /fab-lib/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 56dp 5 | 45dp 6 | 14sp 7 | 8 | -------------------------------------------------------------------------------- /ColorPicker/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jul 29 21:48:54 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-en/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Scan QR Code 3 | Money 4 | Group Chat 5 | Add Contacts 6 | 7 | -------------------------------------------------------------------------------- /ColorPicker/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | resourcePrefix "cpv_" 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 23 10 | } 11 | } 12 | 13 | dependencies { 14 | implementation 'com.android.support:support-v4:23.0.0' 15 | } 16 | -------------------------------------------------------------------------------- /tablayout-lib/src/main/res/drawable/shape_unread.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/auto_search/WechatGlobal.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.auto_search 2 | 3 | import com.blanke.mdwechat.Version 4 | 5 | 6 | object WechatGlobal { 7 | var wxPackageName: String = "" 8 | var wxVersion: Version? = null 9 | lateinit var wxLoader: ClassLoader 10 | lateinit var wxClasses: List 11 | 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/auto_search/Logs.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.auto_search 2 | 3 | import com.blanke.mdwechat.auto_search.bean.LogEvent 4 | import com.blankj.utilcode.util.LogUtils 5 | import org.greenrobot.eventbus.EventBus 6 | 7 | object Logs { 8 | fun i(msg: String) { 9 | EventBus.getDefault().post(LogEvent(msg)) 10 | LogUtils.i(msg) 11 | } 12 | } -------------------------------------------------------------------------------- /ColorPicker/src/main/res/drawable/cpv_ic_arrow_right_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /ColorPicker/src/main/res/drawable/cpv_preset_checked.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/bean/FloatButtonConfig.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.bean 2 | 3 | /** 4 | * Created by blanke on 2017/10/13. 5 | */ 6 | 7 | data class FloatButtonConfig(var info: String = "", var menu: FLoatButtonConfigItem?, var items: Array?) 8 | 9 | data class FLoatButtonConfigItem(var order: Int = 0, var type: String = "", var icon: String = "", var text: String = "") 10 | -------------------------------------------------------------------------------- /fab-lib/src/main/res/anim/fab_scale_up.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug---.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 反馈 3 | about: 提交 bug 的 issue 模板 4 | 5 | --- 6 | 7 | 请按以下步骤填写,不规范的 issue 会被关闭 8 | **bug 描述** 9 | 请描述你遇到的问题 10 | 11 | **环境** 12 | 1. Android 版本 13 | 2. 机型 14 | 3. MDWechat 版本 15 | 4. Xposed 环境及版本 16 | 5. 微信版本 17 | 18 | **复现步骤** 19 | 1. 进入主界面 20 | 2. 点击 xxx 21 | 3. 出现 xxx 现象 22 | 23 | **期望** 24 | 描述期望的结果 25 | 26 | **截图** 27 | 截图 28 | 29 | **日志(可选)** 30 | 日志 31 | 32 | **其他说明** 33 | -------------------------------------------------------------------------------- /fab-lib/src/main/res/anim/fab_scale_down.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ColorPicker/src/main/res/drawable/cpv_btn_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ColorPicker/src/main/res/layout/cpv_preference_circle.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ColorPicker/src/main/res/layout/cpv_preference_square.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ColorPicker/src/main/res/layout/cpv_preference_circle_large.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ColorPicker/src/main/res/layout/cpv_preference_square_large.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fab-lib/src/main/res/anim/fab_slide_in_from_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | -------------------------------------------------------------------------------- /fab-lib/src/main/res/anim/fab_slide_in_from_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/util/ColorUtils.java: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.util; 2 | 3 | import android.graphics.Color; 4 | 5 | /** 6 | * Created by blanke on 2017/10/5. 7 | */ 8 | 9 | public class ColorUtils { 10 | public static int getDarkerColor(int color, float factor) { 11 | float[] hsv = new float[3]; 12 | Color.colorToHSV(color, hsv); 13 | hsv[2] = hsv[2] * factor; 14 | return Color.HSVToColor(hsv); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /fab-lib/src/main/res/anim/fab_slide_out_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | -------------------------------------------------------------------------------- /fab-lib/src/main/res/anim/fab_slide_out_to_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/assets/config/view/floatbutton_readme.txt: -------------------------------------------------------------------------------- 1 | 小程序:com.tencent.mm.plugin.appbrand.ui.AppBrandLauncherUI 2 | 朋友圈:com.tencent.mm.plugin.sns.ui.SnsTimeLineUI 3 | 扫一扫:com.tencent.mm.plugin.scanner.ui.BaseScanUI 4 | 收付款:com.tencent.mm.plugin.offline.ui.WalletOfflineCoinPurseUI 5 | 钱包:com.tencent.mm.plugin.mall.ui.MallIndexUI 6 | 收藏:com.tencent.mm.plugin.fav.ui.FavoriteIndexUI 7 | 群聊:com.tencent.mm.ui.contact.SelectContactUI 8 | 添加好友:com.tencent.mm.plugin.subapp.ui.pluginapp.AddMoreFriendsUI 9 | 10 | 11 | -------------------------------------------------------------------------------- /ColorPicker/src/main/res/drawable/cpv_btn_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ColorPicker/src/main/res/values/dimen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6dp 4 | 66dp 5 | 34dp 6 | 58dp 7 | 50dp 8 | 8dp 9 | 28dp 10 | 40dp 11 | 12 | -------------------------------------------------------------------------------- /fab-lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | 6 | defaultConfig { 7 | minSdkVersion 21 8 | targetSdkVersion 23 9 | versionCode 1 10 | versionName '1.0' 11 | } 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | debuggable false 16 | } 17 | 18 | debug { 19 | minifyEnabled false 20 | debuggable true 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tablayout-lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | version = "2.1.2" 3 | android { 4 | compileSdkVersion 23 5 | 6 | defaultConfig { 7 | minSdkVersion 21 8 | targetSdkVersion 23 9 | versionCode 212 10 | versionName version 11 | } 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 16 | } 17 | } 18 | } 19 | 20 | dependencies { 21 | implementation 'com.android.support:support-v4:23.0.0' 22 | } 23 | -------------------------------------------------------------------------------- /data/help/icon.md: -------------------------------------------------------------------------------- 1 | # 图标替换教程 2 | ## 路径 3 | `/sdcard/mdwechat/icon/` 4 | ## 文件列表 5 | 文件名 | 含义 6 | ---- | --- 7 | bubble_left.9.png | 聊天气泡(左) 8 | bubble_right.9.png| 聊天气泡(右) 9 | tab_bg0.png| 主界面聊天页面背景图 10 | tab_bg1.png| 主界面联系人页面背景图 11 | tab_bg2.png| 主界面发现页面背景图 12 | tab_bg3.png| 主界面设置页面背景图 13 | tab_icon0.png| 主界面聊天页面 tab icon图 14 | tab_icon1.png| 主界面联系人页面 tab icon图 15 | tab_icon2.png| 主界面发现页面 tab icon图 16 | tab_icon3.png| 主界面设置页面 tab icon图 17 | 其余文件| 悬浮按钮 icon 图 18 | ## 修改方法 19 | 在文件管理器中替换掉上述对应文件即可 20 | ## 参考 21 | ### icon 下载网站参考 22 | - [https://material.io/tools/icons/?style=baseline](https://material.io/tools/icons/?style=baseline) 23 | - [http://www.iconfont.cn/](http://www.iconfont.cn/) -------------------------------------------------------------------------------- /fab-lib/src/main/java/com/github/clans/fab/Util.java: -------------------------------------------------------------------------------- 1 | package com.github.clans.fab; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | 6 | final class Util { 7 | 8 | private Util() { 9 | } 10 | 11 | static int dpToPx(Context context, float dp) { 12 | final float scale = context.getResources().getDisplayMetrics().density; 13 | return Math.round(dp * scale); 14 | } 15 | 16 | static boolean hasJellyBean() { 17 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; 18 | } 19 | 20 | static boolean hasLollipop() { 21 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_wechat_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /fab-lib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /ColorPicker/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /tablayout-lib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/lihui/work/AndroidStudio/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/util/ConvertUtils.java: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.util; 2 | 3 | import android.content.Context; 4 | 5 | public class ConvertUtils { 6 | /** 7 | * dp转px * * @param dpValue dp值 * @return px值 8 | */ 9 | public static int dp2px(Context context, float dpValue) { 10 | final float scale = context.getApplicationContext().getResources().getDisplayMetrics().density; 11 | return (int) (dpValue * scale + 0.5f); 12 | } 13 | 14 | /** 15 | * px转dp * * @param pxValue px值 * @return dp值 16 | */ 17 | public static int px2dp(Context context, float pxValue) { 18 | final float scale = context.getApplicationContext().getResources().getDisplayMetrics().density; 19 | return (int) (pxValue / scale + 0.5f); 20 | } 21 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | #org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_mark_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /app/src/main/assets/config/view/floatbutton.txt: -------------------------------------------------------------------------------- 1 | { 2 | "info": "自定义悬浮按钮", 3 | "menu": { 4 | "icon": "ic_add.png" 5 | }, 6 | "items": [ 7 | { 8 | "order": 1, 9 | "type": "com.tencent.mm.plugin.offline.ui.WalletOfflineCoinPurseUI", 10 | "text": "收付款", 11 | "icon": "ic_money.png" 12 | }, 13 | { 14 | "order": 2, 15 | "type": "com.tencent.mm.plugin.scanner.ui.BaseScanUI", 16 | "text": "扫一扫", 17 | "icon": "ic_scan.png" 18 | }, 19 | { 20 | "order": 3, 21 | "type": "com.tencent.mm.ui.contact.SelectContactUI", 22 | "text": "群聊", 23 | "icon": "ic_chat.png" 24 | }, 25 | { 26 | "order": 4, 27 | "type": "com.tencent.mm.plugin.subapp.ui.pluginapp.AddMoreFriendsUI", 28 | "text": "添加好友", 29 | "icon": "ic_person_add.png" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /ColorPicker/gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=1.0.0 2 | VERSION_CODE=100 3 | GROUP=com.jaredrummler 4 | ARTIFACT_ID=colorpicker 5 | 6 | POM_NAME=colorpicker 7 | POM_ARTIFACT_ID=colorpicker 8 | POM_PACKAGING=aar 9 | 10 | POM_DESCRIPTION=A simple good looking color picker component for Android 11 | POM_URL=https://github.com/jaredrummler/ColorPicker 12 | POM_SCM_URL=https://github.com/jaredrummler/ColorPicker 13 | POM_SCM_CONNECTION=scm:git@github.com:jaredrummler/ColorPicker.git 14 | POM_SCM_DEV_CONNECTION=scm:git@github.com:jaredrummler/ColorPicker.git 15 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 16 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 17 | POM_LICENCE_DIST=repo 18 | POM_DEVELOPER_ID=jaredrummler 19 | POM_DEVELOPER_NAME=Jared Rummler 20 | 21 | SNAPSHOT_REPOSITORY_URL=https://oss.sonatype.org/content/repositories/snapshots 22 | RELEASE_REPOSITORY_URL=https://oss.sonatype.org/service/local/staging/deploy/maven2 -------------------------------------------------------------------------------- /ColorPicker/src/main/res/layout/cpv_color_item_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 23 | 24 | -------------------------------------------------------------------------------- /ColorPicker/src/main/res/layout/cpv_color_item_square.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 23 | 24 | -------------------------------------------------------------------------------- /tablayout-lib/src/main/java/com/flyco/tablayout/listener/CustomTabEntity.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.listener; 2 | 3 | import android.graphics.drawable.Drawable; 4 | import android.support.annotation.DrawableRes; 5 | 6 | public interface CustomTabEntity { 7 | String getTabTitle(); 8 | 9 | @DrawableRes 10 | int getTabSelectedIcon(); 11 | 12 | @DrawableRes 13 | int getTabUnselectedIcon(); 14 | 15 | Drawable getTabIcon(); 16 | 17 | 18 | class TabCustomData implements CustomTabEntity { 19 | 20 | @Override 21 | public String getTabTitle() { 22 | return null; 23 | } 24 | 25 | @Override 26 | public int getTabSelectedIcon() { 27 | return 0; 28 | } 29 | 30 | @Override 31 | public int getTabUnselectedIcon() { 32 | return 0; 33 | } 34 | 35 | @Override 36 | public Drawable getTabIcon() { 37 | return null; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/settings/api/APIManager.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.settings.api 2 | 3 | import okhttp3.Callback 4 | import okhttp3.OkHttpClient 5 | import okhttp3.Request 6 | 7 | class APIManager { 8 | private val host = "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/" 9 | 10 | private val client: OkHttpClient by lazy { 11 | OkHttpClient.Builder().build() 12 | } 13 | 14 | fun getWechatConfigs(callback: Callback) { 15 | val req = Request.Builder().url("${host}data/config/wechat/data.json") 16 | .get() 17 | .build() 18 | client.newCall(req).enqueue(callback) 19 | } 20 | 21 | fun downloadWechatConfig(url: String, callback: Callback) { 22 | get(url, callback) 23 | } 24 | 25 | fun get(url: String, callback: Callback) { 26 | val req = Request.Builder().url(url) 27 | .get() 28 | .build() 29 | client.newCall(req).enqueue(callback) 30 | } 31 | } -------------------------------------------------------------------------------- /ColorPicker/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/Objects.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat 2 | 3 | import android.app.Activity 4 | import android.view.MenuItem 5 | import android.view.View 6 | import com.flyco.tablayout.CommonTabLayout 7 | import java.lang.ref.WeakReference 8 | 9 | object Objects { 10 | object Main { 11 | var LauncherUI = WeakReference(null) 12 | var LauncherUI_mViewPager = WeakReference(null) 13 | var LauncherUI_mTabLayout = WeakReference(null) 14 | var HomeUI_mActionBar = WeakReference(null) 15 | var LauncherUI_mWechatXMenuItem = WeakReference(null) 16 | var LauncherUI_mContentLayout = WeakReference(null) 17 | } 18 | 19 | fun clear() { 20 | Main.LauncherUI.clear() 21 | Main.LauncherUI_mViewPager.clear() 22 | Main.LauncherUI_mTabLayout.clear() 23 | Main.HomeUI_mActionBar.clear() 24 | Main.LauncherUI_mWechatXMenuItem.clear() 25 | Main.LauncherUI_mContentLayout.clear() 26 | } 27 | } -------------------------------------------------------------------------------- /ColorPicker/src/main/java/com/jaredrummler/android/colorpicker/ColorShape.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Jared Rummler 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jaredrummler.android.colorpicker; 18 | 19 | import android.support.annotation.IntDef; 20 | 21 | /** 22 | * The shape of the color preview 23 | */ 24 | @IntDef({ColorShape.SQUARE, ColorShape.CIRCLE}) 25 | public @interface ColorShape { 26 | 27 | int SQUARE = 0; 28 | 29 | int CIRCLE = 1; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/blanke/android/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /tablayout-lib/src/main/res/layout/layout_tab_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 20 | 21 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tablayout-lib/src/main/res/layout/layout_tab_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 21 | 22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /data/help/float_button.md: -------------------------------------------------------------------------------- 1 | # 悬浮按钮定制教程 2 | 3 | ## 路径 4 | `/sdcard/mdwechat/config/view/floatbutton.txt` 5 | 6 | ## 文件说明 7 | 1. 文件采用 json 语法,修改必须遵循该格式,否则解析失败,不会显示。 8 | 入门语法教程:[菜鸟教程](http://www.runoob.com/json/json-tutorial.html) 9 | 2. 字段说明 10 | - order: 顺序,越小越靠上 11 | - type:跳转后的类名 12 | - text:显示名称 13 | - icon:图标名称 14 | 15 | ## icon 说明 16 | 所有图标路径在 `/sdcard/mdwechat/icon/`下,如果不存在该图标,不会显示。 17 | 18 | ## type 说明 19 | type 为跳转后的 Activity 完整类名,可以借助 开发者助手、auto.js等工具查看。 20 | 以下是常见类名: 21 | 22 | 名称 | 类名 23 | ---- | --- 24 | 小程序 | com.tencent.mm.plugin.appbrand.ui.AppBrandLauncherUI 25 | 朋友圈 | com.tencent.mm.plugin.sns.ui.SnsTimeLineUI 26 | 扫一扫 | com.tencent.mm.plugin.scanner.ui.BaseScanUI 27 | 收付款 | com.tencent.mm.plugin.offline.ui.WalletOfflineCoinPurseUI 28 | 钱包 | com.tencent.mm.plugin.mall.ui.MallIndexUI 29 | 收藏 | com.tencent.mm.plugin.fav.ui.FavoriteIndexUI 30 | 群聊 | com.tencent.mm.ui.contact.SelectContactUI 31 | 添加好友 | com.tencent.mm.plugin.subapp.ui.pluginapp.AddMoreFriendsUI 32 | 33 | 特殊 type: 34 | 35 | type | 说明 36 | ---- | --- 37 | weiX | 微x模块 38 | 39 | ## 建议 40 | 如果以上教程看完,还是一脸懵逼,建议你还是放弃吧。 -------------------------------------------------------------------------------- /tablayout-lib/src/main/res/layout/layout_tab_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 22 | 23 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /ColorPicker/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | Select a Color 19 | Presets 20 | Custom 21 | Select 22 | Transparency 23 | -------------------------------------------------------------------------------- /tablayout-lib/src/main/res/layout/layout_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 30 | 31 | -------------------------------------------------------------------------------- /tablayout-lib/src/main/res/layout/layout_tab_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 23 | 24 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/Version.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat 2 | 3 | // Version is a helper class for comparing version strings. 4 | class Version(private val versionName: String) { 5 | 6 | val version: List = versionName.split('.').mapNotNull(String::toIntOrNull) 7 | 8 | override fun toString() = versionName 9 | 10 | override fun hashCode(): Int = version.hashCode() 11 | 12 | override fun equals(other: Any?): Boolean = when (other) { 13 | null -> false 14 | !is Version -> false 15 | else -> this.version == other.version 16 | } 17 | 18 | operator fun compareTo(other: Version): Int { 19 | var result = 0 20 | when { 21 | this.version.size > other.version.size -> result = 1 22 | this.version.size < other.version.size -> result = -1 23 | } 24 | 25 | var index = 0 26 | while (index < this.version.size && index < other.version.size) { 27 | when { 28 | this.version[index] > other.version[index] -> return 1 29 | this.version[index] < other.version[index] -> return -1 30 | } 31 | index++ 32 | } 33 | 34 | return result 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/util/ViewTreeUtils.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.util 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import com.blanke.mdwechat.bean.ViewTreeItem 6 | 7 | object ViewTreeUtils { 8 | 9 | /** 10 | * 判断 view 的层级结构是否和定义的相等 11 | */ 12 | fun equals(tree: ViewTreeItem, view: View): Boolean { 13 | if (!equals(tree.clazz, view)) { 14 | return false 15 | } 16 | if (view is ViewGroup) { 17 | if (view.childCount >= tree.children.size) { 18 | for (i in 0 until tree.children.size) { 19 | if (tree.children[i] == null) { 20 | continue 21 | } 22 | if (!equals(tree.children[i]!!, view.getChildAt(i))) { 23 | return false 24 | } 25 | } 26 | return true 27 | } 28 | } else { 29 | if (tree.children.isEmpty()) { 30 | return true 31 | } 32 | } 33 | return false 34 | } 35 | 36 | fun equals(clazz: String, view: View): Boolean { 37 | return view.javaClass.name.contains(clazz, true) 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 17 | 18 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/util/KtUtils.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.util 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import kotlin.concurrent.thread 6 | 7 | fun mainThread(delay: Long = 0, run: () -> Unit) { 8 | Handler(Looper.getMainLooper()).postDelayed(run, delay) 9 | } 10 | 11 | fun waitInvoke(checkInterval: Long = 100, mainThread: Boolean = false, check: () -> Boolean, callBack: () -> Unit) { 12 | thread { 13 | while (true) { 14 | Thread.sleep(checkInterval) 15 | var result = false 16 | if (mainThread) { 17 | var end = false 18 | com.blanke.mdwechat.util.mainThread { 19 | result = check() 20 | end = true 21 | } 22 | while (!end) { 23 | Thread.sleep(100) 24 | } 25 | } else { 26 | result = check() 27 | } 28 | if (result) { 29 | if (mainThread) { 30 | com.blanke.mdwechat.util.mainThread { 31 | callBack() 32 | } 33 | } else { 34 | callBack() 35 | } 36 | break 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /ColorPicker/src/main/java/com/jaredrummler/android/colorpicker/DrawingUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Jared Rummler 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jaredrummler.android.colorpicker; 18 | 19 | import android.content.Context; 20 | import android.util.DisplayMetrics; 21 | import android.util.TypedValue; 22 | 23 | final class DrawingUtils { 24 | 25 | static int dpToPx(Context c, float dipValue) { 26 | DisplayMetrics metrics = c.getResources().getDisplayMetrics(); 27 | float val = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, metrics); 28 | int res = (int) (val + 0.5); // Round 29 | // Ensure at least 1 pixel if val was > 0 30 | return res == 0 && val > 0 ? 1 : res; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/util/NightModeUtils.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.util 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.Color 5 | import android.graphics.drawable.BitmapDrawable 6 | import android.graphics.drawable.Drawable 7 | import com.blanke.mdwechat.WeChatHelper 8 | import com.blanke.mdwechat.config.HookConfig 9 | 10 | object NightModeUtils { 11 | fun isNightMode(): Boolean { 12 | return HookConfig.is_hook_night_mode 13 | } 14 | 15 | fun getBackgroundDrawable(defaultDrawable: Drawable?): Drawable { 16 | return if (isNightMode()) WeChatHelper.darkDrawable else defaultDrawable 17 | ?: WeChatHelper.whiteDrawable 18 | } 19 | 20 | fun getBackgroundDrawable(defaultDrawable: Bitmap?): Drawable { 21 | return when { 22 | isNightMode() -> WeChatHelper.darkDrawable 23 | defaultDrawable != null -> BitmapDrawable(defaultDrawable) 24 | else -> WeChatHelper.whiteDrawable 25 | } 26 | } 27 | 28 | fun getContentTextColor(): Int { 29 | return getTextColor(HookConfig.get_main_text_color_content) 30 | } 31 | 32 | fun getTitleTextColor(): Int { 33 | return getTextColor(HookConfig.get_main_text_color_title) 34 | } 35 | 36 | fun getTextColor(defaultColor: Int?): Int { 37 | return if (isNightMode()) Color.WHITE else defaultColor ?: Color.BLACK 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /tablayout-lib/src/main/res/layout/layout_tab_segment.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 22 | 23 | 24 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_floatbutton.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 21 | 22 | 28 | 29 | 35 | -------------------------------------------------------------------------------- /ColorPicker/src/main/java/com/jaredrummler/android/colorpicker/NestedGridView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Jared Rummler 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jaredrummler.android.colorpicker; 18 | 19 | import android.content.Context; 20 | import android.util.AttributeSet; 21 | import android.widget.GridView; 22 | 23 | public class NestedGridView extends GridView { 24 | 25 | public NestedGridView(Context context) { 26 | super(context); 27 | } 28 | 29 | public NestedGridView(Context context, AttributeSet attrs) { 30 | super(context, attrs); 31 | } 32 | 33 | public NestedGridView(Context context, AttributeSet attrs, int defStyle) { 34 | super(context, attrs, defStyle); 35 | } 36 | 37 | @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 38 | int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); 39 | super.onMeasure(widthMeasureSpec, expandSpec); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/CC.java: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat; 2 | 3 | import android.os.Bundle; 4 | 5 | public class CC { 6 | public static final Class voidd = void.class; 7 | public static final Class LinearLayout = android.widget.LinearLayout.class; 8 | public static final Class RelativeLayout = android.widget.RelativeLayout.class; 9 | public static final Class ImageView = android.widget.ImageView.class; 10 | public static final Class TextView = android.widget.TextView.class; 11 | public static final Class CheckBox = android.widget.CheckBox.class; 12 | public static final Class ProgressBar = android.widget.ProgressBar.class; 13 | public static final Class ViewStub = android.view.ViewStub.class; 14 | public static final Class View = android.view.View.class; 15 | public static final Class String = String.class; 16 | public static final Class Boolean = boolean.class; 17 | public static final Class ListView = android.widget.ListView.class; 18 | public static final Class Object = Object.class; 19 | public static final Class MotionEvent = android.view.MotionEvent.class; 20 | public static final Class Activity = android.app.Activity.class; 21 | public static final Class Int = int.class; 22 | public static final Class Float = float.class; 23 | public static final Class Menu = android.view.Menu.class; 24 | public static final Class Long = long.class; 25 | public static final Class Bundle = android.os.Bundle.class; 26 | public static final Class Intent = android.content.Intent.class; 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /ColorPicker/src/main/java/com/jaredrummler/android/colorpicker/ColorPickerDialogListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Jared Rummler 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jaredrummler.android.colorpicker; 18 | 19 | import android.support.annotation.ColorInt; 20 | 21 | /** 22 | * Callback used for getting the selected color from a color picker dialog. 23 | */ 24 | public interface ColorPickerDialogListener { 25 | 26 | /** 27 | * Callback that is invoked when a color is selected from the color picker dialog. 28 | * 29 | * @param dialogId 30 | * The dialog id used to create the dialog instance. 31 | * @param color 32 | * The selected color 33 | */ 34 | void onColorSelected(int dialogId, @ColorInt int color); 35 | 36 | /** 37 | * Callback that is invoked when the color picker dialog was dismissed. 38 | * 39 | * @param dialogId 40 | * The dialog id used to create the dialog instance. 41 | */ 42 | void onDialogDismissed(int dialogId); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/util/ViewUtils.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.util 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | 6 | object ViewUtils { 7 | fun getChildView(view: View, vararg indexs: Int): View? { 8 | var parentView: View = view 9 | var childView: View? = null 10 | for (index in indexs) { 11 | childView = getChildView(parentView, index) ?: break 12 | parentView = childView 13 | } 14 | return childView 15 | } 16 | 17 | fun getChildView(view: View, index: Int): View? { 18 | if (view is ViewGroup) { 19 | return view.getChildAt(index) 20 | } 21 | return null 22 | } 23 | 24 | fun getParentView(view: View, index: Int): View? { 25 | var currentView = view 26 | for (i in 0 until index) { 27 | val v = currentView.parent 28 | if (v != null && v is View) { 29 | currentView = v 30 | } else { 31 | return null 32 | } 33 | } 34 | return currentView 35 | } 36 | 37 | fun findFirstChildView(rootView: ViewGroup, viewClassName: String): View? { 38 | val childCount = rootView.childCount 39 | for (i in 0 until childCount) { 40 | val v = rootView.getChildAt(i) 41 | if (v.javaClass.name == viewClassName) { 42 | return v 43 | } else if (v is ViewGroup) { 44 | return findFirstChildView(v, viewClassName) 45 | } 46 | } 47 | return null 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/hookers/DebugHooker.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.hookers 2 | 3 | import android.view.MotionEvent 4 | import android.view.View 5 | import com.blanke.mdwechat.CC 6 | import com.blanke.mdwechat.WechatGlobal 7 | import com.blanke.mdwechat.hookers.base.Hooker 8 | import com.blanke.mdwechat.hookers.base.HookerProvider 9 | import com.blanke.mdwechat.util.LogUtil.logView 10 | import de.robv.android.xposed.XC_MethodHook 11 | import de.robv.android.xposed.XposedBridge 12 | import de.robv.android.xposed.XposedHelpers 13 | 14 | object DebugHooker : HookerProvider { 15 | override fun provideStaticHookers(): List? { 16 | return listOf(viewTouchHook) 17 | } 18 | 19 | private val viewTouchHook = Hooker { 20 | XposedHelpers.findAndHookMethod(CC.View, "onTouchEvent", CC.MotionEvent, object : XC_MethodHook() { 21 | @Throws(Throwable::class) 22 | override fun beforeHookedMethod(param: MethodHookParam) { 23 | val view = param.thisObject as View 24 | val event = param.args[0] as MotionEvent 25 | if (event.action == MotionEvent.ACTION_DOWN) { 26 | logView(view) 27 | } 28 | } 29 | }) 30 | val XLogSetup = XposedHelpers.findClass("com.tencent.mm.xlog.app.XLogSetup", WechatGlobal.wxLoader) 31 | XposedBridge.hookAllMethods(XLogSetup, "keep_setupXLog", object : XC_MethodHook() { 32 | override fun beforeHookedMethod(param: MethodHookParam) { 33 | param.args[5] = true 34 | } 35 | }) 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/WechatGlobal.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat 2 | 3 | import android.content.Context 4 | import com.blanke.mdwechat.config.WxVersionConfig 5 | import com.blanke.mdwechat.util.LogUtil 6 | import de.robv.android.xposed.XposedHelpers 7 | import de.robv.android.xposed.callbacks.XC_LoadPackage 8 | 9 | object WechatGlobal { 10 | @Volatile 11 | var wxVersion: Version? = null 12 | @Volatile 13 | var wxPackageName: String = "" 14 | @Volatile 15 | lateinit var wxLoader: ClassLoader 16 | lateinit var wxClasses: List 17 | lateinit var wxVersionConfig: WxVersionConfig 18 | 19 | @JvmStatic 20 | fun init(lpparam: XC_LoadPackage.LoadPackageParam) { 21 | wxPackageName = lpparam.packageName 22 | val context = XposedHelpers.callMethod( 23 | XposedHelpers.callStaticMethod(XposedHelpers.findClass("android.app.ActivityThread", null), 24 | "currentActivityThread"), "getSystemContext") as Context 25 | wxVersion = Version(context.packageManager.getPackageInfo(wxPackageName, 0)?.versionName 26 | ?: "") 27 | wxLoader = lpparam.classLoader 28 | } 29 | 30 | fun wxLazy(initializer: () -> T?): Lazy { 31 | return wxLazy("", initializer) 32 | } 33 | 34 | fun wxLazy(name: String, initializer: () -> T?): Lazy { 35 | return lazy(LazyThreadSafetyMode.PUBLICATION) { 36 | val res = initializer() 37 | if (res == null) { 38 | LogUtil.log("$name == null ") 39 | } 40 | res!! 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/hookers/SchemeHooker.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.hookers 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import com.blanke.mdwechat.Classes.RemittanceAdapterUI 6 | import com.blanke.mdwechat.Methods.WXCustomSchemeEntryActivity_entry 7 | import com.blanke.mdwechat.hookers.base.Hooker 8 | import com.blanke.mdwechat.hookers.base.HookerProvider 9 | import de.robv.android.xposed.XC_MethodHook 10 | import de.robv.android.xposed.XposedBridge 11 | 12 | object SchemeHooker : HookerProvider { 13 | override fun provideStaticHookers(): List? { 14 | return listOf(donateHooker) 15 | } 16 | 17 | private val donateHooker = Hooker { 18 | XposedBridge.hookMethod(WXCustomSchemeEntryActivity_entry, object : XC_MethodHook() { 19 | override fun beforeHookedMethod(param: MethodHookParam) { 20 | val intent = param.args[0] as Intent? 21 | val uri = intent?.data ?: return 22 | val activity = param.thisObject as Activity 23 | if (uri.host == "mdwechat") { 24 | val segments = uri.pathSegments 25 | if (segments.firstOrNull() == "donate") { 26 | activity.startActivity(Intent(activity, RemittanceAdapterUI).apply { 27 | putExtra("scene", 1) 28 | putExtra("pay_channel", 12) 29 | putExtra("receiver_name", "wxp://${segments[1]}") 30 | }) 31 | } 32 | param.result = false 33 | } 34 | } 35 | }) 36 | } 37 | } -------------------------------------------------------------------------------- /ColorPicker/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /data/config/wechat/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "7.0.4.config", 4 | "desc": "7.0.4 国内版", 5 | "url": "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/data/config/wechat/7.0.4/7.0.4.config" 6 | }, 7 | { 8 | "name": "7.0.3.config", 9 | "desc": "7.0.3 国内版", 10 | "url": "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/data/config/wechat/7.0.3/7.0.3.config" 11 | }, 12 | { 13 | "name": "7.0.0.config", 14 | "desc": "7.0.0 国内版", 15 | "url": "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/data/config/wechat/7.0.0/7.0.0.config" 16 | }, 17 | { 18 | "name": "6.7.3.config", 19 | "desc": "6.7.3 国内版", 20 | "url": "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/data/config/wechat/6.7.3/6.7.3.config" 21 | }, 22 | { 23 | "name": "6.7.3.config", 24 | "desc": "6.7.3 play版", 25 | "url": "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/data/config/wechat/6.7.3-play/6.7.3.config" 26 | }, 27 | { 28 | "name": "6.7.2.config", 29 | "desc": "6.7.2 国内版", 30 | "url": "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/data/config/wechat/6.7.2/6.7.2.config" 31 | }, 32 | { 33 | "name": "6.7.2.config", 34 | "desc": "6.7.2 play版", 35 | "url": "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/data/config/wechat/6.7.2-play/6.7.2.config" 36 | }, 37 | { 38 | "name": "6.6.7.config", 39 | "desc": "6.6.7 play版", 40 | "url": "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/data/config/wechat/6.6.7-play/6.6.7.config" 41 | }, 42 | { 43 | "name": "6.6.6.config", 44 | "desc": "6.6.6 国内版,热心网友共享,自行测试", 45 | "url": "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/data/config/wechat/6.6.6/6.6.6.config" 46 | } 47 | ] -------------------------------------------------------------------------------- /tablayout-lib/src/main/java/com/flyco/tablayout/utils/FragmentChangeManager.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.utils; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentTransaction; 6 | 7 | import java.util.ArrayList; 8 | 9 | public class FragmentChangeManager { 10 | private FragmentManager mFragmentManager; 11 | private int mContainerViewId; 12 | /** Fragment切换数组 */ 13 | private ArrayList mFragments; 14 | /** 当前选中的Tab */ 15 | private int mCurrentTab; 16 | 17 | public FragmentChangeManager(FragmentManager fm, int containerViewId, ArrayList fragments) { 18 | this.mFragmentManager = fm; 19 | this.mContainerViewId = containerViewId; 20 | this.mFragments = fragments; 21 | initFragments(); 22 | } 23 | 24 | /** 初始化fragments */ 25 | private void initFragments() { 26 | for (Fragment fragment : mFragments) { 27 | mFragmentManager.beginTransaction().add(mContainerViewId, fragment).hide(fragment).commit(); 28 | } 29 | 30 | setFragments(0); 31 | } 32 | 33 | /** 界面切换控制 */ 34 | public void setFragments(int index) { 35 | for (int i = 0; i < mFragments.size(); i++) { 36 | FragmentTransaction ft = mFragmentManager.beginTransaction(); 37 | Fragment fragment = mFragments.get(i); 38 | if (i == index) { 39 | ft.show(fragment); 40 | } else { 41 | ft.hide(fragment); 42 | } 43 | ft.commit(); 44 | } 45 | mCurrentTab = index; 46 | } 47 | 48 | public int getCurrentTab() { 49 | return mCurrentTab; 50 | } 51 | 52 | public Fragment getCurrentFragment() { 53 | return mFragments.get(mCurrentTab); 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 18 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MDWechat 2 | ==== 3 | # 简介 4 | MDWechat 是一个能让微信 Material Design 化的 Xposed 模块. 5 | 6 | 3.x 版本可是说是 1.x 和 2.x 版本的结合. 7 | 8 | 由于 3.x 不依赖 WechatSpellbook,部分代码参(chao)考(xi) WechatSpellbook 9 | 10 | # 功能 11 | 实现的功能有: 12 | 1. 主界面 TabLayout Material 化,支持自定义图标 13 | 2. ~~主界面搜索 Material 化~~(2.0未加入) 14 | 3. 主界面添加悬浮按钮(FloatingActionButton),支持自定义按钮文本/图标/入口 15 | 4. 全局头像圆角 16 | 5. 全局 ActionBar 颜色修改 17 | 6. 全局状态栏颜色修改,支持半透明/全透明(沉浸) 18 | 7. 主界面列表去掉分割线,增加 Ripple 效果(按下水波纹),支持修改颜色 19 | 8. ~~主界面支持隐藏 发现/设置 页面~~(2.0未加入) 20 | 9. 主界面 4 个页面背景修改 21 | 10. ~~支持聊天列表置顶底色修改~~(2.0未加入) 22 | 11. 聊天气泡修改,支持.9图,支持修改着色,支持修改文本颜色 23 | 12. ~~发现页面支持隐藏朋友圈/扫一扫/摇一摇/附近的人/游戏/购物/小程序~~(微信自带,2.0已去掉) 24 | 13. 移除会话列表下拉小程序,最低支持微信 6.6.2 25 | 14. 识别微X模块入口,移动到悬浮按钮(2.0新增) 26 | 15. 主界面字体颜色修改(2.0新增) 27 | 28 | # 版本支持 29 | 1. ~~支持的微信版本: [酷安渠道版](https://www.coolapk.com/apk/com.tencent.mm)(6.5.19 6.5.22 6.5.23 6.6.0 6.6.1 6.6.2 6.6.3 6.6.5), [play 版](https://play.google.com/store/apps/details?id=com.tencent.mm)(6.5.16 6.5.23 6.6.1 6.6.2)~~(2.0理论上支持任何微信版本,只测试了6.6.7,其他未测) 30 | 2. 只支持 Android 5.0 以上 31 | 32 | # 效果预览 33 | ![main01](https://raw.githubusercontent.com/Blankeer/MDWechat/master/image/main01.png) 34 | ![main02](https://raw.githubusercontent.com/Blankeer/MDWechat/master/image/main02.png) 35 | ![main03](https://raw.githubusercontent.com/Blankeer/MDWechat/master/image/main03.png) 36 | ![main04](https://raw.githubusercontent.com/Blankeer/MDWechat/master/image/main04.png) 37 | ![main05](https://raw.githubusercontent.com/Blankeer/MDWechat/master/image/main05.png) 38 | ![chat01](https://raw.githubusercontent.com/Blankeer/MDWechat/master/image/chat01.png) 39 | 40 | # 使用教程 41 | 有待整理文档到 wiki 42 | 43 | # 存在的问题 44 | 1. 导致微信变卡,这是无法避免的 45 | 2. 悬浮按钮在某些机型(魅族/中兴)上显示异常,在聊天页面会显示 46 | 47 | # 感谢 48 | 1. [WechatSpellbook](https://github.com/Gh0u1L5/WechatSpellbook) 49 | 2. [WechatUI](https://www.coolapk.com/apk/ce.hesh.wechatUI) 50 | 3. [群消息助手](https://github.com/zhudongya123/WechatChatroomHelper) 51 | 4. [WechatMagician](https://github.com/Gh0u1L5/WechatMagician) 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/hookers/DiscoverHooker.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.hookers 2 | 3 | import android.view.View 4 | import com.blanke.mdwechat.Classes 5 | import com.blanke.mdwechat.Fields.PreferenceFragment_mListView 6 | import com.blanke.mdwechat.config.AppCustomConfig 7 | import com.blanke.mdwechat.hookers.base.Hooker 8 | import com.blanke.mdwechat.hookers.base.HookerProvider 9 | import com.blanke.mdwechat.util.LogUtil 10 | import com.blanke.mdwechat.util.NightModeUtils 11 | import de.robv.android.xposed.XC_MethodHook 12 | import de.robv.android.xposed.XposedHelpers 13 | 14 | object DiscoverHooker : HookerProvider { 15 | const val keyInit = "key_init" 16 | 17 | override fun provideStaticHookers(): List? { 18 | return listOf(resumeHook) 19 | } 20 | 21 | private val resumeHook = Hooker { 22 | XposedHelpers.findAndHookMethod(Classes.Fragment, "performResume", object : XC_MethodHook() { 23 | override fun afterHookedMethod(param: MethodHookParam?) { 24 | val fragment = param?.thisObject ?: return 25 | if (fragment.javaClass.name != Classes.DiscoverFragment.name) { 26 | return 27 | } 28 | val isInit = XposedHelpers.getAdditionalInstanceField(fragment, keyInit) 29 | if (isInit != null) { 30 | LogUtil.log("DiscoverFragment 已经hook过") 31 | return 32 | } 33 | XposedHelpers.setAdditionalInstanceField(fragment, keyInit, true) 34 | init(fragment) 35 | } 36 | 37 | private fun init(fragment: Any) { 38 | val listView = PreferenceFragment_mListView.get(fragment) 39 | if (listView != null && listView is View) { 40 | val background = AppCustomConfig.getTabBg(2) 41 | listView.background = NightModeUtils.getBackgroundDrawable(background) 42 | } 43 | } 44 | }) 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/widget/CircleImageView.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.widget 2 | 3 | import android.annotation.TargetApi 4 | import android.content.Context 5 | import android.graphics.Canvas 6 | import android.graphics.Color 7 | import android.graphics.Paint 8 | import android.graphics.Path 9 | import android.os.Build 10 | import android.util.AttributeSet 11 | import android.util.Log 12 | import android.widget.ImageView 13 | 14 | /** 15 | * Created by blanke on 2017/8/1. 16 | */ 17 | 18 | class CircleImageView : ImageView { 19 | private var paint: Paint? = null 20 | internal var path = Path() 21 | 22 | constructor(context: Context) : super(context) {} 23 | 24 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {} 25 | 26 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} 27 | 28 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 29 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { 30 | } 31 | 32 | override fun onDraw(canvas: Canvas) { 33 | if (paint == null) { 34 | paint = Paint() 35 | paint!!.color = Color.RED 36 | // paint.setColor(Color.TRANSPARENT); 37 | // paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); 38 | // path.setFillType(Path.FillType.INVERSE_WINDING); 39 | val w = measuredWidth 40 | val h = measuredHeight 41 | val r = Math.min(w, h) / 2 42 | Log.d("imageview", "r=$r,w=$w,h=$h") 43 | path.addCircle((w / 2).toFloat(), (h / 2).toFloat(), r.toFloat(), Path.Direction.CW) 44 | } 45 | canvas.clipPath(path) 46 | super.onDraw(canvas) 47 | // canvas.drawRect(0, 0, w, h, paint); 48 | // canvas.drawCircle(w / 2, h / 2, r, paint); 49 | // paint.setXfermode(null); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/hookers/AvatarHooker.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.hookers 2 | 3 | import android.graphics.Bitmap 4 | import com.blanke.mdwechat.CC 5 | import com.blanke.mdwechat.Classes.AvatarUtils 6 | import com.blanke.mdwechat.Methods.AvatarUtils_getAvatarBitmaps 7 | import com.blanke.mdwechat.Methods.AvatarUtils_getDefaultAvatarBitmap 8 | import com.blanke.mdwechat.config.HookConfig 9 | import com.blanke.mdwechat.hookers.base.Hooker 10 | import com.blanke.mdwechat.hookers.base.HookerProvider 11 | import com.blanke.mdwechat.util.ImageHelper 12 | import de.robv.android.xposed.XC_MethodHook 13 | import de.robv.android.xposed.XposedHelpers 14 | 15 | object AvatarHooker : HookerProvider { 16 | override fun provideStaticHookers(): List? { 17 | return listOf(avatarUtilsHook) 18 | } 19 | 20 | private val avatarUtilsHook = Hooker { 21 | XposedHelpers.findAndHookMethod(AvatarUtils, AvatarUtils_getDefaultAvatarBitmap.name, object : XC_MethodHook() { 22 | override fun afterHookedMethod(param: MethodHookParam?) { 23 | if (!HookConfig.is_hook_avatar) { 24 | return 25 | } 26 | param?.result?.apply { 27 | param.result = getCircleBitmap(this as Bitmap) 28 | } 29 | } 30 | }) 31 | AvatarUtils_getAvatarBitmaps.forEach { 32 | XposedHelpers.findAndHookMethod(AvatarUtils, it.name, CC.String, object : XC_MethodHook() { 33 | override fun afterHookedMethod(param: MethodHookParam?) { 34 | if (!HookConfig.is_hook_avatar) { 35 | return 36 | } 37 | param?.result?.apply { 38 | param.result = getCircleBitmap(this as Bitmap) 39 | } 40 | } 41 | }) 42 | } 43 | } 44 | 45 | private fun getCircleBitmap(bitmap: Bitmap): Bitmap? { 46 | return ImageHelper.getRoundedCornerBitmap(bitmap, bitmap.height / 2) 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/hookers/main/HomeActionBarHook.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.hookers.main 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import com.blanke.mdwechat.Objects 6 | import com.blanke.mdwechat.config.HookConfig 7 | import com.blanke.mdwechat.util.LogUtil 8 | import com.blanke.mdwechat.util.ViewUtils 9 | import com.blanke.mdwechat.util.waitInvoke 10 | import de.robv.android.xposed.XposedHelpers 11 | import org.jooq.tools.reflect.Reflect.on 12 | 13 | object HomeActionBarHook { 14 | fun fix(viewPagerLinearLayout: ViewGroup) { 15 | val cb = { actionHeight: Int -> 16 | val viewpager = viewPagerLinearLayout.getChildAt(0) 17 | val layoutParams = viewpager.layoutParams as ViewGroup.MarginLayoutParams 18 | val offset = -4 19 | if (!HookConfig.is_hook_hide_actionbar && HookConfig.is_hook_tab) { 20 | layoutParams.topMargin = actionHeight + offset 21 | } else if (HookConfig.is_hook_hide_actionbar && !HookConfig.is_hook_tab) { 22 | layoutParams.topMargin = -actionHeight + offset 23 | } else { 24 | layoutParams.topMargin = offset 25 | } 26 | viewpager.layoutParams = layoutParams 27 | viewpager.requestLayout() 28 | } 29 | val mActionBar = Objects.Main.HomeUI_mActionBar.get() 30 | waitInvoke(10, true, { 31 | XposedHelpers.callMethod(mActionBar, "getHeight") as Int > 0 32 | }, { 33 | val actionHeight = XposedHelpers.callMethod(mActionBar, "getHeight") as Int 34 | LogUtil.log("actionHeight=$actionHeight") 35 | cb(actionHeight) 36 | if (HookConfig.is_hook_hide_actionbar) { 37 | LogUtil.log("隐藏 actionBar $mActionBar") 38 | XposedHelpers.callMethod(mActionBar, "hide") 39 | val actionView = on(mActionBar).call("getCustomView").get() 40 | ViewUtils.getParentView(actionView, 1)?.visibility = View.GONE 41 | } 42 | }) 43 | } 44 | } -------------------------------------------------------------------------------- /data/config/wechat/6.6.6/6.6.6.config: -------------------------------------------------------------------------------- 1 | {"classes":{"ActionBarContainer":"android.support.v7.widget.ActionBarContainer","ActionMenuView":"android.support.v7.view.menu.f","AvatarUtils":"com.tencent.mm.app.c","ContactFragment":"com.tencent.mm.ui.contact.AddressUI$a","ConversationFragment":"com.tencent.mm.ui.conversation.j","ConversationWithAppBrandListView":"com.tencent.mm.ui.conversation.ConversationWithAppBrandListView","CustomViewPager":"com.tencent.mm.ui.base.CustomViewPager","DiscoverFragment":"com.tencent.mm.ui.h","HomeUI":"com.tencent.mm.ui.HomeUI","LauncherUI":"com.tencent.mm.ui.LauncherUI","LauncherUIBottomTabView":"com.tencent.mm.ui.LauncherUIBottomTabView","LauncherUIBottomTabViewItem":"com.tencent.mm.ui.LauncherUIBottomTabView$a","MMFragmentActivity":"com.tencent.mm.ui.MMFragmentActivity","MainTabUI":"com.tencent.mm.ui.w","MainTabUIPageAdapter":"com.tencent.mm.ui.w$a","NoDrawingCacheLinearLayout":"com.tencent.mm.ui.NoDrawingCacheLinearLayout","NoMeasuredTextView":"com.tencent.mm.ui.base.NoMeasuredTextView","PhoneWindow":"com.android.internal.policy.PhoneWindow","PreferenceFragment":"com.tencent.mm.ui.base.preference.i","ScrollingTabContainerView":"android.support.v7.widget.ai","SettingsFragment":"com.tencent.mm.ui.y","TabIconView":"com.tencent.mm.ui.TabIconView","ThreadExecutor":"com.tencent.mm.sdk.platformtools.al","WXCustomSchemeEntryActivity":"com.tencent.mm.plugin.base.stub.WXCustomSchemeEntryActivity","WxViewPager":"com.tencent.mm.ui.mogic.WxViewPager"},"fields":{"ContactFragment_mListView":"oiy","ConversationFragment_mListView":"zvG","HomeUI_mActionBar":"mActionBar","HomeUI_mMainTabUI":"ymR","LauncherUIBottomTabViewItem_mTextViews":["yos","yot"],"LauncherUI_mHomeUI":"ynB","MainTabUI_mCustomViewPager":"yru","PreferenceFragment_mListView":"odm"},"methods":{"AvatarUtils_getAvatarBitmaps":["cx","cy"],"AvatarUtils_getDefaultAvatarBitmap":"uA","ConversationWithAppBrandListView_isAppBrandHeaderEnable":"nx","HomeFragment_lifecycles":["coR","coS","coT","coU","coV","coW"],"LauncherUIBottomTabView_getTabItemView":"Eq","MainTabUIPageAdapter_getCount":"getCount","MainTabUIPageAdapter_onPageScrolled":"a","WXCustomSchemeEntryActivity_entry":"A","WxViewPager_selectedPage":"a"}} -------------------------------------------------------------------------------- /data/config/wechat/6.7.2/6.7.2.config: -------------------------------------------------------------------------------- 1 | {"classes":{"ActionBarContainer":"android.support.v7.widget.ActionBarContainer","ActionMenuView":"android.support.v7.view.menu.h","AvatarUtils":"com.tencent.mm.app.c","ContactFragment":"com.tencent.mm.ui.contact.AddressUI$a","ConversationFragment":"com.tencent.mm.ui.conversation.j","ConversationWithAppBrandListView":"com.tencent.mm.ui.conversation.ConversationWithAppBrandListView","CustomViewPager":"com.tencent.mm.ui.base.CustomViewPager","DiscoverFragment":"com.tencent.mm.ui.h","HomeUI":"com.tencent.mm.ui.HomeUI","LauncherUI":"com.tencent.mm.ui.LauncherUI","LauncherUIBottomTabView":"com.tencent.mm.ui.LauncherUIBottomTabView","LauncherUIBottomTabViewItem":"com.tencent.mm.ui.LauncherUIBottomTabView$a","MMFragmentActivity":"com.tencent.mm.ui.MMFragmentActivity","MainTabUI":"com.tencent.mm.ui.z","MainTabUIPageAdapter":"com.tencent.mm.ui.z$a","NoDrawingCacheLinearLayout":"com.tencent.mm.ui.NoDrawingCacheLinearLayout","NoMeasuredTextView":"com.tencent.mm.ui.base.NoMeasuredTextView","PhoneWindow":"com.android.internal.policy.PhoneWindow","PreferenceFragment":"com.tencent.mm.ui.base.preference.i","ScrollingTabContainerView":"android.support.v7.widget.ar","SettingsFragment":"com.tencent.mm.ui.ac","TabIconView":"com.tencent.mm.ui.TabIconView","ThreadExecutor":"com.tencent.mm.sdk.platformtools.an","WXCustomSchemeEntryActivity":"com.tencent.mm.plugin.base.stub.WXCustomSchemeEntryActivity","WxViewPager":"com.tencent.mm.ui.mogic.WxViewPager"},"fields":{"ContactFragment_mListView":"kVN","ConversationFragment_mListView":"vgg","HomeUI_mActionBar":"mActionBar","HomeUI_mMainTabUI":"tXk","LauncherUIBottomTabViewItem_mTextViews":["tYI","tYJ"],"LauncherUI_mHomeUI":"tXR","MainTabUI_mCustomViewPager":"ubL","PreferenceFragment_mListView":"kQL"},"methods":{"AvatarUtils_getAvatarBitmaps":["cj","ck"],"AvatarUtils_getDefaultAvatarBitmap":"sz","ConversationWithAppBrandListView_isAppBrandHeaderEnable":"nb","HomeFragment_lifecycles":["crM","crN","crO","crP","crQ","crR"],"LauncherUIBottomTabView_getTabItemView":"Ey","MainTabUIPageAdapter_getCount":"getCount","MainTabUIPageAdapter_onPageScrolled":"a","WXCustomSchemeEntryActivity_entry":"w","WxViewPager_selectedPage":"a"}} -------------------------------------------------------------------------------- /data/config/wechat/6.7.3/6.7.3.config: -------------------------------------------------------------------------------- 1 | {"classes":{"ActionBarContainer":"android.support.v7.widget.ActionBarContainer","ActionMenuView":"android.support.v7.view.menu.h","AvatarUtils":"com.tencent.mm.app.c","ContactFragment":"com.tencent.mm.ui.contact.AddressUI$a","ConversationFragment":"com.tencent.mm.ui.conversation.k","ConversationWithAppBrandListView":"com.tencent.mm.ui.conversation.ConversationWithAppBrandListView","CustomViewPager":"com.tencent.mm.ui.base.CustomViewPager","DiscoverFragment":"com.tencent.mm.ui.h","HomeUI":"com.tencent.mm.ui.HomeUI","LauncherUI":"com.tencent.mm.ui.LauncherUI","LauncherUIBottomTabView":"com.tencent.mm.ui.LauncherUIBottomTabView","LauncherUIBottomTabViewItem":"com.tencent.mm.ui.LauncherUIBottomTabView$a","MMFragmentActivity":"com.tencent.mm.ui.MMFragmentActivity","MainTabUI":"com.tencent.mm.ui.z","MainTabUIPageAdapter":"com.tencent.mm.ui.z$a","NoDrawingCacheLinearLayout":"com.tencent.mm.ui.NoDrawingCacheLinearLayout","NoMeasuredTextView":"com.tencent.mm.ui.base.NoMeasuredTextView","PhoneWindow":"com.android.internal.policy.PhoneWindow","PreferenceFragment":"com.tencent.mm.ui.base.preference.i","ScrollingTabContainerView":"android.support.v7.widget.aq","SettingsFragment":"com.tencent.mm.ui.ac","TabIconView":"com.tencent.mm.ui.TabIconView","ThreadExecutor":"com.tencent.mm.sdk.platformtools.ao","WXCustomSchemeEntryActivity":"com.tencent.mm.plugin.base.stub.WXCustomSchemeEntryActivity","WxViewPager":"com.tencent.mm.ui.mogic.WxViewPager"},"fields":{"ContactFragment_mListView":"lBK","ConversationFragment_mListView":"vTt","HomeUI_mActionBar":"mActionBar","HomeUI_mMainTabUI":"uKi","LauncherUIBottomTabViewItem_mTextViews":["uLG","uLH"],"LauncherUI_mHomeUI":"uKP","MainTabUI_mCustomViewPager":"uOI","PreferenceFragment_mListView":"lwE"},"methods":{"AvatarUtils_getAvatarBitmaps":["ch","ci"],"AvatarUtils_getDefaultAvatarBitmap":"sG","ConversationWithAppBrandListView_isAppBrandHeaderEnable":"nG","HomeFragment_lifecycles":["cxD","cxE","cxF","cxG","cxH","cxI"],"LauncherUIBottomTabView_getTabItemView":"FM","MainTabUIPageAdapter_getCount":"getCount","MainTabUIPageAdapter_onPageScrolled":"a","WXCustomSchemeEntryActivity_entry":"w","WxViewPager_selectedPage":"a"}} -------------------------------------------------------------------------------- /data/config/wechat/6.7.3-play/6.7.3.config: -------------------------------------------------------------------------------- 1 | {"classes":{"ActionBarContainer":"android.support.v7.widget.ActionBarContainer","ActionMenuView":"android.support.v7.view.menu.h","AvatarUtils":"com.tencent.mm.app.c","ContactFragment":"com.tencent.mm.ui.contact.AddressUI$a","ConversationFragment":"com.tencent.mm.ui.conversation.k","ConversationWithAppBrandListView":"com.tencent.mm.ui.conversation.ConversationWithAppBrandListView","CustomViewPager":"com.tencent.mm.ui.base.CustomViewPager","DiscoverFragment":"com.tencent.mm.ui.h","HomeUI":"com.tencent.mm.ui.HomeUI","LauncherUI":"com.tencent.mm.ui.LauncherUI","LauncherUIBottomTabView":"com.tencent.mm.ui.LauncherUIBottomTabView","LauncherUIBottomTabViewItem":"com.tencent.mm.ui.LauncherUIBottomTabView$a","MMFragmentActivity":"com.tencent.mm.ui.MMFragmentActivity","MainTabUI":"com.tencent.mm.ui.z","MainTabUIPageAdapter":"com.tencent.mm.ui.z$a","NoDrawingCacheLinearLayout":"com.tencent.mm.ui.NoDrawingCacheLinearLayout","NoMeasuredTextView":"com.tencent.mm.ui.base.NoMeasuredTextView","PhoneWindow":"com.android.internal.policy.PhoneWindow","PreferenceFragment":"com.tencent.mm.ui.base.preference.i","ScrollingTabContainerView":"android.support.v7.widget.aq","SettingsFragment":"com.tencent.mm.ui.ac","TabIconView":"com.tencent.mm.ui.TabIconView","ThreadExecutor":"com.tencent.mm.sdk.platformtools.ao","WXCustomSchemeEntryActivity":"com.tencent.mm.plugin.base.stub.WXCustomSchemeEntryActivity","WxViewPager":"com.tencent.mm.ui.mogic.WxViewPager"},"fields":{"ContactFragment_mListView":"lCe","ConversationFragment_mListView":"vTS","HomeUI_mActionBar":"mActionBar","HomeUI_mMainTabUI":"uKG","LauncherUIBottomTabViewItem_mTextViews":["uMe","uMf"],"LauncherUI_mHomeUI":"uLn","MainTabUI_mCustomViewPager":"uPg","PreferenceFragment_mListView":"lwY"},"methods":{"AvatarUtils_getAvatarBitmaps":["ch","ci"],"AvatarUtils_getDefaultAvatarBitmap":"sI","ConversationWithAppBrandListView_isAppBrandHeaderEnable":"nH","HomeFragment_lifecycles":["cxS","cxT","cxU","cxV","cxW","cxX"],"LauncherUIBottomTabView_getTabItemView":"FN","MainTabUIPageAdapter_getCount":"getCount","MainTabUIPageAdapter_onPageScrolled":"a","WXCustomSchemeEntryActivity_entry":"w","WxViewPager_selectedPage":"a"}} -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/Common.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat 2 | 3 | import android.os.Environment 4 | import com.blanke.mdwechat.util.LogUtil 5 | import com.blanke.mdwechat.util.VXPUtils 6 | import java.io.File 7 | 8 | /** 9 | * Created by blanke on 2017/7/29. 10 | */ 11 | 12 | object Common { 13 | val MY_APPLICATION_PACKAGE = "com.blanke.mdwechat" 14 | val WECHAT_PACKAGENAME = "com.tencent.mm" 15 | val MOD_PREFS = "md_wechat_settings" 16 | val APP_DIR = "mdwechat" 17 | val APP_VXP_DIR = "mdwechat_vxp_debug" 18 | val CONFIG_DIR = "config" 19 | val CONFIG_WECHAT_DIR = "config" + File.separator + "wechat" 20 | val CONFIG_VIEW_DIR = "config" + File.separator + "view" 21 | val LOGS_DIR = "logs" 22 | val ICON_DIR = "icon" 23 | val CONVERSATION_BACKGROUND_FILENAME = "conversation.png" 24 | val CONTACT_BACKGROUND_FILENAME = "contact.png" 25 | val DISCOVER_BACKGROUND_FILENAME = "discover.png" 26 | val SETTINGS_BACKGROUND_FILENAME = "settings.png" 27 | val CHAT_BUBBLE_LEFT_FILENAME = "bubble_left.9.png" 28 | val CHAT_BUBBLE_RIGHT_FILENAME = "bubble_right.9.png" 29 | val FILE_NAME_TAB_PREFIX = "tab_icon" 30 | val FILE_NAME_TAB_BG_PREFIX = "tab_bg" 31 | val FILE_NAME_FLOAT_BUTTON = "floatbutton.txt" 32 | val URL_HELP_FLOAT_BUTTON = "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/data/help/float_button.md" 33 | val URL_HELP_ICON = "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/data/help/icon.md" 34 | val URL_HELP_BUBBLE = "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/data/help/bubble.md" 35 | val URL_JOIN_GROUP = "https://raw.githubusercontent.com/Blankeer/MDWechat/v3.0/data/help/join_group.md" 36 | 37 | val isVXPEnv: Boolean by lazy { 38 | VXPUtils.isVXPEnv() 39 | } 40 | 41 | val APP_DIR_PATH: String by lazy { 42 | // debug VXP 环境下,区分目录 43 | val appDir = if (isVXPEnv && BuildConfig.DEBUG) APP_VXP_DIR else APP_DIR 44 | // LogUtil.log("isVXPEnv = $isVXPEnv") 45 | LogUtil.log("app dir = $appDir") 46 | Environment.getExternalStorageDirectory().absolutePath + File.separator + appDir + File.separator 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/util/DrawableUtils.java: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.util; 2 | 3 | import android.content.res.ColorStateList; 4 | import android.content.res.Resources; 5 | import android.graphics.Bitmap; 6 | import android.graphics.drawable.ColorDrawable; 7 | import android.graphics.drawable.Drawable; 8 | import android.graphics.drawable.RippleDrawable; 9 | import android.graphics.drawable.ShapeDrawable; 10 | import android.graphics.drawable.shapes.RoundRectShape; 11 | 12 | import java.util.Arrays; 13 | 14 | /** 15 | * Created by blanke on 2017/9/2. 16 | */ 17 | 18 | public class DrawableUtils { 19 | 20 | public static Drawable getNineDrawable(Resources resources, Bitmap bitmap) { 21 | return NinePatchBitmapFactory.createNinePatchDrawable(resources, bitmap); 22 | // byte[] chunk = bitmap.getNinePatchChunk(); 23 | // boolean result = NinePatch.isNinePatchChunk(chunk); 24 | // LogUtil.log("chunk=" + Arrays.toString(chunk) + "result=" + result); 25 | // LogUtil.log("h=" + bitmap.getHeight() + ",w=" + bitmap.getWidth()); 26 | // if (chunk == null || !result) { 27 | // return new BitmapDrawable(bitmap); 28 | // } 29 | // return new NinePatchDrawable(resources, bitmap, chunk, new Rect(), null); 30 | } 31 | 32 | public static RippleDrawable getTransparentColorRippleDrawable(int normalColor, int pressedColor) { 33 | return new RippleDrawable(ColorStateList.valueOf(pressedColor), null, getRippleMask(normalColor)); 34 | } 35 | 36 | public static RippleDrawable getColorRippleDrawable(int normalColor, int pressedColor) { 37 | return new RippleDrawable(ColorStateList.valueOf(pressedColor), new ColorDrawable(normalColor), getRippleMask(normalColor)); 38 | } 39 | 40 | private static Drawable getRippleMask(int color) { 41 | float[] outerRadii = new float[8]; 42 | // 3 is radius of final ripple, 43 | // instead of 3 you can give required final radius 44 | Arrays.fill(outerRadii, 3); 45 | 46 | RoundRectShape r = new RoundRectShape(outerRadii, null, null); 47 | ShapeDrawable shapeDrawable = new ShapeDrawable(r); 48 | shapeDrawable.getPaint().setColor(color); 49 | return shapeDrawable; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /data/config/wechat/7.0.0/7.0.0.config: -------------------------------------------------------------------------------- 1 | {"classes":{"ActionBarContainer":"android.support.v7.widget.ActionBarContainer","ActionMenuView":"android.support.v7.view.menu.h","AvatarUtils":"com.tencent.mm.app.c","ContactFragment":"com.tencent.mm.ui.contact.AddressUI$AddressUIFragment","ConversationFragment":"com.tencent.mm.ui.conversation.MainUI","ConversationWithAppBrandListView":"com.tencent.mm.ui.conversation.ConversationWithAppBrandListView","CustomViewPager":"com.tencent.mm.ui.base.CustomViewPager","DiscoverFragment":"com.tencent.mm.ui.FindMoreFriendsUI","HomeUI":"com.tencent.mm.ui.HomeUI","LauncherUI":"com.tencent.mm.ui.LauncherUI","LauncherUIBottomTabView":"com.tencent.mm.ui.LauncherUIBottomTabView","LauncherUIBottomTabViewItem":"com.tencent.mm.ui.LauncherUIBottomTabView$a","MMFragmentActivity":"com.tencent.mm.ui.MMFragmentActivity","MainTabUI":"com.tencent.mm.ui.MainTabUI","MainTabUIPageAdapter":"com.tencent.mm.ui.MainTabUI$TabsAdapter","NoDrawingCacheLinearLayout":"com.tencent.mm.ui.NoDrawingCacheLinearLayout","NoMeasuredTextView":"com.tencent.mm.ui.base.NoMeasuredTextView","PhoneWindow":"com.android.internal.policy.PhoneWindow","PreferenceFragment":"com.tencent.mm.ui.base.preference.MMPreferenceFragment","ScrollingTabContainerView":"android.support.v7.widget.as","SettingsFragment":"com.tencent.mm.ui.MoreTabUI","TabIconView":"com.tencent.mm.ui.TabIconView","ThreadExecutor":"com.tencent.mm.sdk.platformtools.ar","WXCustomSchemeEntryActivity":"com.tencent.mm.plugin.base.stub.WXCustomSchemeEntryActivity","WxViewPager":"com.tencent.mm.ui.mogic.WxViewPager"},"fields":{"ContactFragment_mListView":"mOV","ConversationFragment_mListView":"yfy","HomeUI_mActionBar":"mActionBar","HomeUI_mMainTabUI":"wUT","LauncherUIBottomTabViewItem_mTextViews":["wWA","wWB"],"LauncherUI_mHomeUI":"wVJ","MainTabUI_mCustomViewPager":"wQE","PreferenceFragment_mListView":"mJQ"},"methods":{"AvatarUtils_getAvatarBitmaps":["cF","cG"],"AvatarUtils_getDefaultAvatarBitmap":"zd","ConversationWithAppBrandListView_isAppBrandHeaderEnable":"pC","HomeFragment_lifecycles":["diX","diY","diZ","dja","djb","djc"],"LauncherUIBottomTabView_getTabItemView":"Kz","MainTabUIPageAdapter_getCount":"getCount","MainTabUIPageAdapter_onPageScrolled":"fixAndroidOProgressBarOutScreenFlashProblem","WXCustomSchemeEntryActivity_entry":"L","WxViewPager_selectedPage":"setCurrentItemInternal"}} -------------------------------------------------------------------------------- /data/config/wechat/7.0.3/7.0.3.config: -------------------------------------------------------------------------------- 1 | {"classes":{"ActionBarContainer":"android.support.v7.widget.ActionBarContainer","ActionMenuView":"android.support.v7.view.menu.h","AvatarUtils":"com.tencent.mm.app.c","ContactFragment":"com.tencent.mm.ui.contact.AddressUI$AddressUIFragment","ConversationFragment":"com.tencent.mm.ui.conversation.MainUI","ConversationListView":"com.tencent.mm.ui.conversation.ConversationListView","ConversationWithAppBrandListView":"com.tencent.mm.ui.conversation.ConversationWithAppBrandListView","CustomViewPager":"com.tencent.mm.ui.base.CustomViewPager","DiscoverFragment":"com.tencent.mm.ui.FindMoreFriendsUI","HomeUI":"com.tencent.mm.ui.HomeUI","LauncherUI":"com.tencent.mm.ui.LauncherUI","LauncherUIBottomTabView":"com.tencent.mm.ui.LauncherUIBottomTabView","LauncherUIBottomTabViewItem":"com.tencent.mm.ui.LauncherUIBottomTabView$a","MMFragmentActivity":"com.tencent.mm.ui.MMFragmentActivity","MainTabUI":"com.tencent.mm.ui.MainTabUI","MainTabUIPageAdapter":"com.tencent.mm.ui.MainTabUI$TabsAdapter","NoDrawingCacheLinearLayout":"com.tencent.mm.ui.NoDrawingCacheLinearLayout","NoMeasuredTextView":"com.tencent.mm.ui.base.NoMeasuredTextView","PhoneWindow":"com.android.internal.policy.PhoneWindow","PreferenceFragment":"com.tencent.mm.ui.base.preference.MMPreferenceFragment","ScrollingTabContainerView":"android.support.v7.widget.at","SettingsFragment":"com.tencent.mm.ui.MoreTabUI","TabIconView":"com.tencent.mm.ui.TabIconView","ThreadExecutor":"com.tencent.mm.sdk.platformtools.ar","WXCustomSchemeEntryActivity":"com.tencent.mm.plugin.base.stub.WXCustomSchemeEntryActivity","WxViewPager":"com.tencent.mm.ui.mogic.WxViewPager"},"fields":{"ContactFragment_mListView":"nfq","ConversationFragment_mListView":"yGr","HomeUI_mActionBar":"mActionBar","HomeUI_mMainTabUI":"xvn","LauncherUIBottomTabViewItem_mTextViews":["xwY","xwZ"],"LauncherUI_mHomeUI":"xwg","MainTabUI_mCustomViewPager":"xqV","PreferenceFragment_mListView":"naj"},"methods":{"AvatarUtils_getAvatarBitmaps":["cI","cJ"],"AvatarUtils_getDefaultAvatarBitmap":"zO","ConversationWithAppBrandListView_isAppBrandHeaderEnable":"qi","HomeFragment_lifecycles":["dqA","dqB","dqC","dqx","dqy","dqz"],"LauncherUIBottomTabView_getTabItemView":"LP","MainTabUIPageAdapter_getCount":"getCount","MainTabUIPageAdapter_onPageScrolled":"fixAndroidOProgressBarOutScreenFlashProblem","WXCustomSchemeEntryActivity_entry":"L","WxViewPager_selectedPage":"setCurrentItemInternal"}} -------------------------------------------------------------------------------- /data/config/wechat/7.0.4/7.0.4.config: -------------------------------------------------------------------------------- 1 | {"classes":{"ActionBarContainer":"android.support.v7.widget.ActionBarContainer","ActionMenuView":"android.support.v7.view.menu.h","AvatarUtils":"com.tencent.mm.app.c","ContactFragment":"com.tencent.mm.ui.contact.AddressUI$AddressUIFragment","ConversationFragment":"com.tencent.mm.ui.conversation.MainUI","ConversationListView":"com.tencent.mm.ui.conversation.ConversationListView","ConversationWithAppBrandListView":"com.tencent.mm.ui.conversation.ConversationWithAppBrandListView","CustomViewPager":"com.tencent.mm.ui.base.CustomViewPager","DiscoverFragment":"com.tencent.mm.ui.FindMoreFriendsUI","HomeUI":"com.tencent.mm.ui.HomeUI","LauncherUI":"com.tencent.mm.ui.LauncherUI","LauncherUIBottomTabView":"com.tencent.mm.ui.LauncherUIBottomTabView","LauncherUIBottomTabViewItem":"com.tencent.mm.ui.LauncherUIBottomTabView$a","MMFragmentActivity":"com.tencent.mm.ui.MMFragmentActivity","MainTabUI":"com.tencent.mm.ui.MainTabUI","MainTabUIPageAdapter":"com.tencent.mm.ui.MainTabUI$TabsAdapter","NoDrawingCacheLinearLayout":"com.tencent.mm.ui.NoDrawingCacheLinearLayout","NoMeasuredTextView":"com.tencent.mm.ui.base.NoMeasuredTextView","PhoneWindow":"com.android.internal.policy.PhoneWindow","PreferenceFragment":"com.tencent.mm.ui.base.preference.MMPreferenceFragment","ScrollingTabContainerView":"android.support.v7.widget.au","SettingsFragment":"com.tencent.mm.ui.MoreTabUI","TabIconView":"com.tencent.mm.ui.TabIconView","ThreadExecutor":"com.tencent.mm.sdk.platformtools.aq","WXCustomSchemeEntryActivity":"com.tencent.mm.plugin.base.stub.WXCustomSchemeEntryActivity","WxViewPager":"com.tencent.mm.ui.mogic.WxViewPager"},"fields":{"ContactFragment_mListView":"nIv","ConversationFragment_mListView":"zuW","HomeUI_mActionBar":"mActionBar","HomeUI_mMainTabUI":"yiP","LauncherUIBottomTabViewItem_mTextViews":["ykB","ykC"],"LauncherUI_mHomeUI":"yjJ","MainTabUI_mCustomViewPager":"yeo","PreferenceFragment_mListView":"nDp"},"methods":{"AvatarUtils_getAvatarBitmaps":["cQ","cR"],"AvatarUtils_getDefaultAvatarBitmap":"AZ","ConversationWithAppBrandListView_isAppBrandHeaderEnable":"qL","HomeFragment_lifecycles":["dvY","dvZ","dwa","dwb","dwc","dwd"],"LauncherUIBottomTabView_getTabItemView":"MW","MainTabUIPageAdapter_getCount":"getCount","MainTabUIPageAdapter_onPageScrolled":"fixAndroidOProgressBarOutScreenFlashProblem","WXCustomSchemeEntryActivity_entry":"O","WxViewPager_selectedPage":"setCurrentItemInternal"}} -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion 27 6 | 7 | defaultConfig { 8 | applicationId "com.blanke.mdwechat" 9 | minSdkVersion 21 10 | targetSdkVersion 27 11 | versionCode 37 12 | versionName "3.4.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | lintOptions { 21 | checkReleaseBuilds false 22 | abortOnError false 23 | } 24 | 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | implementation 'com.android.support:appcompat-v7:23.0.0' 34 | 35 | compileOnly 'de.robv.android.xposed:api:53' 36 | compileOnly 'de.robv.android.xposed:api:53:sources' 37 | 38 | implementation project(':fab-lib') 39 | implementation project(':tablayout-lib') 40 | implementation project(':ColorPicker') 41 | 42 | implementation 'com.google.code.gson:gson:2.8.4' 43 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 44 | 45 | implementation 'com.github.Blankeer:rclayout:v1.6.0' 46 | 47 | implementation 'net.dongliu:apk-parser:2.5.3' 48 | implementation 'com.blankj:utilcode:1.19.3' 49 | implementation 'org.greenrobot:eventbus:3.1.1' 50 | implementation 'com.squareup.okhttp3:okhttp:3.11.0' 51 | 52 | implementation 'ru.noties:markwon:1.1.0' 53 | implementation 'ru.noties:markwon-image-loader:1.1.0' 54 | implementation 'org.jooq:jooq:3.11.11' 55 | } 56 | 57 | 58 | afterEvaluate { 59 | installDebug.doLast { 60 | killTargetAPP.execute() 61 | startTargetAPP.execute() 62 | } 63 | } 64 | 65 | def pkg = 'com.tencent.mm' 66 | task killTargetAPP(type: Exec) { 67 | commandLine android.adbExecutable, 'shell', 'am', 'force-stop', pkg 68 | } 69 | 70 | task startTargetAPP(type: Exec) { 71 | //adb shell monkey -p your.app.package.name -c android.intent.category.LAUNCHER 1 72 | commandLine android.adbExecutable, 'shell', 'monkey', '-p', pkg, '-c', 'android.intent.category.LAUNCHER', '1' 73 | } -------------------------------------------------------------------------------- /tablayout-lib/src/main/java/com/flyco/tablayout/utils/UnreadMsgUtils.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.utils; 2 | 3 | 4 | import android.util.DisplayMetrics; 5 | import android.view.View; 6 | import android.widget.RelativeLayout; 7 | 8 | import com.flyco.tablayout.widget.MsgView; 9 | 10 | /** 11 | * 未读消息提示View,显示小红点或者带有数字的红点: 12 | * 数字一位,圆 13 | * 数字两位,圆角矩形,圆角是高度的一半 14 | * 数字超过两位,显示99+ 15 | */ 16 | public class UnreadMsgUtils { 17 | public static void show(MsgView msgView, int num) { 18 | if (msgView == null) { 19 | return; 20 | } 21 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) msgView.getLayoutParams(); 22 | DisplayMetrics dm = msgView.getResources().getDisplayMetrics(); 23 | if (num == 0) { 24 | msgView.setVisibility(View.GONE); 25 | } else if (num < 0) {//圆点,设置默认宽高 26 | msgView.setVisibility(View.VISIBLE); 27 | msgView.setStrokeWidth(0); 28 | msgView.setText(""); 29 | 30 | lp.width = (int) (10 * dm.density); 31 | lp.height = (int) (10 * dm.density); 32 | msgView.setLayoutParams(lp); 33 | } else { 34 | msgView.setVisibility(View.VISIBLE); 35 | lp.height = (int) (18 * dm.density); 36 | if (num > 0 && num < 10) {//圆 37 | lp.width = (int) (18 * dm.density); 38 | msgView.setText(num + ""); 39 | } else if (num > 9 && num < 100) {//圆角矩形,圆角是高度的一半,设置默认padding 40 | lp.width = RelativeLayout.LayoutParams.WRAP_CONTENT; 41 | msgView.setPadding((int) (6 * dm.density), 0, (int) (6 * dm.density), 0); 42 | msgView.setText(num + ""); 43 | } else {//数字超过两位,显示99+ 44 | lp.width = RelativeLayout.LayoutParams.WRAP_CONTENT; 45 | msgView.setPadding((int) (6 * dm.density), 0, (int) (6 * dm.density), 0); 46 | msgView.setText("99+"); 47 | } 48 | msgView.setLayoutParams(lp); 49 | } 50 | } 51 | 52 | public static void setSize(MsgView rtv, int size) { 53 | if (rtv == null) { 54 | return; 55 | } 56 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) rtv.getLayoutParams(); 57 | lp.width = size; 58 | lp.height = size; 59 | rtv.setLayoutParams(lp); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @color/material_amber 5 | @color/material_amber_700 6 | @color/material_black 7 | @color/material_black_54 8 | @color/material_black_87 9 | @color/material_blue 10 | @color/material_blue_700 11 | @color/material_blue_grey 12 | @color/material_blue_grey_700 13 | @color/material_brown 14 | @color/material_brown_700 15 | @color/material_button_pressed_light 16 | @color/material_cyan 17 | @color/material_cyan_700 18 | @color/material_deep_orange 19 | @color/material_deep_orange_700 20 | @color/material_deep_purple 21 | @color/material_deep_purple_700 22 | @color/material_green 23 | @color/material_green_700 24 | @color/material_grey 25 | @color/material_grey_700 26 | @color/material_indigo 27 | @color/material_indigo_700 28 | @color/material_light_blue 29 | @color/material_light_blue_700 30 | @color/material_light_green 31 | @color/material_light_green_700 32 | @color/material_lime 33 | @color/material_lime_700 34 | @color/material_orange 35 | @color/material_orange_700 36 | @color/material_pink 37 | @color/material_pink_300 38 | @color/material_pink_700 39 | @color/material_purple 40 | @color/material_purple_700 41 | @color/material_red 42 | @color/material_red_700 43 | @color/material_teal 44 | @color/material_teal_700 45 | @color/material_white 46 | @color/material_white_54 47 | @color/material_white_87 48 | @color/material_yellow 49 | @color/material_yellow_700 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/hookers/LogHooker.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.hookers 2 | 3 | import android.util.Log 4 | import com.blanke.mdwechat.config.AppCustomConfig 5 | import com.blanke.mdwechat.config.HookConfig 6 | import com.blanke.mdwechat.hookers.base.Hooker 7 | import com.blanke.mdwechat.hookers.base.HookerProvider 8 | import com.blanke.mdwechat.util.FileUtils 9 | import de.robv.android.xposed.XC_MethodHook 10 | import de.robv.android.xposed.XposedBridge 11 | import java.io.File 12 | import java.text.SimpleDateFormat 13 | import java.util.* 14 | 15 | object LogHooker : HookerProvider { 16 | private val dateStr 17 | get() = SimpleDateFormat("yyyy-MM-dd").format(Date()) 18 | 19 | override fun provideStaticHookers(): List? { 20 | return listOf(LogIHooker, LogEHooker) 21 | } 22 | 23 | private val LogIHooker = Hooker { 24 | XposedBridge.hookAllMethods(Log::class.java, "i", object : XC_MethodHook() { 25 | override fun afterHookedMethod(param: MethodHookParam) { 26 | if (!HookConfig.is_hook_log) { 27 | return 28 | } 29 | val msg = param.args[1] as String 30 | if (msg.contains("mdwechat", true)) { 31 | printLog(msg) 32 | } 33 | } 34 | }) 35 | } 36 | 37 | private val LogEHooker = Hooker { 38 | XposedBridge.hookAllMethods(Log::class.java, "e", object : XC_MethodHook() { 39 | override fun afterHookedMethod(param: MethodHookParam) { 40 | if (!HookConfig.is_hook_log) { 41 | return 42 | } 43 | val msg = param.args[1] as String 44 | if (msg.contains("mdwechat", true)) { 45 | printLog(msg) 46 | } 47 | val tr = param.args[param.args.size - 1] 48 | if (tr is Throwable) { 49 | val msg = Log.getStackTraceString(tr) 50 | if (msg.contains("mdwechat", true)) { 51 | printLog(msg) 52 | } 53 | } 54 | } 55 | }) 56 | } 57 | 58 | private fun printLog(log: String) { 59 | val logFile = File(AppCustomConfig.getLogFile(dateStr)) 60 | logFile.parentFile.mkdirs() 61 | val time = SimpleDateFormat("HH:mm:ss").format(Date()) 62 | FileUtils.write(logFile.absolutePath, "$time $log\n", true) 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/util/ImageHelper.java: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.util; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.PaintFlagsDrawFilter; 7 | import android.graphics.PorterDuff; 8 | import android.graphics.PorterDuffXfermode; 9 | import android.graphics.Rect; 10 | import android.graphics.RectF; 11 | import android.graphics.drawable.BitmapDrawable; 12 | import android.graphics.drawable.Drawable; 13 | 14 | public class ImageHelper { 15 | public static Bitmap getBitmapFromDrawable(Drawable drawable) { 16 | if (drawable == null) { 17 | return null; 18 | } 19 | 20 | if (drawable instanceof BitmapDrawable) { 21 | return ((BitmapDrawable) drawable).getBitmap(); 22 | } 23 | 24 | try { 25 | Bitmap bitmap; 26 | 27 | // if (drawable instanceof ColorDrawable) { 28 | // bitmap = Bitmap.createBitmap(2, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG); 29 | // } else { 30 | bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), 31 | drawable.getIntrinsicHeight(), 32 | Bitmap.Config.ARGB_8888); 33 | // } 34 | 35 | Canvas canvas = new Canvas(bitmap); 36 | drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 37 | drawable.draw(canvas); 38 | return bitmap; 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | return null; 42 | } 43 | } 44 | 45 | public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, int pixels) { 46 | Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap 47 | .getHeight(), Bitmap.Config.ARGB_8888); 48 | Canvas canvas = new Canvas(output); 49 | canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG)); 50 | 51 | final int color = 0xff424242; 52 | final Paint paint = new Paint(); 53 | final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); 54 | final RectF rectF = new RectF(rect); 55 | final float roundPx = pixels; 56 | 57 | paint.setAntiAlias(true); 58 | paint.setDither(true); 59 | canvas.drawARGB(0, 0, 0, 0); 60 | paint.setColor(color); 61 | canvas.drawRoundRect(rectF, roundPx, roundPx, paint); 62 | 63 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 64 | canvas.drawBitmap(bitmap, rect, rect, paint); 65 | 66 | return output; 67 | } 68 | } -------------------------------------------------------------------------------- /data/config/wechat/6.7.2-play/6.7.2.config: -------------------------------------------------------------------------------- 1 | { 2 | "classes": { 3 | "ActionBarContainer": "android.support.v7.widget.ActionBarContainer", 4 | "ActionMenuView": "android.support.v7.view.menu.h", 5 | "AvatarUtils": "com.tencent.mm.app.c", 6 | "ContactFragment": "com.tencent.mm.ui.contact.AddressUI$a", 7 | "ConversationFragment": "com.tencent.mm.ui.conversation.j", 8 | "ConversationWithAppBrandListView": "com.tencent.mm.ui.conversation.ConversationWithAppBrandListView", 9 | "CustomViewPager": "com.tencent.mm.ui.base.CustomViewPager", 10 | "DiscoverFragment": "com.tencent.mm.ui.h", 11 | "HomeUI": "com.tencent.mm.ui.HomeUI", 12 | "LauncherUI": "com.tencent.mm.ui.LauncherUI", 13 | "LauncherUIBottomTabView": "com.tencent.mm.ui.LauncherUIBottomTabView", 14 | "LauncherUIBottomTabViewItem": "com.tencent.mm.ui.LauncherUIBottomTabView$a", 15 | "MMFragmentActivity": "com.tencent.mm.ui.MMFragmentActivity", 16 | "MainTabUI": "com.tencent.mm.ui.z", 17 | "MainTabUIPageAdapter": "com.tencent.mm.ui.z$a", 18 | "NoDrawingCacheLinearLayout": "com.tencent.mm.ui.NoDrawingCacheLinearLayout", 19 | "NoMeasuredTextView": "com.tencent.mm.ui.base.NoMeasuredTextView", 20 | "PhoneWindow": "com.android.internal.policy.PhoneWindow", 21 | "PreferenceFragment": "com.tencent.mm.ui.base.preference.i", 22 | "ScrollingTabContainerView": "android.support.v7.widget.ar", 23 | "SettingsFragment": "com.tencent.mm.ui.ac", 24 | "TabIconView": "com.tencent.mm.ui.TabIconView", 25 | "ThreadExecutor": "com.tencent.mm.sdk.platformtools.an", 26 | "WxViewPager": "com.tencent.mm.ui.mogic.WxViewPager" 27 | }, 28 | "fields": { 29 | "ContactFragment_mListView": "kVN", 30 | "ConversationFragment_mListView": "vgg", 31 | "HomeUI_mActionBar": "mActionBar", 32 | "HomeUI_mMainTabUI": "tXk", 33 | "LauncherUIBottomTabViewItem_mTextViews": [ 34 | "tYI", 35 | "tYJ" 36 | ], 37 | "LauncherUI_mHomeUI": "tXR", 38 | "MainTabUI_mCustomViewPager": "ubL", 39 | "PreferenceFragment_mListView": "kQL" 40 | }, 41 | "methods": { 42 | "AvatarUtils_getAvatarBitmaps": [ 43 | "cj", 44 | "ck" 45 | ], 46 | "AvatarUtils_getDefaultAvatarBitmap": "sz", 47 | "ConversationWithAppBrandListView_isAppBrandHeaderEnable": "nb", 48 | "HomeFragment_lifecycles": [ 49 | "crM", 50 | "crN", 51 | "crO", 52 | "crP", 53 | "crQ", 54 | "crR" 55 | ], 56 | "LauncherUIBottomTabView_getTabItemView": "Ey", 57 | "MainTabUIPageAdapter_getCount": "getCount", 58 | "MainTabUIPageAdapter_onPageScrolled": "a", 59 | "WxViewPager_selectedPage": "a" 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/markdown/MarkDownActivity.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.markdown 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.view.View 7 | import android.widget.TextView 8 | import com.blanke.mdwechat.R 9 | import com.blanke.mdwechat.settings.api.APIManager 10 | import com.blankj.utilcode.util.ToastUtils 11 | import okhttp3.Call 12 | import okhttp3.Callback 13 | import okhttp3.Response 14 | import ru.noties.markwon.Markwon 15 | import ru.noties.markwon.SpannableConfiguration 16 | import ru.noties.markwon.il.AsyncDrawableLoader 17 | import ru.noties.markwon.spans.SpannableTheme 18 | import java.io.IOException 19 | 20 | class MarkDownActivity : Activity() { 21 | 22 | companion object { 23 | val URL = "url" 24 | val TITLE = "title" 25 | fun start(act: Activity, title: String, url: String) { 26 | val intent = Intent(act, MarkDownActivity::class.java) 27 | intent.putExtra(URL, url) 28 | intent.putExtra(TITLE, title) 29 | act.startActivity(intent) 30 | } 31 | } 32 | 33 | override fun onCreate(savedInstanceState: Bundle?) { 34 | super.onCreate(savedInstanceState) 35 | setContentView(R.layout.activity_mark_down) 36 | actionBar.setDisplayHomeAsUpEnabled(true) 37 | val url = intent.getStringExtra(URL) 38 | val title = intent.getStringExtra(TITLE) 39 | actionBar.title = title 40 | val markdownView = findViewById(R.id.tv_markdown) 41 | val pb = findViewById(R.id.pb_loading) 42 | APIManager().get(url, object : Callback { 43 | override fun onFailure(call: Call?, e: IOException?) { 44 | ToastUtils.showShort("加载失败:" + e?.message) 45 | finish() 46 | } 47 | 48 | override fun onResponse(call: Call?, response: Response?) { 49 | val content = response?.body()?.string() ?: "" 50 | runOnUiThread { 51 | pb.visibility = View.GONE 52 | val config = SpannableConfiguration 53 | .builder(this@MarkDownActivity) 54 | .asyncDrawableLoader(AsyncDrawableLoader.create()) 55 | .theme(SpannableTheme.builder().build()) 56 | .build() 57 | Markwon.setMarkdown(markdownView, config, content) 58 | } 59 | } 60 | }) 61 | } 62 | 63 | override fun onNavigateUp(): Boolean { 64 | finish() 65 | return true 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/hookers/ActionBarHooker.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.hookers 2 | 3 | import android.graphics.Color 4 | import android.graphics.drawable.ColorDrawable 5 | import android.graphics.drawable.Drawable 6 | import android.view.ViewGroup 7 | import com.blanke.mdwechat.Classes 8 | import com.blanke.mdwechat.Classes.ActionBarContainer 9 | import com.blanke.mdwechat.Version 10 | import com.blanke.mdwechat.WeChatHelper.colorPrimaryDrawable 11 | import com.blanke.mdwechat.WechatGlobal 12 | import com.blanke.mdwechat.hookers.base.Hooker 13 | import com.blanke.mdwechat.hookers.base.HookerProvider 14 | import de.robv.android.xposed.XC_MethodHook 15 | import de.robv.android.xposed.XposedHelpers 16 | import de.robv.android.xposed.XposedHelpers.findAndHookMethod 17 | 18 | object ActionBarHooker : HookerProvider { 19 | 20 | override fun provideStaticHookers(): List? { 21 | return listOf(actionBarHooker) 22 | } 23 | 24 | private val actionBarHooker = Hooker { 25 | findAndHookMethod(ActionBarContainer, "setPrimaryBackground", Drawable::class.java, object : XC_MethodHook() { 26 | override fun beforeHookedMethod(param: MethodHookParam) { 27 | val drawable = param.args[0] 28 | var needHook = true 29 | val actionBar = param.thisObject as ViewGroup 30 | if (drawable is ColorDrawable) { 31 | // LogUtil.log("actionbar color=" + Integer.toHexString(drawable.color)) 32 | if (drawable.color == Color.parseColor("#F2F2F2") 33 | || drawable.color == Color.parseColor("#FFFAFAFA") 34 | || drawable.color == Color.TRANSPARENT) { 35 | needHook = false 36 | } 37 | if (WechatGlobal.wxVersion!! >= Version("7.0.0")) { 38 | if (actionBar.context::class.java.name == Classes.LauncherUI.name) { 39 | needHook = true 40 | } 41 | } 42 | if (drawable.color == colorPrimaryDrawable.color) { 43 | needHook = false 44 | } 45 | } 46 | if (needHook) { 47 | param.args[0] = colorPrimaryDrawable 48 | } 49 | val init_elevation = XposedHelpers.getAdditionalInstanceField(actionBar, "init_elevation") 50 | if (init_elevation == null) { 51 | actionBar.elevation = 5F 52 | XposedHelpers.setAdditionalInstanceField(actionBar, "init_elevation", true) 53 | } 54 | } 55 | }) 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/WechatHook.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat 2 | 3 | import com.blanke.mdwechat.config.HookConfig 4 | import com.blanke.mdwechat.config.WxVersionConfig 5 | import com.blanke.mdwechat.hookers.* 6 | import com.blanke.mdwechat.hookers.base.HookerProvider 7 | import com.blanke.mdwechat.util.LogUtil.log 8 | import de.robv.android.xposed.IXposedHookLoadPackage 9 | import de.robv.android.xposed.callbacks.XC_LoadPackage 10 | 11 | class WechatHook : IXposedHookLoadPackage { 12 | 13 | @Throws(Throwable::class) 14 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { 15 | try { 16 | if (!(lpparam.packageName.contains("com.tencent") && lpparam.packageName.contains("mm"))) 17 | return 18 | // 暂时不 hook 小程序 19 | if (lpparam.processName.contains(":")) { 20 | return 21 | } 22 | WeChatHelper.initPrefs() 23 | if (!HookConfig.is_hook_switch) { 24 | log("模块总开关已关闭") 25 | return 26 | } 27 | log("模块加载成功") 28 | val hookers = mutableListOf( 29 | LauncherUIHooker, 30 | ActionBarHooker, 31 | StatusBarHooker, 32 | AvatarHooker, 33 | ListViewHooker, 34 | ConversationHooker, 35 | ContactHooker, 36 | DiscoverHooker, 37 | SettingsHooker, 38 | SchemeHooker, 39 | LogHooker 40 | ) 41 | if (BuildConfig.DEBUG) { 42 | hookers.add(0, DebugHooker) 43 | } 44 | hookMain(lpparam, hookers) 45 | } catch (e: Throwable) { 46 | log(e) 47 | } 48 | } 49 | 50 | private fun hookMain(lpparam: XC_LoadPackage.LoadPackageParam, plugins: List) { 51 | WechatGlobal.init(lpparam) 52 | try { 53 | WechatGlobal.wxVersionConfig = WxVersionConfig.loadConfig(WechatGlobal.wxVersion!!.toString()) 54 | } catch (e: Exception) { 55 | log("${WechatGlobal.wxVersion} 配置文件不存在或解析失败") 56 | return 57 | } 58 | log("wechat version=" + WechatGlobal.wxVersion 59 | + ",processName=" + lpparam.processName 60 | + ",MDWechat version=" + BuildConfig.VERSION_NAME) 61 | plugins.forEach { provider -> 62 | provider.provideStaticHookers()?.forEach { hooker -> 63 | if (!hooker.hasHooked) { 64 | hooker.hook() 65 | hooker.hasHooked = true 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /data/config/wechat/6.6.7-play/6.6.7.config: -------------------------------------------------------------------------------- 1 | { 2 | "classes": { 3 | "ActionBarContainer": "android.support.v7.widget.ActionBarContainer", 4 | "ActionMenuView": "android.support.v7.view.menu.f", 5 | "AvatarUtils": "com.tencent.mm.app.c", 6 | "ContactFragment": "com.tencent.mm.ui.contact.AddressUI$a", 7 | "ConversationFragment": "com.tencent.mm.ui.conversation.j", 8 | "ConversationWithAppBrandListView": "com.tencent.mm.ui.conversation.ConversationWithAppBrandListView", 9 | "CustomViewPager": "com.tencent.mm.ui.base.CustomViewPager", 10 | "DiscoverFragment": "com.tencent.mm.ui.h", 11 | "HomeUI": "com.tencent.mm.ui.HomeUI", 12 | "LauncherUI": "com.tencent.mm.ui.LauncherUI", 13 | "LauncherUIBottomTabView": "com.tencent.mm.ui.LauncherUIBottomTabView", 14 | "LauncherUIBottomTabViewItem": "com.tencent.mm.ui.LauncherUIBottomTabView$a", 15 | "MMFragmentActivity": "com.tencent.mm.ui.MMFragmentActivity", 16 | "MainTabUI": "com.tencent.mm.ui.z", 17 | "MainTabUIPageAdapter": "com.tencent.mm.ui.z$a", 18 | "NoDrawingCacheLinearLayout": "com.tencent.mm.ui.NoDrawingCacheLinearLayout", 19 | "NoMeasuredTextView": "com.tencent.mm.ui.base.NoMeasuredTextView", 20 | "PhoneWindow": "com.android.internal.policy.PhoneWindow", 21 | "PreferenceFragment": "com.tencent.mm.ui.base.preference.i", 22 | "ScrollingTabContainerView": "android.support.v7.widget.ai", 23 | "SettingsFragment": "com.tencent.mm.ui.ab", 24 | "TabIconView": "com.tencent.mm.ui.TabIconView", 25 | "ThreadExecutor": "com.tencent.mm.sdk.platformtools.am", 26 | "WXCustomSchemeEntryActivity": "com.tencent.mm.plugin.base.stub.WXCustomSchemeEntryActivity", 27 | "WxViewPager": "com.tencent.mm.ui.mogic.WxViewPager" 28 | }, 29 | "fields": { 30 | "ContactFragment_mListView": "kBC", 31 | "ConversationFragment_mListView": "usI", 32 | "HomeUI_mActionBar": "mActionBar", 33 | "HomeUI_mMainTabUI": "tln", 34 | "LauncherUIBottomTabViewItem_mTextViews": [ 35 | "tmM", 36 | "tmN" 37 | ], 38 | "LauncherUI_mHomeUI": "tlV", 39 | "MainTabUI_mCustomViewPager": "tpS", 40 | "PreferenceFragment_mListView": "kwA" 41 | }, 42 | "methods": { 43 | "AvatarUtils_getAvatarBitmaps": [ 44 | "cJ", 45 | "cK" 46 | ], 47 | "AvatarUtils_getDefaultAvatarBitmap": "uM", 48 | "ConversationWithAppBrandListView_isAppBrandHeaderEnable": "mu", 49 | "HomeFragment_lifecycles": [ 50 | "coD", 51 | "coE", 52 | "coF", 53 | "coG", 54 | "coH", 55 | "coI" 56 | ], 57 | "LauncherUIBottomTabView_getTabItemView": "DI", 58 | "MainTabUIPageAdapter_getCount": "getCount", 59 | "MainTabUIPageAdapter_onPageScrolled": "a", 60 | "WXCustomSchemeEntryActivity_entry": "A", 61 | "WxViewPager_selectedPage": "a" 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/hookers/SettingsHooker.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.hookers 2 | 3 | import android.graphics.drawable.NinePatchDrawable 4 | import android.view.View 5 | import com.blanke.mdwechat.CC 6 | import com.blanke.mdwechat.Classes 7 | import com.blanke.mdwechat.Fields.PreferenceFragment_mListView 8 | import com.blanke.mdwechat.Version 9 | import com.blanke.mdwechat.WechatGlobal 10 | import com.blanke.mdwechat.config.AppCustomConfig 11 | import com.blanke.mdwechat.hookers.base.Hooker 12 | import com.blanke.mdwechat.hookers.base.HookerProvider 13 | import com.blanke.mdwechat.util.LogUtil 14 | import com.blanke.mdwechat.util.NightModeUtils 15 | import de.robv.android.xposed.XC_MethodHook 16 | import de.robv.android.xposed.XposedBridge 17 | import de.robv.android.xposed.XposedHelpers 18 | 19 | object SettingsHooker : HookerProvider { 20 | const val keyInit = "key_init" 21 | 22 | override fun provideStaticHookers(): List? { 23 | return listOf(resumeHook) 24 | } 25 | 26 | private val resumeHook = Hooker { 27 | XposedHelpers.findAndHookMethod(Classes.Fragment, "performResume", object : XC_MethodHook() { 28 | override fun afterHookedMethod(param: MethodHookParam?) { 29 | val fragment = param?.thisObject ?: return 30 | if (fragment.javaClass.name != Classes.SettingsFragment.name) { 31 | return 32 | } 33 | val isInit = XposedHelpers.getAdditionalInstanceField(fragment, keyInit) 34 | if (isInit != null) { 35 | LogUtil.log("SettingsFragment 已经hook过") 36 | return 37 | } 38 | XposedHelpers.setAdditionalInstanceField(fragment, keyInit, true) 39 | init(fragment) 40 | } 41 | 42 | private fun init(fragment: Any) { 43 | val listView = PreferenceFragment_mListView.get(fragment) 44 | if (listView != null && listView is View) { 45 | val background = AppCustomConfig.getTabBg(3) 46 | listView.background = NightModeUtils.getBackgroundDrawable(background) 47 | } 48 | } 49 | }) 50 | if (WechatGlobal.wxVersion!! >= Version("7.0.0")) { 51 | XposedBridge.hookAllMethods(CC.View, "setBackground", object : XC_MethodHook() { 52 | override fun beforeHookedMethod(param: MethodHookParam) { 53 | val view = param.thisObject as View 54 | if (view::class.java.name.contains("PullDownListView")) { 55 | val drawable = param.args[0] 56 | if (drawable is NinePatchDrawable) {// 设置页默认的drawable 57 | param.result = null 58 | } 59 | } 60 | } 61 | }) 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/config/AppCustomConfig.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.config 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.BitmapFactory 5 | import com.blanke.mdwechat.Common 6 | import com.blanke.mdwechat.bean.FloatButtonConfig 7 | import com.google.gson.Gson 8 | import java.io.File 9 | import java.io.FileInputStream 10 | import java.io.InputStreamReader 11 | 12 | /** 13 | * Created by blanke on 2017/10/13. 14 | */ 15 | 16 | object AppCustomConfig { 17 | var bitmapScale = 1F 18 | 19 | fun getWxVersionConfig(version: String): WxVersionConfig { 20 | var configName = version + ".config" 21 | // if (HookConfig.is_play) { 22 | // configName = version + "-play.config" 23 | // } 24 | val `is` = FileInputStream(getWxConfigFile(configName)) 25 | return Gson().fromJson(InputStreamReader(`is`), WxVersionConfig::class.java) 26 | } 27 | 28 | fun getWxConfigFile(fileName: String): String { 29 | return Common.APP_DIR_PATH + Common.CONFIG_WECHAT_DIR + File.separator + fileName 30 | } 31 | 32 | fun getConfigFile(fileName: String): String { 33 | return Common.APP_DIR_PATH + Common.CONFIG_DIR + File.separator + fileName 34 | } 35 | 36 | fun getViewConfigFile(fileName: String): String { 37 | return Common.APP_DIR_PATH + Common.CONFIG_VIEW_DIR + File.separator + fileName 38 | } 39 | 40 | fun getLogFile(date: String): String { 41 | return Common.APP_DIR_PATH + Common.LOGS_DIR + File.separator + "MDWechat_log_$date.txt" 42 | } 43 | 44 | fun getTabIcon(index: Int): Bitmap? { 45 | return getScaleBitmap(getIcon(Common.FILE_NAME_TAB_PREFIX + "$index.png")) 46 | } 47 | 48 | fun getBubbleLeftIcon(): Bitmap? { 49 | return getIcon(Common.CHAT_BUBBLE_LEFT_FILENAME) 50 | } 51 | 52 | fun getBubbleRightIcon(): Bitmap? { 53 | return getIcon(Common.CHAT_BUBBLE_RIGHT_FILENAME) 54 | } 55 | 56 | fun getTabBg(index: Int): Bitmap? { 57 | return getIcon(Common.FILE_NAME_TAB_BG_PREFIX + "$index.png") 58 | } 59 | 60 | fun getFloatButtonConfig(): FloatButtonConfig? { 61 | val path = getViewConfigFile(Common.FILE_NAME_FLOAT_BUTTON) 62 | try { 63 | val `is` = FileInputStream(path) 64 | return Gson().fromJson(InputStreamReader(`is`), FloatButtonConfig::class.java) 65 | } catch (e: Exception) { 66 | return null 67 | } 68 | } 69 | 70 | fun getIcon(fileName: String): Bitmap? { 71 | val filePath = Common.APP_DIR_PATH + Common.ICON_DIR + File.separator + fileName 72 | return BitmapFactory.decodeFile(filePath) 73 | } 74 | 75 | fun getScaleBitmap(bitmap: Bitmap?): Bitmap? { 76 | if (bitmap == null) return null 77 | return Bitmap.createScaledBitmap(bitmap, (bitmap.width * bitmapScale).toInt(), (bitmap.height * bitmapScale).toInt(), true) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/config/WxVersionConfig.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.config 2 | 3 | import java.io.IOException 4 | 5 | class WxVersionConfig { 6 | lateinit var classes: Classes 7 | lateinit var fields: Fields 8 | lateinit var methods: Methods 9 | 10 | class Classes { 11 | var ActionBarContainer: String? = null 12 | var ActionMenuView: String? = null 13 | var AvatarUtils: String? = null 14 | var ContactFragment: String? = null 15 | var ConversationFragment: String? = null 16 | var ConversationWithAppBrandListView: String? = null 17 | var ConversationListView: String? = null 18 | var CustomViewPager: String? = null 19 | var DiscoverFragment: String? = null 20 | var HomeUI: String? = null 21 | var LauncherUI: String? = null 22 | var LauncherUIBottomTabView: String? = null 23 | var LauncherUIBottomTabViewItem: String? = null 24 | var MMFragmentActivity: String? = null 25 | var MainTabUI: String? = null 26 | var MainTabUIPageAdapter: String? = null 27 | var NoDrawingCacheLinearLayout: String? = null 28 | var NoMeasuredTextView: String? = null 29 | var PhoneWindow: String? = null 30 | var PreferenceFragment: String? = null 31 | var ScrollingTabContainerView: String? = null 32 | var SettingsFragment: String? = null 33 | var TabIconView: String? = null 34 | var ThreadExecutor: String? = null 35 | var WxViewPager: String? = null 36 | var WXCustomSchemeEntryActivity: String? = null 37 | } 38 | 39 | class Fields { 40 | var ContactFragment_mListView: String? = null 41 | var ConversationFragment_mListView: String? = null 42 | var HomeUI_mActionBar: String? = null 43 | var HomeUI_mMainTabUI: String? = null 44 | var LauncherUIBottomTabViewItem_mTextViews: List? = null 45 | var LauncherUI_mHomeUI: String? = null 46 | var MainTabUI_mCustomViewPager: String? = null 47 | var PreferenceFragment_mListView: String? = null 48 | } 49 | 50 | class Methods { 51 | var AvatarUtils_getAvatarBitmaps: List? = null 52 | var AvatarUtils_getDefaultAvatarBitmap: String? = null 53 | var ConversationWithAppBrandListView_isAppBrandHeaderEnable: String? = null 54 | var HomeFragment_lifecycles: List? = null 55 | var LauncherUIBottomTabView_getTabItemView: String? = null 56 | var MainTabUIPageAdapter_getCount: String? = null 57 | var MainTabUIPageAdapter_onPageScrolled: String? = null 58 | var WxViewPager_selectedPage: String? = null 59 | var WXCustomSchemeEntryActivity_entry: String? = null 60 | } 61 | 62 | companion object { 63 | 64 | @Throws(IOException::class) 65 | fun loadConfig(wxVersion: String): WxVersionConfig { 66 | return AppCustomConfig.getWxVersionConfig(wxVersion) 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/Fields.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat 2 | 3 | import com.blanke.mdwechat.Classes.ContactFragment 4 | import com.blanke.mdwechat.Classes.ConversationFragment 5 | import com.blanke.mdwechat.Classes.HomeUI 6 | import com.blanke.mdwechat.Classes.LauncherUI 7 | import com.blanke.mdwechat.Classes.LauncherUIBottomTabViewItem 8 | import com.blanke.mdwechat.Classes.MainTabUI 9 | import com.blanke.mdwechat.Classes.PreferenceFragment 10 | import com.blanke.mdwechat.WechatGlobal.wxLazy 11 | import java.lang.reflect.Field 12 | 13 | object Fields { 14 | private fun findFieldsWithType(clazz: Class<*>?, typeName: String?): List { 15 | clazz ?: return listOf() 16 | return clazz.declaredFields.filter { 17 | it.type.name == typeName 18 | }.map { 19 | it.isAccessible = true 20 | it 21 | } 22 | } 23 | 24 | private fun findFieldsWithName(clazz: Class<*>?, name: String?): Field? { 25 | clazz ?: return null 26 | return clazz.declaredFields.find { it.name == name }?.apply { isAccessible = true } 27 | } 28 | 29 | private fun findFieldsWithName(clazz: Class<*>?, names: List?): List { 30 | clazz ?: return listOf() 31 | names ?: return listOf() 32 | return clazz.declaredFields.filter { names.contains(it.name) }.map { 33 | it.isAccessible = true 34 | it 35 | } 36 | } 37 | 38 | val LauncherUI_mHomeUI: Field by wxLazy("LauncherUI_mHomeUI") { 39 | findFieldsWithName(LauncherUI, WechatGlobal.wxVersionConfig.fields.LauncherUI_mHomeUI) 40 | } 41 | 42 | val HomeUI_mMainTabUI: Field by wxLazy("HomeUI_mMainTabUI") { 43 | findFieldsWithName(HomeUI, WechatGlobal.wxVersionConfig.fields.HomeUI_mMainTabUI) 44 | } 45 | 46 | val MainTabUI_mCustomViewPager: Field by wxLazy("MainTabUI_mCustomViewPager") { 47 | findFieldsWithName(MainTabUI, WechatGlobal.wxVersionConfig.fields.MainTabUI_mCustomViewPager) 48 | } 49 | 50 | val HomeUI_mActionBar: Field by wxLazy("HomeUI_mActionBar") { 51 | findFieldsWithName(HomeUI, WechatGlobal.wxVersionConfig.fields.HomeUI_mActionBar) 52 | } 53 | 54 | val LauncherUIBottomTabViewItem_mTextViews: List by wxLazy("LauncherUIBottomTabViewItem_mTextViews") { 55 | findFieldsWithName(LauncherUIBottomTabViewItem, WechatGlobal.wxVersionConfig.fields.LauncherUIBottomTabViewItem_mTextViews) 56 | } 57 | 58 | val ConversationFragment_mListView: Field by wxLazy("ConversationFragment_mListView") { 59 | findFieldsWithName(ConversationFragment, WechatGlobal.wxVersionConfig.fields.ConversationFragment_mListView) 60 | } 61 | 62 | val ContactFragment_mListView: Field by wxLazy("ContactFragment_mListView") { 63 | findFieldsWithName(ContactFragment, WechatGlobal.wxVersionConfig.fields.ContactFragment_mListView) 64 | } 65 | 66 | val PreferenceFragment_mListView: Field by wxLazy("PreferenceFragment_mListView") { 67 | findFieldsWithName(PreferenceFragment, WechatGlobal.wxVersionConfig.fields.PreferenceFragment_mListView) 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/auto_search/Fields.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.auto_search 2 | 3 | import com.blanke.mdwechat.CC 4 | import com.blanke.mdwechat.Version 5 | import com.blanke.mdwechat.auto_search.Classes.ContactFragment 6 | import com.blanke.mdwechat.auto_search.Classes.ConversationFragment 7 | import com.blanke.mdwechat.auto_search.Classes.ConversationListView 8 | import com.blanke.mdwechat.auto_search.Classes.ConversationWithAppBrandListView 9 | import com.blanke.mdwechat.auto_search.Classes.CustomViewPager 10 | import com.blanke.mdwechat.auto_search.Classes.HomeUI 11 | import com.blanke.mdwechat.auto_search.Classes.LauncherUI 12 | import com.blanke.mdwechat.auto_search.Classes.LauncherUIBottomTabViewItem 13 | import com.blanke.mdwechat.auto_search.Classes.MainTabUI 14 | import com.blanke.mdwechat.auto_search.Classes.PreferenceFragment 15 | import com.blanke.mdwechat.util.ReflectionUtil.findFieldsWithType 16 | import java.lang.reflect.Field 17 | 18 | object Fields { 19 | val LauncherUI_mHomeUI: Field? 20 | get() { 21 | return findFieldsWithType(LauncherUI!!, HomeUI!!.name) 22 | .firstOrNull()?.apply { isAccessible = true } 23 | } 24 | 25 | val HomeUI_mMainTabUI: Field? 26 | get() { 27 | return findFieldsWithType(HomeUI!!, MainTabUI!!.name) 28 | .firstOrNull()?.apply { isAccessible = true } 29 | } 30 | 31 | val MainTabUI_mCustomViewPager: Field? 32 | get() { 33 | return findFieldsWithType( 34 | MainTabUI!!, CustomViewPager!!.name) 35 | .firstOrNull()?.apply { isAccessible = true } 36 | } 37 | 38 | val HomeUI_mActionBar: Field? 39 | get() { 40 | return findFieldsWithType( 41 | HomeUI!!, "android.support.v7.app.ActionBar") 42 | .firstOrNull()?.apply { isAccessible = true } 43 | } 44 | 45 | val LauncherUIBottomTabViewItem_mTextViews: List? 46 | get() { 47 | return findFieldsWithType(LauncherUIBottomTabViewItem!!, CC.TextView.name) 48 | } 49 | 50 | val ConversationFragment_mListView: Field? 51 | get() { 52 | if (WechatGlobal.wxVersion!! < Version("7.0.3")) { 53 | return findFieldsWithType(ConversationFragment!!, ConversationWithAppBrandListView!!.name) 54 | .firstOrNull()?.apply { isAccessible = true } 55 | } 56 | return findFieldsWithType(ConversationFragment!!, ConversationListView!!.name) 57 | .firstOrNull()?.apply { isAccessible = true } 58 | } 59 | 60 | val ContactFragment_mListView: Field? 61 | get() { 62 | return findFieldsWithType(ContactFragment!!, CC.ListView.name) 63 | .firstOrNull()?.apply { isAccessible = true } 64 | } 65 | 66 | val PreferenceFragment_mListView: Field? 67 | get() { 68 | return findFieldsWithType(PreferenceFragment!!, CC.ListView.name) 69 | .firstOrNull()?.apply { isAccessible = true } 70 | } 71 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/util/FileUtils.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.util 2 | 3 | import android.content.Context 4 | import de.robv.android.xposed.XposedBridge 5 | import java.io.* 6 | 7 | /** 8 | * Created by blanke on 2017/10/11. 9 | */ 10 | object FileUtils { 11 | fun copyAssets(context: Context, appDir: String, dir: String, cover: Boolean = false) { 12 | val assetManager = context.getAssets() 13 | var files: Array? = null 14 | try { 15 | files = assetManager.list(dir) 16 | } catch (e: IOException) { 17 | e.printStackTrace() 18 | } 19 | 20 | if (files != null) { 21 | File(appDir + File.separator + dir + File.separator).mkdirs() 22 | for (filename in files) { 23 | var `in`: InputStream? = null 24 | var out: OutputStream? = null 25 | try { 26 | `in` = assetManager.open(dir + File.separator + filename) 27 | val outFile = File(appDir + File.separator + dir + File.separator + filename) 28 | if (outFile.exists()) { 29 | if (!cover) { 30 | continue 31 | } else { 32 | outFile.delete() 33 | } 34 | } 35 | out = FileOutputStream(outFile) 36 | copyFile(`in`, out) 37 | } catch (e: IOException) { 38 | e.printStackTrace() 39 | } finally { 40 | if (`in` != null) { 41 | try { 42 | `in`!!.close() 43 | } catch (e: IOException) { 44 | e.printStackTrace() 45 | } 46 | 47 | } 48 | if (out != null) { 49 | try { 50 | out!!.close() 51 | } catch (e: IOException) { 52 | e.printStackTrace() 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | @Throws(IOException::class) 61 | fun copyFile(`in`: InputStream, out: OutputStream) { 62 | val buffer = ByteArray(1024) 63 | var read: Int 64 | do { 65 | read = `in`.read(buffer) 66 | if (read == -1) { 67 | break 68 | } 69 | out.write(buffer, 0, read) 70 | } while (true) 71 | } 72 | 73 | fun write(fileName: String, content: String, append: Boolean = false) { 74 | var writer: FileWriter? = null 75 | try { 76 | // 打开一个写文件器,构造函数中的第二个参数true表示以追加形式写文件 77 | writer = FileWriter(fileName, append) 78 | writer.write(content) 79 | } catch (e: IOException) { 80 | XposedBridge.log(e) 81 | } finally { 82 | try { 83 | if (writer != null) { 84 | writer.close() 85 | } 86 | } catch (e: IOException) { 87 | e.printStackTrace() 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/auto_search/Methods.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.auto_search 2 | 3 | import android.graphics.Bitmap 4 | import com.blanke.mdwechat.CC 5 | import com.blanke.mdwechat.CC.voidd 6 | import com.blanke.mdwechat.auto_search.Classes.AvatarUtils 7 | import com.blanke.mdwechat.auto_search.Classes.ContactFragment 8 | import com.blanke.mdwechat.auto_search.Classes.ConversationWithAppBrandListView 9 | import com.blanke.mdwechat.auto_search.Classes.LauncherUIBottomTabView 10 | import com.blanke.mdwechat.auto_search.Classes.LauncherUIBottomTabViewItem 11 | import com.blanke.mdwechat.auto_search.Classes.MainTabUIPageAdapter 12 | import com.blanke.mdwechat.auto_search.Classes.WXCustomSchemeEntryActivity 13 | import com.blanke.mdwechat.auto_search.Classes.WxViewPager 14 | import com.blanke.mdwechat.util.ReflectionUtil.findMethodsByExactParameters 15 | import java.lang.reflect.Method 16 | import java.lang.reflect.Modifier 17 | 18 | 19 | object Methods { 20 | val MainTabUIPageAdapter_getCount: Method? 21 | get() { 22 | return MainTabUIPageAdapter!!.getMethod("getCount") 23 | } 24 | 25 | val MainTabUIPageAdapter_onPageScrolled: Method? 26 | get() { 27 | return findMethodsByExactParameters(MainTabUIPageAdapter!!, voidd, CC.Int, Float::class.java, CC.Int) 28 | .firstOrNull()?.apply { isAccessible = true } 29 | } 30 | 31 | val WxViewPager_selectedPage: Method? 32 | get() { 33 | return findMethodsByExactParameters(WxViewPager!!, voidd, CC.Int, CC.Boolean, CC.Boolean, CC.Int) 34 | .firstOrNull()?.apply { isAccessible = true } 35 | } 36 | 37 | val LauncherUIBottomTabView_getTabItemView: Method? 38 | get() { 39 | return findMethodsByExactParameters(LauncherUIBottomTabView!!, LauncherUIBottomTabViewItem!!, CC.Int) 40 | .firstOrNull()?.apply { isAccessible = true } 41 | } 42 | 43 | val AvatarUtils_getDefaultAvatarBitmap: Method? 44 | get() { 45 | return findMethodsByExactParameters(AvatarUtils!!, Bitmap::class.java) 46 | .firstOrNull()?.apply { isAccessible = true } 47 | } 48 | 49 | val AvatarUtils_getAvatarBitmaps: List? 50 | get() { 51 | return findMethodsByExactParameters(AvatarUtils!!, Bitmap::class.java, CC.String) 52 | } 53 | 54 | val ConversationWithAppBrandListView_isAppBrandHeaderEnable: Method? 55 | get() { 56 | return findMethodsByExactParameters(ConversationWithAppBrandListView!!, CC.Boolean, CC.Boolean) 57 | .firstOrNull()?.apply { isAccessible = true } 58 | } 59 | 60 | // 所有生命周期方法 61 | val HomeFragment_lifecycles: List? 62 | get() { 63 | return findMethodsByExactParameters(ContactFragment!!.superclass, voidd) 64 | .filter { it.modifiers and Modifier.ABSTRACT != 0 } 65 | .filter { it.modifiers and Modifier.PROTECTED != 0 } 66 | } 67 | 68 | val WXCustomSchemeEntryActivity_entry: Method? 69 | get() { 70 | return findMethodsByExactParameters(WXCustomSchemeEntryActivity!!, CC.Boolean, CC.Intent) 71 | .firstOrNull()?.apply { isAccessible = true } 72 | } 73 | } -------------------------------------------------------------------------------- /ColorPicker/src/main/res/layout/cpv_dialog_presets.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 26 | 27 | 34 | 35 | 40 | 41 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 61 | 62 | 68 | 69 | 76 | 77 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #009688 4 | #009688 5 | #FF4081 6 | #ECEFF1 7 | #E0E0E0 8 | #1e000000 9 | #ffffc107 10 | #ffffa000 11 | #ff000000 12 | #8a000000 13 | #de000000 14 | #ff2196f3 15 | #ff1976d2 16 | #ff607d8b 17 | #ff455a64 18 | #ff796648 19 | #ff5d4037 20 | #1acccccc 21 | #1a999999 22 | #26cccccc 23 | #33999999 24 | #40cccccc 25 | #66999999 26 | #ff00bcd4 27 | #ff0097a7 28 | #ffff5722 29 | #ffe64a19 30 | #ff673ab7 31 | #ff512da8 32 | #ff4caf50 33 | #ff388e3c 34 | #ff9e9e9e 35 | #ff616161 36 | #ff3f51b5 37 | #ff303f9f 38 | #ff03a9f4 39 | #ff0288d1 40 | #ff8bc34a 41 | #ff689f38 42 | #ffcddc39 43 | #ffafb42b 44 | #ffff9800 45 | #fff57c00 46 | #ffe91e63 47 | #fff06292 48 | #ffc2185b 49 | #ff9c27b0 50 | #ff7b1fa2 51 | #fff44336 52 | #ffd32f2f 53 | #ff009688 54 | #ff00796b 55 | #ffffffff 56 | #8affffff 57 | #deffffff 58 | #ffffeb3b 59 | #fffbc02d 60 | 61 | -------------------------------------------------------------------------------- /ColorPicker/src/main/res/layout/cpv_dialog_color_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 20 | 21 | 26 | 27 | 32 | 33 | 42 | 43 | 48 | 49 | 53 | 54 | 63 | 64 | 70 | 71 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/Methods.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat 2 | 3 | import com.blanke.mdwechat.Classes.AvatarUtils 4 | import com.blanke.mdwechat.Classes.ContactFragment 5 | import com.blanke.mdwechat.Classes.ConversationWithAppBrandListView 6 | import com.blanke.mdwechat.Classes.LauncherUIBottomTabView 7 | import com.blanke.mdwechat.Classes.MainTabUIPageAdapter 8 | import com.blanke.mdwechat.Classes.WXCustomSchemeEntryActivity 9 | import com.blanke.mdwechat.Classes.WxViewPager 10 | import java.lang.reflect.Method 11 | 12 | object Methods { 13 | private fun findMethodsByName(clazz: Class<*>?, name: String?, vararg parameterTypes: Class<*>): Method? { 14 | name ?: return null 15 | clazz ?: return null 16 | return clazz.getDeclaredMethod(name, *parameterTypes)?.apply { isAccessible = true } 17 | } 18 | 19 | private fun findMethodsByName(clazz: Class<*>?, names: List?, vararg parameterTypes: Class<*>): List { 20 | clazz ?: return listOf() 21 | names ?: return listOf() 22 | return names.map { 23 | findMethodsByName(clazz, it, *parameterTypes) 24 | }.filter { it != null }.map { it!! } 25 | } 26 | 27 | val MainTabUIPageAdapter_getCount: Method by WechatGlobal.wxLazy("MainTabUIPageAdapter_getCount") { 28 | findMethodsByName(MainTabUIPageAdapter, 29 | WechatGlobal.wxVersionConfig.methods.MainTabUIPageAdapter_getCount, 30 | CC.Int) 31 | } 32 | 33 | val MainTabUIPageAdapter_onPageScrolled: Method by WechatGlobal.wxLazy("MainTabUIPageAdapter_onPageScrolled") { 34 | findMethodsByName(MainTabUIPageAdapter, 35 | WechatGlobal.wxVersionConfig.methods.MainTabUIPageAdapter_onPageScrolled, 36 | CC.Int, CC.Float, CC.Int) 37 | } 38 | 39 | val WxViewPager_selectedPage: Method by WechatGlobal.wxLazy("WxViewPager_selectedPage") { 40 | findMethodsByName(WxViewPager, 41 | WechatGlobal.wxVersionConfig.methods.WxViewPager_selectedPage, 42 | CC.Int, CC.Boolean, CC.Boolean, CC.Int) 43 | } 44 | 45 | val LauncherUIBottomTabView_getTabItemView: Method by WechatGlobal.wxLazy("LauncherUIBottomTabView_getTabItemView") { 46 | findMethodsByName(LauncherUIBottomTabView, 47 | WechatGlobal.wxVersionConfig.methods.WxViewPager_selectedPage, 48 | CC.Int) 49 | } 50 | 51 | val AvatarUtils_getDefaultAvatarBitmap: Method by WechatGlobal.wxLazy("AvatarUtils_getDefaultAvatarBitmap") { 52 | findMethodsByName(AvatarUtils, 53 | WechatGlobal.wxVersionConfig.methods.AvatarUtils_getDefaultAvatarBitmap) 54 | } 55 | 56 | val AvatarUtils_getAvatarBitmaps: List by WechatGlobal.wxLazy("AvatarUtils_getAvatarBitmaps") { 57 | findMethodsByName(AvatarUtils, 58 | WechatGlobal.wxVersionConfig.methods.AvatarUtils_getAvatarBitmaps, 59 | CC.String) 60 | } 61 | 62 | val ConversationWithAppBrandListView_isAppBrandHeaderEnable: Method by WechatGlobal.wxLazy("ConversationWithAppBrandListView_isAppBrandHeaderEnable") { 63 | findMethodsByName(ConversationWithAppBrandListView, 64 | WechatGlobal.wxVersionConfig.methods.ConversationWithAppBrandListView_isAppBrandHeaderEnable, 65 | CC.Boolean) 66 | } 67 | 68 | // 所有生命周期方法 69 | val HomeFragment_lifecycles: List by WechatGlobal.wxLazy("ContactFragment_lifecycles") { 70 | findMethodsByName(ContactFragment, 71 | WechatGlobal.wxVersionConfig.methods.HomeFragment_lifecycles) 72 | } 73 | 74 | val WXCustomSchemeEntryActivity_entry: Method by WechatGlobal.wxLazy("WXCustomSchemeEntryActivity_entry") { 75 | findMethodsByName(WXCustomSchemeEntryActivity, 76 | WechatGlobal.wxVersionConfig.methods.WXCustomSchemeEntryActivity_entry, 77 | CC.Intent) 78 | } 79 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/settings/view/DownloadWechatDialog.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.settings.view 2 | 3 | import android.app.Activity 4 | import android.app.AlertDialog 5 | import android.app.ProgressDialog 6 | import android.widget.ListView 7 | import android.widget.SimpleAdapter 8 | import com.blanke.mdwechat.Common 9 | import com.blanke.mdwechat.R 10 | import com.blanke.mdwechat.settings.api.APIManager 11 | import com.blanke.mdwechat.settings.bean.WechatConfig 12 | import com.blankj.utilcode.util.FileIOUtils 13 | import com.blankj.utilcode.util.ToastUtils 14 | import com.google.gson.Gson 15 | import com.google.gson.reflect.TypeToken 16 | import okhttp3.Call 17 | import okhttp3.Callback 18 | import okhttp3.Response 19 | import java.io.IOException 20 | 21 | 22 | object DownloadWechatDialog { 23 | 24 | fun show(context: Activity) { 25 | val progressDialog = ProgressDialog.show(context, "加载中", "正在下载微信配置列表", true, false) 26 | APIManager().getWechatConfigs( 27 | object : Callback { 28 | override fun onFailure(call: Call?, e: IOException?) { 29 | progressDialog.dismiss() 30 | ToastUtils.showLong("下载微信配置列表失败," + e?.message) 31 | } 32 | 33 | override fun onResponse(call: Call?, response: Response) { 34 | progressDialog.dismiss() 35 | val data = Gson().fromJson>(response.body()?.string(), object : TypeToken>() {}.type) 36 | val lists = mutableListOf>() 37 | for (item in data) { 38 | val map = mutableMapOf() 39 | map["name"] = item.name 40 | map["desc"] = item.desc 41 | lists.add(map) 42 | } 43 | val adapter = SimpleAdapter( 44 | context, 45 | lists, 46 | R.layout.item_wechat_config, 47 | arrayOf("name", "desc"), 48 | intArrayOf(R.id.tv_name, R.id.tv_desc) 49 | ) 50 | val listView = ListView(context) 51 | listView.setPadding(10, 0, 10, 20) 52 | listView.adapter = adapter 53 | context.runOnUiThread { 54 | val dialog = AlertDialog.Builder(context) 55 | .setTitle("在线微信配置文件列表") 56 | .setView(listView) 57 | .show() 58 | listView.setOnItemClickListener { parent, view, position, id -> 59 | dialog.dismiss() 60 | val item = data[position] 61 | APIManager().downloadWechatConfig(item.url, object : Callback { 62 | override fun onFailure(call: Call?, e: IOException?) { 63 | ToastUtils.showLong("下载微信配置文件失败," + e?.message) 64 | } 65 | 66 | override fun onResponse(call: Call?, response: Response) { 67 | val outputPath = Common.APP_DIR_PATH + Common.CONFIG_WECHAT_DIR 68 | val succ = FileIOUtils.writeFileFromString("$outputPath/${item.name}", response.body()?.string()) 69 | ToastUtils.showLong("下载微信配置文件${item.name}${if (succ) "成功" else "失败"}") 70 | } 71 | }) 72 | } 73 | } 74 | 75 | } 76 | } 77 | ) 78 | 79 | } 80 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/util/LogUtil.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.util 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | 9 | /** 10 | * Created by blanke on 2017/10/3. 11 | */ 12 | 13 | object LogUtil { 14 | @JvmStatic 15 | fun log(msg: String) { 16 | Log.i("MDWechatModule", "MDWechat $msg") 17 | } 18 | 19 | fun log(t: Throwable) { 20 | Log.e("MDWechatModule", "MDWechat " + Log.getStackTraceString(t)) 21 | } 22 | 23 | fun bundleToString(bundle: Bundle?): String? { 24 | val str = bundle?.keySet()?.joinToString(", ") { 25 | "$it = ${bundle[it]}" 26 | } 27 | return "{$str}" 28 | } 29 | 30 | fun logSuperClasses(clazz: Class<*>) { 31 | log(clazz.name) 32 | if (clazz.superclass != null) { 33 | logSuperClasses(clazz.superclass) 34 | } 35 | } 36 | 37 | fun logStackTraces(methodCount: Int = 15, methodOffset: Int = 3) { 38 | val trace = Thread.currentThread().stackTrace 39 | var level = "" 40 | log("---------logStackTraces start----------") 41 | for (i in methodCount downTo 1) { 42 | val stackIndex = i + methodOffset 43 | if (stackIndex >= trace.size) { 44 | continue 45 | } 46 | val builder = StringBuilder() 47 | builder.append("|") 48 | .append(' ') 49 | .append(level) 50 | .append(trace[stackIndex].className) 51 | .append(".") 52 | .append(trace[stackIndex].methodName) 53 | .append(" ") 54 | .append(" (") 55 | .append(trace[stackIndex].fileName) 56 | .append(":") 57 | .append(trace[stackIndex].lineNumber) 58 | .append(")") 59 | level += " " 60 | log(builder.toString()) 61 | } 62 | log("---------logStackTraces end----------") 63 | } 64 | 65 | fun logParentView(view: View, level: Int = 5) { 66 | log("---------logParentView start----------") 67 | var currentView = view 68 | for (i in 0 until level) { 69 | val v = currentView.parent 70 | if (v != null && v is View) { 71 | logView(v) 72 | currentView = v 73 | } 74 | } 75 | log("---------logParentView end----------") 76 | } 77 | 78 | fun logView(view: View) { 79 | log(getViewLogInfo(view)) 80 | } 81 | 82 | fun getViewLogInfo(view: View): String { 83 | val sb = StringBuffer(view.toString()) 84 | if (view is TextView) { 85 | sb.append("${view.text}(" + view.hint + ")") 86 | } else if (view is ViewGroup) { 87 | sb.append(" childCount = ${view.childCount}") 88 | } 89 | sb.append(" desc= ${view.contentDescription}") 90 | return sb.toString() 91 | } 92 | 93 | fun logViewStackTraces(view: View, level: Int = 0) { 94 | val sb = StringBuffer() 95 | for (i in 0..level) { 96 | sb.append("\t") 97 | } 98 | sb.append(getViewLogInfo(view)) 99 | log(sb.toString()) 100 | if (view is ViewGroup) { 101 | for (i in 0 until view.childCount) { 102 | logViewStackTraces(view.getChildAt(i), level + 1) 103 | } 104 | } 105 | } 106 | 107 | fun findTextViewStackTrace(view: View, text: String): View? { 108 | if (view is TextView) { 109 | if (view.text.contains(text)) { 110 | return view 111 | } 112 | } else if (view is ViewGroup) { 113 | for (i in 0 until view.childCount) { 114 | val res = findTextViewStackTrace(view.getChildAt(i), text) 115 | if (res != null) { 116 | return res 117 | } 118 | } 119 | } 120 | return null 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/hookers/StatusBarHooker.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.hookers 2 | 3 | import android.app.Activity 4 | import android.graphics.Color 5 | import android.graphics.drawable.ColorDrawable 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.view.Window 9 | import android.widget.FrameLayout 10 | import android.widget.LinearLayout 11 | import com.blanke.mdwechat.* 12 | import com.blanke.mdwechat.Classes.PhoneWindow 13 | import com.blanke.mdwechat.WeChatHelper.colorPrimary 14 | import com.blanke.mdwechat.config.HookConfig 15 | import com.blanke.mdwechat.hookers.base.Hooker 16 | import com.blanke.mdwechat.hookers.base.HookerProvider 17 | import com.blanke.mdwechat.util.ColorUtils 18 | import com.blanke.mdwechat.util.mainThread 19 | import com.blankj.utilcode.util.BarUtils 20 | import de.robv.android.xposed.XC_MethodHook 21 | import de.robv.android.xposed.XposedHelpers 22 | import de.robv.android.xposed.XposedHelpers.findAndHookMethod 23 | 24 | 25 | object StatusBarHooker : HookerProvider { 26 | override fun provideStaticHookers(): List? { 27 | return listOf(phoneWindowHook) 28 | } 29 | 30 | private val phoneWindowHook = Hooker { 31 | val color = getStatusBarColor() 32 | if (WechatGlobal.wxVersion!! < Version("7.0.3")) { 33 | findAndHookMethod(PhoneWindow, "setStatusBarColor", CC.Int, object : XC_MethodHook() { 34 | @Throws(Throwable::class) 35 | override fun beforeHookedMethod(param: XC_MethodHook.MethodHookParam?) { 36 | val oldColor = param?.args!![0] as Int 37 | if (oldColor == Color.parseColor("#F2F2F2") 38 | || oldColor == Color.parseColor("#FFFAFAFA") 39 | || oldColor == 0) { 40 | return 41 | } 42 | if (color != oldColor) { 43 | WeChatHelper.reloadPrefs() 44 | val window = param.thisObject as Window 45 | window.statusBarColor = color 46 | window.navigationBarColor = color 47 | param.result = null 48 | } 49 | } 50 | }) 51 | } else { 52 | XposedHelpers.findAndHookMethod(CC.Activity, "onCreate", CC.Bundle, object : XC_MethodHook() { 53 | override fun afterHookedMethod(param: MethodHookParam) { 54 | val activity = param.thisObject as Activity 55 | // LogUtil.log("activity onCreate " + activity) 56 | val statusView = View(activity) 57 | statusView.background = ColorDrawable(getStatusBarColor()) 58 | statusView.elevation = 1F 59 | val statusParam = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) 60 | statusParam.topMargin = 0 61 | statusParam.height = BarUtils.getStatusBarHeight() 62 | 63 | statusView.layoutParams = LinearLayout.LayoutParams( 64 | ViewGroup.LayoutParams.MATCH_PARENT, BarUtils.getStatusBarHeight()) 65 | val rootView = activity.window.decorView as ViewGroup 66 | mainThread(100) { 67 | rootView.addView(statusView) 68 | val window = activity.window 69 | window.statusBarColor = color 70 | window.navigationBarColor = color 71 | } 72 | if (activity::class.java == Classes.LauncherUI) { 73 | mainThread(1000) { 74 | val window = activity.window 75 | window.statusBarColor = color 76 | window.navigationBarColor = color 77 | } 78 | } 79 | } 80 | }) 81 | } 82 | } 83 | 84 | fun getStatusBarColor(): Int { 85 | return if (HookConfig.is_hook_statusbar_transparent) colorPrimary else ColorUtils.getDarkerColor(colorPrimary, 0.85f) 86 | } 87 | } -------------------------------------------------------------------------------- /ColorPicker/src/main/java/com/jaredrummler/android/colorpicker/AlphaPatternDrawable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Jared Rummler 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jaredrummler.android.colorpicker; 18 | 19 | import android.graphics.Bitmap; 20 | import android.graphics.Bitmap.Config; 21 | import android.graphics.Canvas; 22 | import android.graphics.ColorFilter; 23 | import android.graphics.Paint; 24 | import android.graphics.Rect; 25 | import android.graphics.drawable.Drawable; 26 | 27 | /** 28 | * This drawable will draw a simple white and gray chessboard pattern. 29 | * It's the pattern you will often see as a background behind a partly transparent image in many applications. 30 | */ 31 | class AlphaPatternDrawable extends Drawable { 32 | 33 | private int rectangleSize = 10; 34 | 35 | private Paint paint = new Paint(); 36 | private Paint paintWhite = new Paint(); 37 | private Paint paintGray = new Paint(); 38 | 39 | private int numRectanglesHorizontal; 40 | private int numRectanglesVertical; 41 | 42 | /** 43 | * Bitmap in which the pattern will be cached. 44 | * This is so the pattern will not have to be recreated each time draw() gets called. 45 | * Because recreating the pattern i rather expensive. I will only be recreated if the size changes. 46 | */ 47 | private Bitmap bitmap; 48 | 49 | AlphaPatternDrawable(int rectangleSize) { 50 | this.rectangleSize = rectangleSize; 51 | paintWhite.setColor(0xFFFFFFFF); 52 | paintGray.setColor(0xFFCBCBCB); 53 | } 54 | 55 | @Override public void draw(Canvas canvas) { 56 | if (bitmap != null && !bitmap.isRecycled()) { 57 | canvas.drawBitmap(bitmap, null, getBounds(), paint); 58 | } 59 | } 60 | 61 | @Override public int getOpacity() { 62 | return 0; 63 | } 64 | 65 | @Override public void setAlpha(int alpha) { 66 | throw new UnsupportedOperationException("Alpha is not supported by this drawable."); 67 | } 68 | 69 | @Override public void setColorFilter(ColorFilter cf) { 70 | throw new UnsupportedOperationException("ColorFilter is not supported by this drawable."); 71 | } 72 | 73 | @Override protected void onBoundsChange(Rect bounds) { 74 | super.onBoundsChange(bounds); 75 | int height = bounds.height(); 76 | int width = bounds.width(); 77 | numRectanglesHorizontal = (int) Math.ceil((width / rectangleSize)); 78 | numRectanglesVertical = (int) Math.ceil(height / rectangleSize); 79 | generatePatternBitmap(); 80 | } 81 | 82 | /** 83 | * This will generate a bitmap with the pattern as big as the rectangle we were allow to draw on. 84 | * We do this to chache the bitmap so we don't need to recreate it each time draw() is called since it takes a few milliseconds 85 | */ 86 | private void generatePatternBitmap() { 87 | if (getBounds().width() <= 0 || getBounds().height() <= 0) { 88 | return; 89 | } 90 | 91 | bitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888); 92 | Canvas canvas = new Canvas(bitmap); 93 | 94 | Rect r = new Rect(); 95 | boolean verticalStartWhite = true; 96 | for (int i = 0; i <= numRectanglesVertical; i++) { 97 | boolean isWhite = verticalStartWhite; 98 | for (int j = 0; j <= numRectanglesHorizontal; j++) { 99 | r.top = i * rectangleSize; 100 | r.left = j * rectangleSize; 101 | r.bottom = r.top + rectangleSize; 102 | r.right = r.left + rectangleSize; 103 | canvas.drawRect(r, isWhite ? paintWhite : paintGray); 104 | isWhite = !isWhite; 105 | } 106 | verticalStartWhite = !verticalStartWhite; 107 | } 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /fab-lib/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/settings/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat.settings 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import android.net.Uri 7 | import android.os.Bundle 8 | import android.os.Handler 9 | import android.os.Looper 10 | import android.provider.Settings 11 | import android.support.v4.app.ActivityCompat 12 | import android.view.View 13 | import android.widget.Toast 14 | import com.blanke.mdwechat.Common 15 | import com.blanke.mdwechat.R 16 | import com.blanke.mdwechat.config.AppCustomConfig 17 | import com.blanke.mdwechat.util.FileUtils 18 | import java.io.File 19 | import java.io.FileInputStream 20 | import java.io.FileOutputStream 21 | import kotlin.concurrent.thread 22 | 23 | 24 | /** 25 | * Created by blanke on 2017/6/8. 26 | */ 27 | 28 | class SettingsActivity : Activity() { 29 | private lateinit var fab: View 30 | 31 | override fun onCreate(savedInstanceState: Bundle?) { 32 | super.onCreate(savedInstanceState) 33 | setContentView(R.layout.activity_settings) 34 | Common.APP_DIR_PATH 35 | verifyStoragePermissions(this) 36 | fab = findViewById(R.id.fab) 37 | fab.setOnClickListener { 38 | copyConfig() 39 | goToWechatSettingPage() 40 | } 41 | } 42 | 43 | private fun showSettingsFragment() { 44 | findViewById(R.id.pb_loading).visibility = View.GONE 45 | fab.visibility = View.VISIBLE 46 | fragmentManager.beginTransaction().replace(R.id.setting_fl_container, 47 | SettingsFragment()).commit() 48 | } 49 | 50 | private fun copySharedPrefences() { 51 | val sharedPrefsDir = File(filesDir, "../shared_prefs") 52 | val sharedPrefsFile = File(sharedPrefsDir, Common.MOD_PREFS + ".xml") 53 | val sdSPFile = File(AppCustomConfig.getConfigFile(Common.MOD_PREFS + ".xml")) 54 | if (sharedPrefsFile.exists()) { 55 | val outStream = FileOutputStream(sdSPFile) 56 | FileUtils.copyFile(FileInputStream(sharedPrefsFile), outStream) 57 | } else if (sdSPFile.exists()) { // restore sharedPrefsFile 58 | sharedPrefsFile.parentFile.mkdirs() 59 | val input = FileInputStream(sdSPFile) 60 | val outStream = FileOutputStream(sharedPrefsFile) 61 | FileUtils.copyFile(input, outStream) 62 | } 63 | } 64 | 65 | private fun goToWechatSettingPage() { 66 | Toast.makeText(this, R.string.msg_kill_wechat, Toast.LENGTH_SHORT).show() 67 | val intent = Intent(Settings.ACTION_DATA_ROAMING_SETTINGS) 68 | intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS 69 | intent.data = Uri.fromParts("package", Common.WECHAT_PACKAGENAME, null) 70 | startActivity(intent) 71 | } 72 | 73 | private fun copyConfig() { 74 | thread { 75 | FileUtils.copyAssets(this, Common.APP_DIR_PATH, Common.CONFIG_WECHAT_DIR) 76 | FileUtils.copyAssets(this, Common.APP_DIR_PATH, Common.CONFIG_VIEW_DIR) 77 | FileUtils.copyAssets(this, Common.APP_DIR_PATH, Common.ICON_DIR) 78 | copySharedPrefences() 79 | Handler(Looper.getMainLooper()).post { 80 | showSettingsFragment() 81 | } 82 | } 83 | } 84 | 85 | private val REQUEST_EXTERNAL_STORAGE = 1 86 | private val PERMISSIONS_STORAGE = arrayOf("android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE") 87 | fun verifyStoragePermissions(activity: Activity) { 88 | try { 89 | val permission = ActivityCompat.checkSelfPermission(activity, 90 | "android.permission.WRITE_EXTERNAL_STORAGE") 91 | if (permission != PackageManager.PERMISSION_GRANTED) { 92 | ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE) 93 | } else { 94 | copyConfig() 95 | } 96 | } catch (e: Exception) { 97 | e.printStackTrace() 98 | } 99 | } 100 | 101 | override fun onRequestPermissionsResult(requestCode: Int, 102 | permissions: Array, grantResults: IntArray) { 103 | if (requestCode == REQUEST_EXTERNAL_STORAGE) { 104 | if (grantResults.isNotEmpty() 105 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 106 | copyConfig() 107 | } else { 108 | Toast.makeText(this, R.string.msg_permission_fail, Toast.LENGTH_LONG).show() 109 | finish() 110 | } 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /app/src/main/java/com/blanke/mdwechat/WeChatHelper.kt: -------------------------------------------------------------------------------- 1 | package com.blanke.mdwechat 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.content.res.Resources 6 | import android.graphics.Bitmap 7 | import android.graphics.Color 8 | import android.graphics.drawable.ColorDrawable 9 | import android.graphics.drawable.Drawable 10 | import android.graphics.drawable.StateListDrawable 11 | import com.blanke.mdwechat.config.AppCustomConfig 12 | import com.blanke.mdwechat.config.HookConfig 13 | import com.blanke.mdwechat.util.ColorUtils 14 | import com.blanke.mdwechat.util.DrawableUtils 15 | import de.robv.android.xposed.XSharedPreferences 16 | import java.io.File 17 | 18 | object WeChatHelper { 19 | lateinit var XMOD_PREFS: XSharedPreferences 20 | 21 | val colorPrimary: Int by lazy { 22 | HookConfig.get_color_primary 23 | } 24 | 25 | val transparentDrawable: ColorDrawable 26 | get() = ColorDrawable(Color.TRANSPARENT) 27 | 28 | val whiteDrawable: ColorDrawable 29 | get() = ColorDrawable(Color.parseColor("#FAFAFA")) 30 | 31 | val darkDrawable: ColorDrawable 32 | get() = ColorDrawable(Color.parseColor("#303030")) 33 | 34 | val colorPrimaryDrawable: ColorDrawable 35 | get() { 36 | val colorDrawable = ColorDrawable() 37 | colorDrawable.color = colorPrimary 38 | return colorDrawable 39 | } 40 | 41 | val defaultImageRippleDrawable: Drawable? 42 | get() { 43 | val context = Objects.Main.LauncherUI.get() ?: return null 44 | val attrs = intArrayOf(android.R.attr.selectableItemBackground) 45 | val ta = context.obtainStyledAttributes(attrs) 46 | val imageRippleDrawable = ta.getDrawable(0) 47 | ta.recycle() 48 | return imageRippleDrawable!! 49 | } 50 | 51 | val defaultImageRippleBorderDrawable: Drawable? 52 | get() { 53 | val context = Objects.Main.LauncherUI.get() ?: return null 54 | val attrs = intArrayOf(android.R.attr.selectableItemBackgroundBorderless) 55 | val ta = context.obtainStyledAttributes(attrs) 56 | val imageRippleDrawable = ta.getDrawable(0) 57 | ta.recycle() 58 | return imageRippleDrawable!! 59 | } 60 | 61 | fun getLeftBubble(resources: Resources, 62 | isTint: Boolean = HookConfig.is_hook_bubble_tint, 63 | tintColor: Int = HookConfig.get_hook_bubble_tint_left): Drawable? { 64 | val bubble = AppCustomConfig.getBubbleLeftIcon() ?: return null 65 | return getBubble(bubble, resources, isTint, tintColor) 66 | } 67 | 68 | fun getRightBubble(resources: Resources, 69 | isTint: Boolean = HookConfig.is_hook_bubble_tint, 70 | tintColor: Int = HookConfig.get_hook_bubble_tint_right): Drawable? { 71 | val bubble = AppCustomConfig.getBubbleRightIcon() ?: return null 72 | return getBubble(bubble, resources, isTint, tintColor) 73 | } 74 | 75 | private fun getBubble(sourceBitmap: Bitmap, 76 | resources: Resources, 77 | isTint: Boolean = HookConfig.is_hook_bubble_tint, 78 | tintColor: Int = HookConfig.get_hook_bubble_tint_right): Drawable? { 79 | val bubbleDrawable = DrawableUtils.getNineDrawable(resources, sourceBitmap) 80 | val drawable = StateListDrawable() 81 | val pressBubbleDrawable = bubbleDrawable.constantState!!.newDrawable().mutate() 82 | if (isTint) { 83 | bubbleDrawable.setTint(tintColor) 84 | pressBubbleDrawable.setTint(getDarkColor(tintColor)) 85 | } else { 86 | pressBubbleDrawable.setTint(getDarkColor(Color.WHITE)) 87 | } 88 | drawable.addState(intArrayOf(android.R.attr.state_pressed), pressBubbleDrawable) 89 | drawable.addState(intArrayOf(android.R.attr.state_focused), pressBubbleDrawable) 90 | drawable.addState(intArrayOf(), bubbleDrawable) 91 | return drawable 92 | } 93 | 94 | fun startActivity(actName: String) { 95 | val context = Objects.Main.LauncherUI.get() ?: return 96 | val intent = Intent() 97 | intent.setClassName(context as Context, actName) 98 | context.startActivity(intent) 99 | } 100 | 101 | fun initPrefs() { 102 | XMOD_PREFS = XSharedPreferences(File(AppCustomConfig.getConfigFile(Common.MOD_PREFS + ".xml"))) 103 | XMOD_PREFS.makeWorldReadable() 104 | XMOD_PREFS.reload() 105 | } 106 | 107 | fun reloadPrefs() { 108 | XMOD_PREFS.reload() 109 | } 110 | 111 | private fun getDarkColor(color: Int): Int { 112 | return ColorUtils.getDarkerColor(color, 0.8F) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /ColorPicker/src/main/java/com/jaredrummler/android/colorpicker/ColorPaletteAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Jared Rummler 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jaredrummler.android.colorpicker; 18 | 19 | import android.content.Context; 20 | import android.graphics.Color; 21 | import android.graphics.PorterDuff; 22 | import android.support.v4.graphics.ColorUtils; 23 | import android.view.View; 24 | import android.view.ViewGroup; 25 | import android.widget.BaseAdapter; 26 | import android.widget.ImageView; 27 | 28 | class ColorPaletteAdapter extends BaseAdapter { 29 | 30 | /*package*/ final OnColorSelectedListener listener; 31 | /*package*/ final int[] colors; 32 | /*package*/ int selectedPosition; 33 | /*package*/ int colorShape; 34 | 35 | ColorPaletteAdapter(OnColorSelectedListener listener, 36 | int[] colors, 37 | int selectedPosition, 38 | @ColorShape int colorShape) { 39 | this.listener = listener; 40 | this.colors = colors; 41 | this.selectedPosition = selectedPosition; 42 | this.colorShape = colorShape; 43 | } 44 | 45 | @Override public int getCount() { 46 | return colors.length; 47 | } 48 | 49 | @Override public Object getItem(int position) { 50 | return colors[position]; 51 | } 52 | 53 | @Override public long getItemId(int position) { 54 | return position; 55 | } 56 | 57 | @Override public View getView(int position, View convertView, ViewGroup parent) { 58 | final ViewHolder holder; 59 | if (convertView == null) { 60 | holder = new ViewHolder(parent.getContext()); 61 | convertView = holder.view; 62 | } else { 63 | holder = (ViewHolder) convertView.getTag(); 64 | } 65 | holder.setup(position); 66 | return convertView; 67 | } 68 | 69 | void selectNone() { 70 | selectedPosition = -1; 71 | notifyDataSetChanged(); 72 | } 73 | 74 | interface OnColorSelectedListener { 75 | 76 | void onColorSelected(int color); 77 | } 78 | 79 | private final class ViewHolder { 80 | 81 | View view; 82 | ColorPanelView colorPanelView; 83 | ImageView imageView; 84 | int originalBorderColor; 85 | 86 | ViewHolder(Context context) { 87 | int layoutResId; 88 | if (colorShape == ColorShape.SQUARE) { 89 | layoutResId = R.layout.cpv_color_item_square; 90 | } else { 91 | layoutResId = R.layout.cpv_color_item_circle; 92 | } 93 | view = View.inflate(context, layoutResId, null); 94 | colorPanelView = (ColorPanelView) view.findViewById(R.id.cpv_color_panel_view); 95 | imageView = (ImageView) view.findViewById(R.id.cpv_color_image_view); 96 | originalBorderColor = colorPanelView.getBorderColor(); 97 | view.setTag(this); 98 | } 99 | 100 | void setup(int position) { 101 | int color = colors[position]; 102 | int alpha = Color.alpha(color); 103 | colorPanelView.setColor(color); 104 | imageView.setImageResource(selectedPosition == position ? R.drawable.cpv_preset_checked : 0); 105 | if (alpha != 255) { 106 | if (alpha <= ColorPickerDialog.ALPHA_THRESHOLD) { 107 | colorPanelView.setBorderColor(color | 0xFF000000); 108 | imageView.setColorFilter(/*color | 0xFF000000*/Color.BLACK, PorterDuff.Mode.SRC_IN); 109 | } else { 110 | colorPanelView.setBorderColor(originalBorderColor); 111 | imageView.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN); 112 | } 113 | } else { 114 | setColorFilter(position); 115 | } 116 | setOnClickListener(position); 117 | } 118 | 119 | private void setOnClickListener(final int position) { 120 | colorPanelView.setOnClickListener(new View.OnClickListener() { 121 | @Override public void onClick(View v) { 122 | if (selectedPosition != position) { 123 | selectedPosition = position; 124 | notifyDataSetChanged(); 125 | } 126 | listener.onColorSelected(colors[position]); 127 | } 128 | }); 129 | colorPanelView.setOnLongClickListener(new View.OnLongClickListener() { 130 | @Override public boolean onLongClick(View v) { 131 | colorPanelView.showHint(); 132 | return true; 133 | } 134 | }); 135 | } 136 | 137 | private void setColorFilter(int position) { 138 | if (position == selectedPosition && ColorUtils.calculateLuminance(colors[position]) >= 0.65) { 139 | imageView.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN); 140 | } else { 141 | imageView.setColorFilter(null); 142 | } 143 | } 144 | 145 | } 146 | 147 | } -------------------------------------------------------------------------------- /tablayout-lib/src/main/java/com/flyco/tablayout/widget/MsgView.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Color; 6 | import android.graphics.drawable.GradientDrawable; 7 | import android.graphics.drawable.StateListDrawable; 8 | import android.os.Build; 9 | import android.util.AttributeSet; 10 | import android.widget.TextView; 11 | 12 | import com.flyco.tablayout.R; 13 | 14 | /** 用于需要圆角矩形框背景的TextView的情况,减少直接使用TextView时引入的shape资源文件 */ 15 | public class MsgView extends TextView { 16 | private Context context; 17 | private GradientDrawable gd_background = new GradientDrawable(); 18 | private int backgroundColor; 19 | private int cornerRadius; 20 | private int strokeWidth; 21 | private int strokeColor; 22 | private boolean isRadiusHalfHeight; 23 | private boolean isWidthHeightEqual; 24 | 25 | public MsgView(Context context) { 26 | this(context, null); 27 | } 28 | 29 | public MsgView(Context context, AttributeSet attrs) { 30 | this(context, attrs, 0); 31 | } 32 | 33 | public MsgView(Context context, AttributeSet attrs, int defStyleAttr) { 34 | super(context, attrs, defStyleAttr); 35 | this.context = context; 36 | obtainAttributes(context, attrs); 37 | } 38 | 39 | private void obtainAttributes(Context context, AttributeSet attrs) { 40 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MsgView); 41 | backgroundColor = ta.getColor(R.styleable.MsgView_mv_backgroundColor, Color.TRANSPARENT); 42 | cornerRadius = ta.getDimensionPixelSize(R.styleable.MsgView_mv_cornerRadius, 0); 43 | strokeWidth = ta.getDimensionPixelSize(R.styleable.MsgView_mv_strokeWidth, 0); 44 | strokeColor = ta.getColor(R.styleable.MsgView_mv_strokeColor, Color.TRANSPARENT); 45 | isRadiusHalfHeight = ta.getBoolean(R.styleable.MsgView_mv_isRadiusHalfHeight, false); 46 | isWidthHeightEqual = ta.getBoolean(R.styleable.MsgView_mv_isWidthHeightEqual, false); 47 | 48 | ta.recycle(); 49 | } 50 | 51 | @Override 52 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 53 | if (isWidthHeightEqual() && getWidth() > 0 && getHeight() > 0) { 54 | int max = Math.max(getWidth(), getHeight()); 55 | int measureSpec = MeasureSpec.makeMeasureSpec(max, MeasureSpec.EXACTLY); 56 | super.onMeasure(measureSpec, measureSpec); 57 | return; 58 | } 59 | 60 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 61 | } 62 | 63 | @Override 64 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 65 | super.onLayout(changed, left, top, right, bottom); 66 | if (isRadiusHalfHeight()) { 67 | setCornerRadius(getHeight() / 2); 68 | } else { 69 | setBgSelector(); 70 | } 71 | } 72 | 73 | 74 | public void setBackgroundColor(int backgroundColor) { 75 | this.backgroundColor = backgroundColor; 76 | setBgSelector(); 77 | } 78 | 79 | public void setCornerRadius(int cornerRadius) { 80 | this.cornerRadius = dp2px(cornerRadius); 81 | setBgSelector(); 82 | } 83 | 84 | public void setStrokeWidth(int strokeWidth) { 85 | this.strokeWidth = dp2px(strokeWidth); 86 | setBgSelector(); 87 | } 88 | 89 | public void setStrokeColor(int strokeColor) { 90 | this.strokeColor = strokeColor; 91 | setBgSelector(); 92 | } 93 | 94 | public void setIsRadiusHalfHeight(boolean isRadiusHalfHeight) { 95 | this.isRadiusHalfHeight = isRadiusHalfHeight; 96 | setBgSelector(); 97 | } 98 | 99 | public void setIsWidthHeightEqual(boolean isWidthHeightEqual) { 100 | this.isWidthHeightEqual = isWidthHeightEqual; 101 | setBgSelector(); 102 | } 103 | 104 | public int getBackgroundColor() { 105 | return backgroundColor; 106 | } 107 | 108 | public int getCornerRadius() { 109 | return cornerRadius; 110 | } 111 | 112 | public int getStrokeWidth() { 113 | return strokeWidth; 114 | } 115 | 116 | public int getStrokeColor() { 117 | return strokeColor; 118 | } 119 | 120 | public boolean isRadiusHalfHeight() { 121 | return isRadiusHalfHeight; 122 | } 123 | 124 | public boolean isWidthHeightEqual() { 125 | return isWidthHeightEqual; 126 | } 127 | 128 | protected int dp2px(float dp) { 129 | final float scale = context.getResources().getDisplayMetrics().density; 130 | return (int) (dp * scale + 0.5f); 131 | } 132 | 133 | protected int sp2px(float sp) { 134 | final float scale = this.context.getResources().getDisplayMetrics().scaledDensity; 135 | return (int) (sp * scale + 0.5f); 136 | } 137 | 138 | private void setDrawable(GradientDrawable gd, int color, int strokeColor) { 139 | gd.setColor(color); 140 | gd.setCornerRadius(cornerRadius); 141 | gd.setStroke(strokeWidth, strokeColor); 142 | } 143 | 144 | public void setBgSelector() { 145 | StateListDrawable bg = new StateListDrawable(); 146 | 147 | setDrawable(gd_background, backgroundColor, strokeColor); 148 | bg.addState(new int[]{-android.R.attr.state_pressed}, gd_background); 149 | 150 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {//16 151 | setBackground(bg); 152 | } else { 153 | //noinspection deprecation 154 | setBackgroundDrawable(bg); 155 | } 156 | } 157 | } --------------------------------------------------------------------------------