diff --git a/README.md b/README.md index f73ab39..a736c39 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TSBattery -![Eclipse Marketplace](https://img.shields.io/badge/build-passing-brightgreen) +![Eclipse Marketplace](https://img.shields.io/badge/build-pending-dbab09) ![Eclipse Marketplace](https://img.shields.io/badge/license-AGPL3.0-blue) ![Eclipse Marketplace](https://img.shields.io/badge/version-v3.3-green) [![Telegram](https://img.shields.io/static/v1?label=Telegram&message=交流讨论&color=0088cc)](https://t.me/XiaofangInternet) diff --git a/app/build.gradle b/app/build.gradle index fafe18a..31cff9d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,7 +30,7 @@ android { buildTypes { release { - minifyEnabled true + minifyEnabled false signingConfig signingConfigs.debug proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } @@ -42,6 +42,9 @@ android { kotlinOptions { jvmTarget = '1.8' } + buildFeatures { + viewBinding true + } } dependencies { diff --git a/app/src/main/java/com/fankes/tsbattery/ui/activity/MainActivity.kt b/app/src/main/java/com/fankes/tsbattery/ui/activity/MainActivity.kt index b836393..2cd8f1f 100644 --- a/app/src/main/java/com/fankes/tsbattery/ui/activity/MainActivity.kt +++ b/app/src/main/java/com/fankes/tsbattery/ui/activity/MainActivity.kt @@ -27,17 +27,11 @@ import android.content.ComponentName import android.content.Intent import android.content.pm.PackageManager import android.net.Uri -import android.os.Bundle -import android.view.View -import android.widget.LinearLayout -import android.widget.TextView import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.SwitchCompat -import androidx.constraintlayout.utils.widget.ImageFilterView import androidx.core.view.isGone import com.fankes.tsbattery.BuildConfig import com.fankes.tsbattery.R +import com.fankes.tsbattery.databinding.ActivityMainBinding import com.fankes.tsbattery.hook.HookConst.DISABLE_WECHAT_HOOK import com.fankes.tsbattery.hook.HookConst.ENABLE_HIDE_ICON import com.fankes.tsbattery.hook.HookConst.ENABLE_MODULE_VERSION @@ -49,16 +43,15 @@ import com.fankes.tsbattery.hook.HookConst.ENABLE_RUN_INFO import com.fankes.tsbattery.hook.HookConst.QQ_PACKAGE_NAME import com.fankes.tsbattery.hook.HookConst.TIM_PACKAGE_NAME import com.fankes.tsbattery.hook.HookConst.WECHAT_PACKAGE_NAME +import com.fankes.tsbattery.ui.activity.base.BaseActivity import com.fankes.tsbattery.utils.factory.isInstall -import com.fankes.tsbattery.utils.factory.isNotSystemInDarkMode import com.fankes.tsbattery.utils.factory.openSelfSetting import com.fankes.tsbattery.utils.factory.showDialog -import com.gyf.immersionbar.ktx.immersionBar import com.highcapable.yukihookapi.hook.factory.isTaiChiModuleActive import com.highcapable.yukihookapi.hook.factory.modulePrefs import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus -class MainActivity : AppCompatActivity() { +class MainActivity : BaseActivity() { companion object { @@ -69,25 +62,12 @@ class MainActivity : AppCompatActivity() { private const val wechatSupportVersion = "全版本仅支持基础省电,更多功能依然画饼" } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - /** 隐藏系统的标题栏 */ - supportActionBar?.hide() - /** 初始化沉浸状态栏 */ - immersionBar { - statusBarColor(R.color.colorThemeBackground) - autoDarkModeEnable(true) - statusBarDarkFont(isNotSystemInDarkMode) - navigationBarColor(R.color.colorThemeBackground) - navigationBarDarkIcon(isNotSystemInDarkMode) - fitsSystemWindows(true) - } + override fun onCreate() { /** 判断 Hook 状态 */ if (isHooked()) { - findViewById(R.id.main_lin_status).setBackgroundResource(R.drawable.bg_green_round) - findViewById(R.id.main_img_status).setImageResource(R.mipmap.ic_success) - findViewById(R.id.main_text_status).text = "模块已激活" + binding.mainLinStatus.setBackgroundResource(R.drawable.bg_green_round) + binding.mainImgStatus.setImageResource(R.mipmap.ic_success) + binding.mainTextStatus.text = "模块已激活" /** 写入激活的模块版本 */ modulePrefs.putString(ENABLE_MODULE_VERSION, moduleVersion) } else @@ -120,12 +100,12 @@ class MainActivity : AppCompatActivity() { noCancelable() } /** 设置安装状态 */ - findViewById(R.id.main_text_qq_noinstall).isGone = QQ_PACKAGE_NAME.isInstall - findViewById(R.id.main_text_tim_noinstall).isGone = TIM_PACKAGE_NAME.isInstall - findViewById(R.id.main_text_wechat_noinstall).isGone = WECHAT_PACKAGE_NAME.isInstall + binding.mainTextQqNoinstall.isGone = QQ_PACKAGE_NAME.isInstall + binding.mainTextTimNoinstall.isGone = TIM_PACKAGE_NAME.isInstall + binding.mainTextWechatNoinstall.isGone = WECHAT_PACKAGE_NAME.isInstall /** 设置文本 */ - findViewById(R.id.main_text_version).text = "模块版本:$moduleVersion" - findViewById(R.id.main_text_support_qq).apply { + binding.mainTextVersion.text = "模块版本:$moduleVersion" + binding.mainTextSupportQq.apply { text = qqSupportVersion setOnClickListener { showDialog { @@ -135,7 +115,7 @@ class MainActivity : AppCompatActivity() { } } } - findViewById(R.id.main_text_support_tim).apply { + binding.mainTextSupportTim.apply { text = timSupportVersion setOnClickListener { showDialog { @@ -145,7 +125,7 @@ class MainActivity : AppCompatActivity() { } } } - findViewById(R.id.main_text_support_wechat).apply { + binding.mainTextSupportWechat.apply { text = wechatSupportVersion setOnClickListener { showDialog { @@ -155,39 +135,31 @@ class MainActivity : AppCompatActivity() { } } } - /** 初始化 View */ - val qqTimProtectModeSwitch = findViewById(R.id.qqtim_protect_mode_switch) - val qqTimCoreServiceSwitch = findViewById(R.id.shut_core_sv_qqtim_switch) - val qqTimCoreServiceKnSwitch = findViewById(R.id.shut_core_sv_kn_qqtim_switch) - val wechatDisableHookSwitch = findViewById(R.id.disable_wechat_sv_switch) - val hideIconInLauncherSwitch = findViewById(R.id.hide_icon_in_launcher_switch) - val notifyModuleInfoSwitch = findViewById(R.id.notify_module_info_switch) - val notifyNotifyTipSwitch = findViewById(R.id.notify_module_notify_tip_switch) /** 获取 Sp 存储的信息 */ - qqTimProtectModeSwitch.isChecked = modulePrefs.getBoolean(ENABLE_QQTIM_WHITE_MODE) - qqTimCoreServiceSwitch.isChecked = modulePrefs.getBoolean(ENABLE_QQTIM_CORESERVICE_BAN) - qqTimCoreServiceKnSwitch.isChecked = modulePrefs.getBoolean(ENABLE_QQTIM_CORESERVICE_CHILD_BAN) - wechatDisableHookSwitch.isChecked = modulePrefs.getBoolean(DISABLE_WECHAT_HOOK) - hideIconInLauncherSwitch.isChecked = modulePrefs.getBoolean(ENABLE_HIDE_ICON) - notifyModuleInfoSwitch.isChecked = modulePrefs.getBoolean(ENABLE_RUN_INFO) - notifyNotifyTipSwitch.isChecked = modulePrefs.getBoolean(ENABLE_NOTIFY_TIP, default = true) - qqTimProtectModeSwitch.setOnCheckedChangeListener { btn, b -> + binding.qqtimProtectModeSwitch.isChecked = modulePrefs.getBoolean(ENABLE_QQTIM_WHITE_MODE) + binding.qqTimCoreServiceSwitch.isChecked = modulePrefs.getBoolean(ENABLE_QQTIM_CORESERVICE_BAN) + binding.qqTimCoreServiceKnSwitch.isChecked = modulePrefs.getBoolean(ENABLE_QQTIM_CORESERVICE_CHILD_BAN) + binding.wechatDisableHookSwitch.isChecked = modulePrefs.getBoolean(DISABLE_WECHAT_HOOK) + binding.hideIconInLauncherSwitch.isChecked = modulePrefs.getBoolean(ENABLE_HIDE_ICON) + binding.notifyModuleInfoSwitch.isChecked = modulePrefs.getBoolean(ENABLE_RUN_INFO) + binding.notifyNotifyTipSwitch.isChecked = modulePrefs.getBoolean(ENABLE_NOTIFY_TIP, default = true) + binding.qqtimProtectModeSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(ENABLE_QQTIM_WHITE_MODE, b) } - qqTimCoreServiceSwitch.setOnCheckedChangeListener { btn, b -> + binding.qqTimCoreServiceSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(ENABLE_QQTIM_CORESERVICE_BAN, b) } - qqTimCoreServiceKnSwitch.setOnCheckedChangeListener { btn, b -> + binding.qqTimCoreServiceKnSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(ENABLE_QQTIM_CORESERVICE_CHILD_BAN, b) } - wechatDisableHookSwitch.setOnCheckedChangeListener { btn, b -> + binding.wechatDisableHookSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(DISABLE_WECHAT_HOOK, b) } - hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b -> + binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(ENABLE_HIDE_ICON, b) packageManager.setComponentEnabledSetting( @@ -196,22 +168,22 @@ class MainActivity : AppCompatActivity() { PackageManager.DONT_KILL_APP ) } - notifyModuleInfoSwitch.setOnCheckedChangeListener { btn, b -> + binding.notifyModuleInfoSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(ENABLE_RUN_INFO, b) } - notifyNotifyTipSwitch.setOnCheckedChangeListener { btn, b -> + binding.notifyNotifyTipSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(ENABLE_NOTIFY_TIP, b) } /** 快捷操作 QQ */ - findViewById(R.id.quick_qq_button).setOnClickListener { openSelfSetting(QQ_PACKAGE_NAME) } + binding.quickQqButton.setOnClickListener { openSelfSetting(QQ_PACKAGE_NAME) } /** 快捷操作 TIM */ - findViewById(R.id.quick_tim_button).setOnClickListener { openSelfSetting(TIM_PACKAGE_NAME) } + binding.quickTimButton.setOnClickListener { openSelfSetting(TIM_PACKAGE_NAME) } /** 快捷操作微信 */ - findViewById(R.id.quick_wechat_button).setOnClickListener { openSelfSetting(WECHAT_PACKAGE_NAME) } + binding.quickWechatButton.setOnClickListener { openSelfSetting(WECHAT_PACKAGE_NAME) } /** 恰饭! */ - findViewById(R.id.link_with_follow_me).setOnClickListener { + binding.linkWithFollowMe.setOnClickListener { runCatching { startActivity(Intent().apply { setPackage("com.coolapk.market") @@ -225,7 +197,7 @@ class MainActivity : AppCompatActivity() { } } /** 项目地址按钮点击事件 */ - findViewById(R.id.title_github_icon).setOnClickListener { + binding.titleGithubIcon.setOnClickListener { runCatching { startActivity(Intent().apply { action = "android.intent.action.VIEW" diff --git a/app/src/main/java/com/fankes/tsbattery/ui/activity/base/BaseActivity.kt b/app/src/main/java/com/fankes/tsbattery/ui/activity/base/BaseActivity.kt new file mode 100644 index 0000000..5fce547 --- /dev/null +++ b/app/src/main/java/com/fankes/tsbattery/ui/activity/base/BaseActivity.kt @@ -0,0 +1,69 @@ +/* + * TSBattery - A new way to save your battery avoid cancer apps hacker it. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/fankes/TSBattery + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/30. + */ +@file:Suppress("UNCHECKED_CAST") + +package com.fankes.tsbattery.ui.activity.base + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.viewbinding.ViewBinding +import com.fankes.tsbattery.R +import com.fankes.tsbattery.utils.factory.isNotSystemInDarkMode +import com.gyf.immersionbar.ktx.immersionBar +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass +import java.lang.reflect.ParameterizedType + +abstract class BaseActivity : AppCompatActivity() { + + /** 获取绑定布局对象 */ + lateinit var binding: VB + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + javaClass.genericSuperclass.also { type -> + if (type is ParameterizedType) { + binding = (type.actualTypeArguments[0] as Class).method { + name = "inflate" + param(LayoutInflaterClass) + }.get().invoke(layoutInflater) ?: error("binding failed") + setContentView(binding.root) + } else error("binding but got wrong type") + } + /** 隐藏系统的标题栏 */ + supportActionBar?.hide() + /** 初始化沉浸状态栏 */ + immersionBar { + statusBarColor(R.color.colorThemeBackground) + autoDarkModeEnable(true) + statusBarDarkFont(isNotSystemInDarkMode) + navigationBarColor(R.color.colorThemeBackground) + navigationBarDarkIcon(isNotSystemInDarkMode) + fitsSystemWindows(true) + } + /** 装载子类 */ + onCreate() + } + + /** 回调 [onCreate] 方法 */ + abstract fun onCreate() +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/tsbattery/utils/factory/DialogBuilderFactory.kt b/app/src/main/java/com/fankes/tsbattery/utils/factory/DialogBuilderFactory.kt index 9006dc4..32b103b 100644 --- a/app/src/main/java/com/fankes/tsbattery/utils/factory/DialogBuilderFactory.kt +++ b/app/src/main/java/com/fankes/tsbattery/utils/factory/DialogBuilderFactory.kt @@ -19,14 +19,27 @@ * * This file is Created by fankes on 2022/1/7. */ -@file:Suppress("unused") +@file:Suppress("unused", "OPT_IN_USAGE", "EXPERIMENTAL_API_USAGE") package com.fankes.tsbattery.utils.factory -import android.app.AlertDialog +import android.app.Dialog import android.content.Context import android.graphics.Color import android.graphics.drawable.GradientDrawable +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.ProgressBar +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.viewbinding.ViewBinding +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.highcapable.yukihookapi.annotation.DoNotUseField +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass /** * 构造对话框 @@ -39,70 +52,149 @@ fun Context.showDialog(isUseBlackTheme: Boolean = false, it: DialogBuilder.() -> /** * 对话框构造器 * @param context 实例 - * @param isUseBlackTheme 是否使用深色主题 + * @param isUseBlackTheme 是否使用深色主题 - 对 AndroidX 风格无效 */ -class DialogBuilder(private val context: Context, private val isUseBlackTheme: Boolean) { +class DialogBuilder(val context: Context, private val isUseBlackTheme: Boolean) { - private var instance: AlertDialog.Builder? = null // 实例对象 + private var instanceAndroidX: androidx.appcompat.app.AlertDialog.Builder? = null // 实例对象 + private var instanceAndroid: android.app.AlertDialog.Builder? = null // 实例对象 + + private var dialogInstance: Dialog? = null // 对话框实例 + + @DoNotUseField + var customLayoutView: View? = null // 自定义布局 + + /** + * 是否需要使用 AndroidX 风格对话框 + * @return [Boolean] + */ + private val isUsingAndroidX get() = runCatching { context is AppCompatActivity }.getOrNull() ?: false init { - instance = AlertDialog.Builder( - context, - if (isUseBlackTheme) android.R.style.Theme_Material_Dialog else android.R.style.Theme_Material_Light_Dialog - ) + if (isUsingAndroidX) + runCatching { instanceAndroidX = MaterialAlertDialogBuilder(context) } + else runCatching { + instanceAndroid = android.app.AlertDialog.Builder( + context, + if (isUseBlackTheme) android.R.style.Theme_Material_Dialog else android.R.style.Theme_Material_Light_Dialog + ) + } } /** 设置对话框不可关闭 */ - fun noCancelable() = instance?.setCancelable(false) + fun noCancelable() { + if (isUsingAndroidX) + runCatching { instanceAndroidX?.setCancelable(false) } + else runCatching { instanceAndroid?.setCancelable(false) } + } /** 设置对话框标题 */ var title get() = "" set(value) { - instance?.setTitle(value) + if (isUsingAndroidX) + runCatching { instanceAndroidX?.setTitle(value) } + else runCatching { instanceAndroid?.setTitle(value) } } /** 设置对话框消息内容 */ var msg get() = "" set(value) { - instance?.setMessage(value) + if (isUsingAndroidX) + runCatching { instanceAndroidX?.setMessage(value) } + else runCatching { instanceAndroid?.setMessage(value) } } + /** 设置进度条对话框消息内容 */ + var progressContent + get() = "" + set(value) { + if (customLayoutView == null) + customLayoutView = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER or Gravity.START + addView(ProgressBar(context)) + addView(View(context).apply { layoutParams = ViewGroup.LayoutParams(20.dp(context), 5) }) + addView(TextView(context).apply { + tag = "progressContent" + text = value + }) + setPadding(20.dp(context), 20.dp(context), 20.dp(context), 20.dp(context)) + } + else customLayoutView?.findViewWithTag("progressContent")?.text = value + } + + /** + * 设置对话框自定义布局 + * @return [ViewBinding] + */ + inline fun bind() = + T::class.java.method { + name = "inflate" + param(LayoutInflaterClass) + }.get().invoke(LayoutInflater.from(context))?.apply { + customLayoutView = root + } ?: error("binding failed") + /** * 设置对话框确定按钮 * @param text 按钮文本内容 * @param it 点击事件 */ - fun confirmButton(text: String = "确定", it: () -> Unit = {}) = - instance?.setPositiveButton(text) { _, _ -> it() } + fun confirmButton(text: String = "确定", it: () -> Unit = {}) { + if (isUsingAndroidX) + runCatching { instanceAndroidX?.setPositiveButton(text) { _, _ -> it() } } + else runCatching { instanceAndroid?.setPositiveButton(text) { _, _ -> it() } } + } /** * 设置对话框取消按钮 * @param text 按钮文本内容 * @param it 点击事件 */ - fun cancelButton(text: String = "取消", it: () -> Unit = {}) = - instance?.setNegativeButton(text) { _, _ -> it() } + fun cancelButton(text: String = "取消", it: () -> Unit = {}) { + if (isUsingAndroidX) + runCatching { instanceAndroidX?.setNegativeButton(text) { _, _ -> it() } } + else runCatching { instanceAndroid?.setNegativeButton(text) { _, _ -> it() } } + } /** * 设置对话框第三个按钮 * @param text 按钮文本内容 * @param it 点击事件 */ - fun neutralButton(text: String = "更多", it: () -> Unit = {}) = - instance?.setNeutralButton(text) { _, _ -> it() } + fun neutralButton(text: String = "更多", it: () -> Unit = {}) { + if (isUsingAndroidX) + runCatching { instanceAndroidX?.setNeutralButton(text) { _, _ -> it() } } + else runCatching { instanceAndroid?.setNeutralButton(text) { _, _ -> it() } } + } + + /** 取消对话框 */ + fun cancel() = dialogInstance?.cancel() /** 显示对话框 */ - internal fun show() = instance?.create()?.apply { - window?.setBackgroundDrawable(GradientDrawable( - GradientDrawable.Orientation.TOP_BOTTOM, - if (isUseBlackTheme) intArrayOf(0xFF2D2D2D.toInt(), 0xFF2D2D2D.toInt()) - else intArrayOf(Color.WHITE, Color.WHITE) - ).apply { - shape = GradientDrawable.RECTANGLE - gradientType = GradientDrawable.LINEAR_GRADIENT - cornerRadius = 15.dpFloat(this@DialogBuilder.context) - }) - }?.show() + internal fun show() { + if (isUsingAndroidX) runCatching { + instanceAndroidX?.create()?.apply { + customLayoutView?.let { setView(it) } + dialogInstance = this + }?.show() + } else runCatching { + instanceAndroid?.create()?.apply { + customLayoutView?.let { setView(it) } + window?.setBackgroundDrawable( + GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, + if (isUseBlackTheme) intArrayOf(0xFF2D2D2D.toInt(), 0xFF2D2D2D.toInt()) + else intArrayOf(Color.WHITE, Color.WHITE) + ).apply { + shape = GradientDrawable.RECTANGLE + gradientType = GradientDrawable.LINEAR_GRADIENT + cornerRadius = 15.dpFloat(this@DialogBuilder.context) + }) + dialogInstance = this + }?.show() + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index e9e4b33..3140ffe 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -109,13 +109,13 @@ android:layout_marginEnd="3dp" android:alpha="0.8" android:background="@drawable/bg_red_round" - android:paddingLeft="2dp" - android:paddingTop="1dp" - android:paddingRight="2dp" - android:paddingBottom="1dp" + android:paddingLeft="3dp" + android:paddingTop="2dp" + android:paddingRight="3dp" + android:paddingBottom="2dp" android:text="未安装" android:textColor="@color/white" - android:textSize="9sp" + android:textSize="8sp" tools:ignore="SmallSp" /> + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 061284b..2335edf 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file