diff --git a/app/build.gradle b/app/build.gradle index 7cd8336..cb939b9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "io.github.fplus" minSdk 24 targetSdk 33 - versionCode 117 - versionName "1.2.6" + versionCode 121 + versionName "1.2.7" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/assets/update.log b/app/src/main/assets/update.txt similarity index 95% rename from app/src/main/assets/update.log rename to app/src/main/assets/update.txt index 710b0ae..10e22a2 100644 --- a/app/src/main/assets/update.log +++ b/app/src/main/assets/update.txt @@ -1,6 +1,11 @@ v1.2.7 +增加聊天消息防止撤回 增加视频下载自定义编码格式 +增加定时、空闲退出支持保留应用后台 +增加全屏播放“复制链接”唤起下载弹窗 恢复“分享->复制链接”,需手动开启功能 +调整部分逻辑(可能需要重新设置模块) +修复部分小问题 v1.2.6 增加下载弹窗文案复制选项 diff --git a/app/src/main/assets/version b/app/src/main/assets/version index ae8af70..e237dd0 100644 --- a/app/src/main/assets/version +++ b/app/src/main/assets/version @@ -1 +1 @@ -1.2.6-117 \ No newline at end of file +1.2.7-121 \ No newline at end of file diff --git a/app/src/main/java/io/github/fplus/activity/HomeActivity.kt b/app/src/main/java/io/github/fplus/activity/HomeActivity.kt index 5a05874..5742c44 100644 --- a/app/src/main/java/io/github/fplus/activity/HomeActivity.kt +++ b/app/src/main/java/io/github/fplus/activity/HomeActivity.kt @@ -178,7 +178,7 @@ class HomeActivity : ComponentActivity() { onLongClick = { lifecycleScope.launch { updateLog = withContext(Dispatchers.IO) { - val inputStream = assets.open("update.log") + val inputStream = assets.open("update.txt") val bytes = inputStream.readBytes() val text = bytes.decodeToString() inputStream.close() @@ -293,9 +293,9 @@ class HomeActivity : ComponentActivity() { horizontalAlignment = Alignment.CenterHorizontally ) { val packageInfo = - KAppUtils.getPackageInfo(application, Constant.scopes[0].packageName) + KAppUtils.getPackageInfo(application, Constant.scopes.elementAt(0).packageName) val lspatchActive = - HookStatus.isLSPatchActive(application, Constant.scopes[0].packageName) + HookStatus.isLSPatchActive(application, Constant.scopes.elementAt(0).packageName) if (lspatchActive.isNotEmpty()) { moduleState.value = "Lspatch加载成功!" Text( @@ -668,7 +668,7 @@ class HomeActivity : ComponentActivity() { val intent = Intent() intent.setClassName( - Constant.scopes[0].packageName, + Constant.scopes.elementAt(0).packageName, "com.ss.android.ugc.aweme.main.MainActivity" ) intent.putExtra("startModuleSetting", true) diff --git a/aweme/src/main/java/com/bytedance/im/core/model/Message.java b/aweme/src/main/java/com/bytedance/im/core/model/Message.java new file mode 100644 index 0000000..914c02b --- /dev/null +++ b/aweme/src/main/java/com/bytedance/im/core/model/Message.java @@ -0,0 +1,4 @@ +package com.bytedance.im.core.model; + +public class Message { +} diff --git a/aweme/src/main/java/com/ss/android/ugc/aweme/longervideo/landscape/home/activity/LandscapeFeedActivity.java b/aweme/src/main/java/com/ss/android/ugc/aweme/longervideo/landscape/home/activity/LandscapeFeedActivity.java new file mode 100644 index 0000000..b79af0c --- /dev/null +++ b/aweme/src/main/java/com/ss/android/ugc/aweme/longervideo/landscape/home/activity/LandscapeFeedActivity.java @@ -0,0 +1,14 @@ +package com.ss.android.ugc.aweme.longervideo.landscape.home.activity; + +import android.app.Activity; +import android.os.Bundle; + +import androidx.annotation.Nullable; + +public class LandscapeFeedActivity extends Activity { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + throw new RuntimeException("stub!!"); + } +} diff --git a/core/src/main/java/io/github/fplus/Constant.kt b/core/src/main/java/io/github/fplus/Constant.kt index 5448e20..3866d4e 100644 --- a/core/src/main/java/io/github/fplus/Constant.kt +++ b/core/src/main/java/io/github/fplus/Constant.kt @@ -5,7 +5,7 @@ import io.github.xpler.core.wrapper.at object Constant { val modulePackage = "io.github.fplus" - val scopes = arrayOf( + val scopes = setOf( "com.ss.android.ugc.aweme" at ("com.ss.android.ugc.aweme.app.host.AwemeHostApplication" to "com.ss.android.ugc.aweme"), "com.ss.android.ugc.aweme.lite" at ("com.ss.android.ugc.aweme.app.host.AwemeHostApplication" to "com.ss.android.ugc.aweme.lite"), "com.ss.android.ugc.live" at ("com.ss.android.ugc.aweme.app.host.AwemeHostApplication" to "com.ss.android.ugc.live"), diff --git a/core/src/main/java/io/github/fplus/HookInit.kt b/core/src/main/java/io/github/fplus/HookInit.kt index cfe681c..62227db 100644 --- a/core/src/main/java/io/github/fplus/HookInit.kt +++ b/core/src/main/java/io/github/fplus/HookInit.kt @@ -10,11 +10,12 @@ class HookInit : HookEntrance(), ApplicationHookStart { override val modulePackage: String get() = Constant.modulePackage - override val scopes: Array + override val scopes: Set get() = Constant.scopes override fun onCreateBefore(lp: XC_LoadPackage.LoadPackageParam, hostApp: Application) { // + // injectClassLoader(lp,hostApp.classLoader) } override fun onCreateAfter(lp: XC_LoadPackage.LoadPackageParam, hostApp: Application) { 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 ce4152e..0f984ce 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 @@ -138,7 +138,9 @@ class ConfigV1 private constructor() { /// 首页控件透明度 [顶部导航, 视频控件, 视频控件右侧, 底部导航] var translucentValue: List = listOf(50, 50, 50, 50) get() { - field = mmkv.getString("translucentValue", "50, 50, 50, 50")!!.split(",").map { it.trim().toInt() } + field = mmkv.getString("translucentValue", "50, 50, 50, 50")!! + .split(",") + .map { it.trim().toInt() } return if (field.size == 3) { listOf(field[0], field[1], field[1], field[2]) @@ -184,6 +186,28 @@ class ConfigV1 private constructor() { field = value } + /// 隐藏顶部tab + var isHideTopTab: Boolean = false + get() { + field = mmkv.getBoolean("isHideTopTab", false) + return field + } + set(value) { + mmkv.putBoolean("isHideTopTab", value) + field = value + } + + /// 隐藏顶部tab包含的关键字, 逗号隔开 + var hideTopTabKeywords: String = "经验, 探索, 商城" + get() { + field = mmkv.getString("hideTopTabKeywords", "经验, 探索, 商城")!! + return field + } + set(value) { + mmkv.putString("hideTopTabKeywords", value) + field = value + } + /// 隐藏底部加号按钮 var isHidePhotoButton: Boolean = false get() { @@ -344,6 +368,30 @@ class ConfigV1 private constructor() { field = value } + /// 消息防撤回 + var isPreventRecalled: Boolean = false + get() { + field = mmkv.getBoolean("isPreventRecalled", false) + return field + } + set(value) { + mmkv.putBoolean("isPreventRecalled", value) + field = value + } + + /// 撤回其他设置 + var preventRecalledOtherSetting: List = listOf(false) + get() { + field = mmkv.getString("preventRecalledOtherSetting", "false")!! + .split(",") + .map { it.trim().toBoolean() } + return field + } + set(value) { + mmkv.putString("preventRecalledOtherSetting", value.joinToString()) + field = value + } + /// 全屏沉浸式 var isImmersive: Boolean = false get() { @@ -357,7 +405,10 @@ class ConfigV1 private constructor() { // 系统隐藏项(状态栏、导航栏) var systemControllerValue: List = listOf(false, false) get() { - return mmkv.getString("systemControllerValue", "false, false")!!.split(",").map { it.trim().toBoolean() } + field = mmkv.getString("systemControllerValue", "false, false")!! + .split(",") + .map { it.trim().toBoolean() } + return field } set(value) { mmkv.putString("systemControllerValue", value.joinToString()) @@ -384,28 +435,6 @@ class ConfigV1 private constructor() { field = value } - /// 隐藏顶部tab - var isHideTab: Boolean = false - get() { - field = mmkv.getBoolean("isHideTab", false) - return field - } - set(value) { - mmkv.putBoolean("isHideTab", value) - field = value - } - - /// 隐藏顶部tab包含的关键字, 逗号隔开 - var hideTabKeywords: String = "经验, 探索, 商城" - get() { - field = mmkv.getString("hideTabKeywords", "经验, 探索, 商城")!! - return field - } - set(value) { - mmkv.putString("hideTabKeywords", value) - field = value - } - /// 是否开启WebDav var isWebDav: Boolean = false get() { @@ -475,7 +504,9 @@ class ConfigV1 private constructor() { /// 定时退出 [运行时间, 空闲时间] var timedShutdownValue: List = listOf(10, 3) get() { - field = mmkv.getString("timedShutdownValue", "10, 3")!!.split(",").map { it.trim().toInt() } + field = mmkv.getString("timedShutdownValue", "10, 3")!! + .split(",") + .map { it.trim().toInt() } return field } set(value) { @@ -483,6 +514,17 @@ class ConfigV1 private constructor() { field = value } + /// 保留应用后台 + var keepAppBackend: Boolean = false + get() { + field = mmkv.getBoolean("keepAppBackend", false) + return field + } + set(value) { + mmkv.putBoolean("keepAppBackend", 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 b084373..a98a776 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 @@ -48,6 +48,8 @@ object DexkitBuilder { var emojiApiProxyClazz: Class<*>? = null var emojiPopupWindowClazz: Class<*>? = null var bottomCtrlBarClazz: Class<*>? = null + var chatListRecyclerViewAdapterClazz: Class<*>? = null + var chatListRecalledHintClazz: Class<*>? = null var restartUtilsClazz: Class<*>? = null // methods @@ -83,9 +85,9 @@ object DexkitBuilder { this.app = app this.cacheVersion = version - KLogCat.tagD(TAG, "当前进程: ${lpparam.processName}") + KLogCat.tagI(TAG, "当前进程: ${lpparam.processName}") if (readCache()) { - KLogCat.tagD(TAG, "缓存读取成功!") + KLogCat.tagI(TAG, "缓存读取成功!") return } startSearch() @@ -96,7 +98,7 @@ object DexkitBuilder { * Dexkit开始搜索 */ private fun startSearch() { - KLogCat.tagD(TAG, "Dexkit开始搜索: ${lpparam.appInfo.sourceDir}") + KLogCat.tagI(TAG, "Dexkit开始搜索: ${lpparam.appInfo.sourceDir}") System.loadLibrary("dexkit") DexKitBridge.create(lpparam.appInfo.sourceDir).use { bridge -> searchClass(bridge) @@ -399,6 +401,75 @@ object DexkitBuilder { } }.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" + } + add { + type = "java.util.Set" + } + add { + type = "java.util.Set" + } + } + + methods { + add { + name = "onBindViewHolder" + paramTypes = listOf( + "androidx.recyclerview.widget.RecyclerView\$ViewHolder", + "int", + "java.util.List", + ) + } + } + } + }.singleInstance("chatListRecyclerViewAdapter") + + chatListRecalledHintClazz = bridge.findClass { + matcher { + fields { + add { + type = "android.widget.TextView" + } + + add { + type = "com.ss.android.ugc.aweme.views.InterceptTouchLinearLayout" + } + + add { + type { + superClass = "androidx.lifecycle.ViewModel" + } + } + } + + methods { + add { + name = "getFastEventBusSubscriberClass" + returnType = "java.lang.Class" + } + + add { + paramTypes = listOf( + null, + "int", + "java.util.List" + ) + } + } + } + }.singleInstance("chatListRecalledHint") + restartUtilsClazz = bridge.findClass { searchPackages("X") matcher { @@ -492,7 +563,7 @@ object DexkitBuilder { readClassCache(cache) readMethodsCache(cache) - KLogCat.tagD(TAG, cache.toString(2)) + KLogCat.tagI(TAG, cache.toString(2)) return true } @@ -520,6 +591,8 @@ object DexkitBuilder { 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() } @@ -550,14 +623,14 @@ object DexkitBuilder { // 拓展方法 private fun Map.singleInstance(key: String): Class<*>? { val classData = this[key]?.singleOrNull() - KLogCat.tagD(TAG, "found-class[$key]: ${classData?.name}") + KLogCat.tagI(TAG, "found-class[$key]: ${classData?.name}") classCacheJson.put(key, "${classData?.name}") return classData?.getInstance(lpparam.classLoader) } private fun ClassDataList.singleInstance(label: String): Class<*>? { val classData = this.singleOrNull() - KLogCat.tagD(TAG, "found-class[$label]: ${classData?.name}") + KLogCat.tagI(TAG, "found-class[$label]: ${classData?.name}") classCacheJson.put(label, "${classData?.name}") return classData?.getInstance(lpparam.classLoader) } @@ -568,7 +641,7 @@ object DexkitBuilder { return this.filter { it.isMethod }.map { - KLogCat.tagD(TAG, "found-method[$label]: $it") + KLogCat.tagI(TAG, "found-method[$label]: $it") array.put(it.toJson()) it.getMethodInstance(lpparam.classLoader) } diff --git a/core/src/main/java/io/github/fplus/core/hook/ChatListRecalledHint.kt b/core/src/main/java/io/github/fplus/core/hook/ChatListRecalledHint.kt new file mode 100644 index 0000000..20474e1 --- /dev/null +++ b/core/src/main/java/io/github/fplus/core/hook/ChatListRecalledHint.kt @@ -0,0 +1,60 @@ +package io.github.fplus.core.hook + +import android.widget.TextView +import com.bytedance.im.core.model.Message +import com.freegang.ktutils.extension.asOrNull +import com.freegang.ktutils.log.KLogCat +import com.freegang.ktutils.reflect.fieldGet +import com.freegang.ktutils.reflect.methodInvoke +import com.freegang.ktutils.reflect.methods +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.OnAfter +import io.github.xpler.core.entity.Param +import io.github.xpler.core.hookBlockRunning + +class HChatListRecalledHint : BaseHook() { + companion object { + const val TAG = "HChatListRecalledHint" + } + + private val config get() = ConfigV1.get() + + override fun setTargetClass(): Class<*> { + return DexkitBuilder.chatListRecalledHintClazz ?: NoneHook::class.java + } + + @OnAfter + fun testAfter( + params: XC_MethodHook.MethodHookParam, + @Param("null") any: Any?, + i: Int, + list: List<*>?, + ) { + hookBlockRunning(params) { + if (!config.isPreventRecalled) { + return + } + + if (!config.preventRecalledOtherSetting.getOrElse(0) { false }) { + val messages = thisObject.methods(returnType = Message::class.java) + .filter { it.parameterTypes.isEmpty() } + .map { it.invoke(thisObject) } + + val message = messages.firstOrNull() ?: return + val isSelf = message.methodInvoke("isSelf") + if (isSelf == true) return + } + + val textView = thisObject.fieldGet(type = TextView::class.java) + textView?.asOrNull()?.apply { + text = "${text.removeSuffix(" (没收到)")} (没收到)" + } + }.onFailure { + KLogCat.tagE(TAG, it) + } + } +} \ 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 dff7db8..aa265c8 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 @@ -15,11 +15,11 @@ import io.github.fplus.core.config.ConfigV1 import io.github.fplus.core.helper.DexkitBuilder import io.github.fplus.plugin.proxy.v1.PluginBridge import io.github.xpler.core.log.XplerLog -import io.github.xpler.loader.hostClassloader import kotlin.system.exitProcess class DouYinMain(private val app: Application) { companion object { + var inBackend = false var timedExitCountDown: CountDownTimer? = null var freeExitCountDown: CountDownTimer? = null } @@ -27,8 +27,7 @@ class DouYinMain(private val app: Application) { init { runCatching { // 插件化注入 - val stubClazz = hostClassloader!!.loadClass("com.ss.android.ugc.aweme.setting.ui.AboutActivity") - PluginBridge.init(app, stubClazz) + PluginBridge.init(app, "com.ss.android.ugc.aweme.setting.ui.AboutActivity") // 加载配置 ConfigV1.initialize(app) @@ -56,11 +55,12 @@ class DouYinMain(private val app: Application) { // search and hook DexkitBuilder.running( app = app, - version = 10, + version = 15, searchBefore = { HActivity() HMainActivity() HDetailActivity() + HLandscapeFeedActivity() HLivePlayActivity() HDisallowInterceptRelativeLayout() HMainTabStripScrollView() @@ -77,8 +77,6 @@ class DouYinMain(private val app: Application) { HDialog() // HDialogFragment() // HPopupWindow() - - HFragment() }, searchAfter = { HSideBarNestedScrollView() @@ -95,7 +93,12 @@ class DouYinMain(private val app: Application) { HVerticalViewPager() HDetailPageFragment() HEmojiDetailDialogNew() + HEmojiPopupWindow() HBottomCtrlBar() + + HMessage() + HChatListRecyclerViewAdapter() + HChatListRecalledHint() } ) @@ -115,7 +118,7 @@ class DouYinMain(private val app: Application) { val timedExit = config.timedShutdownValue[0] * 60 * 1000L val freeExit = config.timedShutdownValue[1] * 60 * 1000L - if (timedExit >= 60 * 1000 * 3) { + if (timedExit >= 60 * 1000L * 3) { timedExitCountDown = object : CountDownTimer(timedExit, 1000) { override fun onTick(millisUntilFinished: Long) { val second = millisUntilFinished / 1000 @@ -129,15 +132,15 @@ class DouYinMain(private val app: Application) { } override fun onFinish() { - if (!config.isTimedExit) return - KActivityUtils.getActivities().forEach { it.finishAndRemoveTask() } - Process.killProcess(Process.myPid()) - exitProcess(1) + if (!config.isTimedExit) + return + + keepOrKill(app, config) } } } - if (freeExit >= 60 * 1000 * 3) { + if (freeExit >= 60 * 1000L * 3) { freeExitCountDown = object : CountDownTimer(freeExit, 1000) { override fun onTick(millisUntilFinished: Long) { val second = millisUntilFinished / 1000 @@ -151,14 +154,29 @@ class DouYinMain(private val app: Application) { } override fun onFinish() { - if (!config.isTimedExit) return - KActivityUtils.getActivities().forEach { it.finishAndRemoveTask() } - Process.killProcess(Process.myPid()) - exitProcess(1) + if (!config.isTimedExit) + return + + keepOrKill(app, config) } } } } + + // + 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) + } + } } fun CountDownTimer.restart() { diff --git a/core/src/main/java/io/github/fplus/core/hook/HAbstractFeedAdapter.kt b/core/src/main/java/io/github/fplus/core/hook/HAbstractFeedAdapter.kt index f41d3c1..b0686a9 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HAbstractFeedAdapter.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HAbstractFeedAdapter.kt @@ -51,7 +51,7 @@ class HAbstractFeedAdapter : BaseHook() { // 垫高 view.forEachChild { if (background is GradientDrawable) background = null } val bottomPadding = view.context.dip2px(58f) // BottomTabBarHeight - val viewGroup = view.children.last().asOrNull() ?: return + val viewGroup = view.children.lastOrNull()?.asOrNull() ?: return viewGroup.updatePadding(bottom = bottomPadding) launch { diff --git a/core/src/main/java/io/github/fplus/core/hook/HChatListRecyclerViewAdapter.kt b/core/src/main/java/io/github/fplus/core/hook/HChatListRecyclerViewAdapter.kt new file mode 100644 index 0000000..b994589 --- /dev/null +++ b/core/src/main/java/io/github/fplus/core/hook/HChatListRecyclerViewAdapter.kt @@ -0,0 +1,92 @@ +package io.github.fplus.core.hook + +import android.graphics.Color +import android.view.Gravity +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.TextView +import androidx.core.view.children +import androidx.core.view.isVisible +import com.bytedance.im.core.model.Message +import com.freegang.ktutils.extension.asOrNull +import com.freegang.ktutils.log.KLogCat +import com.freegang.ktutils.reflect.fieldGet +import com.freegang.ktutils.reflect.methodInvoke +import com.freegang.ktutils.reflect.methods +import com.freegang.ktutils.view.firstOrNull +import com.freegang.ktutils.view.idName +import com.freegang.ktutils.view.parentView +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.OnAfter +import io.github.xpler.core.hookBlockRunning + +class HChatListRecyclerViewAdapter : BaseHook() { + companion object { + const val TAG = "HChatListRecyclerViewAdapter" + } + + private val config get() = ConfigV1.get() + + + override fun setTargetClass(): Class<*> { + return DexkitBuilder.chatListRecyclerViewAdapterClazz ?: NoneHook::class.java + } + + @OnAfter("onBindViewHolder") + fun onBindViewHolderAfter(params: XC_MethodHook.MethodHookParam) { + hookBlockRunning(params) { + if (!config.isPreventRecalled) { + return + } + + val itemView = args[0]?.fieldGet("itemView")?.asOrNull() ?: return + + val messages = args[0].methods(returnType = Message::class.java) + .filter { it.parameterTypes.isEmpty() } + .map { it.invoke(args[0]) } + + val message = messages.firstOrNull() ?: return + val ext = message.methodInvoke("getExt")?.asOrNull>() ?: return + val isSelf = message.methodInvoke("isSelf") + + val addedFlag = "RecalledHint" + val viewGroup = itemView + .firstOrNull { it.idName == "@id/content" } + ?.parentView + ?: return + + val last = viewGroup.children.last() + if (last.tag != addedFlag) { + val view = TextView(itemView.context) + view.text = "已撤回" + view.setTextColor(Color.GRAY) + view.textSize = 12f + view.isVisible = false + view.gravity = Gravity.CENTER + view.tag = addedFlag + viewGroup.addView(view) + } + + if (ext.containsKey("f:prevent_recalled")) { + if (last.tag == addedFlag) { + last.isVisible = true + last.asOrNull()?.gravity = if (isSelf == true) { + Gravity.START + } else { + Gravity.END + } + } + } else { + if (last.tag == addedFlag) { + last.isVisible = false + } + } + }.onFailure { + KLogCat.tagE(TAG, it) + } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/HCommentListPageFragment.kt b/core/src/main/java/io/github/fplus/core/hook/HCommentListPageFragment.kt index fcc8203..05a1cef 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HCommentListPageFragment.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HCommentListPageFragment.kt @@ -30,7 +30,7 @@ class HCommentListPageFragment : BaseHook() { } @OnAfter - @ReturnType(type = CommentColorMode::class) + @ReturnType(name = "com.ss.android.ugc.aweme.comment.constants.CommentColorMode") fun changeCommentColorModeAfter(params: XC_MethodHook.MethodHookParam/*, mode: CommentColorMode?*/) { hookBlockRunning(params) { if (!config.isCommentColorMode) return 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 b429825..2698601 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 @@ -25,7 +25,7 @@ class HDetailActivity : BaseHook() { @OnAfter("onResume") fun onResumeAfter(params: XC_MethodHook.MethodHookParam) { hookBlockRunning(params) { - addClipboardListener(thisActivity as DetailActivity) + addClipboardListener(thisActivity) }.onFailure { KLogCat.tagE(TAG, it) } 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 b00e939..8d15939 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 @@ -5,9 +5,13 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.RelativeLayout +import android.widget.TextView +import androidx.core.view.updatePadding +import com.freegang.ktutils.display.dip2px import com.freegang.ktutils.log.KLogCat import com.freegang.ktutils.reflect.methodInvoke import com.freegang.ktutils.view.firstOrNull +import com.freegang.ktutils.view.forEachWhereChild import com.freegang.ktutils.view.postRunning import com.ss.android.ugc.aweme.feed.model.Aweme import de.robv.android.xposed.XC_MethodHook @@ -70,11 +74,14 @@ class HDetailPageFragment : BaseHook() { HDetailPageFragment.isComment = true // 我也发一张 - /*view.findViewsByExact(TextView::class.java) { - "$text".contains("我也发") || "$contentDescription".contains("我也发") - }.ifNotEmpty { - binding.rightSpace.updatePadding(right = KDisplayUtils.dip2px(view.context, 128f)) - }*/ + view.forEachWhereChild { + if(this is TextView){ + "$text".contains("我也发") || "$contentDescription".contains("我也发") + binding.rightSpace.updatePadding(right = view.context.dip2px(128f)) + return@forEachWhereChild true + } + return@forEachWhereChild false + } } }.onFailure { KLogCat.tagE(TAG, it) 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 e523975..b8a5272 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 @@ -1,20 +1,17 @@ package io.github.fplus.core.hook import android.os.Bundle -import android.view.View import android.widget.TextView import com.freegang.ktutils.app.contentView import com.freegang.ktutils.extension.asOrNull -import com.freegang.ktutils.reflect.field +import com.freegang.ktutils.reflect.fieldGet import com.freegang.ktutils.view.firstOrNull import com.freegang.ktutils.view.postDelayedRunning -import com.ss.android.ugc.aweme.emoji.base.BaseEmoji +import com.ss.android.ugc.aweme.base.model.UrlModel import com.ss.android.ugc.aweme.emoji.similaremoji.EmojiDetailDialogNew import com.ss.android.ugc.aweme.emoji.store.view.EmojiBottomSheetDialog -import com.ss.android.ugc.aweme.emoji.utils.EmojiApi 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.SaveEmojiLogic import io.github.xpler.core.argsOrEmpty import io.github.xpler.core.hookClass @@ -31,9 +28,14 @@ class HEmojiDetailDialogNew : BaseHook() { override fun onInit() { // 该类是 retrofit2 代理类的Hook, 直接通过实例获取class进行hook - DexkitBuilder.emojiApiProxyClazz?.let { it -> + /*DexkitBuilder.emojiApiProxyClazz?.let { it -> val emojiApiField = it.field(type = EmojiApi::class.java) val emojiApi = emojiApiField?.get(null) + KLogCat.d( + "emojiApiProxyClazz: $it", + "emojiApi: $emojiApi", + "emojiApi: ${emojiApi?.javaClass?.simpleName}", + ) if (emojiApi != null) { lpparam.hookClass(emojiApi::class.java) .method( @@ -56,7 +58,18 @@ class HEmojiDetailDialogNew : BaseHook() { } } } - } + }*/ + + lpparam.hookClass(EmojiDetailDialogNew::class.java) + .constructorsAll { + onBefore { + if (argsOrEmpty.isEmpty()) + return@onBefore + + val url = args[0].fieldGet(type = UrlModel::class.java)?.asOrNull() + urlList = url?.urlList ?: emptyList() + } + } lpparam.hookClass(EmojiBottomSheetDialog::class.java) .method("onCreate", Bundle::class.java) { @@ -86,37 +99,5 @@ class HEmojiDetailDialogNew : BaseHook() { } } } - - // 表情弹层 - DexkitBuilder.emojiPopupWindowClazz?.let { it -> - lpparam.hookClass(it) - .methodAll { - onAfter { - if (!config.isEmoji) return@onAfter - if (argsOrEmpty.isNotEmpty()) { - val emoji = argsOrEmpty[0].asOrNull() - popUrlList = popUrlList.ifEmpty { - emoji?.detailEmoji?.animateUrl?.urlList - ?: emoji?.detailEmoji?.staticUrl?.urlList - ?: emptyList() - } - } - - if (popUrlList.isEmpty()) return@onAfter - val view = result?.asOrNull() ?: return@onAfter - if (view is TextView) { - if (view.text == "添加到表情") { - view.text = "添加到表情 (长按保存)" - view.isLongClickable = true - view.isHapticFeedbackEnabled = false - view.setOnLongClickListener { - SaveEmojiLogic(this@HEmojiDetailDialogNew, it.context, popUrlList) - true - } - } - } - } - } - } } } \ No newline at end of file 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 new file mode 100644 index 0000000..5830a8e --- /dev/null +++ b/core/src/main/java/io/github/fplus/core/hook/HEmojiPopupWindow.kt @@ -0,0 +1,70 @@ +package io.github.fplus.core.hook + +import android.widget.TextView +import com.freegang.ktutils.extension.asOrNull +import com.freegang.ktutils.log.KLogCat +import com.ss.android.ugc.aweme.emoji.base.BaseEmoji +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.SaveEmojiLogic +import io.github.xpler.core.entity.NoneHook +import io.github.xpler.core.entity.OnAfter +import io.github.xpler.core.entity.ReturnType +import io.github.xpler.core.hookBlockRunning + +class HEmojiPopupWindow : BaseHook() { + companion object { + const val TAG = "HEmojiPopupWindow" + } + + private val config get() = ConfigV1.get() + + private var popUrlList: List = emptyList() + + override fun setTargetClass(): Class<*> { + return DexkitBuilder.emojiPopupWindowClazz ?: NoneHook::class.java + } + + @OnAfter + fun emojiAfter( + params: XC_MethodHook.MethodHookParam, + emoji: BaseEmoji?, + ) { + hookBlockRunning(params) { + if (!config.isEmoji) + return + + popUrlList = popUrlList.ifEmpty { + emoji?.detailEmoji?.animateUrl?.urlList + ?: emoji?.detailEmoji?.staticUrl?.urlList + ?: emptyList() + } + }.onFailure { + KLogCat.tagE(TAG, it) + } + } + + @OnAfter + @ReturnType("Lcom/bytedance/ies/dmt/ui/widget/DmtTextView;") + fun textViewAfter(params: XC_MethodHook.MethodHookParam) { + hookBlockRunning(params) { + if (!config.isEmoji) + return + + val view = result?.asOrNull() ?: return + if (view.text == "添加到表情") { + view.text = "添加到表情 (长按保存)" + view.isLongClickable = true + view.isHapticFeedbackEnabled = false + view.setOnLongClickListener { + SaveEmojiLogic(this@HEmojiPopupWindow, it.context, popUrlList) + true + } + } + }.onFailure { + KLogCat.tagE(TAG, it) + } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/hook/HFlippableViewPager.kt b/core/src/main/java/io/github/fplus/core/hook/HFlippableViewPager.kt index 0e4826e..416f009 100644 --- a/core/src/main/java/io/github/fplus/core/hook/HFlippableViewPager.kt +++ b/core/src/main/java/io/github/fplus/core/hook/HFlippableViewPager.kt @@ -21,7 +21,7 @@ class HFlippableViewPager : BaseHook() { @OnBefore("onInterceptTouchEvent", "onTouchEvent", "dispatchTouchEvent") fun onTouchEventBefore(params: XC_MethodHook.MethodHookParam, event: MotionEvent) { hookBlockRunning(params) { - if (!config.isHideTab) return + if (!config.isHideTopTab) return result = false // 禁止ViewPager左右滑动 }.onFailure { KLogCat.tagE(TAG, 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 new file mode 100644 index 0000000..8136e5b --- /dev/null +++ b/core/src/main/java/io/github/fplus/core/hook/HLandscapeFeedActivity.kt @@ -0,0 +1,65 @@ +package io.github.fplus.core.hook + +import android.app.Activity +import com.freegang.ktutils.extension.asOrNull +import com.freegang.ktutils.log.KLogCat +import com.freegang.ktutils.reflect.method +import com.ss.android.ugc.aweme.feed.model.Aweme +import com.ss.android.ugc.aweme.longervideo.landscape.home.activity.LandscapeFeedActivity +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.hook.logic.ClipboardLogic +import io.github.fplus.core.hook.logic.DownloadLogic +import io.github.xpler.core.entity.OnAfter +import io.github.xpler.core.entity.OnBefore +import io.github.xpler.core.hookBlockRunning +import io.github.xpler.core.thisActivity + +class HLandscapeFeedActivity : BaseHook() { + companion object { + const val TAG = "HLandscapeFeedActivity" + } + + private val config get() = ConfigV1.get() + + private val clipboardLogic = ClipboardLogic(this) + + @OnAfter("onResume") + fun onResumeAfter(params: XC_MethodHook.MethodHookParam) { + hookBlockRunning(params) { + addClipboardListener(thisActivity) + }.onFailure { + KLogCat.tagE(HDetailActivity.TAG, it) + } + } + + @OnBefore("onPause") + fun onPauseBefore(params: XC_MethodHook.MethodHookParam) { + hookBlockRunning(params) { + removeClipboardListener(thisActivity) + }.onFailure { + KLogCat.tagE(HDetailActivity.TAG, it) + } + } + + private fun addClipboardListener(activity: Activity) { + if (!config.isDownload) return + if (!config.isCopyDownload) return + + clipboardLogic.addClipboardListener(activity) { clipData, firstText -> + val method = activity.method(returnType = Aweme::class.java) + val aweme = method?.invoke(activity)?.asOrNull() + + DownloadLogic( + this@HLandscapeFeedActivity, + activity, + aweme, + ) + } + } + + private fun removeClipboardListener(activity: Activity) { + clipboardLogic.removeClipboardListener(activity) + } +} \ 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 6ead423..846acd7 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 @@ -94,6 +94,10 @@ class HMainActivity : BaseHook() { @OnAfter("onResume") fun onResume(params: XC_MethodHook.MethodHookParam) { hookBlockRunning(params) { + if (DouYinMain.inBackend) { + DouYinMain.inBackend = false + DouYinMain.timedExitCountDown?.restart() + } addClipboardListener(thisActivity) initView(thisActivity) is32BisTips(thisActivity) @@ -154,15 +158,15 @@ class HMainActivity : BaseHook() { private fun initMainTitleBar() { // 隐藏顶部选项卡 - if (config.isHideTab) { - val hideTabKeywords = config.hideTabKeywords + if (config.isHideTopTab) { + val hideTabKeywords = config.hideTopTabKeywords .removePrefix(",").removePrefix(",") .removeSuffix(",").removeSuffix(",") .replace("\\s".toRegex(), "") .replace("[,,]".toRegex(), "|") .toRegex() mainTitleBar?.forEachChild { - if (config.isHideTab) { + if (config.isHideTopTab) { if ("$contentDescription".contains(hideTabKeywords)) { isVisible = false } diff --git a/core/src/main/java/io/github/fplus/core/hook/HMessage.kt b/core/src/main/java/io/github/fplus/core/hook/HMessage.kt new file mode 100644 index 0000000..c1e0bf2 --- /dev/null +++ b/core/src/main/java/io/github/fplus/core/hook/HMessage.kt @@ -0,0 +1,50 @@ +package io.github.fplus.core.hook + +import com.bytedance.im.core.model.Message +import com.freegang.ktutils.extension.asOrNull +import com.freegang.ktutils.log.KLogCat +import com.freegang.ktutils.reflect.methodInvoke +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.HookEntity +import io.github.xpler.core.entity.OnAfter +import io.github.xpler.core.hookBlockRunning + +class HMessage : BaseHook() { + companion object { + const val TAG = "HMessage" + } + + private val config get() = ConfigV1.get() + + @OnAfter("isRecalled") + fun isRecalledAfter(params: XC_MethodHook.MethodHookParam) { + hookBlockRunning(params) { + if (!config.isPreventRecalled) { + return + } + + if (!config.preventRecalledOtherSetting.getOrElse(0) { false }) { + val isSelf = thisObject.methodInvoke("isSelf") + if (isSelf == true) return + } + + val content = "${thisObject.methodInvoke("getContent")}" + if (content == "{\"aweType\":0,\"text\":\"Recall Content Hided\"}") { + return + } + + val ext = thisObject.methodInvoke("getExt")?.asOrNull>() ?: return + if (ext.containsKey("s:is_recalled")) { + ext.remove("s:is_recalled") + ext.put("f:prevent_recalled", "true") + thisObject.methodInvoke("setExt", args = arrayOf(ext)) + + result = false + } + }.onFailure { + KLogCat.tagE(TAG, it) + } + } +} \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/core/ui/ModuleTheme.kt b/core/src/main/java/io/github/fplus/core/ui/ModuleTheme.kt index 19339d8..3674452 100644 --- a/core/src/main/java/io/github/fplus/core/ui/ModuleTheme.kt +++ b/core/src/main/java/io/github/fplus/core/ui/ModuleTheme.kt @@ -24,36 +24,18 @@ fun ModuleTheme( followSystem: Boolean = true, content: @Composable () -> Unit, ) { - if (followSystem) { - MaterialTheme( - typography = autoTypography(isDark = isSystemInDarkTheme()), - colors = autoColors(isDark = isSystemInDarkTheme()), - ) { - rememberSystemUiController().run { - setSystemBarsColor( - color = MaterialTheme.colors.background, - darkIcons = false, - ) - } - - Surface( - modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars), - color = MaterialTheme.colors.background, - contentColor = MaterialTheme.colors.background, - content = content, - ) - } - return - } + val typography = autoTypography(isDark = if (followSystem) isSystemInDarkTheme() else isDark) + val colors = autoColors(isDark = if (followSystem) isSystemInDarkTheme() else isDark) + val darkIcons = !((if (followSystem) isSystemInDarkTheme() else isDark)) MaterialTheme( - typography = autoTypography(isDark = isDark), - colors = autoColors(isDark = isDark), + typography = typography, + colors = colors, ) { rememberSystemUiController().run { setSystemBarsColor( color = MaterialTheme.colors.background, - darkIcons = !isDark, + darkIcons = darkIcons, ) } 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 28a4200..e1040b1 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 @@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.Checkbox import androidx.compose.material.CircularProgressIndicator @@ -53,6 +54,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.OffsetMapping import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.style.TextOverflow @@ -168,7 +170,7 @@ class FreedomSettingActivity : XplerActivity() { interactionSource = remember { MutableInteractionSource() }, onLongClick = { if (!model.hasDexkitCache) { - KToastUtils.show(application, "没有类日志!") + KToastUtils.show(application, "没有类日志") return@combinedClickable } showLogDialog = true @@ -232,7 +234,7 @@ class FreedomSettingActivity : XplerActivity() { runCatching { // pluginAssets assets - .open("update.log") + .open("update.txt") .use { updateLog = it .readBytes() @@ -271,16 +273,17 @@ class FreedomSettingActivity : XplerActivity() { item { TranslucentItem() } item { RemoveStickerItem() } item { RemoveBottomCtrlBarItem() } + item { PreventRecalledItem() } item { DoubleClickTypeItem() } item { LongtimeVideoToastItem() } - item { DHidePhotoButtonItem() } + item { HideTopTabItem() } + item { HidePhotoButtonItem() } item { VideoOptionBarFilterItem() } item { VideoFilterItem() } item { DialogFilterItem() } item { NeatModeItem() } item { ImmersiveItem() } item { CommentColorModeItem() } - item { HideTabItem() } item { WebDavItem() } item { TimedExitItem() } /*item { DisablePluginItem() }*/ @@ -567,6 +570,52 @@ class FreedomSettingActivity : XplerActivity() { ) } + @Composable + private fun PreventRecalledItem() { + var showSettingDialog by remember { mutableStateOf(false) } + + SwitchItem( + text = "消息防撤回", + subtext = "阻止聊天消息撤回,点击调整相关设置", + checked = model.isPreventRecalled.observeAsState(false), + onClick = { + showSettingDialog = true + }, + onCheckedChange = { + model.changeIsPreventRecalled(it) + } + ) + + if (showSettingDialog) { + val otherSetting = model.preventRecalledOtherSetting.value ?: listOf(false) + val allowMineRecalled = remember { mutableStateOf(otherSetting.getOrElse(0) { false }) } + + FMessageDialog( + title = "其他设置", + confirm = "更改", + onlyConfirm = true, + onConfirm = { + showSettingDialog = false + model.changePreventRecalledOtherSetting( + listOf( + allowMineRecalled.value + ) + ) + }, + ) { + Column { + CheckBoxItem( + text = "保留自己撤回的消息", + checked = allowMineRecalled, + onCheckedChange = { + allowMineRecalled.value = it + } + ) + } + } + } + } + @Composable private fun DoubleClickTypeItem() { var showDoubleClickModeDialog by remember { mutableStateOf(false) } @@ -652,7 +701,86 @@ class FreedomSettingActivity : XplerActivity() { } @Composable - private fun DHidePhotoButtonItem() { + private fun HideTopTabItem() { + var showKeywordsEditorDialog by remember { mutableStateOf(false) } + var showTipsDialog by remember { mutableStateOf(false) } + + SwitchItem( + text = "隐藏顶部选项", + subtext = "隐藏顶部标签页, 点击设置关键字", + checked = model.isHideTopTab.observeAsState(false), + onClick = { + showKeywordsEditorDialog = true + }, + onCheckedChange = { + showRestartAppDialog.value = true + if (it) { + showTipsDialog = true + } + model.changeIsHideTopTab(it) + }, + ) + + if (showKeywordsEditorDialog) { + var hideTabKeywords by remember { + mutableStateOf( + model.hideTopTabKeywords.value ?: "" + ) + } + FMessageDialog( + title = "请输入关键字, 用逗号分开", + cancel = "取消", + confirm = "确定", + onCancel = { showKeywordsEditorDialog = false }, + onConfirm = { + showKeywordsEditorDialog = false + model.setHideTabKeywords(hideTabKeywords) + }, + ) { + FCard( + border = FCardBorder(borderWidth = 1.0.dp), + ) { + BasicTextField( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp, horizontal = 12.dp), + value = hideTabKeywords, + maxLines = 1, + singleLine = true, + textStyle = MaterialTheme.typography.body1, + onValueChange = { + hideTabKeywords = it + }, + ) + } + } + } + + if (showTipsDialog) { + FMessageDialog( + title = "提示", + cancel = "关闭", + confirm = "开启", + onCancel = { + showTipsDialog = false + model.changeIsHideTopTab(false) + }, + onConfirm = { + showTipsDialog = false + showRestartAppDialog.value = true + model.changeIsHideTopTab(true) + }, + ) { + Text( + text = "一旦开启顶部选项隐藏, 将禁止左右滑动切换, 具体效果自行查看!", + style = MaterialTheme.typography.body1, + ) + } + } + } + + @Composable + private fun HidePhotoButtonItem() { var showIsDisablePhotoDialog by remember { mutableStateOf(false) } SwitchItem( @@ -1284,85 +1412,6 @@ class FreedomSettingActivity : XplerActivity() { } } - @Composable - private fun HideTabItem() { - var showHideTabKeywordsEditorDialog by remember { mutableStateOf(false) } - var showHideTabTipsDialog by remember { mutableStateOf(false) } - - SwitchItem( - text = "隐藏顶部选项", - subtext = "点击设置关键字", - checked = model.isHideTab.observeAsState(false), - onClick = { - showHideTabKeywordsEditorDialog = true - }, - onCheckedChange = { - showRestartAppDialog.value = true - if (it) { - showHideTabTipsDialog = true - } - model.changeIsHideTab(it) - }, - ) - - if (showHideTabKeywordsEditorDialog) { - var hideTabKeywords by remember { - mutableStateOf( - model.hideTabKeywords.value ?: "" - ) - } - FMessageDialog( - title = "请输入关键字, 用逗号分开", - cancel = "取消", - confirm = "确定", - onCancel = { showHideTabKeywordsEditorDialog = false }, - onConfirm = { - showHideTabKeywordsEditorDialog = false - model.setHideTabKeywords(hideTabKeywords) - }, - ) { - FCard( - border = FCardBorder(borderWidth = 1.0.dp), - ) { - BasicTextField( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 12.dp, horizontal = 12.dp), - value = hideTabKeywords, - maxLines = 1, - singleLine = true, - textStyle = MaterialTheme.typography.body1, - onValueChange = { - hideTabKeywords = it - }, - ) - } - } - } - - if (showHideTabTipsDialog) { - FMessageDialog( - title = "提示", - cancel = "关闭", - confirm = "开启", - onCancel = { - showHideTabTipsDialog = false - model.changeIsHideTab(false) - }, - onConfirm = { - showHideTabTipsDialog = false - showRestartAppDialog.value = true - model.changeIsHideTab(true) - }, - ) { - Text( - text = "一旦开启顶部选项隐藏, 将禁止左右滑动切换, 具体效果自行查看!", - style = MaterialTheme.typography.body1, - ) - } - } - } - @OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) @Composable private fun WebDavItem() { @@ -1601,6 +1650,7 @@ class FreedomSettingActivity : XplerActivity() { @Composable private fun TimedExitItem() { var showTimedExitSettingDialog by remember { mutableStateOf(false) } + var showKeepAppBackendTips by remember { mutableStateOf(false) } SwitchItem( text = "定时退出", @@ -1620,7 +1670,6 @@ class FreedomSettingActivity : XplerActivity() { var timedExit by remember { mutableStateOf("${times[0]}") } var freeExit by remember { mutableStateOf("${times[1]}") } - KToastUtils.show(application, "低于3分钟将不执行~") FMessageDialog( title = "定时退出时间设置", cancel = "取消", @@ -1629,14 +1678,15 @@ class FreedomSettingActivity : XplerActivity() { showTimedExitSettingDialog = false }, onConfirm = { - showTimedExitSettingDialog = false val intTimedExit = timedExit.toIntOrNull() ?: -1 val intFreeExit = freeExit.toIntOrNull() ?: -1 if (intTimedExit < 0 || intFreeExit < 0) { KToastUtils.show(application, "请输入正确的分钟数") return@FMessageDialog } - KToastUtils.show(application, "设置成功, 下次启动生效") + + showTimedExitSettingDialog = false + KToastUtils.show(application, "低于3分钟将不执行, 下次启动生效!") model.setTimedShutdownValue(listOf(intTimedExit, intFreeExit)) } ) { @@ -1657,6 +1707,7 @@ class FreedomSettingActivity : XplerActivity() { maxLines = 1, singleLine = true, textStyle = MaterialTheme.typography.body1, + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), decorationBox = { innerTextField -> if (timedExit.isEmpty()) Text( text = "运行超过指定时间", @@ -1693,6 +1744,7 @@ class FreedomSettingActivity : XplerActivity() { maxLines = 1, singleLine = true, textStyle = MaterialTheme.typography.body1, + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), decorationBox = { innerTextField -> if (freeExit.isEmpty()) Text( text = "空闲超过指定时间", @@ -1712,9 +1764,44 @@ class FreedomSettingActivity : XplerActivity() { ) } } + Divider(modifier = Modifier.padding(vertical = 12.dp)) + CheckBoxItem( + text = "保留应用后台", + checked = model.keepAppBackend.observeAsState(initial = false), + onCheckedChange = { + showKeepAppBackendTips = it + model.changeKeepAppBackend(it) + }, + ) } } } + + if (showKeepAppBackendTips) { + FMessageDialog( + title = "提示", + onlyConfirm = true, + confirm = "确定", + onConfirm = { + showKeepAppBackendTips = false + } + ) { + Text( + text = buildAnnotatedString { + append("开启后“定时/空闲退出”将由") + withStyle(SpanStyle(Color.Red)) { + append("退出应用") + } + append("变为") + withStyle(SpanStyle(Color.Red)) { + append("切换后台") + } + append(",但在某些情况下应用进程可能会被系统调度杀死。") + }, + style = MaterialTheme.typography.body1, + ) + } + } } @Deprecated("暂存区") 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 7b6c122..ea0b1c1 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 @@ -64,6 +64,12 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) private var _isRemoveBottomCtrlBar = MutableLiveData(false) val isRemoveBottomCtrlBar: LiveData = _isRemoveBottomCtrlBar + private var _isPreventRecalled = MutableLiveData(false) + val isPreventRecalled: LiveData = _isPreventRecalled + + private var _preventRecalledOtherSetting = MutableLiveData(listOf(false)) + val preventRecalledOtherSetting: LiveData> = _preventRecalledOtherSetting + private var _isDoubleClickType = MutableLiveData(false) val isDoubleClickType: LiveData = _isDoubleClickType @@ -73,6 +79,12 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) private var _isLongtimeVideoToast = MutableLiveData(false) val isLongtimeVideoToast: LiveData = _isLongtimeVideoToast + private var _isHideTopTab = MutableLiveData(false) + var isHideTopTab: LiveData = _isHideTopTab + + private var _hideTopTabKeywords = MutableLiveData("") + var hideTopTabKeywords: LiveData = _hideTopTabKeywords + private var _isHidePhotoButton = MutableLiveData(false) val isDHidePhotoButton: LiveData = _isHidePhotoButton @@ -118,12 +130,6 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) private var _commentColorMode = MutableLiveData(0) val commentColorMode: LiveData = _commentColorMode - private var _isHideTab = MutableLiveData(false) - var isHideTab: LiveData = _isHideTab - - private var _hideTabKeywords = MutableLiveData("") - var hideTabKeywords: LiveData = _hideTabKeywords - private var _isWebDav = MutableLiveData(false) var isWebDav: LiveData = _isWebDav @@ -145,6 +151,9 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) private var _timedShutdownValue = MutableLiveData(listOf(10, 3)) var timedShutdownValue: LiveData> = _timedShutdownValue + private var _keepAppBackend = MutableLiveData(false) + var keepAppBackend: LiveData = _keepAppBackend + private var _isDisablePlugin = MutableLiveData(false) val isDisablePlugin: LiveData = _isDisablePlugin @@ -174,6 +183,8 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) changeTranslucentValue(config.translucentValue) changeIsRemoveSticker(config.isRemoveSticker) changeIsRemoveBottomCtrlBar(config.isRemoveBottomCtrlBar) + changeIsPreventRecalled(config.isPreventRecalled) + changePreventRecalledOtherSetting(config.preventRecalledOtherSetting) changeIsDoubleClickType(config.isDoubleClickType) changeDoubleClickType(config.doubleClickType) changeIsLongtimeVideoToast(config.isLongtimeVideoToast) @@ -192,13 +203,14 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) changeSystemControllerValue(config.systemControllerValue) changeIsCommentColorMode(config.isCommentColorMode) changeCommentColorMode(config.commentColorMode) - changeIsHideTab(config.isHideTab) - setHideTabKeywords(config.hideTabKeywords) + changeIsHideTopTab(config.isHideTopTab) + setHideTabKeywords(config.hideTopTabKeywords) changeIsWebDav(config.isWebDav) loadWebHistory() setWebDavConfig(config.webDavConfig) changeIsTimeExit(config.isTimedExit) setTimedShutdownValue(config.timedShutdownValue) + changeKeepAppBackend(config.keepAppBackend) changeIsDisablePlugin(config.isDisablePlugin) } } @@ -269,6 +281,17 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) config.isRemoveBottomCtrlBar = value } + // 消息防撤回 + fun changeIsPreventRecalled(value: Boolean) { + _isPreventRecalled.value = value + config.isPreventRecalled = value + } + + fun changePreventRecalledOtherSetting(value: List) { + _preventRecalledOtherSetting.value = value + config.preventRecalledOtherSetting = value + } + // 是否开启更改双击响应类型 fun changeIsDoubleClickType(value: Boolean) { _isDoubleClickType.value = value @@ -382,15 +405,15 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) } // 隐藏顶部tab - fun changeIsHideTab(value: Boolean) { - _isHideTab.value = value - config.isHideTab = value + fun changeIsHideTopTab(value: Boolean) { + _isHideTopTab.value = value + config.isHideTopTab = value } // 隐藏顶部tab包含的关键字, 逗号隔开 fun setHideTabKeywords(hideTabKeywords: String) { - _hideTabKeywords.value = hideTabKeywords - config.hideTabKeywords = hideTabKeywords + _hideTopTabKeywords.value = hideTabKeywords + config.hideTopTabKeywords = hideTabKeywords } // WebDav @@ -475,6 +498,12 @@ class FreedomSettingVM(application: Application) : AndroidViewModel(application) config.timedShutdownValue = value } + // 保留应用后台 + fun changeKeepAppBackend(value: Boolean) { + _keepAppBackend.value = value + config.keepAppBackend = value + } + // 去插件化 fun changeIsDisablePlugin(value: Boolean) { _isDisablePlugin.value = value diff --git a/core/src/main/java/io/github/fplus/plugin/PluginClassloader.kt b/core/src/main/java/io/github/fplus/plugin/PluginClassloader.kt index 8eefdc7..5b7e263 100644 --- a/core/src/main/java/io/github/fplus/plugin/PluginClassloader.kt +++ b/core/src/main/java/io/github/fplus/plugin/PluginClassloader.kt @@ -4,21 +4,23 @@ import io.github.xpler.loader.hostClassloader import io.github.xpler.loader.moduleClassloader class PluginClassloader : ClassLoader() { - override fun findClass(name: String?): Class<*> { + override fun loadClass(name: String, resolve: Boolean): Class<*> { + // Log.d("XplerLog", "load: $name") + val loadedClass = findLoadedClass(name) if (loadedClass != null) return loadedClass try { return moduleClassloader!!.loadClass(name) - } catch (e: Exception) { - // KLogCat.e(e) + } catch (e: ClassNotFoundException) { + // e.printStackTrace() } try { return hostClassloader!!.loadClass(name) - } catch (e: Exception) { - // KLogCat.e(e) + } catch (e: ClassNotFoundException) { + // e.printStackTrace() } - return super.findClass(name) + return super.loadClass(name, resolve) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/plugin/activity/XplerActivity.kt b/core/src/main/java/io/github/fplus/plugin/activity/XplerActivity.kt index 124316a..543b549 100644 --- a/core/src/main/java/io/github/fplus/plugin/activity/XplerActivity.kt +++ b/core/src/main/java/io/github/fplus/plugin/activity/XplerActivity.kt @@ -16,15 +16,13 @@ import io.github.fplus.plugin.base.BaseActivity abstract class XplerActivity : BaseActivity() { - private val mClassLoader: PluginClassloader? = null + private val mClassLoader by lazy { PluginClassloader() } private var mResources: Resources? = null private val content = mutableStateOf<(@Composable () -> Unit)?>(null) - override fun getClassLoader(): ClassLoader { - return mClassLoader ?: super.getClassLoader() - } + override fun getClassLoader(): ClassLoader = mClassLoader override fun getResources(): Resources { return mResources ?: super.getResources() diff --git a/core/src/main/java/io/github/fplus/plugin/activity/XplerComponentActivity.kt b/core/src/main/java/io/github/fplus/plugin/activity/XplerComponentActivity.kt index df68a0e..a0667d2 100644 --- a/core/src/main/java/io/github/fplus/plugin/activity/XplerComponentActivity.kt +++ b/core/src/main/java/io/github/fplus/plugin/activity/XplerComponentActivity.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.setViewTreeLifecycleOwner +import androidx.lifecycle.setViewTreeViewModelStoreOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner import com.freegang.ktutils.extension.asOrNull import io.github.fplus.plugin.PluginClassloader @@ -56,7 +57,8 @@ abstract class XplerComponentActivity : BaseComponentActivity() { private fun initViewTreeOwners() { window!!.decorView.setViewTreeLifecycleOwner(this) - window!!.decorView.setViewTreeOnBackPressedDispatcherOwner(this) + window!!.decorView.setViewTreeViewModelStoreOwner(this) window!!.decorView.setViewTreeSavedStateRegistryOwner(this) + window!!.decorView.setViewTreeOnBackPressedDispatcherOwner(this) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/plugin/base/BaseActivity.kt b/core/src/main/java/io/github/fplus/plugin/base/BaseActivity.kt index db98f73..d7ea036 100644 --- a/core/src/main/java/io/github/fplus/plugin/base/BaseActivity.kt +++ b/core/src/main/java/io/github/fplus/plugin/base/BaseActivity.kt @@ -18,6 +18,7 @@ import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.setViewTreeLifecycleOwner +import androidx.lifecycle.setViewTreeViewModelStoreOwner import androidx.savedstate.SavedStateRegistry import androidx.savedstate.SavedStateRegistryController import androidx.savedstate.SavedStateRegistryOwner @@ -146,8 +147,9 @@ open class BaseActivity : Activity(), } private fun initViewTreeOwners() { + window!!.decorView.setViewTreeViewModelStoreOwner(this) window!!.decorView.setViewTreeLifecycleOwner(this) - window!!.decorView.setViewTreeOnBackPressedDispatcherOwner(this) window!!.decorView.setViewTreeSavedStateRegistryOwner(this) + window!!.decorView.setViewTreeOnBackPressedDispatcherOwner(this) } } \ No newline at end of file diff --git a/core/src/main/java/io/github/fplus/plugin/proxy/v1/PluginBridge.kt b/core/src/main/java/io/github/fplus/plugin/proxy/v1/PluginBridge.kt index c7b5ccf..b6f21cc 100644 --- a/core/src/main/java/io/github/fplus/plugin/proxy/v1/PluginBridge.kt +++ b/core/src/main/java/io/github/fplus/plugin/proxy/v1/PluginBridge.kt @@ -8,6 +8,10 @@ import io.github.fplus.plugin.proxySingle object PluginBridge { fun init(application: Application, stubActivity: Class<*>) { + init(application, stubActivity.name) + } + + fun init(application: Application, stubActivity: String) { hookPackageManager(application, stubActivity) hookInstrumentation(application, stubActivity) } @@ -19,7 +23,7 @@ object PluginBridge { @SuppressLint("DiscouragedPrivateApi", "PrivateApi") private fun hookInstrumentation( application: Application, - subActivityClass: Class<*>, + subActivityClassName: String, ) { // 1.from ContextImpl get mMainThread field value (ActivityThread obj) // 2.from ActivityThread get mInstrumentation field (Instrumentation obj) @@ -45,7 +49,7 @@ object PluginBridge { val mInstrumentationObj = mInstrumentationField.get(activityThreadObj) as Instrumentation // 4.reset set value - mInstrumentationField.set(activityThreadObj, PluginInstrumentation(mInstrumentationObj, subActivityClass)) + mInstrumentationField.set(activityThreadObj, PluginInstrumentation(mInstrumentationObj, subActivityClassName)) } catch (e: IllegalAccessException) { e.printStackTrace() } catch (e: NoSuchFieldException) { @@ -63,7 +67,7 @@ object PluginBridge { @SuppressLint("PrivateApi", "DiscouragedPrivateApi") private fun hookPackageManager( application: Application, - stubActivityClass: Class<*>, + stubActivityClassName: String, ) { try { // 获取到 ActivityThread 中的静态字段 sPackageManager 一个静态的 IPackageManager @@ -78,7 +82,7 @@ object PluginBridge { if (method.name == "getActivityInfo") { args?.forEachIndexed { index, _ -> if (args[index] is ComponentName) { - args[index] = ComponentName(application.packageName, stubActivityClass.name) + args[index] = ComponentName(application.packageName, stubActivityClassName) } } } diff --git a/core/src/main/java/io/github/fplus/plugin/proxy/v1/PluginInstrumentation.kt b/core/src/main/java/io/github/fplus/plugin/proxy/v1/PluginInstrumentation.kt index 9cb98f0..4da6f89 100644 --- a/core/src/main/java/io/github/fplus/plugin/proxy/v1/PluginInstrumentation.kt +++ b/core/src/main/java/io/github/fplus/plugin/proxy/v1/PluginInstrumentation.kt @@ -20,7 +20,7 @@ import java.lang.reflect.Method @Keep class PluginInstrumentation( private val mBase: Instrumentation, - private val stubActivity: Class<*>, + private val stubActivity: String, ) : Instrumentation() { companion object { const val PLUGIN_PROXY_ACTIVITY = "xpler_plugin" @@ -136,7 +136,7 @@ class PluginInstrumentation( try { val pluginClazz = pluginClassloader?.loadClass(intent.component?.className) if (pluginClazz != null && IXplerActivity::class.java.isAssignableFrom(pluginClazz)) { - newIntent = Intent(who, stubActivity) + newIntent = Intent().apply { setClassName(who!!, stubActivity) } intent.extras?.let { newIntent.putExtras(it) } newIntent.putExtra(PLUGIN_PROXY_ACTIVITY, pluginClazz.name) } diff --git a/core/src/main/java/io/github/fplus/plugin/proxy/v2/PluginBridgeV2.kt b/core/src/main/java/io/github/fplus/plugin/proxy/v2/PluginBridgeV2.kt index 84a450f..2e9a330 100644 --- a/core/src/main/java/io/github/fplus/plugin/proxy/v2/PluginBridgeV2.kt +++ b/core/src/main/java/io/github/fplus/plugin/proxy/v2/PluginBridgeV2.kt @@ -25,6 +25,10 @@ object PluginBridgeV2 { private const val XPLER_ORIGIN_INTENT = "XPLER_ORIGIN_INTENT" fun init(application: Application, stubActivity: Class<*>) { + init(application, stubActivity.name) + } + + fun init(application: Application, stubActivity: String) { hookStartActivity(application, stubActivity) hookLauncherActivity() hookInstrumentation(application) @@ -43,7 +47,10 @@ object PluginBridgeV2 { * @param subActivityClazz 在AndroidManifest.xml中注册了的Activity */ @SuppressLint("DiscouragedPrivateApi", "PrivateApi") - private fun hookStartActivity(context: Context, subActivityClazz: Class<*>) { + private fun hookStartActivity( + context: Context, + subActivityClassName: String, + ) { // Android 10+ // 1.获取ActivityTaskManager的Class对象 // package android.app; @@ -62,7 +69,7 @@ object PluginBridgeV2 { val iActivityTaskManagerSingletonObj = iActivityTaskManagerSingletonField.get(null) // 5. - handleIActivityTaskManager(context, subActivityClazz, iActivityTaskManagerSingletonObj) + handleIActivityTaskManager(context, subActivityClassName, iActivityTaskManagerSingletonObj) } /** @@ -72,7 +79,7 @@ object PluginBridgeV2 { @SuppressLint("DiscouragedPrivateApi", "PrivateApi") private fun handleIActivityTaskManager( context: Context, - subActivityClazz: Class<*>, + subActivityClassName: String, IActivityTaskManagerSingletonObj: Any?, ) { try { @@ -109,7 +116,7 @@ object PluginBridgeV2 { val iActivityTaskManagerProxy = Proxy.newProxyInstance( Thread.currentThread().contextClassLoader, arrayOf(iActivityTaskManagerClazz), - IActivityInvocationHandler(iActivityTaskManager, context, subActivityClazz) + IActivityInvocationHandler(iActivityTaskManager, context, subActivityClassName) ) // 13.重新赋值 @@ -135,7 +142,7 @@ object PluginBridgeV2 { private class IActivityInvocationHandler( private val mIActivityManager: Any?, private val mContext: Context, - private val mSubActivityClazz: Class<*>, + private val mSubActivityClassName: String, ) : InvocationHandler { @Throws(InvocationTargetException::class, IllegalAccessException::class) override fun invoke(proxy: Any, method: Method, args: Array?): Any? { @@ -150,7 +157,7 @@ object PluginBridgeV2 { // 将启动的未注册的Activity对应的Intent,替换为安全的注册了的桩Activity的Intent // 1.将未注册的Activity对应的Intent,改为安全的Intent,既在AndroidManifest.xml中配置了的Activity的Intent val originIntent = args[intentIndex] as Intent - val safeIntent = Intent(mContext, mSubActivityClazz) + val safeIntent = Intent().also { it.setClassName(mContext, mSubActivityClassName) } // public class Intent implements Parcelable; // Intent类已经实现了Parcelable接口 safeIntent.putExtra(XPLER_ORIGIN_INTENT, originIntent) @@ -421,7 +428,11 @@ object PluginBridgeV2 { val classLoader = moduleClassloader ?: PluginBridgeV2.classLoader ?: return false // 是否模块Activity, - val pluginActivityClazz = classLoader.loadClass(pluginActivityClassName) ?: return false - return IXplerActivity::class.java.isAssignableFrom(pluginActivityClazz) + return try { + val pluginActivityClazz = classLoader.loadClass(pluginActivityClassName) ?: return false + IXplerActivity::class.java.isAssignableFrom(pluginActivityClazz) + } catch (e: ClassNotFoundException) { + false + } } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a52ae68..9d4dbfc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,8 +6,7 @@ appcompat = "1.6.1" android-material = "1.8.0" lifecycle-runtime-ktx = "2.5.1" activity-compose = "1.5.1" -compose-bom = "2023.03.00" -compose-material = "1.3.1" +compose-bom = "2023.04.00" accompanist-systemuicontroller = "0.28.0" runtime-livedata = "1.3.3" okhttp3 = "4.8.0" @@ -27,7 +26,7 @@ compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = ui = { group = "androidx.compose.ui", name = "ui" } ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } -compose-material = { group = "androidx.compose.material", name = "material", version.ref = "compose-material" } +compose-material = { group = "androidx.compose.material", name = "material" } accompanist-systemuicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanist-systemuicontroller" } runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "runtime-livedata" } okhttp3 = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp3" } diff --git a/readme.md b/readme.md index 2928449..6c50768 100644 --- a/readme.md +++ b/readme.md @@ -18,6 +18,7 @@ - 首页控件半透明防烧屏 - 首页清爽模式隐藏大部分控件 - 顶部Tab栏自定义隐藏 +- 聊天消息防撤回 - 禁用双击点赞 - 双击打开评论区 - 全屏沉浸式播放 @@ -29,7 +30,7 @@ ## 支持版本 - 理论上支持:23.5.0 ~ 至今 -- 具体版本号列表可参考:[version.json](https://github.com/GangJust/FreedomPlus/blob/master/versions.json) +- 版本号**参考**列表:[version.json](https://github.com/GangJust/FreedomPlus/blob/master/versions.json) diff --git a/versions.json b/versions.json index d93fb00..525fe4b 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"] \ 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"] \ No newline at end of file diff --git a/xpler b/xpler index e6bf259..24bc298 160000 --- a/xpler +++ b/xpler @@ -1 +1 @@ -Subproject commit e6bf259f988f9cedb16360b64dcc9747221502e7 +Subproject commit 24bc298b96533ae7e7048ca14374d83f0f3839e9