From 16f3083b5d6c10a7fbf20ca6e8f17489bea351ae Mon Sep 17 00:00:00 2001 From: GangJust Date: Tue, 30 Apr 2024 14:53:44 +0800 Subject: [PATCH] optimize compatibility --- app/build.gradle | 4 +- app/src/main/assets/update.txt | 10 + app/src/main/assets/version | 2 +- .../android/ugc/aweme/feed/model/Aweme.java | 2 + .../ugc/aweme/feed/model/VideoItemParams.java | 4 + .../ugc/aweme/live/LivePlayActivity.kt | 7 + .../android/ugc/aweme/profile/model/User.java | 5 +- .../io/github/fplus/core/base/BaseHook.kt | 30 +- .../io/github/fplus/core/config/ConfigV1.kt | 76 +- .../github/fplus/core/helper/DexkitBuilder.kt | 828 ++++++++++-------- .../fplus/core/helper/TimerExitHelper.kt | 141 +++ .../io/github/fplus/core/hook/DouYinMain.kt | 105 +-- .../io/github/fplus/core/hook/HActivity.kt | 31 +- .../fplus/core/hook/HChatListRecalledHint.kt | 4 +- .../github/fplus/core/hook/HCrashTolerance.kt | 86 ++ .../fplus/core/hook/HCustomizedUISeekBar.kt | 53 -- .../github/fplus/core/hook/HDetailActivity.kt | 19 +- .../fplus/core/hook/HDetailPageFragment.kt | 2 +- .../java/io/github/fplus/core/hook/HDialog.kt | 8 +- .../hook/HDisallowInterceptRelativeLayout.kt | 29 +- .../fplus/core/hook/HEmojiDetailDialog.kt | 4 +- .../fplus/core/hook/HEmojiDetailDialogNew.kt | 2 +- .../fplus/core/hook/HEmojiPopupWindow.kt | 4 +- .../fplus/core/hook/HFeedAvatarPresenter.kt | 80 ++ .../core/hook/HGifEmojiDetailActivity.kt | 2 +- .../core/hook/HHomeBottomTabServiceImpl.kt | 62 ++ .../fplus/core/hook/HInteractStickerParent.kt | 10 +- .../fplus/core/hook/HLandscapeFeedActivity.kt | 9 +- .../fplus/core/hook/HLivePlayActivity.kt | 7 +- .../fplus/core/hook/HLongPressLayout.kt | 386 ++++++++ .../github/fplus/core/hook/HMainActivity.kt | 96 +- ...ottomTabItem.kt => HMainBottomPhotoTab.kt} | 22 +- .../core/hook/HMainTabStripScrollView.kt | 8 +- .../hook/HPenetrateTouchRelativeLayout.kt | 11 +- .../fplus/core/hook/HPlayerController.kt | 38 +- .../fplus/core/hook/HPoiCreateInstanceImpl.kt | 43 - .../fplus/core/hook/HVerticalViewPager.kt | 98 ++- .../fplus/core/hook/HVideoPlayerHelper.kt | 1 + .../fplus/core/hook/HVideoViewHolder.kt | 106 ++- .../core/hook/HVideoViewHolderRootView.kt | 1 + .../github/fplus/core/hook/logic/AwemeExt.kt | 33 + .../fplus/core/hook/logic/DownloadLogic.kt | 61 +- .../fplus/core/hook/logic/SaveAudioLogic.kt | 2 +- .../fplus/core/hook/logic/SaveCommentLogic.kt | 8 +- .../fplus/core/hook/logic/SaveEmojiLogic.kt | 4 +- .../ui/activity/FreedomSettingActivity.kt | 292 +++++- .../core/ui/viewmodel/FreedomSettingVM.kt | 196 +++-- versions.json | 2 +- xpler | 2 +- 49 files changed, 2102 insertions(+), 934 deletions(-) create mode 100644 aweme/src/main/java/com/ss/android/ugc/aweme/feed/model/VideoItemParams.java create mode 100644 aweme/src/main/java/com/ss/android/ugc/aweme/live/LivePlayActivity.kt create mode 100644 core/src/main/java/io/github/fplus/core/helper/TimerExitHelper.kt create mode 100644 core/src/main/java/io/github/fplus/core/hook/HCrashTolerance.kt delete mode 100644 core/src/main/java/io/github/fplus/core/hook/HCustomizedUISeekBar.kt create mode 100644 core/src/main/java/io/github/fplus/core/hook/HFeedAvatarPresenter.kt create mode 100644 core/src/main/java/io/github/fplus/core/hook/HHomeBottomTabServiceImpl.kt create mode 100644 core/src/main/java/io/github/fplus/core/hook/HLongPressLayout.kt rename core/src/main/java/io/github/fplus/core/hook/{HMainBottomTabItem.kt => HMainBottomPhotoTab.kt} (74%) delete mode 100644 core/src/main/java/io/github/fplus/core/hook/HPoiCreateInstanceImpl.kt create mode 100644 core/src/main/java/io/github/fplus/core/hook/logic/AwemeExt.kt diff --git a/app/build.gradle b/app/build.gradle index c23fc0f..658ddc0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "io.github.fplus" minSdk 24 targetSdk 33 - versionCode 122 - versionName "1.2.8" + versionCode 123 + versionName "1.2.9" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/assets/update.txt b/app/src/main/assets/update.txt index 5c0bb4a..550e398 100644 --- a/app/src/main/assets/update.txt +++ b/app/src/main/assets/update.txt @@ -1,3 +1,13 @@ +v1.2.9 +增加底部选项自定义关键字隐藏 +增加部分误触复确认弹窗提示 +增加崩溃拦截容错处理选项 +调整获取下载链接为随机下标 +调整空闲退出在直播间时暂停计时 +修复清爽模式长按偶弹官方菜单问题 +移除双击视频暂停选项 +调整部分逻辑(可能需要重新设置模块) + v1.2.8 修复定时、空闲后台重复退出问题 移除启动时底部提示 diff --git a/app/src/main/assets/version b/app/src/main/assets/version index 23de760..e930c33 100644 --- a/app/src/main/assets/version +++ b/app/src/main/assets/version @@ -1 +1 @@ -1.2.8-122 \ No newline at end of file +1.2.9-123 \ No newline at end of file diff --git a/aweme/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java b/aweme/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java index 064d9af..019e8fe 100644 --- a/aweme/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java +++ b/aweme/src/main/java/com/ss/android/ugc/aweme/feed/model/Aweme.java @@ -41,6 +41,8 @@ public abstract class Aweme { public List images; + public String shareUrl; + public Aweme() { throw new RuntimeException("stub!!"); } diff --git a/aweme/src/main/java/com/ss/android/ugc/aweme/feed/model/VideoItemParams.java b/aweme/src/main/java/com/ss/android/ugc/aweme/feed/model/VideoItemParams.java new file mode 100644 index 0000000..e1317a5 --- /dev/null +++ b/aweme/src/main/java/com/ss/android/ugc/aweme/feed/model/VideoItemParams.java @@ -0,0 +1,4 @@ +package com.ss.android.ugc.aweme.feed.model; + +public class VideoItemParams { +} diff --git a/aweme/src/main/java/com/ss/android/ugc/aweme/live/LivePlayActivity.kt b/aweme/src/main/java/com/ss/android/ugc/aweme/live/LivePlayActivity.kt new file mode 100644 index 0000000..82d1a04 --- /dev/null +++ b/aweme/src/main/java/com/ss/android/ugc/aweme/live/LivePlayActivity.kt @@ -0,0 +1,7 @@ +package com.ss.android.ugc.aweme.live + +import android.app.Activity + +class LivePlayActivity : Activity() { + +} \ No newline at end of file diff --git a/aweme/src/main/java/com/ss/android/ugc/aweme/profile/model/User.java b/aweme/src/main/java/com/ss/android/ugc/aweme/profile/model/User.java index 14ca097..64acc72 100644 --- a/aweme/src/main/java/com/ss/android/ugc/aweme/profile/model/User.java +++ b/aweme/src/main/java/com/ss/android/ugc/aweme/profile/model/User.java @@ -73,5 +73,8 @@ public class User { public String uid; - + //1: 已关注, 0: 未关注 + public int getFollowStatus() { + throw new RuntimeException("Stub!"); + } } diff --git a/core/src/main/java/io/github/fplus/core/base/BaseHook.kt b/core/src/main/java/io/github/fplus/core/base/BaseHook.kt index ca5a2de..ca8927a 100644 --- a/core/src/main/java/io/github/fplus/core/base/BaseHook.kt +++ b/core/src/main/java/io/github/fplus/core/base/BaseHook.kt @@ -128,15 +128,20 @@ abstract class BaseHook : HookEntity() { } @Synchronized - fun showDialog(view: View) { + fun showDialog( + view: View, + onDismiss: () -> Unit = {}, + ) { kDialog = if (kDialog == null) KDialog() else kDialog kDialog!!.setView(view) kDialog!!.show() + kDialog!!.setOnDismissListener { onDismiss.invoke() } } @Synchronized fun showComposeDialog( context: Context, + onDismiss: () -> Unit = {}, content: @Composable (closeHandler: () -> Unit) -> Unit ) { XplerDialogWrapper(context).apply { @@ -145,6 +150,9 @@ abstract class BaseHook : HookEntity() { content.invoke(this::dismiss) } } + setOnDismissListener { + onDismiss.invoke() + } }.show() } @@ -157,6 +165,7 @@ abstract class BaseHook : HookEntity() { singleButton: Boolean = false, // 只会响应 onConfirm 方法 onConfirm: () -> Unit = {}, onCancel: () -> Unit = {}, + onDismiss: () -> Unit = {}, ) { val isDarkMode = context.isDarkMode val dialogView = context.inflateModuleView(R.layout.dialog_message_layout) @@ -202,7 +211,10 @@ abstract class BaseHook : HookEntity() { onConfirm.invoke() } - showDialog(binding.root) + showDialog( + view = binding.root, + onDismiss = onDismiss, + ) } fun showProgressDialog( @@ -210,6 +222,7 @@ abstract class BaseHook : HookEntity() { title: CharSequence, progress: Int = 0, listener: (dialog: KDialog, progress: ProgressDialogNotification) -> Unit, + onDismiss: () -> Unit = {}, ) { val isDarkMode = context.isDarkMode val dialogView = KtXposedHelpers.inflateView(context, R.layout.dialog_progress_layout) @@ -232,7 +245,10 @@ abstract class BaseHook : HookEntity() { ) ) - showDialog(binding.root) + showDialog( + view = binding.root, + onDismiss = onDismiss + ) } fun showInputChoiceDialog( @@ -247,6 +263,7 @@ abstract class BaseHook : HookEntity() { cancel: CharSequence = "取消", onChoice: (view: View, input1: String, input2: String, item: CharSequence, position: Int) -> Unit, onCancel: () -> Unit = {}, + onDismiss: () -> Unit = {}, ) { val isDarkMode = context.isDarkMode val dialogView = KtXposedHelpers.inflateView(context, R.layout.dialog_input_choice_layout) @@ -315,7 +332,10 @@ abstract class BaseHook : HookEntity() { ) } - showDialog(binding.root) + showDialog( + view = binding.root, + onDismiss = onDismiss + ) } fun showChoiceDialog( @@ -371,7 +391,6 @@ abstract class BaseHook : HookEntity() { showDialog(binding.root) } - private fun getHintTextColor(isDarkMode: Boolean): Int { return if (isDarkMode) { Color.parseColor("#FFAAAAAA") @@ -396,7 +415,6 @@ abstract class BaseHook : HookEntity() { } } - fun showNotification( context: Context, notifyId: Int, diff --git a/core/src/main/java/io/github/fplus/core/config/ConfigV1.kt b/core/src/main/java/io/github/fplus/core/config/ConfigV1.kt index c520ef1..61f763b 100644 --- a/core/src/main/java/io/github/fplus/core/config/ConfigV1.kt +++ b/core/src/main/java/io/github/fplus/core/config/ConfigV1.kt @@ -59,35 +59,35 @@ class ConfigV1 private constructor() { } /// 按视频创作者单独创建文件夹 - var isOwnerDir: Boolean = false + var ownerDir: Boolean = false get() { - field = mmkv.getBoolean("isOwnerDir", false) + field = mmkv.getBoolean("ownerDir", false) return field } set(value) { - mmkv.putBoolean("isOwnerDir", value) + mmkv.putBoolean("ownerDir", value) field = value } /// 通知栏下载 - var isNotification: Boolean = false + var notificationDownload: Boolean = false get() { - field = mmkv.getBoolean("isNotification", false) + field = mmkv.getBoolean("notificationDownload", false) return field } set(value) { - mmkv.putBoolean("isNotification", value) + mmkv.putBoolean("notificationDownload", value) field = value } /// 复制链接时弹出下载 - var isCopyDownload: Boolean = false + var copyLinkDownload: Boolean = false get() { - field = mmkv.getBoolean("isCopyDownload", false) + field = mmkv.getBoolean("copyLinkDownload", false) return field } set(value) { - mmkv.putBoolean("isCopyDownload", value) + mmkv.putBoolean("copyLinkDownload", value) field = value } @@ -103,24 +103,24 @@ class ConfigV1 private constructor() { } /// 表情包/评论区视频、图片保存 - var isEmoji: Boolean = false + var isEmojiDownload: Boolean = false get() { - field = mmkv.getBoolean("isEmoji", false) + field = mmkv.getBoolean("isEmojiDownload", false) return field } set(value) { - mmkv.putBoolean("isEmoji", value) + mmkv.putBoolean("isEmojiDownload", value) field = value } /// 震动反馈 - var isVibrate: Boolean = false + var vibrate: Boolean = false get() { - field = mmkv.getBoolean("isVibrate", false) + field = mmkv.getBoolean("vibrate", false) return field } set(value) { - mmkv.putBoolean("isVibrate", value) + mmkv.putBoolean("vibrate", value) field = value } @@ -164,7 +164,7 @@ class ConfigV1 private constructor() { field = value } - /// 双击响应类型: 0=暂停视频, 1=打开评论, 2=点赞视频 + /// 双击响应类型: 1=打开评论, 2=点赞视频 var doubleClickType: Int = 2 get() { field = mmkv.getInt("doubleClickType", 2) @@ -208,6 +208,28 @@ class ConfigV1 private constructor() { field = value } + /// 隐藏底部tab + var isHideBottomTab: Boolean = false + get() { + field = mmkv.getBoolean("isHideBottomTab", false) + return field + } + set(value) { + mmkv.putBoolean("isHideBottomTab", value) + field = value + } + + /// 隐藏底部tab包含的关键字, 逗号隔开 + var hideBottomTabKeywords: String = "商城, 朋友, 消息" + get() { + field = mmkv.getString("hideBottomTabKeywords", "商城, 朋友, 消息")!! + return field + } + set(value) { + mmkv.putString("hideBottomTabKeywords", value) + field = value + } + /// 隐藏底部加号按钮 var isHidePhotoButton: Boolean = false get() { @@ -230,6 +252,17 @@ class ConfigV1 private constructor() { field = value } + /// 手势误触复确认 + var isPreventAccidentalTouch: Boolean = false + get() { + field = mmkv.getBoolean("isPreventAccidentalTouch", false) + return field + } + set(value) { + mmkv.putBoolean("isPreventAccidentalTouch", value) + field = value + } + /// 视频右侧控件栏 var isVideoOptionBarFilter: Boolean = false get() { @@ -525,6 +558,17 @@ class ConfigV1 private constructor() { field = value } + /// 崩溃容错 + var isCrashTolerance: Boolean = false + get() { + field = mmkv.getBoolean("isCrashTolerance", false) + return field + } + set(value) { + mmkv.putBoolean("isCrashTolerance", value) + field = value + } + /// 去插件化 var isDisablePlugin: Boolean = false get() { diff --git a/core/src/main/java/io/github/fplus/core/helper/DexkitBuilder.kt b/core/src/main/java/io/github/fplus/core/helper/DexkitBuilder.kt index 9ed4185..6373e45 100644 --- a/core/src/main/java/io/github/fplus/core/helper/DexkitBuilder.kt +++ b/core/src/main/java/io/github/fplus/core/helper/DexkitBuilder.kt @@ -11,12 +11,12 @@ import com.freegang.ktutils.text.KTextUtils import io.github.fplus.core.config.ConfigV1 import io.github.xpler.core.findClass import io.github.xpler.core.findMethod -import io.github.xpler.core.log.XplerLog import io.github.xpler.core.lpparam import org.json.JSONArray import org.json.JSONObject import org.luckypray.dexkit.DexKitBridge import org.luckypray.dexkit.query.enums.StringMatchType +import org.luckypray.dexkit.result.ClassData import org.luckypray.dexkit.result.ClassDataList import org.luckypray.dexkit.result.MethodData import org.luckypray.dexkit.result.MethodDataList @@ -36,26 +36,30 @@ object DexkitBuilder { var sideBarNestedScrollViewClazz: Class<*>? = null var cornerExtensionsPopupWindowClazz: Class<*>? = null var mainBottomTabViewClazz: Class<*>? = null - var mainBottomTabItemClazz: Class<*>? = null + var mainBottomPhotoTabClazz: Class<*>? = null var commentListPageFragmentClazz: Class<*>? = null var conversationFragmentClazz: Class<*>? = null var seekBarSpeedModeBottomContainerClazz: Class<*>? = null - var poiCreateInstanceImplClazz: Class<*>? = null var videoPlayerHelperClazz: Class<*>? = null var abstractFeedAdapterClazz: Class<*>? = null var recommendFeedFetchPresenterClazz: Class<*>? = null var fullFeedFollowFetchPresenterClazz: Class<*>? = null var detailPageFragmentClazz: Class<*>? = null - var emojiApiProxyClazz: Class<*>? = null var emojiPopupWindowClazz: Class<*>? = null var bottomCtrlBarClazz: Class<*>? = null var chatListRecyclerViewAdapterClazz: Class<*>? = null var chatListRecalledHintClazz: Class<*>? = null var restartUtilsClazz: Class<*>? = null + var longPressEventClazz: Class<*>? = null + var doubleClickEventClazz: Class<*>? = null - // methods + var videoViewHolderClazz: Class<*>? = null var videoViewHolderMethods: List = listOf() + var feedAvatarPresenterClazz: Class<*>? = null + var livePhotoClazz: Class<*>? = null + var tabLandingClazz: Class<*>? = null + /** * 只是为了解决出现的各种稀奇古怪的情况。 * 部分未作混淆的类在 Dexkit 搜索期间, 会跳过构造方法的勾子(勾不住构造方法), 但是它的普通方法却能勾住。 @@ -102,445 +106,540 @@ object DexkitBuilder { KLogCat.tagI(TAG, "Dexkit开始搜索: ${lpparam.appInfo.sourceDir}") System.loadLibrary("dexkit") DexKitBridge.create(lpparam.appInfo.sourceDir).use { bridge -> - searchClass(bridge) - searchMethod(bridge) - } - } - - /** - * 搜索类 - */ - private fun searchClass(bridge: DexKitBridge) { - sideBarNestedScrollViewClazz = bridge.findClass { - matcher { - className = "com.ss.android.ugc.aweme.sidebar.SideBarNestedScrollView" - } - }.singleInstance("sideBarNestedScrollView") - - cornerExtensionsPopupWindowClazz = bridge.findClass { - matcher { - superClass = "android.widget.PopupWindow" - fields { - add { - type = "android.view.LayoutInflater" - } - add { - type = "android.app.Dialog" - } + val sideBarNestedScrollView = bridge.findClass { + matcher { + className = "com.ss.android.ugc.aweme.sidebar.SideBarNestedScrollView" } - methods { - add { - paramTypes = listOf("android.widget.PopupWindow") - } - add { - paramTypes = listOf("boolean") - } - add { - returnType = "android.view.View" + } + sideBarNestedScrollViewClazz = sideBarNestedScrollView.instance("sideBarNestedScrollView") + + val cornerExtensionsPopupWindow = bridge.findClass { + matcher { + superClass = "android.widget.PopupWindow" + fields { + add { + type = "android.view.LayoutInflater" + } + add { + type = "android.app.Dialog" + } } - add { - name = "dismiss" + methods { + add { + paramTypes = listOf("android.widget.PopupWindow") + } + add { + paramTypes = listOf("boolean") + } + add { + returnType = "android.view.View" + } + add { + name = "dismiss" + } } } } - }.singleInstance("coenerExtendsionsPoupWindow") + cornerExtensionsPopupWindowClazz = cornerExtensionsPopupWindow.instance("coenerExtendsionsPoupWindow") - mainBottomTabItemClazz = bridge.findClass { - matcher { - methods { - add { - name = "getNowImageRes" - } - add { - name = "getOperator" - } - add { - name = "getRefreshTab" - returnType = "android.view.View" + val mainBottomPhotoTab = bridge.findClass { + matcher { + methods { + add { + name = "getNowImageRes" + } + add { + name = "getOperator" + } + add { + name = "getRefreshTab" + returnType = "android.view.View" + } } } } - }.singleInstance("mainBottomTabItem") + mainBottomPhotoTabClazz = mainBottomPhotoTab.instance("mainBottomPhotoTab") - commentListPageFragmentClazz = bridge.findClass { - matcher { - fields { - add { - type = "com.ss.android.ugc.aweme.comment.widget.CommentNestedLayout" - } - add { - type = "com.ss.android.ugc.aweme.comment.param.VideoCommentPageParam" + val commentListPageFragment = bridge.findClass { + matcher { + fields { + add { + type = "com.ss.android.ugc.aweme.comment.widget.CommentNestedLayout" + } + add { + type = "com.ss.android.ugc.aweme.comment.param.VideoCommentPageParam" + } } - } - methods { - add { - returnType = "com.ss.android.ugc.aweme.comment.constants.CommentColorMode" + methods { + add { + returnType = "com.ss.android.ugc.aweme.comment.constants.CommentColorMode" + } } - } - usingStrings = listOf( - "com/ss/android/ugc/aweme/comment/ui/CommentListPageFragment", - "CommentListPageFragment", - ) + usingStrings = listOf( + "com/ss/android/ugc/aweme/comment/ui/CommentListPageFragment", + "CommentListPageFragment", + ) + } } - }.singleInstance("commentListPageFragment") + commentListPageFragmentClazz = commentListPageFragment.instance("commentListPageFragment") - conversationFragmentClazz = bridge.findClass { - matcher { - fields { - add { - type = "com.ss.android.ugc.aweme.conversation.CommentConversationLayout" - } - add { - type = "com.ss.android.ugc.aweme.comment.widget.CommentNestedLayout" + val conversationFragment = bridge.findClass { + matcher { + fields { + add { + type = "com.ss.android.ugc.aweme.conversation.CommentConversationLayout" + } + add { + type = "com.ss.android.ugc.aweme.comment.widget.CommentNestedLayout" + } } - } - usingStrings = listOf( - "com/ss/android/ugc/aweme/comment/ui/ConversationFragment", - "ConversationFragment", - ) + usingStrings = listOf( + "com/ss/android/ugc/aweme/comment/ui/ConversationFragment", + "ConversationFragment", + ) + } } - }.singleInstance("conversationFragment") + conversationFragmentClazz = conversationFragment.instance("conversationFragment") - abstractFeedAdapterClazz = bridge.findClass { - matcher { - fields { - add { - type = "android.view.LayoutInflater" - } - add { - type = "com.ss.android.ugc.aweme.feed.model.BaseFeedPageParams" + val abstractFeedAdapter = bridge.findClass { + matcher { + fields { + add { + type = "android.view.LayoutInflater" + } + add { + type = "com.ss.android.ugc.aweme.feed.model.BaseFeedPageParams" + } } - } - methods { - add { - name = "getItemPosition" - } - add { - name = "finishUpdate" + methods { + add { + name = "getItemPosition" + } + add { + name = "finishUpdate" + } } - } - usingStrings { - add("AbstractFeedAdapter aweme.aid = ") + usingStrings { + add("AbstractFeedAdapter aweme.aid = ") + } } } - }.singleInstance("abstractFeedAdapter") + abstractFeedAdapterClazz = abstractFeedAdapter.instance("abstractFeedAdapter") - recommendFeedFetchPresenterClazz = bridge.findClass { - matcher { - methods { - add { - name = "onSuccess" + val recommendFeedFetchPresenter = bridge.findClass { + matcher { + methods { + add { + name = "onSuccess" + } } + addUsingString("com.ss.android.ugc.aweme.feed.presenter.RecommendFeedFetchPresenter") + addUsingString("enter_from") + addUsingString("homepage_hot") } - addUsingString("com.ss.android.ugc.aweme.feed.presenter.RecommendFeedFetchPresenter") - addUsingString("enter_from") - addUsingString("homepage_hot") } - }.singleInstance("recommendFeedFetchPresenter") - - fullFeedFollowFetchPresenterClazz = bridge.findClass { - matcher { - methods { - add { - name = "onSuccess" + recommendFeedFetchPresenterClazz = + recommendFeedFetchPresenter.instance("recommendFeedFetchPresenter") + + val fullFeedFollowFetchPresenter = bridge.findClass { + matcher { + methods { + add { + name = "onSuccess" + } } + addUsingString("com.ss.android.ugc.aweme.feed.presenter.FullFeedFollowFetchPresenter") + addUsingString("enter_from") + addUsingString("homepage_follow") } - addUsingString("com.ss.android.ugc.aweme.feed.presenter.FullFeedFollowFetchPresenter") - addUsingString("enter_from") - addUsingString("homepage_follow") } - }.singleInstance("fullFeedFollowFetchPresenter") - - emojiPopupWindowClazz = bridge.findClass { - matcher { - methods { - add { - modifiers = Modifier.PRIVATE - returnType = "com.ss.android.ugc.aweme.base.ui.RemoteImageView" - } - add { - modifiers = Modifier.PRIVATE - returnType = "com.bytedance.ies.dmt.ui.widget.DmtTextView" - } - add { - modifiers = Modifier.PUBLIC - paramTypes = listOf("android.content.Context") - } - add { - modifiers = Modifier.PRIVATE - paramTypes = listOf("com.ss.android.ugc.aweme.emoji.base.BaseEmoji") - } - add { - modifiers = Modifier.PRIVATE - paramTypes = listOf( - "com.ss.android.ugc.aweme.emoji.base.BaseEmoji", - "com.ss.android.ugc.aweme.base.ui.RemoteImageView", - ) + fullFeedFollowFetchPresenterClazz = + fullFeedFollowFetchPresenter.instance("fullFeedFollowFetchPresenter") + + val emojiPopupWindow = bridge.findClass { + matcher { + methods { + add { + modifiers = Modifier.PRIVATE + returnType = "com.ss.android.ugc.aweme.base.ui.RemoteImageView" + } + add { + modifiers = Modifier.PRIVATE + returnType = "com.bytedance.ies.dmt.ui.widget.DmtTextView" + } + add { + modifiers = Modifier.PUBLIC + paramTypes = listOf("android.content.Context") + } + add { + modifiers = Modifier.PRIVATE + paramTypes = listOf("com.ss.android.ugc.aweme.emoji.base.BaseEmoji") + } + add { + modifiers = Modifier.PRIVATE + paramTypes = listOf( + "com.ss.android.ugc.aweme.emoji.base.BaseEmoji", + "com.ss.android.ugc.aweme.base.ui.RemoteImageView", + ) + } } } } - }.singleInstance("emojiPopupWindow") - - seekBarSpeedModeBottomContainerClazz = bridge.findClass { - // findFirst = true - matcher { - methods { - add { - name = "getMSpeedText" - returnType = "android.widget.TextView" - } - add { - name = "getMBottomLayout" - returnType = "android.view.View" - } - add { - name = "getLoadingProgressBar" - returnType = "com.ss.android.ugc.aweme.feed.widget.LineProgressBar" + emojiPopupWindowClazz = emojiPopupWindow.instance("emojiPopupWindow") + + val seekBarSpeedModeBottomContainer = bridge.findClass { + // findFirst = true + matcher { + methods { + add { + name = "getMSpeedText" + returnType = "android.widget.TextView" + } + add { + name = "getMBottomLayout" + returnType = "android.view.View" + } + add { + name = "getLoadingProgressBar" + returnType = "com.ss.android.ugc.aweme.feed.widget.LineProgressBar" + } } } } - }.singleInstance("seekBarSpeedModeBottomContainer") + seekBarSpeedModeBottomContainerClazz = + seekBarSpeedModeBottomContainer.instance("seekBarSpeedModeBottomContainer") - poiCreateInstanceImplClazz = bridge.findClass { - matcher { - className = "com.ss.android.ugc.aweme.poi.PoiCreateInstanceImpl" - } - }.singleInstance("poiCreateInstanceImpl") + val mainBottomTabView = bridge.findClass { + matcher { + superClass = "android.widget.FrameLayout" - emojiApiProxyClazz = bridge.findClass { - matcher { - fields { - add { - type = "com.ss.android.ugc.aweme.emoji.utils.EmojiApi" - } - add { - type = "com.ss.android.ugc.aweme.emoji.store.EmojiShopApi" + fields { + add { + type = "com.bytedance.dux.image.DuxImageView" + } } - } - methods { - add { - returnType = "com.ss.android.ugc.aweme.emoji.utils.EmojiApi" + methods { + add { + name = "getBottomColor" + } + add { + name = "setBackgroundDrawable" + paramTypes = listOf("android.graphics.drawable.Drawable") + } + add { + name = "setBackgroundResource" + } + add { + name = "setBackgroundColor" + } + add { + name = "setVisibility" + } + add { + name = "setAlpha" + } } - add { - returnType = "com.ss.android.ugc.aweme.emoji.store.EmojiShopApi" + + usingStrings { + add("alpha", StringMatchType.Equals) + add("translationY", StringMatchType.Equals) + add("MainBottomTabView", StringMatchType.Equals) } } + } + mainBottomTabViewClazz = mainBottomTabView.instance("mainBottomTabView") + + val bottomCtrlBar = bridge.findClass { + searchPackages("X") + matcher { + superClass = "android.widget.FrameLayout" + fields { + add { + annotations { + add { + type = "dalvik.annotation.Signature" + addElement { + name = "value" + arrayValue { + addString("IPauseCtrlAction") + } + } + } + } + } + } - usingStrings { - add("https://", StringMatchType.Equals) - add("/aweme/v1/", StringMatchType.Equals) } } - }.singleInstance("emojiApiProxy") - - mainBottomTabViewClazz = bridge.findClass { - matcher { - superClass = "android.widget.FrameLayout" + bottomCtrlBarClazz = bottomCtrlBar.instance("bottomCtrlBar") + + val chatListRecyclerViewAdapter = bridge.findClass { + // searchPackages("X") + matcher { + fields { + add { + type = "com.ss.android.ugc.aweme.im.sdk.chat.SessionInfo" + } + add { + type = "androidx.recyclerview.widget.RecyclerView" + } + add { + type = "androidx.recyclerview.widget.RecyclerView\$ItemAnimator" + } + add { + type = "java.util.Set" + } + add { + type = "java.util.Set" + } + } - fields { - add { - type = "com.bytedance.dux.image.DuxImageView" + methods { + add { + name = "onBindViewHolder" + paramTypes = listOf( + "androidx.recyclerview.widget.RecyclerView\$ViewHolder", + "int", + "java.util.List", + ) + } } } + } + chatListRecyclerViewAdapterClazz = chatListRecyclerViewAdapter.instance("chatListRecyclerViewAdapter") - methods { - add { - name = "getBottomColor" - } - add { - name = "setBackgroundDrawable" - paramTypes = listOf("android.graphics.drawable.Drawable") - } - add { - name = "setBackgroundResource" + val chatListRecalledHint = bridge.findClass { + matcher { + fields { + add { + type = "android.widget.TextView" + } + + add { + type = "com.ss.android.ugc.aweme.views.InterceptTouchLinearLayout" + } + + add { + type { + superClass = "androidx.lifecycle.ViewModel" + } + } } - add { - name = "setBackgroundColor" + + methods { + add { + name = "getFastEventBusSubscriberClass" + returnType = "java.lang.Class" + } + + add { + paramTypes = listOf( + null, + "int", + "java.util.List" + ) + } } - add { - name = "setVisibility" + } + } + chatListRecalledHintClazz = chatListRecalledHint.instance("chatListRecalledHint") + + val restartUtils = bridge.findClass { + searchPackages("X") + matcher { + methods { + add { + paramTypes = listOf("android.content.Context") + usingNumbers = listOf(0x10008000) + } } - add { - name = "setAlpha" + usingStrings { + add("System.exit returned normally, while it was supposed to halt JVM.") } } - - usingStrings { - add("alpha", StringMatchType.Equals) - add("translationY", StringMatchType.Equals) - add("MainBottomTabView", StringMatchType.Equals) - } } - }.singleInstance("mainBottomTabView") - - bottomCtrlBarClazz = bridge.findClass { - searchPackages("X") - matcher { - superClass = "android.widget.FrameLayout" - fields { - add { - annotations { - add { - type = "dalvik.annotation.Signature" + restartUtilsClazz = restartUtils.instance("restartUtils") + + val longPressEvent = bridge.findClass { + matcher { + interfaces { + add { + addAnnotation { addElement { name = "value" - arrayValue { - addString("IPauseCtrlAction") + value { + classValue { + this.className = "com.ss.android.ugc.aweme.feed.ui.LongPressLayout" + } } } } } } - } - - } - }.singleInstance("bottomCtrlBar") - chatListRecyclerViewAdapterClazz = bridge.findClass { - // searchPackages("X") - matcher { - fields { - add { - type = "com.ss.android.ugc.aweme.im.sdk.chat.SessionInfo" - } - add { - type = "androidx.recyclerview.widget.RecyclerView" - } - add { - type = "androidx.recyclerview.widget.RecyclerView\$ItemAnimator" + addField { + type = "com.ss.android.ugc.aweme.feed.model.Aweme" } - add { - type = "java.util.Set" - } - add { - type = "java.util.Set" + addField { + type = "android.content.Context" } } - - methods { - add { - name = "onBindViewHolder" - paramTypes = listOf( - "androidx.recyclerview.widget.RecyclerView\$ViewHolder", - "int", - "java.util.List", - ) + } + longPressEventClazz = longPressEvent.instance("longPressEvent") + + val doubleClickEvent = bridge.findClass { + matcher { + fieldCount(1) + methods { + add { + paramTypes = listOf("boolean") + } + add { + paramTypes = listOf( + "android.view.View", + "android.view.MotionEvent", + "android.view.MotionEvent", + "android.view.MotionEvent", + ) + } } } } - }.singleInstance("chatListRecyclerViewAdapter") + doubleClickEventClazz = doubleClickEvent.instance("doubleClickEvent") - chatListRecalledHintClazz = bridge.findClass { - matcher { - fields { - add { - type = "android.widget.TextView" - } - - add { - type = "com.ss.android.ugc.aweme.views.InterceptTouchLinearLayout" + val videoViewHolder = bridge.findClass { + matcher { + className = "com.ss.android.ugc.aweme.feed.adapter.VideoViewHolder" + } + } + videoViewHolderClazz = videoViewHolder.instance("videoViewHolder") + videoViewHolderMethods = videoViewHolder.findMethod { + matcher { + usingFields { + add { + field { + type = "com.ss.android.ugc.aweme.feed.ui.PenetrateTouchRelativeLayout" + } + } + add { + field { + type = "com.ss.android.ugc.aweme.feed.model.VideoItemParams" + } + } } - add { - type { - superClass = "androidx.lifecycle.ViewModel" + invokeMethods { + add { + name = "isCleanMode" + } + add { + name = "getContext" } } } + }.instanceAll("videoViewHolderMethods") - methods { - add { - name = "getFastEventBusSubscriberClass" - returnType = "java.lang.Class" + val livePhoto = bridge.findClass { + matcher { + fields { + add { + type = "com.ss.android.ugc.aweme.feed.model.VideoItemParams" + } + add { + type = "com.bytedance.ies.dmt.ui.widget.DmtTextView" + } + add { + type = "android.widget.ImageView" + } } - - add { - paramTypes = listOf( - null, - "int", - "java.util.List" - ) + methods { + add { + paramTypes = listOf("com.ss.android.ugc.aweme.kiwi.model.QModel") + } + add { + paramTypes = listOf("com.ss.android.ugc.aweme.feed.model.Aweme") + } } } } - }.singleInstance("chatListRecalledHint") + livePhotoClazz = livePhoto.instance("livePhoto") + + val tabLanding = bridge.findClass { + matcher { + fields { + add { + type = + "com.ss.android.ugc.aweme.feed.plato.business.mainarchitecture.tablandguide.TabLandGuideTriggerEventType" + } + add { + type = "com.bytedance.dux.image.DuxImageView" + } - restartUtilsClazz = bridge.findClass { - searchPackages("X") - matcher { - methods { - add { - paramTypes = listOf("android.content.Context") - usingNumbers = listOf(0x10008000) - } - } - usingStrings { - add("System.exit returned normally, while it was supposed to halt JVM.") - } - } - }.singleInstance("restartUtils") - - val findMaps = bridge.batchFindClassUsingStrings { - addSearchGroup { - groupName = "videoPlayerHelper" - usingStrings = listOf( - "isDoubleClickResExist >>> channel empty", - "当前无网络,暂不可用", - "暂不支持点赞操作", - ) - } - addSearchGroup { - groupName = "detailPageFragment" - usingStrings = listOf( - "a1128.b7947", - "com/ss/android/ugc/aweme/detail/ui/DetailPageFragment", - "DetailActOtherNitaView", - ) - } - } - DexkitBuilder.videoPlayerHelperClazz = findMaps.singleInstance("videoPlayerHelper") - DexkitBuilder.detailPageFragmentClazz = findMaps.singleInstance("detailPageFragment") - } + add { + type = "com.ss.android.ugc.aweme.feed.model.VideoItemParams" + } - /** - * 搜索方法 - */ - private fun searchMethod(bridge: DexKitBridge) { - videoViewHolderMethods = bridge.findClass { - matcher { - className = "com.ss.android.ugc.aweme.feed.adapter.VideoViewHolder" - } - }.findMethod { - matcher { - usingFields { - add { - field { - type = "com.ss.android.ugc.aweme.feed.ui.PenetrateTouchRelativeLayout" + add { + type = "com.ss.android.ugc.aweme.feed.plato.business.mainarchitecture.tablandguide.TabId" } } - add { - field { - type = "com.ss.android.ugc.aweme.feed.model.VideoItemParams" + methods { + add { + paramTypes = listOf("com.ss.android.ugc.aweme.feed.model.VideoItemParams") } } } + } + tabLandingClazz = tabLanding.instance("tabLanding") + + + // + // by using string + val findMaps = bridge.batchFindClassUsingStrings { + addSearchGroup { + groupName = "videoPlayerHelper" + usingStrings = listOf( + "isDoubleClickResExist >>> channel empty", + "当前无网络,暂不可用", + "暂不支持点赞操作", + ) + } - invokeMethods { - add { - name = "isCleanMode" - } - add { - name = "getContext" - } + addSearchGroup { + groupName = "detailPageFragment" + usingStrings = listOf( + "a1128.b7947", + "com/ss/android/ugc/aweme/detail/ui/DetailPageFragment", + "DetailActOtherNitaView", + ) + } + + addSearchGroup { + groupName = "feedAvatarPresenter" + usingStrings = listOf( + "com/ss/android/ugc/aweme/feed/quick/presenter/FeedAvatarPresenter", + "当前无网络,暂不可用", + "follow", + "click_hea", + ) } } - }.allMethodInstance("videoViewHolderMethods") + + val videoPlayerHelper = findMaps["videoPlayerHelper"] + videoPlayerHelperClazz = videoPlayerHelper.instance("videoPlayerHelper") + + val detailPageFragment = findMaps["detailPageFragment"] + detailPageFragmentClazz = detailPageFragment.instance("detailPageFragment") + + val feedAvatarPresenter = findMaps["feedAvatarPresenter"] + feedAvatarPresenterClazz = feedAvatarPresenter.instance("feedAvatarPresenter") + } } /** @@ -579,22 +678,26 @@ object DexkitBuilder { sideBarNestedScrollViewClazz = classCache.getStringOrDefault("sideBarNestedScrollView").loadOrFindClass() cornerExtensionsPopupWindowClazz = classCache.getStringOrDefault("coenerExtendsionsPoupWindow").loadOrFindClass() mainBottomTabViewClazz = classCache.getStringOrDefault("mainBottomTabView").loadOrFindClass() - mainBottomTabItemClazz = classCache.getStringOrDefault("mainBottomTabItem").loadOrFindClass() + mainBottomPhotoTabClazz = classCache.getStringOrDefault("mainBottomPhotoTab").loadOrFindClass() commentListPageFragmentClazz = classCache.getStringOrDefault("commentListPageFragment").loadOrFindClass() conversationFragmentClazz = classCache.getStringOrDefault("conversationFragment").loadOrFindClass() seekBarSpeedModeBottomContainerClazz = classCache.getStringOrDefault("seekBarSpeedModeBottomContainer").loadOrFindClass() - poiCreateInstanceImplClazz = classCache.getStringOrDefault("poiCreateInstanceImpl").loadOrFindClass() videoPlayerHelperClazz = classCache.getStringOrDefault("videoPlayerHelper").loadOrFindClass() abstractFeedAdapterClazz = classCache.getStringOrDefault("abstractFeedAdapter").loadOrFindClass() recommendFeedFetchPresenterClazz = classCache.getStringOrDefault("recommendFeedFetchPresenter").loadOrFindClass() fullFeedFollowFetchPresenterClazz = classCache.getStringOrDefault("fullFeedFollowFetchPresenter").loadOrFindClass() emojiPopupWindowClazz = classCache.getStringOrDefault("emojiPopupWindow").loadOrFindClass() detailPageFragmentClazz = classCache.getStringOrDefault("detailPageFragment").loadOrFindClass() - emojiApiProxyClazz = classCache.getStringOrDefault("emojiApiProxy").loadOrFindClass() bottomCtrlBarClazz = classCache.getStringOrDefault("bottomCtrlBar").loadOrFindClass() chatListRecyclerViewAdapterClazz = classCache.getStringOrDefault("chatListRecyclerViewAdapter").loadOrFindClass() chatListRecalledHintClazz = classCache.getStringOrDefault("chatListRecalledHint").loadOrFindClass() restartUtilsClazz = classCache.getStringOrDefault("restartUtils").loadOrFindClass() + longPressEventClazz = classCache.getStringOrDefault("longPressEvent").loadOrFindClass() + doubleClickEventClazz = classCache.getStringOrDefault("doubleClickEvent").loadOrFindClass() + videoViewHolderClazz = classCache.getStringOrDefault("videoViewHolder").loadOrFindClass() + livePhotoClazz = classCache.getStringOrDefault("livePhoto").loadOrFindClass() + tabLandingClazz = classCache.getStringOrDefault("tabLanding").loadOrFindClass() + feedAvatarPresenterClazz = classCache.getStringOrDefault("feedAvatarPresenter").loadOrFindClass() } /** @@ -622,22 +725,17 @@ object DexkitBuilder { } // 拓展方法 - private fun Map.singleInstance(key: String): Class<*>? { - val classData = this[key]?.singleOrNull() - KLogCat.tagI(TAG, "found-class[$key]: ${classData?.name}") - classCacheJson.put(key, "${classData?.name}") - return classData?.getInstance(lpparam.classLoader) + private fun ClassDataList?.instance(label: String): Class<*>? { + return this?.singleOrNull().instance(label) } - private fun ClassDataList.singleInstance(label: String): Class<*>? { - val classData = this.singleOrNull() - KLogCat.tagI(TAG, "found-class[$label]: ${classData?.name}") - if (classData == null) XplerLog.d("not found class: $label") - classCacheJson.put(label, "${classData?.name}") - return classData?.getInstance(lpparam.classLoader) + private fun ClassData?.instance(label: String): Class<*>? { + KLogCat.tagI(TAG, "found-class[$label]: ${this?.name}") + classCacheJson.put(label, "${this?.name}") + return this?.getInstance(lpparam.classLoader) } - private fun MethodDataList.allMethodInstance(label: String): List { + private fun MethodDataList.instanceAll(label: String): List { val array = JSONArray() methodsCacheJson.put(label, array) return this.filter { diff --git a/core/src/main/java/io/github/fplus/core/helper/TimerExitHelper.kt b/core/src/main/java/io/github/fplus/core/helper/TimerExitHelper.kt new file mode 100644 index 0000000..ed3fd43 --- /dev/null +++ b/core/src/main/java/io/github/fplus/core/helper/TimerExitHelper.kt @@ -0,0 +1,141 @@ +package io.github.fplus.core.helper + +import android.app.Application +import android.content.Intent +import android.os.CountDownTimer +import android.os.Process +import com.freegang.ktutils.app.KActivityUtils +import com.freegang.ktutils.app.KAppUtils +import io.github.fplus.core.hook.DouYinMain.Companion.freeExitHelper +import kotlin.system.exitProcess + +class TimerExitHelper @JvmOverloads constructor( + app: Application, + millis: Long, + private var keepBackground: Boolean = true, + private var onTick: OnTickListener? = null +) { + private var app: Application? = app + private var initMillis: Long = millis + private var remainingMillis: Long = millis + private var countDownTimer: CountDownTimer? = null + + private var mIsStarted = false + private var mIsPaused = false + + init { + this.countDownTimer = createCountDownTimer(millis) + } + + /** + * 开始定时退出 + */ + fun start() { + if (mIsStarted) { + return + } + + mIsStarted = true + countDownTimer?.start() + } + + /** + * 重启定时退出 + */ + fun restart() { + countDownTimer?.cancel() + countDownTimer = createCountDownTimer(initMillis) + mIsStarted = true + mIsPaused = false + countDownTimer?.start() + } + + /** + * 暂停定时退出 + */ + fun pause() { + if (mIsPaused) { + return + } + + mIsPaused = true + countDownTimer?.cancel() + countDownTimer = createCountDownTimer(remainingMillis) + } + + /** + * 恢复定时退出 + */ + fun resume() { + if (!mIsPaused) { + return + } + + mIsPaused = false + countDownTimer?.start() + } + + /** + * 取消定时退出 + */ + fun cancel() { + if (!mIsStarted) { + return + } + + countDownTimer?.cancel() + } + + /** + * 是否已经开始 + */ + val isStarted: Boolean + get() = mIsStarted + + /** + * 是否处于暂停状态 + */ + val isPaused: Boolean + get() = mIsPaused + + /** + * 创建定时退出实例 + * @param millis Long 定时退出时间 + */ + private fun createCountDownTimer(millis: Long): CountDownTimer { + return object : CountDownTimer(millis, 1000) { + override fun onTick(millisUntilFinished: Long) { + this@TimerExitHelper.remainingMillis = millisUntilFinished + this@TimerExitHelper.onTick?.onTick(millisUntilFinished) + + if (!KAppUtils.isAppInForeground(app!!)) { + this@TimerExitHelper.pause() + } + } + + override fun onFinish() { + keepOrKill() + } + } + } + + /** + * 保持后台运行或者杀死进程 + */ + private fun keepOrKill() { + if (keepBackground) { + val intent = Intent(Intent.ACTION_MAIN) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.addCategory(Intent.CATEGORY_HOME) + app!!.startActivity(intent) + } else { + KActivityUtils.getActivities().forEach { it.finishAndRemoveTask() } + Process.killProcess(Process.myPid()) + exitProcess(1) + } + } + + fun interface OnTickListener { + fun onTick(millisUntilFinished: Long) + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/DouYinMain.kt b/core/src/main/java/io/github/fplus/core/hook/DouYinMain.kt index 608897a..b9833d8 100644 --- a/core/src/main/java/io/github/fplus/core/hook/DouYinMain.kt +++ b/core/src/main/java/io/github/fplus/core/hook/DouYinMain.kt @@ -2,26 +2,22 @@ package io.github.fplus.core.hook import android.app.Application import android.content.Intent -import android.os.CountDownTimer -import android.os.Process import com.freegang.ktutils.app.KActivityUtils import com.freegang.ktutils.app.KAppCrashUtils import com.freegang.ktutils.app.KAppUtils import com.freegang.ktutils.app.KToastUtils import com.freegang.ktutils.log.KLogCat -import com.ss.android.ugc.aweme.feed.model.Aweme import io.github.fplus.Constant import io.github.fplus.core.config.ConfigV1 import io.github.fplus.core.helper.DexkitBuilder +import io.github.fplus.core.helper.TimerExitHelper import io.github.fplus.plugin.proxy.v1.PluginBridge import io.github.xpler.core.log.XplerLog -import kotlin.system.exitProcess class DouYinMain(private val app: Application) { companion object { - var inBackend = false - var timedExitCountDown: CountDownTimer? = null - var freeExitCountDown: CountDownTimer? = null + var timerExitHelper: TimerExitHelper? = null + var freeExitHelper: TimerExitHelper? = null } init { @@ -39,6 +35,7 @@ class DouYinMain(private val app: Application) { // 日志工具 XplerLog.setTag("Freedom+") KLogCat.init(app) + KLogCat.setTag("Freedom+") // KLogCat.silence() //静默 // 全局异常捕获工具 @@ -56,7 +53,7 @@ class DouYinMain(private val app: Application) { // search and hook DexkitBuilder.running( app = app, - version = 16, + version = 19, searchBefore = { HActivity() HMainActivity() @@ -66,9 +63,8 @@ class DouYinMain(private val app: Application) { HDisallowInterceptRelativeLayout() HMainTabStripScrollView() HFlippableViewPager() - HCustomizedUISeekBar() HPlayerController() - HVideoViewHolderRootView() + // HVideoViewHolderRootView() HPenetrateTouchRelativeLayout() HInteractStickerParent() // HCommentAudioView() @@ -79,16 +75,19 @@ class DouYinMain(private val app: Application) { // HPopupWindow() }, searchAfter = { + HCrashTolerance() HSideBarNestedScrollView() HCornerExtensionsPopupWindow() HMainBottomTabView() - HMainBottomTabItem() + HMainBottomPhotoTab() HCommentListPageFragment() HConversationFragment() - HPoiCreateInstanceImpl() HSeekBarSpeedModeBottomMask() - HVideoPlayerHelper() + // HVideoPlayerHelper() + HLongPressLayout() HVideoViewHolder() + HFeedAvatarPresenter() + HHomeBottomTabServiceImpl() HAbstractFeedAdapter() HVerticalViewPager() HDetailPageFragment() @@ -118,79 +117,31 @@ class DouYinMain(private val app: Application) { val freeExit = config.timedShutdownValue[1] * 60 * 1000L if (timedExit >= 60 * 1000L * 3) { - timedExitCountDown = object : CountDownTimer(timedExit, 1000) { - override fun onTick(millisUntilFinished: Long) { - val second = millisUntilFinished / 1000 - // KLogCat.d("定时倒计时: ${second}秒") - if (!KAppUtils.isAppInForeground(app)) { - inBackend = true - cancel() - } - if (second == 30L) { - KToastUtils.show(app, "抖音将在30秒后定时退出") - } - if (second <= 5) { - KToastUtils.show(app, "定时退出倒计时${second}s") - } + timerExitHelper = TimerExitHelper(app, timedExit, config.keepAppBackend) { + val second = it / 1000L + if (second == 30L) { + KToastUtils.show(app, "抖音将在30秒后定时退出") } - - override fun onFinish() { - if (!config.isTimedExit) - return - - keepOrKill(app, config) + if (second <= 5) { + KToastUtils.show(app, "定时退出倒计时${second}s") } + + // KLogCat.d("定时退出进行中: ${second}s") } } if (freeExit >= 60 * 1000L * 3) { - freeExitCountDown = object : CountDownTimer(freeExit, 1000) { - override fun onTick(millisUntilFinished: Long) { - val second = millisUntilFinished / 1000 - // KLogCat.d("空闲倒计时: ${second}秒") - if (!KAppUtils.isAppInForeground(app)) { - cancel() - } - if (second == 30L) { - KToastUtils.show(app, "长时间无操作, 抖音将在30秒后空闲退出") - } - if (second <= 5) { - KToastUtils.show(app, "空闲退出倒计时${second}s") - } + freeExitHelper = TimerExitHelper(app, timedExit, config.keepAppBackend) { + val second = it / 1000L + if (second == 30L) { + KToastUtils.show(app, "长时间无操作, 抖音将在30秒后空闲退出") } - - override fun onFinish() { - if (!config.isTimedExit) - return - - keepOrKill(app, config) + if (second <= 5) { + KToastUtils.show(app, "空闲退出倒计时${second}s") } - } - } - } - // - private fun keepOrKill(app: Application, config: ConfigV1) { - if (config.keepAppBackend) { - inBackend = true - val intent = Intent(Intent.ACTION_MAIN) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - intent.addCategory(Intent.CATEGORY_HOME) - app.startActivity(intent) - } else { - KActivityUtils.getActivities().forEach { it.finishAndRemoveTask() } - Process.killProcess(Process.myPid()) - exitProcess(1) + // KLogCat.d("空闲退出进行中: ${second}s") + } } } -} - -fun CountDownTimer.restart() { - cancel() - start() -} - -fun Aweme.sortString(): String { - val desc = "$desc".replace(Regex("\\s"), "") - return "awemeType=${awemeType}, desc=$desc" } \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/HActivity.kt b/core/src/main/java/io/github/fplus/core/hook/HActivity.kt index 5cb4e49..aa51580 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HActivity.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HActivity.kt @@ -6,6 +6,7 @@ import androidx.core.view.updatePadding import com.freegang.extension.contentView import com.freegang.extension.navBarInteractionMode import com.freegang.extension.navigationBarHeight +import com.ss.android.ugc.aweme.live.LivePlayActivity import de.robv.android.xposed.XC_MethodHook import io.github.fplus.core.base.BaseHook import io.github.fplus.core.config.ConfigV1 @@ -27,13 +28,17 @@ class HActivity : BaseHook() { @OnBefore("dispatchTouchEvent") fun dispatchTouchEventBefore(params: XC_MethodHook.MethodHookParam, event: MotionEvent) { hookBlockRunning(params) { - DouYinMain.freeExitCountDown?.restart() + val activity = thisObject as Activity + DouYinMain.freeExitHelper?.restart() - if (thisActivity is FreedomSettingActivity) return + if (activity is FreedomSettingActivity) + return + + if (activity is LivePlayActivity) + DouYinMain.freeExitHelper?.cancel() if (config.isImmersive) { // 底部三键导航 - val activity = thisObject as Activity if (activity.navBarInteractionMode == 0 && !config.systemControllerValue[1]) { ImmersiveHelper.systemBarColor(activity, navigationBarColor = null) } else { @@ -48,10 +53,16 @@ class HActivity : BaseHook() { @OnBefore("onResume") fun onResumeBefore(params: XC_MethodHook.MethodHookParam) { hookBlockRunning(params) { - DouYinMain.freeExitCountDown?.restart() - if (DouYinMain.inBackend) { - DouYinMain.inBackend = false - DouYinMain.timedExitCountDown?.restart() + val activity = thisObject as Activity + + if (activity is LivePlayActivity) { + DouYinMain.freeExitHelper?.cancel() + } else { + DouYinMain.freeExitHelper?.restart() + } + + if (DouYinMain.timerExitHelper?.isPaused == true) { + DouYinMain.timerExitHelper?.restart() } }.onFailure { XplerLog.e(it) @@ -62,7 +73,10 @@ class HActivity : BaseHook() { fun onWindowFocusChangedAfter(params: XC_MethodHook.MethodHookParam, boolean: Boolean) { hookBlockRunning(params) { singleLaunchMain { - if (thisActivity is FreedomSettingActivity) return@singleLaunchMain + val activity = thisObject as Activity + + if (activity is FreedomSettingActivity) + return@singleLaunchMain if (config.isImmersive) { ImmersiveHelper.immersive( @@ -72,7 +86,6 @@ class HActivity : BaseHook() { ) // 底部三键导航 - val activity = thisObject as Activity ImmersiveHelper.systemBarColor(activity) if (activity.navBarInteractionMode == 0 && !config.systemControllerValue[1]) { activity.contentView.apply { diff --git a/core/src/main/java/io/github/fplus/core/hook/HChatListRecalledHint.kt b/core/src/main/java/io/github/fplus/core/hook/HChatListRecalledHint.kt index 93810ee..edabfc1 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HChatListRecalledHint.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HChatListRecalledHint.kt @@ -28,9 +28,9 @@ class HChatListRecalledHint : BaseHook() { } @OnAfter - fun testAfter( + fun methodAfter( params: XC_MethodHook.MethodHookParam, - @Param("null") any: Any?, + @Param any: Any?, i: Int, list: List<*>?, ) { diff --git a/core/src/main/java/io/github/fplus/core/hook/HCrashTolerance.kt b/core/src/main/java/io/github/fplus/core/hook/HCrashTolerance.kt new file mode 100644 index 0000000..3901a86 --- /dev/null +++ b/core/src/main/java/io/github/fplus/core/hook/HCrashTolerance.kt @@ -0,0 +1,86 @@ +package io.github.fplus.core.hook + +import com.freegang.ktutils.app.KAppUtils +import com.freegang.ktutils.app.KToastUtils +import com.ss.android.ugc.aweme.feed.model.VideoItemParams +import com.ss.android.ugc.aweme.kiwi.model.QModel +import de.robv.android.xposed.XC_MethodHook +import io.github.fplus.core.base.BaseHook +import io.github.fplus.core.config.ConfigV1 +import io.github.fplus.core.helper.DexkitBuilder +import io.github.xpler.core.entity.EmptyHook +import io.github.xpler.core.entity.NoneHook +import io.github.xpler.core.entity.OnAfter +import io.github.xpler.core.hookBlockRunning +import io.github.xpler.core.wrapper.CallMethods + +/// 崩溃容错,处理官方可能造成的系列崩溃问题 +class HCrashTolerance : BaseHook() { + companion object { + const val TAG = "HCrashTolerance" + } + + val config get() = ConfigV1.get() + + override fun onInit() { + if (!config.isCrashTolerance) + return + + HPoiFeed() + HLivePhoto() + HTabLanding() + } + + inner class HPoiFeed : BaseHook(), CallMethods { + override fun setTargetClass(): Class<*> { + return findClass("com.ss.android.ugc.aweme.poi.anchor.poi.flavor.PoiFeedAnchor") + } + + override fun callOnBeforeMethods(params: XC_MethodHook.MethodHookParam) { + + } + + override fun callOnAfterMethods(params: XC_MethodHook.MethodHookParam) { + hookBlockRunning(params) { + resultOrThrowable + }.onFailure { + params.throwable = null + // KToastUtils.show(KAppUtils.getApplication, "尝试崩溃拦截:${it.message}") + } + } + } + + inner class HLivePhoto : BaseHook() { + + override fun setTargetClass(): Class<*> { + return DexkitBuilder.livePhotoClazz ?: NoneHook::class.java + } + + @OnAfter + fun methodAfter(params: XC_MethodHook.MethodHookParam, qModel: QModel?) { + hookBlockRunning(params) { + resultOrThrowable + }.onFailure { + params.throwable = null + // KToastUtils.show(KAppUtils.getApplication, "尝试崩溃拦截:${it.message}") + } + } + } + + inner class HTabLanding : BaseHook() { + + override fun setTargetClass(): Class<*> { + return DexkitBuilder.tabLandingClazz ?: NoneHook::class.java + } + + @OnAfter + fun methodAfter(params: XC_MethodHook.MethodHookParam, item: VideoItemParams?) { + hookBlockRunning(params) { + resultOrThrowable + }.onFailure { + params.throwable = null + // KToastUtils.show(KAppUtils.getApplication, "尝试崩溃拦截:${it.message}") + } + } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/HCustomizedUISeekBar.kt b/core/src/main/java/io/github/fplus/core/hook/HCustomizedUISeekBar.kt deleted file mode 100644 index 5d5c6ed..0000000 --- a/core/src/main/java/io/github/fplus/core/hook/HCustomizedUISeekBar.kt +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.fplus.core.hook - -import android.widget.FrameLayout -import androidx.core.view.updateMargins -import com.freegang.extension.dip2px -import com.ss.android.ugc.aweme.feed.ui.seekbar.SeekBarState -import de.robv.android.xposed.XC_MethodHook -import io.github.fplus.core.base.BaseHook -import io.github.fplus.core.config.ConfigV1 -import io.github.xpler.core.entity.OnAfter -import io.github.xpler.core.hookBlockRunning -import io.github.xpler.core.log.XplerLog -import io.github.xpler.core.thisView - -class HCustomizedUISeekBar : BaseHook() { - companion object { - const val TAG = "HCustomizedUISeekBar" - - @get:Synchronized - @set:Synchronized - var action: SeekBarState.Action? = null - } - - private val config get() = ConfigV1.get() - - override fun setTargetClass(): Class<*> { - return findClass("com.ss.android.ugc.aweme.feed.ui.seekbar.CustomizedUISeekBar") - } - - // @OnAfter("setAlpha") - fun setAlphaAfter(params: XC_MethodHook.MethodHookParam, alpha: Float) { - hookBlockRunning(params) { - if (config.isImmersive) { - thisView.apply { - val lp = layoutParams as FrameLayout.LayoutParams? - lp?.updateMargins(context.dip2px(58f)) - layoutParams = layoutParams - } - } - }.onFailure { - XplerLog.e(it) - } - } - - @OnAfter - fun methodAfter(params: XC_MethodHook.MethodHookParam, action: SeekBarState.Action?) { - hookBlockRunning(params) { - HCustomizedUISeekBar.action = action - }.onFailure { - XplerLog.e(it) - } - } -} \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/HDetailActivity.kt b/core/src/main/java/io/github/fplus/core/hook/HDetailActivity.kt index ebd30d8..52f21b4 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HDetailActivity.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HDetailActivity.kt @@ -1,7 +1,10 @@ package io.github.fplus.core.hook import android.app.Activity +import com.freegang.extension.asOrNull +import com.freegang.extension.method import com.ss.android.ugc.aweme.detail.ui.DetailActivity +import com.ss.android.ugc.aweme.feed.model.Aweme import de.robv.android.xposed.XC_MethodHook import io.github.fplus.core.base.BaseHook import io.github.fplus.core.config.ConfigV1 @@ -41,14 +44,22 @@ class HDetailActivity : BaseHook() { } private fun addClipboardListener(activity: Activity) { - if (!config.isDownload) return - if (!config.isCopyDownload) return + if (!config.isDownload) + return + + if (!config.copyLinkDownload) + return + + val method = activity.method(returnType = Aweme::class.java) + val aweme = method?.invoke(activity)?.asOrNull() + ?: HVideoViewHolder.aweme + + clipboardLogic.addClipboardListener(activity) { _, _ -> - clipboardLogic.addClipboardListener(activity) { clipData, firstText -> DownloadLogic( this@HDetailActivity, activity, - HVideoViewHolder.aweme, + aweme, ) } } diff --git a/core/src/main/java/io/github/fplus/core/hook/HDetailPageFragment.kt b/core/src/main/java/io/github/fplus/core/hook/HDetailPageFragment.kt index a339dc9..8332ebd 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HDetailPageFragment.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HDetailPageFragment.kt @@ -42,7 +42,7 @@ class HDetailPageFragment : BaseHook() { @OnAfter("onViewCreated") fun onViewCreatedAfter(param: XC_MethodHook.MethodHookParam, view: View, bundle: Bundle?) { hookBlockRunning(param) { - if (!config.isEmoji) return + if (!config.isEmojiDownload) return // HDetailPageFragment.isComment = false diff --git a/core/src/main/java/io/github/fplus/core/hook/HDialog.kt b/core/src/main/java/io/github/fplus/core/hook/HDialog.kt index 3f774ec..035ae49 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HDialog.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HDialog.kt @@ -5,6 +5,7 @@ import android.widget.TextView import com.freegang.extension.ellipsis import com.freegang.extension.forEachWhereChild import com.freegang.ktutils.app.KToastUtils +import com.freegang.ktutils.log.KLogCat import de.robv.android.xposed.XC_MethodHook import io.github.fplus.core.base.BaseHook import io.github.fplus.core.config.ConfigV1 @@ -21,10 +22,11 @@ class HDialog : BaseHook() { private val dialogFilterKeywords by lazy { config.dialogFilterKeywords - .removePrefix(",").removePrefix(",") - .removeSuffix(",").removeSuffix(",") + .replace(",", ",") .replace("\\s".toRegex(), "") - .replace("[,,]".toRegex(), "|") + .removePrefix(",").removeSuffix(",") + .replace(",", "|") + .replace("\\|+".toRegex(), "|") .toRegex() } diff --git a/core/src/main/java/io/github/fplus/core/hook/HDisallowInterceptRelativeLayout.kt b/core/src/main/java/io/github/fplus/core/hook/HDisallowInterceptRelativeLayout.kt index 0082f3a..56593a6 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HDisallowInterceptRelativeLayout.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HDisallowInterceptRelativeLayout.kt @@ -40,22 +40,23 @@ class HDisallowInterceptRelativeLayout : BaseHook(), override fun callOnAfterConstructors(params: XC_MethodHook.MethodHookParam) { hookBlockRunning(params) { - if (config.isImmersive) { - thisViewGroup.postRunning { - runCatching { - it.forEachChild { child -> - // 移除顶部间隔 - if (child.javaClass.name == "android.view.View") { - child.removeInParent() - } - // 移除底部间隔 - if (child.javaClass.name == "com.ss.android.ugc.aweme.feed.ui.bottom.BottomSpace") { - child.removeInParent() - } + if (!config.isImmersive) + return + + thisViewGroup.postRunning { + runCatching { + it.forEachChild { child -> + // 移除顶部间隔 + if (child.javaClass.name == "android.view.View") { + child.removeInParent() + } + // 移除底部间隔 + if (child.javaClass.name == "com.ss.android.ugc.aweme.feed.ui.bottom.BottomSpace") { + child.removeInParent() } - }.onFailure { - XplerLog.e(it) } + }.onFailure { + XplerLog.e(it) } } } diff --git a/core/src/main/java/io/github/fplus/core/hook/HEmojiDetailDialog.kt b/core/src/main/java/io/github/fplus/core/hook/HEmojiDetailDialog.kt index 18593e5..907f4a6 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HEmojiDetailDialog.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HEmojiDetailDialog.kt @@ -32,7 +32,7 @@ class HEmojiDetailDialog : BaseHook(), CallMethods { lpparam.hookClass(EmojiBottomSheetDialog::class.java) .method("onCreate", Bundle::class.java) { onAfter { - if (!config.isEmoji) return@onAfter + if (!config.isEmojiDownload) return@onAfter if (!targetClazz.isInstance(thisObject)) return@onAfter // 非 EmojiDetailDialog, 直接结束 singleLaunchMain { @@ -66,7 +66,7 @@ class HEmojiDetailDialog : BaseHook(), CallMethods { override fun callOnAfterMethods(params: XC_MethodHook.MethodHookParam) { hookBlockRunning(params) { - if (!config.isEmoji) return + if (!config.isEmojiDownload) return if (urlList.isNotEmpty()) return val urlModel = thisObject.fieldGet(type = UrlModel::class.java) diff --git a/core/src/main/java/io/github/fplus/core/hook/HEmojiDetailDialogNew.kt b/core/src/main/java/io/github/fplus/core/hook/HEmojiDetailDialogNew.kt index 458aa11..534fa6f 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HEmojiDetailDialogNew.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HEmojiDetailDialogNew.kt @@ -41,7 +41,7 @@ class HEmojiDetailDialogNew : BaseHook() { lpparam.hookClass(EmojiBottomSheetDialog::class.java) .method("onCreate", Bundle::class.java) { onAfter { - if (!config.isEmoji) return@onAfter + if (!config.isEmojiDownload) return@onAfter // 非 EmojiDetailDialogNew, 直接结束 if (!targetClazz.isInstance(thisObject)) { diff --git a/core/src/main/java/io/github/fplus/core/hook/HEmojiPopupWindow.kt b/core/src/main/java/io/github/fplus/core/hook/HEmojiPopupWindow.kt index 3fa274b..c16cf9a 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HEmojiPopupWindow.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HEmojiPopupWindow.kt @@ -33,7 +33,7 @@ class HEmojiPopupWindow : BaseHook() { emoji: BaseEmoji?, ) { hookBlockRunning(params) { - if (!config.isEmoji) + if (!config.isEmojiDownload) return popUrlList = popUrlList.ifEmpty { @@ -50,7 +50,7 @@ class HEmojiPopupWindow : BaseHook() { @ReturnType("Lcom/bytedance/ies/dmt/ui/widget/DmtTextView;") fun textViewAfter(params: XC_MethodHook.MethodHookParam) { hookBlockRunning(params) { - if (!config.isEmoji) + if (!config.isEmojiDownload) return val view = result?.asOrNull() ?: return diff --git a/core/src/main/java/io/github/fplus/core/hook/HFeedAvatarPresenter.kt b/core/src/main/java/io/github/fplus/core/hook/HFeedAvatarPresenter.kt new file mode 100644 index 0000000..1cca87a --- /dev/null +++ b/core/src/main/java/io/github/fplus/core/hook/HFeedAvatarPresenter.kt @@ -0,0 +1,80 @@ +package io.github.fplus.core.hook + +import android.view.View +import android.view.ViewGroup +import com.freegang.extension.asOrNull +import com.freegang.extension.fieldGet +import com.freegang.extension.methodInvoke +import com.ss.android.ugc.aweme.feed.model.Aweme +import com.ss.android.ugc.aweme.profile.model.User +import de.robv.android.xposed.XC_MethodHook +import io.github.fplus.core.base.BaseHook +import io.github.fplus.core.config.ConfigV1 +import io.github.fplus.core.helper.DexkitBuilder +import io.github.xpler.core.entity.NoneHook +import io.github.xpler.core.entity.OnBefore +import io.github.xpler.core.hookBlockRunning +import io.github.xpler.core.log.XplerLog + +class HFeedAvatarPresenter : BaseHook() { + companion object { + const val TAG = "HFeedAvatarPresenter" + } + + private val config + get() = ConfigV1.get() + + private var clickAgain: Boolean = false + + override fun setTargetClass(): Class<*> { + return DexkitBuilder.feedAvatarPresenterClazz ?: NoneHook::class.java + } + + @OnBefore + fun onClickBefore( + params: XC_MethodHook.MethodHookParam, + view: View?, + ) { + hookBlockRunning(params) { + if (!config.isPreventAccidentalTouch) + return + + if (view !is ViewGroup) + return + + val aweme: Aweme = thisObject.fieldGet( + type = Aweme::class.java, + )?.asOrNull() ?: return + + val user: User = thisObject.methodInvoke( + returnType = User::class.java, + args = arrayOf(aweme), + )?.asOrNull() ?: return + + // KLogCat.d("followStatus: ${user.followStatus}") + if (user.followStatus == 1) // 已关注 + return + + val nodeInfo = view.createAccessibilityNodeInfo() + if (!clickAgain && "${nodeInfo.contentDescription}".contains("关注")) { + showMessageDialog( + context = view.context, + title = "温馨提示", + content = "你点击了关注,是否关注该博主?", + cancel = "取消", + confirm = "确定", + onConfirm = { + clickAgain = true + view.performClick() + }, + ) + + result = Void.TYPE + } + + clickAgain = false + }.onFailure { + XplerLog.tagE(TAG, it) + } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/HGifEmojiDetailActivity.kt b/core/src/main/java/io/github/fplus/core/hook/HGifEmojiDetailActivity.kt index feb9bfd..001aa2b 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HGifEmojiDetailActivity.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HGifEmojiDetailActivity.kt @@ -32,7 +32,7 @@ class HGifEmojiDetailActivity : BaseHook() { @OnBefore("onCreate") fun onCreate(params: XC_MethodHook.MethodHookParam, bundle: Bundle?) { hookBlockRunning(params) { - if (!config.isEmoji) return + if (!config.isEmojiDownload) return val gifEmoji = thisActivity.intent.getSerializableExtra("gif_emoji") as Emoji? ?: return val animateUrl = gifEmoji.fieldGet(name = "animateUrl") diff --git a/core/src/main/java/io/github/fplus/core/hook/HHomeBottomTabServiceImpl.kt b/core/src/main/java/io/github/fplus/core/hook/HHomeBottomTabServiceImpl.kt new file mode 100644 index 0000000..94084c0 --- /dev/null +++ b/core/src/main/java/io/github/fplus/core/hook/HHomeBottomTabServiceImpl.kt @@ -0,0 +1,62 @@ +package io.github.fplus.core.hook + +import android.content.Context +import android.view.View +import com.freegang.extension.getOnClickListener +import de.robv.android.xposed.XC_MethodHook +import io.github.fplus.core.base.BaseHook +import io.github.fplus.core.config.ConfigV1 +import io.github.xpler.core.entity.OnBefore +import io.github.xpler.core.hookBlockRunning +import io.github.xpler.core.log.XplerLog + +class HHomeBottomTabServiceImpl : BaseHook() { + companion object { + const val TAG = "HHomeBottomTabServiceImpl" + } + + private val config + get() = ConfigV1.get() + + private var lastType: String? = "HOME" + private var clickAgain: Boolean = false + + override fun setTargetClass(): Class<*> { + return findClass("com.ss.android.ugc.aweme.homepage.tab.business.bottom.HomeBottomTabServiceImpl") + } + + @OnBefore + fun methodBefore( + params: XC_MethodHook.MethodHookParam, + context: Context?, + type: String?, + view: View? + ) { + hookBlockRunning(params) { + if (!config.isPreventAccidentalTouch) + return + + if (!clickAgain && lastType == "HOME" && type == "HOME") { + val onClick = view?.getOnClickListener + showMessageDialog( + context = context!!, + title = "温馨提示", + content = "你点击了首页,是否触发刷新?", + cancel = "取消", + confirm = "确定", + onConfirm = { + clickAgain = true + onClick?.onClick(view) + } + ) + + result = Void.TYPE + } + + lastType = type + clickAgain = false + }.onFailure { + XplerLog.tagE(TAG, it) + } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/HInteractStickerParent.kt b/core/src/main/java/io/github/fplus/core/hook/HInteractStickerParent.kt index f956576..91ebed5 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HInteractStickerParent.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HInteractStickerParent.kt @@ -24,11 +24,11 @@ class HInteractStickerParent : BaseHook(), CallConstructo override fun callOnAfterConstructors(params: XC_MethodHook.MethodHookParam) { hookBlockRunning(params) { - // 移除悬浮贴纸 - if (config.isRemoveSticker) { - thisViewGroup.postRunning { child -> - child.removeInParent() - } + if (!config.isRemoveSticker) + return + + thisViewGroup.postRunning { child -> + child.removeInParent() } }.onFailure { XplerLog.e(it) diff --git a/core/src/main/java/io/github/fplus/core/hook/HLandscapeFeedActivity.kt b/core/src/main/java/io/github/fplus/core/hook/HLandscapeFeedActivity.kt index 4726240..76544e0 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HLandscapeFeedActivity.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HLandscapeFeedActivity.kt @@ -44,10 +44,13 @@ class HLandscapeFeedActivity : BaseHook() { } private fun addClipboardListener(activity: Activity) { - if (!config.isDownload) return - if (!config.isCopyDownload) return + if (!config.isDownload) + return - clipboardLogic.addClipboardListener(activity) { clipData, firstText -> + if (!config.copyLinkDownload) + return + + clipboardLogic.addClipboardListener(activity) { _, _ -> val method = activity.method(returnType = Aweme::class.java) val aweme = method?.invoke(activity)?.asOrNull() diff --git a/core/src/main/java/io/github/fplus/core/hook/HLivePlayActivity.kt b/core/src/main/java/io/github/fplus/core/hook/HLivePlayActivity.kt index 027cd55..7f0c3d2 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HLivePlayActivity.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HLivePlayActivity.kt @@ -1,5 +1,6 @@ package io.github.fplus.core.hook +import com.ss.android.ugc.aweme.live.LivePlayActivity import de.robv.android.xposed.XC_MethodHook import io.github.fplus.core.base.BaseHook import io.github.fplus.core.config.ConfigV1 @@ -10,7 +11,7 @@ import io.github.xpler.core.hookBlockRunning import io.github.xpler.core.log.XplerLog import io.github.xpler.core.thisActivity -class HLivePlayActivity : BaseHook() { +class HLivePlayActivity : BaseHook() { companion object { const val TAG = "HLivePlayActivity" @@ -18,10 +19,6 @@ class HLivePlayActivity : BaseHook() { private val config get() = ConfigV1.get() - override fun setTargetClass(): Class<*> { - return findClass("com.ss.android.ugc.aweme.live.LivePlayActivity") - } - @OnBefore("onWindowFocusChanged") @OnAfter("onWindowFocusChanged") fun onWindowFocusChangedAfter(params: XC_MethodHook.MethodHookParam, boolean: Boolean) { diff --git a/core/src/main/java/io/github/fplus/core/hook/HLongPressLayout.kt b/core/src/main/java/io/github/fplus/core/hook/HLongPressLayout.kt new file mode 100644 index 0000000..fc246de --- /dev/null +++ b/core/src/main/java/io/github/fplus/core/hook/HLongPressLayout.kt @@ -0,0 +1,386 @@ +package io.github.fplus.core.hook + +import android.app.ActivityOptions +import android.content.Intent +import android.view.MotionEvent +import android.view.View +import android.view.ViewConfiguration +import android.view.ViewGroup +import android.widget.TextView +import androidx.core.view.children +import androidx.core.view.isVisible +import com.freegang.extension.activeActivity +import com.freegang.extension.asOrNull +import com.freegang.extension.fieldGet +import com.freegang.extension.firstParentOrNull +import com.freegang.extension.forEachChild +import com.freegang.extension.isDarkMode +import com.freegang.extension.screenSize +import com.freegang.ktutils.other.KAutomationUtils +import com.freegang.ktutils.view.KViewUtils +import com.ss.android.ugc.aweme.ad.feed.VideoViewHolderRootView +import com.ss.android.ugc.aweme.feed.model.Aweme +import com.ss.android.ugc.aweme.feed.ui.PenetrateTouchRelativeLayout +import de.robv.android.xposed.XC_MethodHook +import io.github.fplus.core.base.BaseHook +import io.github.fplus.core.config.ConfigV1 +import io.github.fplus.core.helper.DexkitBuilder +import io.github.fplus.core.hook.logic.DownloadLogic +import io.github.fplus.core.ui.activity.FreedomSettingActivity +import io.github.xpler.core.entity.OnAfter +import io.github.xpler.core.hookBlockRunning +import io.github.xpler.core.hookClass +import io.github.xpler.core.log.XplerLog +import io.github.xpler.core.thisView + + +class HLongPressLayout : BaseHook() { + companion object { + const val TAG = "HLongPressLayout" + } + + private val config + get() = ConfigV1.get() + + private var lastTouchDown: Long = 0 + private var numberOfTaps: Int = 0 + private var lastTapTimeMs: Long = 0 + private var touchDownX: Float = 0f + private var touchDownY: Float = 0f + + private val LONG_PRESS_TIME = ViewConfiguration.getLongPressTimeout() + private val DOUBLE_TAP_TIME = ViewConfiguration.getDoubleTapTimeout() + + private var longPressRunnable: Runnable? = null + + private var aweme: Aweme? = null + + override fun setTargetClass(): Class<*> { + return findClass("com.ss.android.ugc.aweme.feed.ui.LongPressLayout") + } + + @OnAfter("onTouchEvent") + fun onTouchEventAfter(params: XC_MethodHook.MethodHookParam, event: MotionEvent) { + hookBlockRunning(params) { + longPressRunnable = longPressRunnable ?: Runnable { onLongPress(thisView, event) } + + when (event.action) { + MotionEvent.ACTION_DOWN -> { + longPressRunnable?.let { handler.postDelayed(it, LONG_PRESS_TIME.toLong()) } + touchDownX = event.x + touchDownY = event.y + lastTouchDown = System.currentTimeMillis() + } + + MotionEvent.ACTION_UP -> { + longPressRunnable?.let { + handler.removeCallbacks(it) + longPressRunnable = null + } + + if (System.currentTimeMillis() - lastTouchDown < LONG_PRESS_TIME) { + // onClick(thisView,event) + if (System.currentTimeMillis() - lastTapTimeMs < DOUBLE_TAP_TIME) { + if (numberOfTaps == 1) { + onDoubleClick(thisView, event) + numberOfTaps = 0 + } + } else { + numberOfTaps = 1 + } + lastTapTimeMs = System.currentTimeMillis() + } + } + + else -> { + if (isMoved(event) || event.action == MotionEvent.ACTION_CANCEL) { + longPressRunnable?.let { + handler.removeCallbacks(it) + longPressRunnable = null + } + } + } + } + + }.onFailure { + XplerLog.tagE(TAG, it) + } + } + + private fun isMoved(event: MotionEvent): Boolean { + return Math.abs(touchDownX - event.x) > 10 || Math.abs(touchDownY - event.y) > 10 + } + + private fun onClick(view: View, event: MotionEvent) { + + } + + private fun onDoubleClick(view: View, event: MotionEvent) { + if (!config.isDoubleClickType) + return + + val holderRootView = view.firstParentOrNull(VideoViewHolderRootView::class.java) ?: return + when (config.doubleClickType) { + 1 -> onClickView(holderRootView, targetContent = Regex("评论(.*?),按钮"))// 打开评论区 + 2 -> {} // 点赞 + } + } + + private fun onLongPress(view: View, event: MotionEvent) { + // 预留出长按快进 + if (event.x < screenSize.width / 8 || event.x > screenSize.width - screenSize.width / 8) + return + + if (!config.isNeatMode) + return + + if (!config.longPressMode) { + if (event.y < screenSize.height / 2) + return + } else { + if (event.y > screenSize.height / 2) + return + } + + val holderRootView = view.firstParentOrNull(VideoViewHolderRootView::class.java) ?: return + showOptionsMenu(holderRootView) + } + + private fun showOptionsMenu(view: ViewGroup) { + if (HDetailPageFragment.isComment) return + + val items = getChoiceItems(view).toTypedArray() + showChoiceDialog( + context = view.context, + title = "Freedom+", + items = items, + onChoice = { _, item, _ -> + onChoiceSelected(view, item) + } + ) + } + + private fun getChoiceItems(view: View): List { + val items = mutableListOf("评论", "收藏", "分享") + + if (config.isDownload) { + items.add("下载") + } + + if (config.isNeatMode) { + items.add(0, if (!config.neatModeState) "清爽模式" else "普通模式") + } + + if (config.isVideoFilter) { + items.add("过滤统计") + } + + if (!config.isDisablePlugin) { + items.add("模块设置") + } + return items + } + + private fun onChoiceSelected(view: View, item: CharSequence) { + when (item) { + "清爽模式", "普通模式" -> { + config.neatModeState = !config.neatModeState + toggleView(view, !config.neatModeState) + showToast(view.context, if (config.neatModeState) "清爽模式" else "普通模式") + } + + "评论" -> { + onClickView(view, targetContent = Regex("评论(.*?),按钮")) + } + + "收藏" -> { + onClickView(view, targetContent = Regex("收藏(.*?),按钮")) + } + + "分享" -> { + onClickView(view, targetContent = Regex("分享(.*?),按钮")) + } + + "下载" -> { + DownloadLogic( + hook = this@HLongPressLayout, + context = view.context, + aweme = aweme, + ) + } + + "过滤统计" -> { + val builder = StringBuilder() + if (HVerticalViewPager.filterLiveCount > 0) { + builder.append("直播过滤: ") + .append(HVerticalViewPager.filterLiveCount) + .append("\n") + } + if (HVerticalViewPager.filterImageCount > 0) { + builder.append("图文过滤: ") + .append(HVerticalViewPager.filterImageCount) + .append("\n") + } + if (HVerticalViewPager.filterAdCount > 0) { + builder.append("广告过滤: ") + .append(HVerticalViewPager.filterAdCount) + .append("\n") + } + if (HVerticalViewPager.filterLongVideoCount > 0) { + builder.append("长视频过滤: ") + .append(HVerticalViewPager.filterLongVideoCount) + .append("\n") + } + if (HVerticalViewPager.filterRecommendedCardsCount > 0) { + builder.append("推荐卡片过滤: ") + .append(HVerticalViewPager.filterRecommendedCardsCount) + .append("\n") + } + if (HVerticalViewPager.filterRecommendedMerchantsCount > 0) { + builder.append("推荐商家过滤: ") + .append(HVerticalViewPager.filterRecommendedMerchantsCount) + .append("\n") + } + if (HVerticalViewPager.filterEmptyDescCount > 0) { + builder.append("空文案过滤: ") + .append(HVerticalViewPager.filterEmptyDescCount) + .append("\n") + } + if (HVerticalViewPager.filterOtherCount > 0) { + builder.append("关键字过滤: ").append(HVerticalViewPager.filterOtherCount) + } + val msg = builder.toString().trim() + if (msg.isEmpty()) { + showToast(view.context, "未过滤视频") + } else { + showToast(view.context, msg) + } + } + + "模块设置" -> { + val intent = Intent().setClass(view.context, FreedomSettingActivity::class.java) + intent.putExtra("isDark", view.context.isDarkMode) + val options = ActivityOptions.makeCustomAnimation( + activeActivity, + android.R.anim.slide_in_left, + android.R.anim.slide_out_right + ) + view.context.startActivity(intent, options.toBundle()) + } + } + } + + private fun toggleView(view: View, visible: Boolean) { + val viewHolderRootView = view as VideoViewHolderRootView + val monitorScrollFrameLayout = viewHolderRootView.children.lastOrNull { + it.javaClass.name.contains("MonitorScrollFrameLayout") + }?.asOrNull() + + // 清爽模式 + monitorScrollFrameLayout?.children?.forEach { + if (it is PenetrateTouchRelativeLayout) { + it.isVisible = visible + } + } + + HMainActivity.toggleView(visible) + } + + private fun onClickView( + parent: View, + targetView: Class? = null, + targetText: Regex = Regex(""), + targetHint: Regex = Regex(""), + targetContent: Regex = Regex(""), + ) { + parent.forEachChild { + var needClick = false + if (it is TextView) { + needClick = "${it.text}".containsNotEmpty(targetText) + needClick = needClick || "${it.hint}".containsNotEmpty(targetHint) + } + needClick = needClick || "${it.contentDescription}".containsNotEmpty(targetContent) + needClick = needClick || (targetView?.isInstance(it) ?: false) + if (!needClick) return@forEachChild + // KLogCat.d("找到: \n${this}") + + // 是否具有点击事件 + val onClickListener = KViewUtils.getOnClickListener(it) + if (onClickListener != null) { + if (!KViewUtils.isFastClick(200L)) { + onClickListener.onClick(it) + } + return@forEachChild + } + // KLogCat.d("没有点击事件") + + // 模拟手势 + val location = IntArray(2) { 0 } + it.getLocationOnScreen(location) + if (location[1] > 0 && location[1] < screenSize.height) { // 是否在屏幕内 + location[0] = location[0] + it.right / 2 + location[1] = location[1] + it.bottom / 2 + if (!KViewUtils.isFastClick(200)) { + KAutomationUtils.simulateClickByView(it, location[0].toFloat(), location[1].toFloat()) + } + return@forEachChild + } + } + } + + private fun CharSequence.containsNotEmpty(regex: Regex): Boolean { + if (regex.pattern.isEmpty()) return false + return contains(regex) + } + + override fun onInit() { + + // 长按菜单事件拦截 + DexkitBuilder.longPressEventClazz?.also { + lpparam.hookClass(it) + .method( + "onLongPressAwemeSure", + Float::class.java, + Float::class.java, + ) { + onBefore { + val x = args[0] as Float + val y = args[1] as Float + + if (!config.isNeatMode) + return@onBefore + + aweme = thisObject.fieldGet(type = Aweme::class.java)?.asOrNull() + ?: HVideoViewHolder.aweme + + if (config.longPressMode) { + if (y < screenSize.height / 2) + result = Void.TYPE + } else { + if (y > screenSize.height / 2) + result = Void.TYPE + } + } + } + } + + // 双击点赞事件拦截 + DexkitBuilder.doubleClickEventClazz?.also { + lpparam.hookClass(it) + .methodAllByParamTypes( + View::class.java, + MotionEvent::class.java, + MotionEvent::class.java, + MotionEvent::class.java, + ) { + onBefore { + if (!config.isDoubleClickType) + return@onBefore + + if (config.doubleClickType == 1) + result = Void.TYPE + } + } + } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/HMainActivity.kt b/core/src/main/java/io/github/fplus/core/hook/HMainActivity.kt index 09fe000..433059d 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HMainActivity.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HMainActivity.kt @@ -8,10 +8,13 @@ import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.View +import android.view.ViewGroup +import android.widget.RelativeLayout import androidx.core.view.isVisible import com.freegang.extension.appVersionCode import com.freegang.extension.appVersionName import com.freegang.extension.contentView +import com.freegang.extension.firstParentOrNull import com.freegang.extension.forEachChild import com.freegang.extension.is64BitDalvik import com.freegang.extension.isDarkMode @@ -19,6 +22,7 @@ import com.freegang.extension.parentView import com.freegang.extension.postRunning import com.freegang.extension.removeInParent import com.ss.android.ugc.aweme.homepage.ui.titlebar.MainTitleBar +import com.ss.android.ugc.aweme.homepage.ui.view.MainTabStripScrollView import com.ss.android.ugc.aweme.main.MainActivity import de.robv.android.xposed.XC_MethodHook import io.github.fplus.core.base.BaseHook @@ -87,7 +91,7 @@ class HMainActivity : BaseHook() { hookBlockRunning(params) { val activity = thisActivity XplerLog.d("version: ${KtXposedHelpers.moduleVersionName(activity)} - ${activity.appVersionName}(${activity.appVersionCode})") - DouYinMain.timedExitCountDown?.restart() + DouYinMain.timerExitHelper?.restart() }.onFailure { XplerLog.e(it) } @@ -96,9 +100,11 @@ class HMainActivity : BaseHook() { @OnAfter("onResume") fun onResume(params: XC_MethodHook.MethodHookParam) { hookBlockRunning(params) { - addClipboardListener(thisActivity) - initView(thisActivity) - is32BisTips(thisActivity) + val activity = thisObject as Activity + + addClipboardListener(activity) + initView(activity) + is32BisTips(activity) }.onFailure { XplerLog.e(it) } @@ -117,7 +123,7 @@ class HMainActivity : BaseHook() { private fun addClipboardListener(activity: Activity) { if (!config.isDownload) return - if (!config.isCopyDownload) return + if (!config.copyLinkDownload) return clipboardLogic.addClipboardListener(activity) { clipData, firstText -> DownloadLogic( @@ -135,6 +141,16 @@ class HMainActivity : BaseHook() { private fun initView(activity: Activity) { activity.contentView.postRunning { it.forEachChild { child -> + // 新版本顶栏不居中 + if (child is MainTabStripScrollView) { + val lp = child.layoutParams + if (lp is RelativeLayout.LayoutParams) { + child.layoutParams = lp.apply { + this.addRule(RelativeLayout.CENTER_IN_PARENT) + } + } + } + if (child is MainTitleBar) { mainTitleBar = child } @@ -157,17 +173,18 @@ class HMainActivity : BaseHook() { private fun initMainTitleBar() { // 隐藏顶部选项卡 if (config.isHideTopTab) { - val hideTabKeywords = config.hideTopTabKeywords - .removePrefix(",").removePrefix(",") - .removeSuffix(",").removeSuffix(",") + val keywordsRegex = config.hideTopTabKeywords + .replace(",", ",") .replace("\\s".toRegex(), "") - .replace("[,,]".toRegex(), "|") + .removePrefix(",").removeSuffix(",") + .replace(",", "|") + .replace("\\|+".toRegex(), "|") .toRegex() - mainTitleBar?.forEachChild { - if (config.isHideTopTab) { - if ("${it.contentDescription}".contains(hideTabKeywords)) { - it.isVisible = false - } + + mainTitleBar?.forEachChild { child -> + val desc = "${child.contentDescription}" + if (desc.contains(keywordsRegex)) { + child.isVisible = false } } } @@ -180,6 +197,28 @@ class HMainActivity : BaseHook() { } private fun initBottomTabView() { + // 隐藏底部选项卡 + if (config.isHideBottomTab) { + val keywordsRegex = config.hideBottomTabKeywords + .replace(",", ",") + .replace("\\s".toRegex(), "") + .removePrefix(",").removeSuffix(",") + .replace(",".toRegex(), "|") + .replace("\\|+".toRegex(), "|") + .toRegex() + + bottomTabView?.forEachChild { child -> + val desc = "${child.contentDescription}" + if (desc.contains(keywordsRegex)) { + val tabItem = child.firstParentOrNull(ViewGroup::class.java) { parent -> + parent.javaClass.name.startsWith("X") + } + + tabItem?.isVisible = false + } + } + } + // 底部导航栏透明度 if (config.isTranslucent) { val alphaValue = config.translucentValue[3] / 100f @@ -196,22 +235,23 @@ class HMainActivity : BaseHook() { } private fun initDisallowInterceptRelativeLayout() { - if (config.isImmersive) { - disallowInterceptRelativeLayout?.postRunning { - runCatching { - it.forEachChild { child -> - // 移除顶部间隔 - if (child.javaClass.name == "android.view.View") { - child.removeInParent() - } - // 移除底部间隔 - if (child.javaClass.name == "com.ss.android.ugc.aweme.feed.ui.bottom.BottomSpace") { - child.removeInParent() - } + if (!config.isImmersive) + return + + disallowInterceptRelativeLayout?.postRunning { + runCatching { + it.forEachChild { child -> + // 移除顶部间隔 + if (child.javaClass.name == "android.view.View") { + child.removeInParent() + } + // 移除底部间隔 + if (child.javaClass.name == "com.ss.android.ugc.aweme.feed.ui.bottom.BottomSpace") { + child.removeInParent() } - }.onFailure { - XplerLog.e(it) } + }.onFailure { + XplerLog.e(it) } } } diff --git a/core/src/main/java/io/github/fplus/core/hook/HMainBottomTabItem.kt b/core/src/main/java/io/github/fplus/core/hook/HMainBottomPhotoTab.kt similarity index 74% rename from core/src/main/java/io/github/fplus/core/hook/HMainBottomTabItem.kt rename to core/src/main/java/io/github/fplus/core/hook/HMainBottomPhotoTab.kt index 37bd05a..2086a45 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HMainBottomTabItem.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HMainBottomPhotoTab.kt @@ -16,15 +16,15 @@ import io.github.xpler.core.log.XplerLog import io.github.xpler.core.thisView -class HMainBottomTabItem : BaseHook() { +class HMainBottomPhotoTab : BaseHook() { companion object { - const val TAG = "HMainBottomTabItem" + const val TAG = "HMainBottomPhotoTab" } val config get() = ConfigV1.get() override fun setTargetClass(): Class<*> { - return DexkitBuilder.mainBottomTabItemClazz ?: NoneHook::class.java + return DexkitBuilder.mainBottomPhotoTabClazz ?: NoneHook::class.java } @OnAfter @@ -41,8 +41,8 @@ class HMainBottomTabItem : BaseHook() { return } - view.forEachChild { - if ("${it.contentDescription}".contains(Regex("拍摄|道具"))) { + view.forEachChild { child -> + if ("${child.contentDescription}".contains(Regex("拍摄|道具"))) { // 隐藏按钮 if (config.photoButtonType == 2) { view.isVisible = false @@ -50,10 +50,10 @@ class HMainBottomTabItem : BaseHook() { } // 占位按钮, 移除加号图标 - if (it is ImageView) { - it.setImageDrawable(null) - it.background = null - it.foreground = null + if (child is ImageView) { + child.setImageDrawable(null) + child.background = null + child.foreground = null } // 允许拍摄直接结束逻辑 @@ -64,10 +64,10 @@ class HMainBottomTabItem : BaseHook() { // 不允许拍摄 view.setOnClickListener { - KToastUtils.show(it.context, "已禁止拍摄") + KToastUtils.show(child.context, "已禁止拍摄") } view.setOnLongClickListener { - KToastUtils.show(it.context, "已禁止拍摄") + KToastUtils.show(child.context, "已禁止拍摄") true } } diff --git a/core/src/main/java/io/github/fplus/core/hook/HMainTabStripScrollView.kt b/core/src/main/java/io/github/fplus/core/hook/HMainTabStripScrollView.kt index a9613d1..2fc1439 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HMainTabStripScrollView.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HMainTabStripScrollView.kt @@ -24,10 +24,10 @@ class HMainTabStripScrollView : BaseHook(), override fun callOnAfterMethods(params: XC_MethodHook.MethodHookParam) { hookBlockRunning(params) { - // 透明度 - if (config.isTranslucent) { - thisViewGroup.firstParentOrNull(MainTitleBar::class.java)?.alpha = config.translucentValue[0] / 100f - } + if (!config.isTranslucent) + return + + thisViewGroup.firstParentOrNull(MainTitleBar::class.java)?.alpha = config.translucentValue[0] / 100f }.onFailure { XplerLog.e(it) } diff --git a/core/src/main/java/io/github/fplus/core/hook/HPenetrateTouchRelativeLayout.kt b/core/src/main/java/io/github/fplus/core/hook/HPenetrateTouchRelativeLayout.kt index 25cfc96..ba70448 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HPenetrateTouchRelativeLayout.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HPenetrateTouchRelativeLayout.kt @@ -31,7 +31,7 @@ class HPenetrateTouchRelativeLayout : BaseHook(), return } - if (visibility == View.GONE || visibility == View.INVISIBLE) { + if (visibility == View.GONE/* || visibility == View.INVISIBLE*/) { return } @@ -47,6 +47,15 @@ class HPenetrateTouchRelativeLayout : BaseHook(), } } + @OnBefore + fun methodBefore(params: XC_MethodHook.MethodHookParam, visibility: Int, string: String?) { + hookBlockRunning(params) { + setVisibilityBefore(params, visibility) + }.onFailure { + XplerLog.tagE(TAG, it) + } + } + override fun callOnBeforeMethods(params: XC_MethodHook.MethodHookParam) { } diff --git a/core/src/main/java/io/github/fplus/core/hook/HPlayerController.kt b/core/src/main/java/io/github/fplus/core/hook/HPlayerController.kt index 2909bbb..a4a9a12 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HPlayerController.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HPlayerController.kt @@ -1,8 +1,12 @@ package io.github.fplus.core.hook -import com.freegang.extension.methodInvoke +import android.view.View +import androidx.core.view.isVisible +import com.freegang.extension.asOrNull +import com.freegang.extension.fields import com.freegang.extension.methods import com.ss.android.ugc.aweme.feed.adapter.VideoViewHolder +import com.ss.android.ugc.aweme.feed.ui.PenetrateTouchRelativeLayout import de.robv.android.xposed.XC_MethodHook import io.github.fplus.core.base.BaseHook import io.github.fplus.core.config.ConfigV1 @@ -73,11 +77,12 @@ class HPlayerController : BaseHook() { } } - // @OnBefore("onPlayCompletedFirstTime") + @OnBefore("onPlayCompletedFirstTime") fun onPlayCompletedFirstTimeAfter(params: XC_MethodHook.MethodHookParam, aid: String?) { hookBlockRunning(params) { // isPlaying = false // callOpenCleanMode(params, false) + // onSwipeUp(params) }.onFailure { XplerLog.e(it) } @@ -107,12 +112,33 @@ class HPlayerController : BaseHook() { return } - val methodFirst = params.thisObject.methods(returnType = VideoViewHolder::class.java) + val method = params.thisObject + .methods(returnType = VideoViewHolder::class.java) .firstOrNull { it.parameterTypes.isEmpty() } - val videoViewHolder = methodFirst?.invoke(params.thisObject) - videoViewHolder?.methodInvoke(name = "openCleanMode", args = arrayOf(bool)) + val videoViewHolder = method?.invoke(params.thisObject) - // + val field = videoViewHolder + ?.fields(type = PenetrateTouchRelativeLayout::class.java) + ?.firstOrNull { it.type == PenetrateTouchRelativeLayout::class.java } + val view = field?.get(videoViewHolder)?.asOrNull() + + // toggle + view?.isVisible = !bool HMainActivity.toggleView(!bool) } + + private fun onSwipeUp(params: XC_MethodHook.MethodHookParam) { + val method = params.thisObject + .methods(returnType = VideoViewHolder::class.java) + .firstOrNull { it.parameterTypes.isEmpty() } + val videoViewHolder = method?.invoke(params.thisObject) + + val fields = videoViewHolder + ?.fields(type = View::class.java) + + fields?.map { "\nfield: ${it}\nvalue: ${it.get(videoViewHolder)}" } + ?.let { + XplerLog.d(*it.toTypedArray()) + } + } } \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/HPoiCreateInstanceImpl.kt b/core/src/main/java/io/github/fplus/core/hook/HPoiCreateInstanceImpl.kt deleted file mode 100644 index d2b7474..0000000 --- a/core/src/main/java/io/github/fplus/core/hook/HPoiCreateInstanceImpl.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.github.fplus.core.hook - -import android.app.Activity -import android.view.ViewGroup -import de.robv.android.xposed.XC_MethodHook -import io.github.fplus.core.base.BaseHook -import io.github.fplus.core.helper.DexkitBuilder -import io.github.xpler.core.entity.EmptyHook -import io.github.xpler.core.entity.KeepParam -import io.github.xpler.core.entity.NoneHook -import io.github.xpler.core.entity.OnBefore -import io.github.xpler.core.entity.Param -import io.github.xpler.core.hookBlockRunning -import io.github.xpler.core.log.XplerLog -import org.json.JSONObject - -class HPoiCreateInstanceImpl : BaseHook() { - companion object { - const val TAG = "HPoiCreateInstanceImpl" - } - - override fun setTargetClass(): Class<*> { - return DexkitBuilder.poiCreateInstanceImplClazz ?: NoneHook::class.java - } - - @OnBefore - fun testBefore( - params: XC_MethodHook.MethodHookParam, - @KeepParam obj: Any?, - @Param("com.ss.android.ugc.aweme.poi.anchor.AnchorType") anchorType: Any?, - parent: ViewGroup?, - eventType: String?, - activity: Activity?, - @KeepParam obj1: Any?, - json: JSONObject?, - ) { - hookBlockRunning(params) { - result = null - }.onFailure { - XplerLog.e(it) - } - } -} \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/HVerticalViewPager.kt b/core/src/main/java/io/github/fplus/core/hook/HVerticalViewPager.kt index e43ea2e..3d4a835 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HVerticalViewPager.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HVerticalViewPager.kt @@ -6,6 +6,7 @@ import com.freegang.extension.fieldGet import com.freegang.extension.fieldSet import com.freegang.extension.methodInvoke import com.freegang.ktutils.app.KToastUtils +import com.freegang.ktutils.log.KLogCat import com.freegang.ktutils.text.KTextUtils import com.ss.android.ugc.aweme.common.widget.VerticalViewPager import com.ss.android.ugc.aweme.feed.model.Aweme @@ -54,6 +55,7 @@ class HVerticalViewPager : BaseHook() { config.videoFilterKeywords .replace(",", ",") .replace("\\s".toRegex(), "") + .removePrefix(",").removeSuffix(",") .split(",") .toSet() } @@ -62,57 +64,12 @@ class HVerticalViewPager : BaseHook() { filterKeywordsAndTypes .filter { !config.videoFilterTypes.contains(it) } .joinToString("|") + .replace("\\|+".toRegex(), "|") .toRegex() } private var durationRunnable: Runnable? = null - override fun onInit() { - DexkitBuilder.recommendFeedFetchPresenterClazz?.runCatching { - lpparam.hookClass(this) - .method("onSuccess") { - onBefore { - if (!config.isVideoFilter) return@onBefore - - val mModel = thisObject.fieldGet(name = "mModel") - val mData = mModel?.fieldGet(name = "mData") - if (mData?.javaClass?.name?.contains("FeedItemList") == true) { - val items = mData.fieldGet(name = "items")?.asOrNull>() ?: emptyList() - if (items.size < 3) return@onBefore - - mData.fieldSet(name = "items", filterAwemeList(items)) - // val array = items.map { it.sortString() }.toTypedArray() - // XplerLog.tagD(TAG, arrayOf("推荐视频列表", array.joinToString("\n"))) - } - } - } - }?.onFailure { - XplerLog.e(it) - } - - DexkitBuilder.fullFeedFollowFetchPresenterClazz?.runCatching { - lpparam.hookClass(this) - .method("onSuccess") { - onBefore { - if (!config.isVideoFilter) return@onBefore - - val mModel = thisObject.fieldGet(name = "mModel") - val mData = mModel?.fieldGet(name = "mData") - if (mData?.javaClass?.name?.contains("FollowFeedList") == true) { - val mItems = mData.fieldGet(name = "mItems")?.asOrNull>() ?: emptyList() - if (mItems.size < 3) return@onBefore - - mData.fieldSet("mItems", filterFollowFeedList(mItems)) - // val array = mItems.map { it.aweme.sortString() }.toTypedArray() - // XplerLog.tagD(TAG, arrayOf("关注视频列表", array.joinToString("\n"))) - } - } - } - }?.onFailure { - XplerLog.e(it) - } - } - @OnAfter("onInterceptTouchEvent") fun onInterceptTouchEvent(params: XC_MethodHook.MethodHookParam, event: MotionEvent) { longVideoJudge(params, event) @@ -247,7 +204,8 @@ class HVerticalViewPager : BaseHook() { null } - keywordsRegex.pattern.isNotBlank() && KTextUtils.get(aweme.desc).contains(keywordsRegex) -> { + keywordsRegex.pattern.trim().isNotEmpty() + && KTextUtils.get(aweme.desc).contains(keywordsRegex) -> { HVerticalViewPager.filterOtherCount += 1 null } @@ -255,4 +213,50 @@ class HVerticalViewPager : BaseHook() { else -> aweme } } + + override fun onInit() { + DexkitBuilder.recommendFeedFetchPresenterClazz?.runCatching { + lpparam.hookClass(this) + .method("onSuccess") { + onBefore { + if (!config.isVideoFilter) return@onBefore + + val mModel = thisObject.fieldGet(name = "mModel") + val mData = mModel?.fieldGet(name = "mData") + if (mData?.javaClass?.name?.contains("FeedItemList") == true) { + val items = mData.fieldGet(name = "items")?.asOrNull>() ?: emptyList() + if (items.size < 3) return@onBefore + + mData.fieldSet(name = "items", filterAwemeList(items)) + // val array = items.map { it.sortString() }.toTypedArray() + // XplerLog.tagD(TAG, arrayOf("推荐视频列表", array.joinToString("\n"))) + } + } + } + }?.onFailure { + XplerLog.e(it) + } + + DexkitBuilder.fullFeedFollowFetchPresenterClazz?.runCatching { + lpparam.hookClass(this) + .method("onSuccess") { + onBefore { + if (!config.isVideoFilter) return@onBefore + + val mModel = thisObject.fieldGet(name = "mModel") + val mData = mModel?.fieldGet(name = "mData") + if (mData?.javaClass?.name?.contains("FollowFeedList") == true) { + val mItems = mData.fieldGet(name = "mItems")?.asOrNull>() ?: emptyList() + if (mItems.size < 3) return@onBefore + + mData.fieldSet("mItems", filterFollowFeedList(mItems)) + // val array = mItems.map { it.aweme.sortString() }.toTypedArray() + // XplerLog.tagD(TAG, arrayOf("关注视频列表", array.joinToString("\n"))) + } + } + } + }?.onFailure { + XplerLog.e(it) + } + } } \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/HVideoPlayerHelper.kt b/core/src/main/java/io/github/fplus/core/hook/HVideoPlayerHelper.kt index 114bb74..309e4e4 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HVideoPlayerHelper.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HVideoPlayerHelper.kt @@ -10,6 +10,7 @@ import io.github.xpler.core.entity.OnBefore import io.github.xpler.core.hookBlockRunning import io.github.xpler.core.log.XplerLog +@Deprecated("旧逻辑暂存") class HVideoPlayerHelper : BaseHook() { companion object { const val TAG = "HVideoPlayerHelper" diff --git a/core/src/main/java/io/github/fplus/core/hook/HVideoViewHolder.kt b/core/src/main/java/io/github/fplus/core/hook/HVideoViewHolder.kt index 689772a..f65a211 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HVideoViewHolder.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HVideoViewHolder.kt @@ -13,27 +13,22 @@ import com.freegang.extension.fieldGets import com.freegang.extension.firstParentOrNull import com.freegang.extension.forEachChild import com.freegang.extension.getSiblingViewAt -import com.freegang.extension.method import com.freegang.extension.methodInvoke import com.freegang.extension.postRunning -import com.ss.android.ugc.aweme.feed.adapter.VideoViewHolder import com.ss.android.ugc.aweme.feed.model.Aweme import com.ss.android.ugc.aweme.feed.ui.FeedRightScaleView import com.ss.android.ugc.aweme.feed.ui.PenetrateTouchRelativeLayout import de.robv.android.xposed.XC_MethodHook -import de.robv.android.xposed.XposedBridge import io.github.fplus.core.base.BaseHook import io.github.fplus.core.config.ConfigV1 import io.github.fplus.core.helper.DexkitBuilder +import io.github.xpler.core.entity.NoneHook import io.github.xpler.core.entity.OnAfter import io.github.xpler.core.entity.OnBefore -import io.github.xpler.core.hook import io.github.xpler.core.hookBlockRunning import io.github.xpler.core.log.XplerLog -import io.github.xpler.core.wrapper.CallConstructors -class HVideoViewHolder : BaseHook(), - CallConstructors { +class HVideoViewHolder : BaseHook() { companion object { const val TAG = "HVideoViewHolder" @@ -49,13 +44,18 @@ class HVideoViewHolder : BaseHook(), private val videoOptionBarFilterKeywords by lazy { config.videoOptionBarFilterKeywords - .removePrefix(",").removePrefix(",") - .removeSuffix(",").removeSuffix(",") + .replace(",", ",") .replace("\\s".toRegex(), "") - .replace("[,,]".toRegex(), "|") + .removePrefix(",").removeSuffix(",") + .replace(",".toRegex(), "|") + .replace("\\|+".toRegex(), "|") .toRegex() } + override fun setTargetClass(): Class<*> { + return DexkitBuilder.videoViewHolderClazz ?: NoneHook::class.java + } + private fun addOnDraw(view: View?) { if (view == null) { XplerLog.d("addOnDraw", "view == null") @@ -101,22 +101,6 @@ class HVideoViewHolder : BaseHook(), XplerLog.d("监听集合", *first.map { "$it" }.toTypedArray()) } - private fun callOpenCleanMode(params: XC_MethodHook.MethodHookParam, bool: Boolean) { - if (!config.isNeatMode) { - return - } - - if (!config.neatModeState) { - return - } - - val first = params.thisObject.method(name = "openCleanMode", paramTypes = arrayOf(Boolean::class.java)) - XposedBridge.invokeOriginalMethod(first, params.thisObject, arrayOf(bool)) - - // - HMainActivity.toggleView(!bool) - } - private fun getAllView(params: XC_MethodHook.MethodHookParam): List { val views = params.thisObject.fieldGets(type = View::class.java) return views.asOrNull>() ?: emptyList() @@ -138,17 +122,20 @@ class HVideoViewHolder : BaseHook(), view.postRunning { val isAvatarImageWithLive = videoOptionBarFilterKeywords.pattern.contains("头像") - view.forEachChild { - if (isAvatarImageWithLive && this.javaClass.name.contains("AvatarImageWithLive")) { - it.firstParentOrNull(RelativeLayout::class.java)?.isVisible = false + view.forEachChild { child -> + if (isAvatarImageWithLive && child.javaClass.name.contains("AvatarImageWithLive")) { + val parentView = child.firstParentOrNull(RelativeLayout::class.java) + parentView?.isVisible = false } - if ("${it.contentDescription}".contains(videoOptionBarFilterKeywords)) { - it.firstParentOrNull(FrameLayout::class.java)?.isVisible = false + if ("${child.contentDescription}".contains(videoOptionBarFilterKeywords)) { + val parentView = child.firstParentOrNull(FrameLayout::class.java) + parentView?.isVisible = false } - if (it is TextView && "${it.text}".contains(videoOptionBarFilterKeywords)) { - it.firstParentOrNull(FrameLayout::class.java)?.isVisible = false + if (child is TextView && "${child.text}".contains(videoOptionBarFilterKeywords)) { + val parentView = child.firstParentOrNull(FrameLayout::class.java) + parentView?.isVisible = false } } val isMusicContainer = videoOptionBarFilterKeywords.pattern.contains("音乐") @@ -171,6 +158,36 @@ class HVideoViewHolder : BaseHook(), return params.thisObject.methodInvoke(name = "getContext")?.asOrNull() } + @OnBefore("isCleanMode") + fun isCleanModeBefore(params: XC_MethodHook.MethodHookParam, view: View?, bool: Boolean) { + hookBlockRunning(params) { + if (!config.isNeatMode) + return + + if (!config.neatModeState) + return + + result = Void.TYPE + }.onFailure { + XplerLog.tagE(TAG, it) + } + } + + @OnBefore("openCleanMode") + fun openCleanModeBefore(params: XC_MethodHook.MethodHookParam, bool: Boolean) { + hookBlockRunning(params) { + if (!config.isNeatMode) + return + + if (!config.neatModeState) + return + + result = Void.TYPE + }.onFailure { + XplerLog.tagE(TAG, it) + } + } + @OnAfter("getAweme") fun getAwemeAfter(params: XC_MethodHook.MethodHookParam) { hookBlockRunning(params) { @@ -195,7 +212,6 @@ class HVideoViewHolder : BaseHook(), fun onViewHolderSelectedAfter(params: XC_MethodHook.MethodHookParam, index: Int) { hookBlockRunning(params) { // XplerLog.d("onViewHolderSelected") - callOpenCleanMode(params, true) val container = getWidgetContainer(params) addOnDraw(container) }.onFailure { @@ -206,7 +222,7 @@ class HVideoViewHolder : BaseHook(), @OnAfter("onViewHolderUnSelected") fun onViewHolderUnSelectedAfter(params: XC_MethodHook.MethodHookParam) { hookBlockRunning(params) { - // XplerLog.d("onViewHolderSelected") + // XplerLog.d("onViewHolderUnSelected") val container = getWidgetContainer(params) removeOnDraw(container) }.onFailure { @@ -234,24 +250,4 @@ class HVideoViewHolder : BaseHook(), XplerLog.e(it) } } - - override fun callOnBeforeConstructors(params: XC_MethodHook.MethodHookParam) { - - } - - override fun callOnAfterConstructors(params: XC_MethodHook.MethodHookParam) { - hookBlockRunning(params) { - callOpenCleanMode(params, true) - } - } - - override fun onInit() { - DexkitBuilder.videoViewHolderMethods - .firstOrNull { it.name[0] in 'A'..'Z' } - ?.hook { - onAfter { - callOpenCleanMode(this, true) - } - } - } } \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/HVideoViewHolderRootView.kt b/core/src/main/java/io/github/fplus/core/hook/HVideoViewHolderRootView.kt index 90fbb1c..bb4e5d2 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HVideoViewHolderRootView.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HVideoViewHolderRootView.kt @@ -36,6 +36,7 @@ import io.github.xpler.core.thisView import io.github.xpler.core.thisViewGroup import kotlin.math.abs +@Deprecated("旧逻辑暂存") class HVideoViewHolderRootView : BaseHook() { companion object { const val TAG = "HVideoViewHolderRootView" diff --git a/core/src/main/java/io/github/fplus/core/hook/logic/AwemeExt.kt b/core/src/main/java/io/github/fplus/core/hook/logic/AwemeExt.kt new file mode 100644 index 0000000..b5e2a9f --- /dev/null +++ b/core/src/main/java/io/github/fplus/core/hook/logic/AwemeExt.kt @@ -0,0 +1,33 @@ +package io.github.fplus.core.hook.logic + +import com.ss.android.ugc.aweme.feed.model.Aweme +import com.ss.ugc.aweme.ImageUrlStruct + +fun Aweme.getH265VideoUrlList(): List { + val video = video ?: return emptyList() + return video.playAddrH265?.urlList ?: emptyList() +} + +fun Aweme.getH264VideoUrlList(): List { + val video = video ?: return emptyList() + return video.h264PlayAddr?.urlList ?: emptyList() +} + +fun Aweme.getAutoVideoUrlList(): List { + val video = video ?: return emptyList() + return video.playAddr?.urlList ?: emptyList() +} + +fun Aweme.getImageUrlList(): List { + return images ?: return emptyList() +} + +fun Aweme.getMusicUrlList(): List { + val music = music ?: return emptyList() + return music.playUrl?.urlList ?: emptyList() +} + +fun Aweme.sortString(): String { + val desc = "$desc".replace(Regex("\\s"), "") + return "awemeType=${awemeType}, desc=$desc" +} \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/logic/DownloadLogic.kt b/core/src/main/java/io/github/fplus/core/hook/logic/DownloadLogic.kt index a8cd906..9575012 100644 --- a/core/src/main/java/io/github/fplus/core/hook/logic/DownloadLogic.kt +++ b/core/src/main/java/io/github/fplus/core/hook/logic/DownloadLogic.kt @@ -12,11 +12,11 @@ import com.freegang.extension.secureFilename import com.freegang.ktutils.app.IProgressNotification import com.freegang.ktutils.app.KNotifiUtils import com.freegang.ktutils.app.KToastUtils +import com.freegang.ktutils.log.KLogCat import com.freegang.ktutils.media.KMediaUtils import com.freegang.ktutils.net.KHttpUtils import com.freegang.ktutils.text.KTextUtils import com.ss.android.ugc.aweme.feed.model.Aweme -import com.ss.ugc.aweme.ImageUrlStruct import de.robv.android.xposed.XposedBridge import io.github.fplus.core.base.BaseHook import io.github.fplus.core.config.ConfigV1 @@ -27,12 +27,11 @@ import kotlinx.coroutines.withContext import java.io.File import java.io.FileOutputStream - /// 下载(视频/图文/音乐)逻辑 class DownloadLogic( - private val hook: BaseHook<*>, - private val context: Context, - private val aweme: Aweme?, + val hook: BaseHook<*>, + val context: Context, + val aweme: Aweme?, ) { companion object { @@ -63,7 +62,7 @@ class DownloadLogic( mPureNickname = aweme.author.nickname.pureFileName // mOwnerDir: 如果需要按视频创作者单独创建文件夹: `/外置存储器/Download/Freedom/${video|music|picture}/昵称(账号)` - mOwnerDir = if (config.isOwnerDir) "${mPureNickname}(${mShortId})" else "" + mOwnerDir = if (config.ownerDir) "${mPureNickname}(${mShortId})" else "" // 默认下载路径: `/外置存储器/Download/Freedom/video` mVideoParent = ConfigV1.getFreedomDir(context).child("video") @@ -90,32 +89,12 @@ class DownloadLogic( } } - private fun getVideoUrlList(aweme: Aweme): List { - val video = aweme.video ?: return emptyList() - - return when (config.videoCoding) { - "H265" -> video.playAddrH265?.urlList ?: emptyList() - "H264" -> video.h264PlayAddr?.urlList ?: emptyList() - "Auto" -> video.playAddr?.urlList ?: emptyList() - else -> emptyList() - } - } - - private fun getImageUrlList(aweme: Aweme): List { - return aweme.images ?: return emptyList() - } - - private fun getMusicUrlList(aweme: Aweme): List { - val music = aweme.music ?: return emptyList() - return music.playUrl?.urlList ?: emptyList() - } - /** * 显示下载选择弹层 * @param aweme */ private fun showChoiceDialog(aweme: Aweme) { - val urlList = getImageUrlList(aweme) + val urlList = aweme.getImageUrlList() val items = mutableListOf("文案", if (urlList.isEmpty()) "视频" else "图片", "背景音乐") if (config.isWebDav) { items.add(if (urlList.isEmpty()) "视频(WebDav)" else "图片(WebDav)") @@ -124,7 +103,7 @@ class DownloadLogic( hook.showInputChoiceDialog( context = context, title = "Freedom+", - showInput1 = config.isOwnerDir, + showInput1 = config.ownerDir, input1Hint = "创作者: $mOwnerDir", input1DefaultValue = mOwnerDir, input2Hint = "文件名: $mPureFileName", @@ -175,14 +154,20 @@ class DownloadLogic( * @param aweme */ private fun downloadVideo(aweme: Aweme, isWebDav: Boolean = false) { - val videoUrlList = getVideoUrlList(aweme) + val videoUrlList = when (config.videoCoding) { + "H265" -> aweme.getH265VideoUrlList() + "H264" -> aweme.getH264VideoUrlList() + "Auto" -> aweme.getAutoVideoUrlList() + else -> emptyList() + } + if (videoUrlList.isEmpty()) { hook.showToast(context, "未获取到视频信息") return } // 构建视频文件名 mPureFileName = mPureFileName.secureFilename(".mp4") - if (config.isNotification) { + if (config.notificationDownload) { showDownloadByNotification(videoUrlList, mVideoParent, mPureFileName, isWebDav) } else { showDownloadByDialog(videoUrlList, mVideoParent, mPureFileName, isWebDav) @@ -194,14 +179,14 @@ class DownloadLogic( * @param aweme */ private fun downloadMusic(aweme: Aweme, isWebDav: Boolean = false) { - val musicUrlList = getMusicUrlList(aweme) + val musicUrlList = aweme.getMusicUrlList() if (musicUrlList.isEmpty()) { hook.showToast(context, "未获取到背景音乐") return } // 构建视频文件名 mPureFileName = mPureFileName.secureFilename(".mp3") - if (config.isNotification) { + if (config.notificationDownload) { showDownloadByNotification(musicUrlList, mMusicParent, mPureFileName, isWebDav) } else { showDownloadByDialog(musicUrlList, mMusicParent, mPureFileName, isWebDav) @@ -213,13 +198,13 @@ class DownloadLogic( * @param aweme */ private fun downloadImages(aweme: Aweme, isWebDav: Boolean = false) { - val structList = getImageUrlList(aweme) + val structList = aweme.getImageUrlList() if (structList.isEmpty()) { hook.showToast(context, "未获取到图片信息") return } - if (config.isNotification) { + if (config.notificationDownload) { // 发送通知 hook.showDownloadNotification( context = context, @@ -235,7 +220,7 @@ class DownloadLogic( File(mImageParent.need(), mPureFileName.secureFilename("_${index + 1}.jpg")) val finished = download( - urlStruct.urlList.first(), + urlStruct.urlList.random(), downloadFile, it, "$index/${aweme.images.size} %s%%" @@ -307,7 +292,7 @@ class DownloadLogic( File(mImageParent.need(), mPureFileName.secureFilename("_${index + 1}.jpg")) val finished = download( - urlStruct.urlList.first(), + urlStruct.urlList.random(), downloadFile, notify, "$index/${aweme.images.size} %s%%" @@ -376,7 +361,7 @@ class DownloadLogic( // 下载逻辑 hook.singleLaunchIO(pureFileName) { val downloadFile = File(parentPath.need(), pureFileName) - val finished = download(urlList.first(), downloadFile, it, "下载中 %s%%") + val finished = download(urlList.random(), downloadFile, it, "下载中 %s%%") if (finished) { hook.refresh { val message = if (isWebDav) "下载成功, 正在上传WebDav!" else "下载成功!" @@ -419,7 +404,7 @@ class DownloadLogic( // 下载逻辑 hook.singleLaunchIO(pureFileName) { val downloadFile = File(parentPath.need(), pureFileName) - val finished = download(urlList.first(), downloadFile, notify, "%s%%") + val finished = download(urlList.random(), downloadFile, notify, "%s%%") if (finished) { hook.refresh { dialog.dismiss() diff --git a/core/src/main/java/io/github/fplus/core/hook/logic/SaveAudioLogic.kt b/core/src/main/java/io/github/fplus/core/hook/logic/SaveAudioLogic.kt index 3ee689b..fc50c3b 100644 --- a/core/src/main/java/io/github/fplus/core/hook/logic/SaveAudioLogic.kt +++ b/core/src/main/java/io/github/fplus/core/hook/logic/SaveAudioLogic.kt @@ -33,7 +33,7 @@ class SaveAudioLogic( if (result) { hook.showToast(context, "保存成功!") KMediaUtils.notifyMediaUpdate(context, file.absolutePath) - if (config.isVibrate) hook.vibrate(context, 5L) + if (config.vibrate) hook.vibrate(context, 5L) } else { hook.showToast(context, "保存失败!") } diff --git a/core/src/main/java/io/github/fplus/core/hook/logic/SaveCommentLogic.kt b/core/src/main/java/io/github/fplus/core/hook/logic/SaveCommentLogic.kt index 9ffd7b9..e1b19b3 100644 --- a/core/src/main/java/io/github/fplus/core/hook/logic/SaveCommentLogic.kt +++ b/core/src/main/java/io/github/fplus/core/hook/logic/SaveCommentLogic.kt @@ -64,11 +64,11 @@ class SaveCommentLogic( // 构建保存文件名 hook.showToast(context, "保存图片, 请稍后..") val file = File(parentPath, "${System.currentTimeMillis() / 1000}.png") - val result = KHttpUtils.download(urlList.first(), FileOutputStream(file)) + val result = KHttpUtils.download(urlList.random(), FileOutputStream(file)) if (result) { hook.showToast(context, "保存成功!") KMediaUtils.notifyMediaUpdate(context, file.absolutePath) - if (config.isVibrate) hook.vibrate(context, 5L) + if (config.vibrate) hook.vibrate(context, 5L) } else { hook.showToast(context, "保存失败!") } @@ -84,11 +84,11 @@ class SaveCommentLogic( // 构建保存文件名 hook.showToast(context, "保存视频, 请稍后..") val file = File(parentPath, "${System.currentTimeMillis() / 1000}.mp4") - val result = KHttpUtils.download(urlList.first(), FileOutputStream(file)) + val result = KHttpUtils.download(urlList.random(), FileOutputStream(file)) if (result) { hook.showToast(context, "保存成功!") KMediaUtils.notifyMediaUpdate(context, file.absolutePath) - if (config.isVibrate) hook.vibrate(context, 5L) + if (config.vibrate) hook.vibrate(context, 5L) } else { hook.showToast(context, "保存失败!") } diff --git a/core/src/main/java/io/github/fplus/core/hook/logic/SaveEmojiLogic.kt b/core/src/main/java/io/github/fplus/core/hook/logic/SaveEmojiLogic.kt index a8434d4..ef87ca8 100644 --- a/core/src/main/java/io/github/fplus/core/hook/logic/SaveEmojiLogic.kt +++ b/core/src/main/java/io/github/fplus/core/hook/logic/SaveEmojiLogic.kt @@ -42,11 +42,11 @@ class SaveEmojiLogic( // 构建保存文件名 hook.showToast(context, "保存表情, 请稍后..") val file = File(parentPath, "${System.currentTimeMillis() / 1000}.gif") - val result = KHttpUtils.download(urlList.first(), FileOutputStream(file)) + val result = KHttpUtils.download(urlList.random(), FileOutputStream(file)) if (result) { hook.showToast(context, "保存成功!") KMediaUtils.notifyMediaUpdate(context, file.absolutePath) - if (config.isVibrate) hook.vibrate(context, 5L) + if (config.vibrate) hook.vibrate(context, 5L) } else { hook.showToast(context, "保存失败!") } diff --git a/core/src/main/java/io/github/fplus/core/ui/activity/FreedomSettingActivity.kt b/core/src/main/java/io/github/fplus/core/ui/activity/FreedomSettingActivity.kt index c6feb58..f35ffc3 100644 --- a/core/src/main/java/io/github/fplus/core/ui/activity/FreedomSettingActivity.kt +++ b/core/src/main/java/io/github/fplus/core/ui/activity/FreedomSettingActivity.kt @@ -85,6 +85,7 @@ import kotlinx.coroutines.withContext import kotlin.random.Random import kotlin.system.exitProcess +@OptIn(ExperimentalFoundationApi::class) class FreedomSettingActivity : XplerActivity() { private val model by lazy { ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application)) @@ -277,7 +278,9 @@ class FreedomSettingActivity : XplerActivity() { item { DoubleClickTypeItem() } item { LongtimeVideoToastItem() } item { HideTopTabItem() } + item { HideBottomTabItem() } item { HidePhotoButtonItem() } + item { PreventAccidentalTouch() } item { VideoOptionBarFilterItem() } item { VideoFilterItem() } item { DialogFilterItem() } @@ -286,6 +289,7 @@ class FreedomSettingActivity : XplerActivity() { item { CommentColorModeItem() } item { WebDavItem() } item { TimedExitItem() } + item { CrashToleranceItem() } /*item { DisablePluginItem() }*/ } } @@ -371,27 +375,26 @@ class FreedomSettingActivity : XplerActivity() { text = "视频创作者单独创建文件夹", checked = model.isOwnerDir.observeAsState(false), onCheckedChange = { - model.changeIsOwnerDir(it) + model.setOwnerDir(it) } ) CheckBoxItem( text = "通知栏显示下载进度", checked = model.isNotification.observeAsState(false), onCheckedChange = { - model.changeIsNotification(it) + model.setNotificationDownload(it) } ) CheckBoxItem( text = "“分享->复制链接”弹出下载", checked = model.isCopyDownload.observeAsState(false), onCheckedChange = { - model.changeIsCopyDownload(it) + model.setCopyLinkDownload(it) } ) Divider() - Box { val menus by remember { mutableStateOf(listOf("Auto", "H264", "H265")) } val videoCoding by model.videoCoding.observeAsState("Auto") @@ -401,7 +404,7 @@ class FreedomSettingActivity : XplerActivity() { value = videoCoding, menus = menus, onSelected = { - model.changeVideoCoding(it) + model.setVideoCoding(it) } ) } @@ -422,7 +425,7 @@ class FreedomSettingActivity : XplerActivity() { showSettingDialog = true }, onCheckedChange = { - model.changeIsEmoji(it) + model.changeIsEmojiDownload(it) } ) @@ -440,7 +443,7 @@ class FreedomSettingActivity : XplerActivity() { text = "震动反馈", checked = model.isVibrate.observeAsState(false), onCheckedChange = { - model.changeIsVibrate(it) + model.setVibrate(it) } ) } @@ -478,7 +481,7 @@ class FreedomSettingActivity : XplerActivity() { onlyConfirm = true, onConfirm = { showTransparentDialog = false - model.changeTranslucentValue( + model.setTranslucentValue( listOf( topBarTransparent, videoAssemblyTransparent, @@ -551,6 +554,7 @@ class FreedomSettingActivity : XplerActivity() { private fun RemoveStickerItem() { SwitchItem( text = "移除悬浮挑战/评论贴纸", + subtext = "部分视频出现的悬浮挑战,视频评论回复等控件", checked = model.isRemoveSticker.observeAsState(false), onCheckedChange = { model.changeIsRemoveSticker(it) @@ -575,8 +579,20 @@ class FreedomSettingActivity : XplerActivity() { var showSettingDialog by remember { mutableStateOf(false) } SwitchItem( - text = "消息防撤回", - subtext = "阻止聊天消息撤回,点击调整相关设置", + text = buildAnnotatedString { + append("消息防撤回 ") + withStyle( + SpanStyle( + color = Color.Red, + fontSize = MaterialTheme.typography.body2.fontSize, + ) + ) { + append("(失效不修)") + } + }, + subtext = buildAnnotatedString { + append("阻止聊天消息撤回,点击调整相关设置") + }, checked = model.isPreventRecalled.observeAsState(false), onClick = { showSettingDialog = true @@ -641,26 +657,12 @@ class FreedomSettingActivity : XplerActivity() { onConfirm = { showDoubleClickModeDialog = false }, ) { Column { - Row(verticalAlignment = Alignment.CenterVertically) { - RadioButton( - selected = radioIndex == 0, - onClick = { - radioIndex = 0 - model.changeDoubleClickType(radioIndex) - }, - ) - Spacer(modifier = Modifier.padding(horizontal = 4.dp)) - Text( - text = "暂停视频", - style = MaterialTheme.typography.body1, - ) - } Row(verticalAlignment = Alignment.CenterVertically) { RadioButton( selected = radioIndex == 1, onClick = { radioIndex = 1 - model.changeDoubleClickType(radioIndex) + model.setDoubleClickType(radioIndex) }, ) Spacer(modifier = Modifier.padding(horizontal = 4.dp)) @@ -674,7 +676,7 @@ class FreedomSettingActivity : XplerActivity() { selected = radioIndex == 2, onClick = { radioIndex = 2 - model.changeDoubleClickType(radioIndex) + model.setDoubleClickType(radioIndex) }, ) Spacer(modifier = Modifier.padding(horizontal = 4.dp)) @@ -707,7 +709,7 @@ class FreedomSettingActivity : XplerActivity() { SwitchItem( text = "隐藏顶部选项", - subtext = "隐藏顶部标签页, 点击设置关键字", + subtext = "隐藏顶部标签, 点击设置关键字", checked = model.isHideTopTab.observeAsState(false), onClick = { showKeywordsEditorDialog = true @@ -734,7 +736,7 @@ class FreedomSettingActivity : XplerActivity() { onCancel = { showKeywordsEditorDialog = false }, onConfirm = { showKeywordsEditorDialog = false - model.setHideTabKeywords(hideTabKeywords) + model.setHideTopTabKeywords(hideTabKeywords) }, ) { FCard( @@ -779,6 +781,115 @@ class FreedomSettingActivity : XplerActivity() { } } + @OptIn(ExperimentalFoundationApi::class) + @Composable + private fun HideBottomTabItem() { + var showKeywordsEditorDialog by remember { mutableStateOf(false) } + var showKeywordsTips by remember { mutableStateOf(false) } + + SwitchItem( + text = "隐藏底部选项", + subtext = "隐藏底部选项, 点击设置关键字", + checked = model.isHideBottomTab.observeAsState(false), + onClick = { + showKeywordsEditorDialog = true + }, + onCheckedChange = { + showRestartAppDialog.value = true + model.changeIsHideBottomTab(it) + }, + ) + + if (showKeywordsEditorDialog) { + var hideBottomKeywords by remember { + mutableStateOf( + model.hideBottomTabKeywords.value ?: "" + ) + } + FMessageDialog( + title = { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 16.dp) + ) { + Text( + text = "请输入关键字, 用逗号分开", + style = MaterialTheme.typography.body1, + modifier = Modifier.weight(1f), + ) + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = "帮助", + modifier = Modifier + .size(16.dp) + .combinedClickable( + indication = null, + interactionSource = remember { MutableInteractionSource() }, + onClick = { + showKeywordsTips = true + }, + ), + ) + } + }, + cancel = "取消", + confirm = "确定", + onCancel = { showKeywordsEditorDialog = false }, + onConfirm = { + showKeywordsEditorDialog = false + model.setHideBottomTabKeywords(hideBottomKeywords) + showRestartAppDialog.value = true + }, + ) { + FCard( + border = FCardBorder(borderWidth = 1.0.dp), + ) { + BasicTextField( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp, horizontal = 12.dp), + value = hideBottomKeywords, + maxLines = 1, + singleLine = true, + textStyle = MaterialTheme.typography.body1, + onValueChange = { + hideBottomKeywords = it + }, + ) + } + } + } + + if (showKeywordsTips) { + FMessageDialog( + title = "提示", + onlyConfirm = true, + confirm = "确定", + onConfirm = { + showKeywordsTips = false + } + ) { + Text( + text = buildAnnotatedString { + append("因为底部加号按钮的单独处理选项, ") + append("关键字中出如果现") + withStyle(SpanStyle(Color.Red)) { + append("“拍摄”") + } + append(", 也将隐藏底部拍摄按钮, ") + append("故") + withStyle(SpanStyle(Color.Red)) { + append("“允许/禁止拍摄”") + } + append("将会失效。") + }, + style = MaterialTheme.typography.body1, + ) + } + } + } + @Composable private fun HidePhotoButtonItem() { var showIsDisablePhotoDialog by remember { mutableStateOf(false) } @@ -786,7 +897,7 @@ class FreedomSettingActivity : XplerActivity() { SwitchItem( text = "隐藏底部加号按钮", subtext = "点击更改加号按钮响应状态", - checked = model.isDHidePhotoButton.observeAsState(false), + checked = model.isHidePhotoButton.observeAsState(false), onClick = { showIsDisablePhotoDialog = true }, @@ -813,7 +924,7 @@ class FreedomSettingActivity : XplerActivity() { selected = radioIndex == 0, onClick = { radioIndex = 0 - model.changePhotoButtonType(radioIndex) + model.setPhotoButtonType(radioIndex) }, ) Spacer(modifier = Modifier.padding(horizontal = 4.dp)) @@ -827,7 +938,7 @@ class FreedomSettingActivity : XplerActivity() { selected = radioIndex == 1, onClick = { radioIndex = 1 - model.changePhotoButtonType(radioIndex) + model.setPhotoButtonType(radioIndex) }, ) Spacer(modifier = Modifier.padding(horizontal = 4.dp)) @@ -841,7 +952,7 @@ class FreedomSettingActivity : XplerActivity() { selected = radioIndex == 2, onClick = { radioIndex = 2 - model.changePhotoButtonType(radioIndex) + model.setPhotoButtonType(radioIndex) }, ) Spacer(modifier = Modifier.padding(horizontal = 4.dp)) @@ -855,6 +966,21 @@ class FreedomSettingActivity : XplerActivity() { } } + @Composable + private fun PreventAccidentalTouch() { + SwitchItem( + text = "手势误触复确认", + subtext = "底部首页、头像关注点击时显示复确认弹窗", + checked = model.isPreventAccidentalTouch.observeAsState(false), + onClick = { + + }, + onCheckedChange = { + model.changeIsPreventAccidentalTouch(it) + } + ) + } + @OptIn(ExperimentalFoundationApi::class) @Composable private fun VideoOptionBarFilterItem() { @@ -1188,7 +1314,7 @@ class FreedomSettingActivity : XplerActivity() { text = "弹窗被关闭时提示", checked = model.dialogDismissTips.observeAsState(initial = false), onCheckedChange = { - model.changeDialogDismissTips(it) + model.setDialogDismissTips(it) }, ) } @@ -1252,7 +1378,7 @@ class FreedomSettingActivity : XplerActivity() { RadioButton( selected = longPressMode, onClick = { - model.changeLongPressMode(true) + model.setLongPressMode(true) }, ) Spacer(modifier = Modifier.padding(horizontal = 4.dp)) @@ -1265,7 +1391,7 @@ class FreedomSettingActivity : XplerActivity() { RadioButton( selected = !longPressMode, onClick = { - model.changeLongPressMode(false) + model.setLongPressMode(false) }, ) Spacer(modifier = Modifier.padding(horizontal = 4.dp)) @@ -1306,7 +1432,7 @@ class FreedomSettingActivity : XplerActivity() { onlyConfirm = true, onConfirm = { showSettingDialog = false - model.changeSystemControllerValue( + model.setSystemControllerValue( listOf( isHideStatusBar.value, isHideNavigateBar.value, @@ -1440,12 +1566,12 @@ class FreedomSettingActivity : XplerActivity() { } if (it) { isWebDavWaiting = true - model.initWebDav { test, msg -> + model.testWebDav { test, msg -> KToastUtils.show(application, msg) isWebDavWaiting = false if (test) { model.changeIsWebDav(true) - return@initWebDav + return@testWebDav } model.changeIsWebDav(false) } @@ -1552,14 +1678,14 @@ class FreedomSettingActivity : XplerActivity() { val webDavConfig = WebDav.Config(host, username, password) isWaiting = true model.setWebDavConfig(webDavConfig) - model.initWebDav { test, msg -> + model.testWebDav { test, msg -> KToastUtils.show(application, msg) isWaiting = false if (test) { showWebDavConfigEditorDialog = false model.changeIsWebDav(true) model.addWebDavConfig(webDavConfig) - return@initWebDav + return@testWebDav } model.changeIsWebDav(false) } @@ -1770,7 +1896,7 @@ class FreedomSettingActivity : XplerActivity() { checked = model.keepAppBackend.observeAsState(initial = false), onCheckedChange = { showKeepAppBackendTips = it - model.changeKeepAppBackend(it) + model.setKeepAppBackend(it) }, ) } @@ -1804,6 +1930,22 @@ class FreedomSettingActivity : XplerActivity() { } } + @Composable + private fun CrashToleranceItem() { + SwitchItem( + text = "崩溃容错", + subtext = "尝试对官方部分崩溃逻辑进行拦截,不保证绝对成功", + checked = model.isCrashTolerance.observeAsState(false), + onClick = { + + }, + onCheckedChange = { + model.changeIsCrashTolerance(it) + showRestartAppDialog.value = true + } + ) + } + @Deprecated("暂存区") @Composable private fun DisablePluginItem() { @@ -1930,6 +2072,74 @@ class FreedomSettingActivity : XplerActivity() { } } + @OptIn(ExperimentalFoundationApi::class) + @Composable + private fun SwitchItem( + text: AnnotatedString, + subtext: AnnotatedString = buildAnnotatedString { append("") }, + isWaiting: Boolean = false, + checked: State, + onCheckedChange: (checked: Boolean) -> Unit, + onClick: () -> Unit = {}, + onLongClick: () -> Unit = {}, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 4.dp) + .combinedClickable( + indication = null, + interactionSource = remember { MutableInteractionSource() }, + onClick = { + onClick.invoke() + }, + onLongClick = { + onLongClick.invoke() + } + ) + .then(if (subtext.isNotBlank()) Modifier.padding(vertical = 4.dp) else Modifier), + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + modifier = Modifier.weight(1f), + ) { + Text( + text = text, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.body1, + ) + if (subtext.isNotBlank()) { + Text( + modifier = Modifier.padding(vertical = 2.dp), + text = subtext, + style = MaterialTheme.typography.body2, + ) + } + } + if (isWaiting) { + BoxWithConstraints( + modifier = Modifier + .wrapContentSize(Alignment.Center) + .padding(17.dp), // switch: width = 34.dp + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator( + strokeWidth = 2.dp, + modifier = Modifier.size(MaterialTheme.typography.body1.fontSize.asDp), + ) + } + } else { + Switch( + checked = checked.value, + onCheckedChange = { + onCheckedChange.invoke(it) + }, + ) + } + } + } + @Composable private fun CheckBoxItem( text: String, diff --git a/core/src/main/java/io/github/fplus/core/ui/viewmodel/FreedomSettingVM.kt b/core/src/main/java/io/github/fplus/core/ui/viewmodel/FreedomSettingVM.kt index 85d9f8a..dd06865 100644 --- a/core/src/main/java/io/github/fplus/core/ui/viewmodel/FreedomSettingVM.kt +++ b/core/src/main/java/io/github/fplus/core/ui/viewmodel/FreedomSettingVM.kt @@ -34,23 +34,23 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) private var _isDownload = MutableLiveData(false) val isDownload: LiveData = _isDownload - private var _isOwnerDir = MutableLiveData(false) - val isOwnerDir: LiveData = _isOwnerDir + private var _ownerDir = MutableLiveData(false) + val isOwnerDir: LiveData = _ownerDir - private var _isNotification = MutableLiveData(false) - val isNotification: LiveData = _isNotification + private var _notificationDownload = MutableLiveData(false) + val isNotification: LiveData = _notificationDownload - private var _isCopyDownload = MutableLiveData(false) - val isCopyDownload: LiveData = _isCopyDownload + private var _isCopyLinkDownload = MutableLiveData(false) + val isCopyDownload: LiveData = _isCopyLinkDownload private var _videoCoding = MutableLiveData("") val videoCoding: LiveData = _videoCoding - private var _isEmoji = MutableLiveData(false) - val isEmoji: LiveData = _isEmoji + private var _isEmojiDownload = MutableLiveData(false) + val isEmoji: LiveData = _isEmojiDownload - private var _isVibrate = MutableLiveData(false) - val isVibrate: LiveData = _isVibrate + private var _vibrate = MutableLiveData(false) + val isVibrate: LiveData = _vibrate private var _isTranslucent = MutableLiveData(false) val isTranslucent: LiveData = _isTranslucent @@ -80,28 +80,37 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) val isLongtimeVideoToast: LiveData = _isLongtimeVideoToast private var _isHideTopTab = MutableLiveData(false) - var isHideTopTab: LiveData = _isHideTopTab + val isHideTopTab: LiveData = _isHideTopTab private var _hideTopTabKeywords = MutableLiveData("") - var hideTopTabKeywords: LiveData = _hideTopTabKeywords + val hideTopTabKeywords: LiveData = _hideTopTabKeywords + + private var _isHideBottomTab = MutableLiveData(false) + val isHideBottomTab: LiveData = _isHideBottomTab + + private var _hideBottomTabKeywords = MutableLiveData("") + val hideBottomTabKeywords: LiveData = _hideBottomTabKeywords private var _isHidePhotoButton = MutableLiveData(false) - val isDHidePhotoButton: LiveData = _isHidePhotoButton + val isHidePhotoButton: LiveData = _isHidePhotoButton private var _photoButtonType = MutableLiveData(2) val photoButtonType: LiveData = _photoButtonType + private var _isPreventAccidentalTouch = MutableLiveData(false) + val isPreventAccidentalTouch: LiveData = _isPreventAccidentalTouch + private var _isVideoOptionBarFilter = MutableLiveData(false) val isVideoOptionBarFilter: LiveData = _isVideoOptionBarFilter private var _videoOptionBarFilterKeywords = MutableLiveData("") - var videoOptionBarFilterKeywords: LiveData = _videoOptionBarFilterKeywords + val videoOptionBarFilterKeywords: LiveData = _videoOptionBarFilterKeywords private var _isVideoFilter = MutableLiveData(false) val isVideoFilter: LiveData = _isVideoFilter private var _videoFilterKeywords = MutableLiveData("") - var videoFilterKeywords: LiveData = _videoFilterKeywords + val videoFilterKeywords: LiveData = _videoFilterKeywords private var _isDialogFilter = MutableLiveData(false) val isDialogFilter: LiveData = _isDialogFilter @@ -110,7 +119,7 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) val dialogDismissTips: LiveData = _dialogDismissTips private var _dialogFilterKeywords = MutableLiveData("") - var dialogFilterKeywords: LiveData = _dialogFilterKeywords + val dialogFilterKeywords: LiveData = _dialogFilterKeywords private var _isNeatMode = MutableLiveData(false) val isNeatMode: LiveData = _isNeatMode @@ -131,28 +140,31 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) val commentColorMode: LiveData = _commentColorMode private var _isWebDav = MutableLiveData(false) - var isWebDav: LiveData = _isWebDav + val isWebDav: LiveData = _isWebDav private var _webDavHost = MutableLiveData("") - var webDavHost: LiveData = _webDavHost + val webDavHost: LiveData = _webDavHost private var _webDavUsername = MutableLiveData("") - var webDavUsername: LiveData = _webDavUsername + val webDavUsername: LiveData = _webDavUsername private var _webDavPassword = MutableLiveData("") - var webDavPassword: LiveData = _webDavPassword + val webDavPassword: LiveData = _webDavPassword private var _webDavHistory = MutableLiveData(emptyList()) - var webDavHistory: LiveData> = _webDavHistory + val webDavHistory: LiveData> = _webDavHistory private var _isTimedExit = MutableLiveData(false) - var isTimedExit: LiveData = _isTimedExit + val isTimedExit: LiveData = _isTimedExit private var _timedShutdownValue = MutableLiveData(listOf(10, 3)) - var timedShutdownValue: LiveData> = _timedShutdownValue + val timedShutdownValue: LiveData> = _timedShutdownValue private var _keepAppBackend = MutableLiveData(false) - var keepAppBackend: LiveData = _keepAppBackend + val keepAppBackend: LiveData = _keepAppBackend + + private var _isCrashTolerance = MutableLiveData(false) + val isCrashTolerance: LiveData = _isCrashTolerance private var _isDisablePlugin = MutableLiveData(false) val isDisablePlugin: LiveData = _isDisablePlugin @@ -173,44 +185,48 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) viewModelScope.launch { config = withContext(Dispatchers.IO) { ConfigV1.get() } changeIsDownload(config.isDownload) - changeIsOwnerDir(config.isOwnerDir) - changeIsNotification(config.isNotification) - changeIsCopyDownload(config.isCopyDownload) - changeVideoCoding(config.videoCoding) - changeIsEmoji(config.isEmoji) - changeIsVibrate(config.isVibrate) + setOwnerDir(config.ownerDir) + setNotificationDownload(config.notificationDownload) + setCopyLinkDownload(config.copyLinkDownload) + setVideoCoding(config.videoCoding) + changeIsEmojiDownload(config.isEmojiDownload) + setVibrate(config.vibrate) changeIsTranslucent(config.isTranslucent) - changeTranslucentValue(config.translucentValue) + setTranslucentValue(config.translucentValue) changeIsRemoveSticker(config.isRemoveSticker) changeIsRemoveBottomCtrlBar(config.isRemoveBottomCtrlBar) changeIsPreventRecalled(config.isPreventRecalled) changePreventRecalledOtherSetting(config.preventRecalledOtherSetting) changeIsDoubleClickType(config.isDoubleClickType) - changeDoubleClickType(config.doubleClickType) + setDoubleClickType(config.doubleClickType) changeIsLongtimeVideoToast(config.isLongtimeVideoToast) - changeIsHidePhotoButton(config.isHidePhotoButton) - changePhotoButtonType(config.photoButtonType) changeIsVideoOptionBarFilter(config.isVideoOptionBarFilter) setVideoOptionBarFilterKeywords(config.videoOptionBarFilterKeywords) changeIsVideoFilter(config.isVideoFilter) setVideoFilterKeywords(config.videoFilterKeywords) changeIsDialogFilter(config.isDialogFilter) - changeDialogDismissTips(config.dialogDismissTips) + setDialogDismissTips(config.dialogDismissTips) setDialogFilterKeywords(config.dialogFilterKeywords) changeIsNeatMode(config.isNeatMode) - changeLongPressMode(config.longPressMode) + setLongPressMode(config.longPressMode) changeIsImmersive(config.isImmersive) - changeSystemControllerValue(config.systemControllerValue) + setSystemControllerValue(config.systemControllerValue) changeIsCommentColorMode(config.isCommentColorMode) changeCommentColorMode(config.commentColorMode) changeIsHideTopTab(config.isHideTopTab) - setHideTabKeywords(config.hideTopTabKeywords) + setHideTopTabKeywords(config.hideTopTabKeywords) + changeIsHideBottomTab(config.isHideBottomTab) + setHideBottomTabKeywords(config.hideBottomTabKeywords) + changeIsHidePhotoButton(config.isHidePhotoButton) + setPhotoButtonType(config.photoButtonType) + changeIsPreventAccidentalTouch(config.isPreventAccidentalTouch) changeIsWebDav(config.isWebDav) loadWebHistory() setWebDavConfig(config.webDavConfig) changeIsTimeExit(config.isTimedExit) setTimedShutdownValue(config.timedShutdownValue) - changeKeepAppBackend(config.keepAppBackend) + setKeepAppBackend(config.keepAppBackend) + changeIsCrashTolerance(config.isCrashTolerance) changeIsDisablePlugin(config.isDisablePlugin) } } @@ -222,39 +238,39 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) } // 视频创作者单独创建文件夹 - fun changeIsOwnerDir(value: Boolean) { - _isOwnerDir.value = value - config.isOwnerDir = value + fun setOwnerDir(value: Boolean) { + _ownerDir.value = value + config.ownerDir = value } // 通知栏下载 - fun changeIsNotification(value: Boolean) { - _isNotification.value = value - config.isNotification = value + fun setNotificationDownload(value: Boolean) { + _notificationDownload.value = value + config.notificationDownload = value } // 复制链接时弹出下载 - fun changeIsCopyDownload(value: Boolean) { - _isCopyDownload.value = value - config.isCopyDownload = value + fun setCopyLinkDownload(value: Boolean) { + _isCopyLinkDownload.value = value + config.copyLinkDownload = value } // 视频编码类型 - fun changeVideoCoding(value: String) { + fun setVideoCoding(value: String) { _videoCoding.value = value config.videoCoding = value } // 表情包保存 - fun changeIsEmoji(value: Boolean) { - _isEmoji.value = value - config.isEmoji = value + fun changeIsEmojiDownload(value: Boolean) { + _isEmojiDownload.value = value + config.isEmojiDownload = value } - // 震动反馈保存 - fun changeIsVibrate(value: Boolean) { - _isVibrate.value = value - config.isVibrate = value + // 震动反馈 + fun setVibrate(value: Boolean) { + _vibrate.value = value + config.vibrate = value } // 首页控件半透明 @@ -264,7 +280,7 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) } /// 首页控件透明度 - fun changeTranslucentValue(value: List) { + fun setTranslucentValue(value: List) { _translucentValue.value = value config.translucentValue = value } @@ -287,6 +303,7 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) config.isPreventRecalled = value } + // 防撤回其他设置 fun changePreventRecalledOtherSetting(value: List) { _preventRecalledOtherSetting.value = value config.preventRecalledOtherSetting = value @@ -299,17 +316,41 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) } // 双击响应类型 - fun changeDoubleClickType(value: Int) { + fun setDoubleClickType(value: Int) { _doubleClickType.value = value config.doubleClickType = value } - // 视频时长超过5分钟提示 + // 视频时长超过10分钟提示 fun changeIsLongtimeVideoToast(value: Boolean) { _isLongtimeVideoToast.value = value config.isLongtimeVideoToast = value } + // 隐藏顶部tab + fun changeIsHideTopTab(value: Boolean) { + _isHideTopTab.value = value + config.isHideTopTab = value + } + + // 隐藏顶部tab包含的关键字, 逗号隔开 + fun setHideTopTabKeywords(keywords: String) { + _hideTopTabKeywords.value = keywords + config.hideTopTabKeywords = keywords + } + + // 隐藏底部tab + fun changeIsHideBottomTab(value: Boolean) { + _isHideBottomTab.value = value + config.isHideBottomTab = value + } + + // 隐藏底部tab包含的关键字, 逗号隔开 + fun setHideBottomTabKeywords(keywords: String) { + _hideBottomTabKeywords.value = keywords + config.hideBottomTabKeywords = keywords + } + // 隐藏底部加号按钮 fun changeIsHidePhotoButton(value: Boolean) { _isHidePhotoButton.value = value @@ -317,12 +358,16 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) } // 改变底部加号拍摄模式 - fun changePhotoButtonType(value: Int) { + fun setPhotoButtonType(value: Int) { _photoButtonType.value = value config.photoButtonType = value } - val videoOptionBarFilterTypes get() = config.videoOptionBarFilterTypes + // 手势误触复确认 + fun changeIsPreventAccidentalTouch(value: Boolean) { + _isPreventAccidentalTouch.value = value + config.isPreventAccidentalTouch = value + } // 视频右侧控件栏 fun changeIsVideoOptionBarFilter(value: Boolean) { @@ -330,6 +375,8 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) config.isVideoOptionBarFilter = value } + val videoOptionBarFilterTypes get() = config.videoOptionBarFilterTypes + // 视频过滤关键字 fun setVideoOptionBarFilterKeywords(value: String) { _videoOptionBarFilterKeywords.value = value @@ -357,7 +404,7 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) } // 弹窗关闭提示 - fun changeDialogDismissTips(value: Boolean) { + fun setDialogDismissTips(value: Boolean) { _dialogDismissTips.value = value config.dialogDismissTips = value } @@ -381,13 +428,13 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) } // 系统隐藏项(状态栏、导航栏) - fun changeSystemControllerValue(value: List) { + fun setSystemControllerValue(value: List) { _systemControllerValue.value = value config.systemControllerValue = value } // 清爽模式弹窗响应模式 - fun changeLongPressMode(value: Boolean) { + fun setLongPressMode(value: Boolean) { _longPressMode.value = value config.longPressMode = value } @@ -404,18 +451,6 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) config.commentColorMode = value } - // 隐藏顶部tab - fun changeIsHideTopTab(value: Boolean) { - _isHideTopTab.value = value - config.isHideTopTab = value - } - - // 隐藏顶部tab包含的关键字, 逗号隔开 - fun setHideTabKeywords(hideTabKeywords: String) { - _hideTopTabKeywords.value = hideTabKeywords - config.hideTopTabKeywords = hideTabKeywords - } - // WebDav fun changeIsWebDav(value: Boolean) { _isWebDav.value = value @@ -423,7 +458,7 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) } // 初始化WebDav - fun initWebDav(block: (Boolean, String) -> Unit) { + fun testWebDav(block: (Boolean, String) -> Unit) { if (!hasWebDavConfig()) { block.invoke(false, "请填写WebDav配置!") return @@ -499,11 +534,16 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) } // 保留应用后台 - fun changeKeepAppBackend(value: Boolean) { + fun setKeepAppBackend(value: Boolean) { _keepAppBackend.value = value config.keepAppBackend = value } + fun changeIsCrashTolerance(value: Boolean) { + _isCrashTolerance.value = value + config.isCrashTolerance = value + } + // 去插件化 fun changeIsDisablePlugin(value: Boolean) { _isDisablePlugin.value = value diff --git a/versions.json b/versions.json index 525fe4b..98b4894 100644 --- a/versions.json +++ b/versions.json @@ -1 +1 @@ -["23.5.0","23.6.0","23.7.0","23.8.0","23.9.0","24.0.0","24.1.0","24.2.0","24.3.0","24.4.0","24.5.0","24.6.0","24.7.0","24.8.0","24.9.0","25.0.0","25.1.0","25.2.0","25.3.0","25.4.0","25.5.0","25.6.0","25.7.0","25.8.0","25.9.0","26.0.0","26.1.0","26.2.0","26.3.0","26.4.0","26.5.0","26.6.0","26.7.0","26.8.0","26.9.0","27.0.0","27.1.0","27.2.0","27.3.0","27.4.0","27.5.0","27.6.0","27.7.0","27.8.0","27.9.0","28.0.0","28.1.0","28.2.0","28.3.0","28.4.0","28.5.0","28.6.0","28.7.0","28.8.0"] \ No newline at end of file +["23.5.0","23.6.0","23.7.0","23.8.0","23.9.0","24.0.0","24.1.0","24.2.0","24.3.0","24.4.0","24.5.0","24.6.0","24.7.0","24.8.0","24.9.0","25.0.0","25.1.0","25.2.0","25.3.0","25.4.0","25.5.0","25.6.0","25.7.0","25.8.0","25.9.0","26.0.0","26.1.0","26.2.0","26.3.0","26.4.0","26.5.0","26.6.0","26.7.0","26.8.0","26.9.0","27.0.0","27.1.0","27.2.0","27.3.0","27.4.0","27.5.0","27.6.0","27.7.0","27.8.0","27.9.0","28.0.0","28.1.0","28.2.0","28.3.0","28.4.0","28.5.0","28.6.0","28.7.0","28.8.0","28.9.0","29.0.0","29.1.0","29.2.0","29.3.0","29.4.0","29.5.0","29.6.0","29.7.0"] \ No newline at end of file diff --git a/xpler b/xpler index 24bc298..0d0419c 160000 --- a/xpler +++ b/xpler @@ -1 +1 @@ -Subproject commit 24bc298b96533ae7e7048ca14374d83f0f3839e9 +Subproject commit 0d0419c23fe72d939937a5e72022fa6477a15048