63 Commits
3.7 ... 3.99.6

Author SHA1 Message Date
8b657a8aab Update version to 3.99.6 | Support QQ 8.9.2 2022-07-21 19:22:03 +08:00
f0ab310620 Support QQ 8.9.2 2022-07-21 19:20:43 +08:00
711e0ac730 Update version to 3.99.5 | Support many QQ 8.x.x version 2022-07-20 22:58:46 +08:00
b1e83a1073 Update gradle version 2022-07-20 22:54:43 +08:00
b562337995 Update Kotlin version to 1.7.10 2022-07-20 22:50:16 +08:00
6b990c50f8 ReFormat code style 2022-07-20 22:48:26 +08:00
Fankesyooni
bae91688b1 Merge pull request #20 from chase535/patch-1
适配大量 QQ 8.x.x
2022-07-20 12:50:43 +08:00
chase535
a59d260321 Update MainActivity.kt 2022-07-20 12:28:24 +08:00
chase535
e850a73c49 Update HookEntry.kt 2022-07-20 11:38:09 +08:00
chase535
1dc725c294 适配大量 QQ 8.x.x
适配了绝大多数(可能是全部)8.x.x版本的正式版QQ
2022-07-20 11:13:58 +08:00
ce89da3ee8 Merge dependencies 2022-07-20 02:08:42 +08:00
81331dfe81 Update version to 3.99 | Support QQ 8.9.0 and 8.9.1 2022-07-19 22:11:34 +08:00
20f1ceca4d Support QQ 8.9.0 and 8.9.1 2022-07-19 22:02:04 +08:00
fc8589f1ee Fix hook BaseChatPie failure bug 2022-07-19 21:57:06 +08:00
Fankesyooni
67378b2ebf Merge pull request #19 from StarWishsama/patch-1
适配 QQ 8.9.0
2022-07-17 01:04:11 +08:00
NoRainCity
bf4ecd8345 Update QQ 8.9.0 2022-07-16 20:53:33 +08:00
6e8d900dc5 Update version to 3.98 | Support QQ 8.8.98 2022-07-05 22:49:18 +08:00
3d12d3f4a5 Added QQ version support situation 2022-07-05 22:45:17 +08:00
4b36437dac Modify BugHook luckily 2022-07-05 22:04:04 +08:00
93789f163b Support QQ 8.8.98 2022-07-05 22:01:49 +08:00
32d8937ddb Update version to 3.97 | Support QQ 8.8.95 2022-06-16 02:25:01 +08:00
38c9d78f1e Update Gradle & Kotlin & PlatformSDK
- Update Kotlin version to 1.7.0
- Update Gradle dependencies
- Merge legacy code
2022-06-10 17:13:58 +08:00
0ee03340da Merge hook function 2022-06-10 17:10:51 +08:00
54ee2358cf Make UI to Primary Theme 2022-06-08 15:07:23 +08:00
55708eb96a Merge DialogBuilderFactory with new code style 2022-06-07 16:55:29 +08:00
c4de8ae448 Update version to 3.96 | Support QQ 8.8.93 2022-06-04 03:20:38 +08:00
9e092742da Fix GithubReleaseTool to LocalTime 2022-06-04 03:04:54 +08:00
fb27c107f4 Added BuildConfig.VERSION_NAME changed 2022-06-03 23:54:21 +08:00
2be9a3d934 Update version to 3.95 2022-05-31 02:06:35 +08:00
d7dbecb65c Update YukiHookAPI 2022-05-31 02:02:26 +08:00
6c2b3f12b3 Merge code 2022-05-30 02:11:30 +08:00
f1c520586a Merge code 2022-05-30 02:03:14 +08:00
e9c99343c3 Merge code 2022-05-30 01:43:47 +08:00
20de713ffd Merge systemBar support with native 2022-05-30 00:47:47 +08:00
3b78e8a515 Repair Licenses 2022-05-30 00:16:53 +08:00
cf898092bd Update version to 3.9 | Support QQ 8.8.90 2022-05-30 00:10:35 +08:00
251a5151df Added YukiPromoteTool 2022-05-30 00:03:57 +08:00
59e93bc040 Support QQ 8.8.90 2022-05-29 22:51:25 +08:00
d440e85be4 Update YukiHookAPI 2022-05-29 04:12:54 +08:00
e1dc99de01 Update YukiHookAPI 2022-05-27 03:34:16 +08:00
4eab1f7046 Update YukiHookAPI 2022-05-25 04:36:25 +08:00
3fc294dd8d Update YukiHookAPI 2022-05-10 01:52:07 +08:00
a2c06cc201 Update YukiHookAPI 2022-05-06 15:01:27 +08:00
6f39d5fdab Update YukiHookAPI 2022-05-04 14:01:41 +08:00
5ada7585e3 Update YukiHookAPI 2022-05-04 10:14:29 +08:00
31b8f157e5 Update YukiHookAPI 2022-05-04 09:31:13 +08:00
e71f137f74 Update Kotlin version 2022-05-04 07:01:04 +08:00
9e9bbcc8ca Update YukiHookAPI 2022-05-04 07:00:17 +08:00
8043457a6e Update version to 3.8,support QQ 8.8.88 2022-05-01 12:26:13 +08:00
d4a1536d70 Update YukiHookAPI 2022-05-01 12:15:20 +08:00
5c784f65f8 Merge code 2022-04-25 02:46:13 +08:00
1c6c6e6480 Update YukiHookAPI 2022-04-18 03:12:32 +08:00
4712d0063b Update YukiHookAPI 2022-04-18 03:11:27 +08:00
583c72470b Merge code 2022-04-15 15:26:01 +08:00
8cf945b670 Update YukiHookAPI 2022-04-15 05:11:06 +08:00
6fba93e1fa Merge code 2022-04-13 04:48:52 +08:00
288563ef68 Merge code 2022-04-13 04:46:15 +08:00
d1e4c17817 Update YukiHookAPI 2022-04-13 04:42:25 +08:00
c8ec50cb29 Update YukiHookAPI 2022-04-10 03:13:58 +08:00
b6e042b9d0 Update YukiHookAPI 2022-04-09 02:28:05 +08:00
3c4789d9a0 Update README.md 2022-04-09 01:48:48 +08:00
46ff5f4bcf Update YukiHookAPI 2022-04-05 22:46:12 +08:00
09eeeeab5f Update YukiHookAPI 2022-04-04 22:53:27 +08:00
33 changed files with 316 additions and 1851 deletions

View File

@@ -35,7 +35,7 @@ fun a(test: String) {
val a = "" // 变量注释
```
- ⚠️注意:只允许两个 // 后方要有空格
- ⚠️ 注意:只允许两个 // 后方要有空格
## 项目要求

View File

@@ -2,40 +2,46 @@
[![Blank](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/fankes/TSBattery)
[![Blank](https://img.shields.io/badge/license-AGPL3.0-blue)](https://github.com/fankes/TSBattery/blob/master/LICENSE)
[![Blank](https://img.shields.io/badge/version-v3.7-green)](https://github.com/fankes/TSBattery/releases)
[![Blank](https://img.shields.io/badge/version-v3.99.6-green)](https://github.com/fankes/TSBattery/releases)
[![Blank](https://img.shields.io/github/downloads/fankes/TSBattery/total?label=Release)](https://github.com/fankes/TSBattery/releases)
[![Blank](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.tsbattery/total?label=LSPosed%20Repo&logo=Android&style=flat&labelColor=F48FB1&logoColor=ffffff)](https://github.com/Xposed-Modules-Repo/com.fankes.tsbattery/releases)
[![Telegram](https://img.shields.io/static/v1?label=Telegram&message=交流讨论&color=0088cc)](https://t.me/XiaofangInternet)
[![Telegram](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram)](https://t.me/XiaofangInternet)
<br/><br/>
![banner](https://github.com/fankes/TSBattery/blob/master/banner.png)<br/>
A new way to save your battery avoid cancer apps hacker it.<br/>
A new way to save your battery avoid cancer apps hacker it.
TSBattery 是一个旨在使 QQ、TIM、微信 变得更省电的开源 Xposed 模块。
# Developer
## Developer
[酷安 @星夜不荟](http://www.coolapk.com/u/876977)
# 适配说明
## 适配说明
- 支持并建议使用 <b>LSPosed</b>(若作用域没有自动出现推荐请勾选 QQ、TIM、微信)
- 可以使用 <b>~~EdXposed~~</b>,但随时停止支持
- <b>太极无极 · 阴</b> 支持性不是很好,建议使用 <b>太极无极 · 阳</b>
- 支持 <b>Pine</b>(梦境模块) 但是部分功能有限制
- 请不要使用 <b>~~应用转生~~</b>,发生封号情况后果自负
# 请勿用于非法用途
## 请勿用于非法用途
- 本模块完全开源免费,如果好用你可以打赏支持开发,但是请不要用于非法用途。
- 本模块发布地址仅有 [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.tsbattery/releases)、
[Release](https://github.com/fankes/TSBattery/releases) 及 [蓝奏云](https://fankes.lanzouy.com/b02zfz3sj),从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
# 开始贡献
## 开始贡献
欢迎为此项目进行新版本的适配代码贡献!<br/>
- [CONTRIBUTING](https://github.com/fankes/TSBattery/blob/master/CONTRIBUTING.md)
# 许可证
## 许可证
- [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html)
@@ -56,5 +62,6 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)<br/><br/>
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)
版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com)

View File

@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.devtools.ksp' version '1.6.10-1.0.4'
id 'com.google.devtools.ksp' version '1.7.10-1.0.6'
}
android {
@@ -15,8 +15,7 @@ android {
v2SigningEnabled true
}
}
compileSdkVersion 31
buildToolsVersion "31.0.0"
compileSdkVersion 32
defaultConfig {
applicationId "com.fankes.tsbattery"
@@ -25,20 +24,14 @@ android {
versionCode rootProject.ext.appVersionCode
versionName rootProject.ext.appVersionName
kotlinOptions {
freeCompilerArgs = [
'-Xno-param-assertions',
'-Xno-call-assertions',
'-Xno-receiver-assertions'
]
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled true
minifyEnabled rootProject.ext.enableR8
shrinkResources rootProject.ext.enableR8
zipAlignEnabled rootProject.ext.enableR8
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
@@ -49,38 +42,30 @@ android {
}
kotlinOptions {
jvmTarget = '11'
freeCompilerArgs = [
'-Xno-param-assertions',
'-Xno-call-assertions',
'-Xno-receiver-assertions'
]
}
buildFeatures {
viewBinding true
}
}
/** 移除无效耗时 lint Task */
tasks.whenTaskAdded {
task -> if (task.name == "lintVitalRelease") task.enabled = false
}
/** 移除无效耗时 lint Task */
tasks.whenTaskAdded {
task -> if (task.name == "lintVitalAnalyzeRelease") task.enabled = false
}
/** 移除无效耗时 lint Task */
tasks.whenTaskAdded {
task -> if (task.name == "lintVitalReportRelease") task.enabled = false
lintOptions {
checkReleaseBuilds false
}
}
dependencies {
compileOnly 'de.robv.android.xposed:api:82'
implementation 'com.highcapable.yukihookapi:api:1.0.70'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.70'
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
implementation 'com.geyifeng.immersionbar:immersionbar:3.2.0'
implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.0'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.highcapable.yukihookapi:api:1.0.92'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.92'
implementation 'com.github.duanhong169:drawabletoolbox:1.0.7'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.7'
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

View File

@@ -20,7 +20,6 @@
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontwarn
-ignorewarnings
-optimizationpasses 10
-dontusemixedcaseclassnames
@@ -35,13 +34,6 @@
-renamesourcefileattribute P
-keepattributes SourceFile,LineNumberTable
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static *** throwUninitializedProperty(...);
public static *** throwUninitializedPropertyAccessException(...);

View File

@@ -19,31 +19,15 @@
*
* This file is Created by fankes on 2021/11/9.
*/
@file:Suppress("unused")
package com.fankes.tsbattery.application
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
class TSApplication : Application() {
companion object {
/** 全局静态实例 */
private var context: TSApplication? = null
/**
* 调用全局静态实例
* @return [TSApplication]
*/
val appContext get() = context ?: error("App is death")
}
class TSApplication : ModuleApplication() {
override fun onCreate() {
super.onCreate()
/** 设置静态实例 */
context = this
/** 跟随系统夜间模式 */
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}

View File

@@ -52,10 +52,10 @@ import com.highcapable.yukihookapi.hook.log.loggerE
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.type.android.*
import com.highcapable.yukihookapi.hook.type.java.*
import com.highcapable.yukihookapi.hook.xposed.proxy.YukiHookXposedInitProxy
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
@InjectYukiHookWithXposed
class HookEntry : YukiHookXposedInitProxy {
@InjectYukiHookWithXposed(isUsingResourcesHook = false)
class HookEntry : IYukiHookXposedInit {
companion object {
@@ -84,6 +84,9 @@ class HookEntry : YukiHookXposedInitProxy {
)
}
/** 是否完全支持当前版本 */
private var isHookClientSupport = true
/**
* 这个类 QQ 的 BaseChatPie 是控制聊天界面的
*
@@ -104,24 +107,78 @@ class HookEntry : YukiHookXposedInitProxy {
*/
private fun PackageParam.hookQQBaseChatPie(version: String) {
when (version) {
"8.2.11" -> {
"8.0.0" -> {
interceptBaseChatPie(methodName = "bq")
interceptBaseChatPie(methodName = "aL")
}
"8.0.5", "8.0.7" -> {
interceptBaseChatPie(methodName = "bw")
interceptBaseChatPie(methodName = "aQ")
}
"8.1.0", "8.1.3" -> {
interceptBaseChatPie(methodName = "bE")
interceptBaseChatPie(methodName = "aT")
}
"8.1.5" -> {
interceptBaseChatPie(methodName = "bF")
interceptBaseChatPie(methodName = "aT")
}
"8.1.8", "8.2.0", "8.2.6" -> {
interceptBaseChatPie(methodName = "bC")
interceptBaseChatPie(methodName = "aT")
}
"8.2.7", "8.2.8", "8.2.11", "8.3.0" -> {
interceptBaseChatPie(methodName = "bE")
interceptBaseChatPie(methodName = "aV")
}
"8.8.17" -> {
"8.3.5" -> {
interceptBaseChatPie(methodName = "bR")
interceptBaseChatPie(methodName = "aX")
}
"8.3.6" -> {
interceptBaseChatPie(methodName = "cp")
interceptBaseChatPie(methodName = "aX")
}
"8.3.9" -> {
interceptBaseChatPie(methodName = "cj")
interceptBaseChatPie(methodName = "aT")
}
"8.4.1", "8.4.5" -> {
interceptBaseChatPie(methodName = "ck")
interceptBaseChatPie(methodName = "aT")
}
"8.4.8", "8.4.10", "8.4.17", "8.4.18", "8.5.0" -> {
interceptBaseChatPie(methodName = "remainScreenOn")
interceptBaseChatPie(methodName = "cancelRemainScreenOn")
}
"8.5.5" -> {
interceptBaseChatPie(methodName = "bT")
interceptBaseChatPie(methodName = "aN")
}
"8.6.0", "8.6.5", "8.7.0", "8.7.5", "8.7.8", "8.8.0", "8.8.3", "8.8.5" -> {
interceptBaseChatPie(methodName = "ag")
interceptBaseChatPie(methodName = "ah")
}
"8.8.11", "8.8.12" -> {
interceptBaseChatPie(methodName = "bc")
interceptBaseChatPie(methodName = "bd")
}
"8.8.17", "8.8.20" -> {
interceptBaseChatPie(methodName = "bd")
interceptBaseChatPie(methodName = "be")
}
"8.8.23" -> {
"8.8.23", "8.8.28" -> {
interceptBaseChatPie(methodName = "bf")
interceptBaseChatPie(methodName = "bg")
}
/** 8.8.35 贡献者StarWishsama */
"8.8.33" -> {
interceptBaseChatPie(methodName = "bg")
interceptBaseChatPie(methodName = "bh")
}
"8.8.35", "8.8.38" -> {
interceptBaseChatPie(methodName = "bi")
interceptBaseChatPie(methodName = "bj")
}
/** 贡献者JiZhi-Error */
"8.8.50" -> {
interceptBaseChatPie(methodName = "bj")
interceptBaseChatPie(methodName = "bk")
@@ -130,11 +187,26 @@ class HookEntry : YukiHookXposedInitProxy {
interceptBaseChatPie(methodName = "bk")
interceptBaseChatPie(methodName = "bl")
}
"8.8.83", "8.8.85" -> {
"8.8.83", "8.8.85", "8.8.88", "8.8.90" -> {
interceptBaseChatPie(methodName = "bl")
interceptBaseChatPie(methodName = "bm")
}
else -> loggerD(msg = "$version not supported!")
"8.8.93", "8.8.95" -> {
interceptBaseChatPie(methodName = "J3")
interceptBaseChatPie(methodName = "S")
}
"8.8.98" -> {
interceptBaseChatPie(methodName = "M3")
interceptBaseChatPie(methodName = "S")
}
"8.9.0", "8.9.1", "8.9.2" -> {
interceptBaseChatPie(methodName = "N3")
interceptBaseChatPie(methodName = "S")
}
else -> {
isHookClientSupport = false
loggerD(msg = "$version not supported!")
}
}
}
@@ -147,6 +219,7 @@ class HookEntry : YukiHookXposedInitProxy {
injectMember {
method {
name = methodName
emptyParam()
returnType = UnitType
}
intercept()
@@ -159,7 +232,7 @@ class HookEntry : YukiHookXposedInitProxy {
injectMember {
method {
name = "acquireLocked"
returnType = UnitType
emptyParam()
}
intercept()
}
@@ -175,11 +248,11 @@ class HookEntry : YukiHookXposedInitProxy {
}
beforeHook {
if (prefs.get(DataConst.ENABLE_NOTIFY_TIP))
when (firstArgs<CharSequence>()) {
when (args().first().cast<CharSequence>()) {
"QQ正在后台运行" ->
args().set("QQ正在后台运行 - TSBattery 守护中")
args().first().set("QQ正在后台运行 - TSBattery 守护中")
"TIM正在后台运行" ->
args().set("TIM正在后台运行 - TSBattery 守护中")
args().first().set("TIM正在后台运行 - TSBattery 守护中")
}
}
}
@@ -187,11 +260,12 @@ class HookEntry : YukiHookXposedInitProxy {
/**
* 提示模块运行信息 QQ、TIM、微信
* @param isQQTIM 是否为 QQ、TIM
* @param isQQ 是否为 QQ
* @param isTIM 是否为 TIM
*/
private fun PackageParam.hookModuleRunningInfo(isQQTIM: Boolean) =
private fun PackageParam.hookModuleRunningInfo(isQQ: Boolean = false, isTIM: Boolean = false) =
when {
isQQTIM -> SplashActivityClass.hook {
isQQ || isTIM -> SplashActivityClass.hook {
/**
* Hook 启动界面的第一个 [Activity]
* QQ 和 TIM 都是一样的类
@@ -208,7 +282,9 @@ class HookEntry : YukiHookXposedInitProxy {
showDialog {
title = "TSBattery 已激活"
msg = "[提示模块运行信息功能已打开]\n\n" +
"模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n" +
(if (isQQ && isHookClientSupport.not())
"❎ 当前版本 $versionName($versionCode) 不在兼容列表,请自行测试是否生效~\n\n"
else "✅ 模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n") +
"已生效模块版本:${prefs.get(DataConst.ENABLE_MODULE_VERSION)}\n" +
"当前模式:${if (prefs.get(DataConst.ENABLE_QQTIM_WHITE_MODE)) "保守模式" else "完全模式"}" +
"\n\n包名:${packageName}\n版本:$versionName($versionCode)" +
@@ -347,7 +423,7 @@ class HookEntry : YukiHookXposedInitProxy {
method {
name = "setRightText"
param(CharSequenceType)
}.call(prefs.get(DataConst.ENABLE_MODULE_VERSION))
}.call("${if (isQQ && isHookClientSupport.not()) "❎" else "✅"} ${prefs.get(DataConst.ENABLE_MODULE_VERSION)}")
method {
name = "setBgType"
param(IntType)
@@ -357,7 +433,9 @@ class HookEntry : YukiHookXposedInitProxy {
instance<Activity>().apply {
showDialog {
title = "TSBattery 守护中"
msg = "已生效模块版本:${prefs.get(DataConst.ENABLE_MODULE_VERSION)}\n" +
msg = (if (isQQ && isHookClientSupport.not())
"❎ 当前版本 $versionName($versionCode) 不在兼容列表,请自行测试是否生效~\n\n" else "") +
"已生效模块版本:${prefs.get(DataConst.ENABLE_MODULE_VERSION)}\n" +
"当前模式:${if (prefs.get(DataConst.ENABLE_QQTIM_WHITE_MODE)) "保守模式" else "完全模式"}" +
"\n\n包名:${packageName}\n版本:$versionName($versionCode)" +
"\n\n模块只对挂后台锁屏情况下有省电效果," +
@@ -400,6 +478,7 @@ class HookEntry : YukiHookXposedInitProxy {
debugTag = "TSBattery"
isDebug = false
isEnableModulePrefsCache = false
isEnableDataChannel = false
}
override fun onHook() = encase {
@@ -407,19 +486,11 @@ class HookEntry : YukiHookXposedInitProxy {
hookSystemWakeLock()
hookNotification()
hookCoreService(isQQ = true)
hookModuleRunningInfo(isQQTIM = true)
hookModuleRunningInfo(isQQ = true)
hookQQSettingsSettingActivity(isQQ = true)
if (prefs.get(DataConst.ENABLE_QQTIM_WHITE_MODE)) return@loadApp
/** 通过在 [SplashActivityClass] 里取到应用的版本号 */
SplashActivityClass.hook {
injectMember {
method {
name = "doOnCreate"
param(BundleClass)
}
afterHook { hookQQBaseChatPie(instance<Activity>().versionName) }
}
}
/** 通过在生命周期里取到应用的版本号 */
onAppLifecycle { onCreate { hookQQBaseChatPie(versionName) } }
/**
* 干掉消息收发功能的电源锁
* 每个版本的差异暂未做排查
@@ -628,14 +699,14 @@ class HookEntry : YukiHookXposedInitProxy {
hookSystemWakeLock()
hookNotification()
hookCoreService(isQQ = false)
hookModuleRunningInfo(isQQTIM = true)
hookModuleRunningInfo(isTIM = true)
hookQQSettingsSettingActivity(isQQ = false)
}
loadApp(WECHAT_PACKAGE_NAME) {
if (prefs.get(DataConst.DISABLE_WECHAT_HOOK)) return@loadApp
hookSystemWakeLock()
hookModuleRunningInfo(isQQTIM = false)
hookModuleRunningInfo()
loggerD(msg = "ウイチャット:それが機能するかどうかはわかりませんでした")
}
}
}
}

View File

@@ -37,18 +37,32 @@ import com.fankes.tsbattery.hook.HookConst.WECHAT_PACKAGE_NAME
import com.fankes.tsbattery.ui.activity.base.BaseActivity
import com.fankes.tsbattery.utils.factory.*
import com.fankes.tsbattery.utils.tool.GithubReleaseTool
import com.highcapable.yukihookapi.hook.factory.isModuleActive
import com.highcapable.yukihookapi.hook.factory.isTaiChiModuleActive
import com.fankes.tsbattery.utils.tool.YukiPromoteTool
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
class MainActivity : BaseActivity<ActivityMainBinding>() {
companion object {
private const val moduleVersion = BuildConfig.VERSION_NAME
private const val qqSupportVersion =
"8.2.11(Play)、8.8.17、8.8.23、8.8.35、8.8.38、8.8.50、8.8.55、8.8.68、8.8.80、8.8.83、8.8.85 (8.2.11、8.5.5~8.8.85)"
private val qqSupportVersions = arrayOf(
"8.0.0", "8.0.5", "8.0.7", "8.1.0", "8.1.3", "8.1.5", "8.1.8",
"8.2.0", "8.2.6", "8.2.7", "8.2.8", "8.2.11", "8.3.0", "8.3.5",
"8.3.6", "8.3.9", "8.4.1", "8.4.5", "8.4.8", "8.4.10", "8.4.17",
"8.4.18", "8.5.0", "8.5.5", "8.6.0", "8.6.5", "8.7.0", "8.7.5",
"8.7.8", "8.8.0", "8.8.3", "8.8.5", "8.8.11", "8.8.12", "8.8.17",
"8.8.20", "8.8.23", "8.8.28", "8.8.33", "8.8.35", "8.8.38", "8.8.50",
"8.8.55", "8.8.68", "8.8.80", "8.8.83", "8.8.85", "8.8.88", "8.8.90",
"8.8.93", "8.8.95", "8.8.98", "8.9.0", "8.9.1", "8.9.2"
)
private val qqSupportVersion by lazy {
if (qqSupportVersions.isNotEmpty()) {
var value = ""
qqSupportVersions.forEach { value += "$it" }
"${value.trim().let { it.substring(0, it.lastIndex) }}\n\n其余版本请自行测试是否有效。"
} else "empty"
}
private const val timSupportVersion = "2+、3+ (并未完全测试每个版本)"
private const val wechatSupportVersion = "全版本仅支持基础省电,更多功能依然画饼"
@@ -66,7 +80,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
}
/** 判断 Hook 状态 */
if (isModuleActive) {
if (YukiHookAPI.Status.isModuleActive) {
binding.mainLinStatus.setBackgroundResource(R.drawable.bg_green_round)
binding.mainImgStatus.setImageResource(R.mipmap.ic_success)
binding.mainTextStatus.text = "模块已激活"
@@ -74,6 +88,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
refreshActivateExecutor()
/** 写入激活的模块版本 */
modulePrefs.put(DataConst.ENABLE_MODULE_VERSION, moduleVersion)
/** 推广、恰饭 */
YukiPromoteTool.promote(context = this)
} else
showDialog {
title = "模块没有激活"
@@ -89,14 +105,14 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
noCancelable()
}
/** 推荐使用 LSPosed */
if (isTaiChiModuleActive)
if (YukiHookAPI.Status.isTaiChiModuleActive)
showDialog {
title = "兼容性提示"
msg = "若你的设备已 Root推荐使用 LSPosed 激活模块,太极可能会出现模块设置无法保存的问题。"
confirmButton(text = "我知道了")
}
/** 检测应用转生 */
if (("com.bug.xposed").isInstall)
/** 检测应用转生 - 如果模块已激活就不再检测 */
if (("com.bug.xposed").isInstall && YukiHookAPI.Status.isModuleActive.not())
showDialog {
title = "环境异常"
msg = "检测到“应用转生”已被安装,为了保证模块的安全和稳定,请卸载更换其他 Hook 框架后才能继续使用。"
@@ -201,11 +217,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
/** 刷新模块激活使用的方式 */
private fun refreshActivateExecutor() {
when {
YukiHookModuleStatus.executorVersion > 0 ->
YukiHookAPI.Status.executorVersion > 0 ->
binding.mainTextApiWay.text =
"Activated by ${YukiHookModuleStatus.executorName} API ${YukiHookModuleStatus.executorVersion}"
isTaiChiModuleActive -> binding.mainTextApiWay.text = "Activated by TaiChi"
"Activated by ${YukiHookAPI.Status.executorName} API ${YukiHookAPI.Status.executorVersion}"
YukiHookAPI.Status.isTaiChiModuleActive -> binding.mainTextApiWay.text = "Activated by TaiChi"
else -> binding.mainTextApiWay.text = "Activated by anonymous"
}
}
}
}

View File

@@ -23,12 +23,14 @@
package com.fankes.tsbattery.ui.activity.base
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.WindowCompat
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
@@ -52,13 +54,14 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
/** 隐藏系统的标题栏 */
supportActionBar?.hide()
/** 初始化沉浸状态栏 */
immersionBar {
statusBarColor(R.color.colorThemeBackground)
autoDarkModeEnable(true)
statusBarDarkFont(isNotSystemInDarkMode)
navigationBarColor(R.color.colorThemeBackground)
navigationBarDarkIcon(isNotSystemInDarkMode)
fitsSystemWindows(true)
WindowCompat.getInsetsController(window, window.decorView).apply {
isAppearanceLightStatusBars = isNotSystemInDarkMode
isAppearanceLightNavigationBars = isNotSystemInDarkMode
}
ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also {
window?.statusBarColor = it
window?.navigationBarColor = it
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) window?.navigationBarDividerColor = it
}
/** 装载子类 */
onCreate()

View File

@@ -29,9 +29,9 @@ import android.graphics.Color
import android.text.TextUtils
import android.util.AttributeSet
import androidx.appcompat.widget.SwitchCompat
import com.fankes.tsbattery.utils.drawable.drawabletoolbox.DrawableBuilder
import com.fankes.tsbattery.utils.factory.dp
import com.fankes.tsbattery.utils.factory.isSystemInDarkMode
import top.defaults.drawabletoolbox.DrawableBuilder
class MaterialSwitch(context: Context, attrs: AttributeSet?) : SwitchCompat(context, attrs) {

View File

@@ -1,292 +0,0 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
@file:Suppress("SameParameterValue")
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.annotation.SuppressLint
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.RotateDrawable
import android.os.Build
import java.lang.reflect.Field
import java.lang.reflect.Method
private val gradientState = resolveGradientState()
private fun resolveGradientState(): Class<*> {
val classes = GradientDrawable::class.java.declaredClasses
for (singleClass in classes) {
if (singleClass.simpleName == "GradientState") return singleClass
}
throw RuntimeException("GradientState could not be found in thisAny GradientDrawable implementation")
}
private val rotateState = resolveRotateState()
private fun resolveRotateState(): Class<*> {
val classes = RotateDrawable::class.java.declaredClasses
for (singleClass in classes) {
if (singleClass.simpleName == "RotateState") return singleClass
}
throw RuntimeException("RotateState could not be found in thisAny RotateDrawable implementation")
}
@Throws(SecurityException::class, NoSuchFieldException::class)
private fun resolveField(source: Class<*>, fieldName: String): Field {
val field = source.getDeclaredField(fieldName)
field.isAccessible = true
return field
}
@Throws(SecurityException::class, NoSuchMethodException::class)
private fun resolveMethod(
source: Class<*>,
methodName: String,
vararg parameterTypes: Class<*>
): Method {
val method = source.getDeclaredMethod(methodName, *parameterTypes)
method.isAccessible = true
return method
}
fun setInnerRadius(drawable: GradientDrawable, value: Int) {
try {
val innerRadius = resolveField(gradientState, "mInnerRadius")
innerRadius.setInt(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setInnerRadiusRatio(drawable: GradientDrawable, value: Float) {
try {
val innerRadius = resolveField(gradientState, "mInnerRadiusRatio")
innerRadius.setFloat(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setThickness(drawable: GradientDrawable, value: Int) {
try {
val innerRadius = resolveField(gradientState, "mThickness")
innerRadius.setInt(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setThicknessRatio(drawable: GradientDrawable, value: Float) {
try {
val innerRadius = resolveField(gradientState, "mThicknessRatio")
innerRadius.setFloat(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setUseLevelForShape(drawable: GradientDrawable, value: Boolean) {
try {
val useLevelForShape = resolveField(gradientState, "mUseLevelForShape")
useLevelForShape.setBoolean(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
@SuppressLint("ObsoleteSdkInt")
fun setOrientation(drawable: GradientDrawable, value: GradientDrawable.Orientation) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
drawable.orientation = value
} else {
try {
val orientation = resolveField(gradientState, "mOrientation")
orientation.set(drawable.constantState, value)
val rectIdDirty = resolveField(GradientDrawable::class.java, "mRectIsDirty")
rectIdDirty.setBoolean(drawable, true)
drawable.invalidateSelf()
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
@SuppressLint("ObsoleteSdkInt")
fun setColors(drawable: GradientDrawable, value: IntArray) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
drawable.colors = value
} else {
try {
val colors = resolveField(gradientState, "mColors")
colors.set(drawable.constantState, value)
drawable.invalidateSelf()
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
fun setGradientRadiusType(drawable: GradientDrawable, value: Int) {
try {
val type = resolveField(gradientState, "mGradientRadiusType")
type.setInt(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setGradientRadius(drawable: GradientDrawable, value: Float) {
try {
val gradientRadius = resolveField(gradientState, "mGradientRadius")
gradientRadius.setFloat(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setStrokeColor(drawable: GradientDrawable, value: Int) {
try {
val type = resolveField(gradientState, "mStrokeColor")
type.setInt(drawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
fun setDrawable(rotateDrawable: RotateDrawable, drawable: Drawable) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
rotateDrawable.drawable = drawable
} else {
try {
val drawableField = resolveField(rotateState, "mDrawable")
val stateField = resolveField(RotateDrawable::class.java, "mState")
drawableField.set(stateField.get(rotateDrawable), drawable)
drawable.callback = rotateDrawable
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
fun setPivotX(rotateDrawable: RotateDrawable, value: Float) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
rotateDrawable.pivotX = value
} else {
try {
val pivotXField = resolveField(rotateState, "mPivotX")
pivotXField.setFloat(rotateDrawable.constantState, value)
val pivotXRelField = resolveField(rotateState, "mPivotXRel")
pivotXRelField.setBoolean(rotateDrawable.constantState, true)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
fun setPivotY(rotateDrawable: RotateDrawable, value: Float) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
rotateDrawable.pivotY = value
} else {
try {
val pivotYField = resolveField(rotateState, "mPivotY")
pivotYField.setFloat(rotateDrawable.constantState, value)
val pivotYRelField = resolveField(rotateState, "mPivotYRel")
pivotYRelField.setBoolean(rotateDrawable.constantState, true)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
fun setFromDegrees(rotateDrawable: RotateDrawable, value: Float) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
rotateDrawable.fromDegrees = value
} else {
try {
val fromDegreesField = resolveField(rotateState, "mFromDegrees")
fromDegreesField.setFloat(rotateDrawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
fun setToDegrees(rotateDrawable: RotateDrawable, value: Float) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
rotateDrawable.toDegrees = value
} else {
try {
val toDegreesField = resolveField(rotateState, "mToDegrees")
toDegreesField.setFloat(rotateDrawable.constantState, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}
fun setRadius(rippleDrawable: RippleDrawable, value: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
rippleDrawable.radius = value
} else {
try {
val setRadiusMethod =
resolveMethod(RippleDrawable::class.java, "setMaxRadius", Int::class.java)
setRadiusMethod.invoke(rippleDrawable, value)
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
}
}

View File

@@ -1,29 +0,0 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
class Constants {
companion object {
const val DEFAULT_COLOR = 0xFFBA68C8.toInt()
}
}

View File

@@ -1,489 +0,0 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.util.StateSet
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
class DrawableBuilder {
private var properties = DrawableProperties()
private var order: AtomicInteger = AtomicInteger(1)
private var transformsMap = TreeMap<Int, (Drawable) -> Drawable>()
private var baseDrawable: Drawable? = null
fun batch(properties: DrawableProperties) = apply { this.properties = properties.copy() }
fun baseDrawable(drawable: Drawable) = apply { baseDrawable = drawable }
// <shape>
fun shape(shape: Int) = apply { properties.shape = shape }
fun rectangle() = apply { shape(GradientDrawable.RECTANGLE) }
fun oval() = apply { shape(GradientDrawable.OVAL) }
fun line() = apply { shape(GradientDrawable.LINE) }
fun ring() = apply { shape(GradientDrawable.RING) }
fun innerRadius(innerRadius: Int) = apply { properties.innerRadius = innerRadius }
fun innerRadiusRatio(innerRadiusRatio: Float) =
apply { properties.innerRadiusRatio = innerRadiusRatio }
fun thickness(thickness: Int) = apply { properties.thickness = thickness }
fun thicknessRatio(thicknessRatio: Float) = apply { properties.thicknessRatio = thicknessRatio }
fun useLevelForRing(use: Boolean = true) = apply { properties.useLevelForRing = use }
// <corner>
fun cornerRadius(cornerRadius: Int) = apply { properties.cornerRadius = cornerRadius }
fun topLeftRadius(topLeftRadius: Int) = apply { properties.topLeftRadius = topLeftRadius }
fun topRightRadius(topRightRadius: Int) = apply { properties.topRightRadius = topRightRadius }
fun bottomRightRadius(bottomRightRadius: Int) =
apply { properties.bottomRightRadius = bottomRightRadius }
fun bottomLeftRadius(bottomLeftRadius: Int) =
apply { properties.bottomLeftRadius = bottomLeftRadius }
fun rounded() = apply { cornerRadius(Int.MAX_VALUE) }
fun cornerRadii(
topLeftRadius: Int,
topRightRadius: Int,
bottomRightRadius: Int,
bottomLeftRadius: Int
) = apply {
topLeftRadius(topLeftRadius); topRightRadius(topRightRadius); bottomRightRadius(
bottomRightRadius
); bottomLeftRadius(bottomLeftRadius)
}
// <gradient>
fun gradient(useGradient: Boolean = true) = apply { properties.useGradient = useGradient }
fun gradientType(type: Int) = apply { properties.type = type }
fun linearGradient() = apply { gradientType(GradientDrawable.LINEAR_GRADIENT) }
fun radialGradient() = apply { gradientType(GradientDrawable.RADIAL_GRADIENT) }
fun sweepGradient() = apply { gradientType(GradientDrawable.SWEEP_GRADIENT) }
fun angle(angle: Int) = apply { properties.angle = angle }
fun centerX(centerX: Float) = apply { properties.centerX = centerX }
fun centerY(centerY: Float) = apply { properties.centerY = centerY }
fun center(centerX: Float, centerY: Float) = apply { centerX(centerX); centerY(centerY) }
fun useCenterColor(useCenterColor: Boolean = true) =
apply { properties.useCenterColor = useCenterColor }
fun startColor(startColor: Int) = apply { properties.startColor = startColor }
fun centerColor(centerColor: Int) = apply {
properties.centerColor = centerColor
useCenterColor(true)
}
fun endColor(endColor: Int) = apply { properties.endColor = endColor }
fun gradientColors(startColor: Int, endColor: Int, centerColor: Int?) = apply {
startColor(startColor); endColor(endColor)
useCenterColor(centerColor != null)
centerColor?.let {
centerColor(it)
}
}
fun gradientRadiusType(gradientRadiusType: Int) =
apply { properties.gradientRadiusType = gradientRadiusType }
fun gradientRadius(gradientRadius: Float) = apply { properties.gradientRadius = gradientRadius }
fun gradientRadius(radius: Float, type: Int) =
apply { gradientRadius(radius); gradientRadiusType(type) }
fun gradientRadiusInPixel(radius: Float) =
apply { gradientRadius(radius); gradientRadiusType(DrawableProperties.RADIUS_TYPE_PIXELS) }
fun gradientRadiusInFraction(radius: Float) =
apply { gradientRadius(radius); gradientRadiusType(DrawableProperties.RADIUS_TYPE_FRACTION) }
fun useLevelForGradient(use: Boolean) = apply { properties.useLevelForGradient = use }
fun useLevelForGradient() = apply { useLevelForGradient(true) }
// <size>
fun width(width: Int) = apply { properties.width = width }
fun height(height: Int) = apply { properties.height = height }
fun size(width: Int, height: Int) = apply { width(width); height(height) }
fun size(size: Int) = apply { width(size).height(size) }
// <solid>
fun solidColor(solidColor: Int) = apply { properties.solidColor = solidColor }
private var solidColorPressed: Int? = null
fun solidColorPressed(color: Int?) = apply { solidColorPressed = color }
private var solidColorPressedWhenRippleUnsupported: Int? = null
fun solidColorPressedWhenRippleUnsupported(color: Int?) =
apply { solidColorPressedWhenRippleUnsupported = color }
private var solidColorDisabled: Int? = null
fun solidColorDisabled(color: Int?) = apply { solidColorDisabled = color }
private var solidColorSelected: Int? = null
fun solidColorSelected(color: Int?) = apply { solidColorSelected = color }
fun solidColorStateList(colorStateList: ColorStateList) =
apply { properties.solidColorStateList = colorStateList }
// <stroke>
fun strokeWidth(strokeWidth: Int) = apply { properties.strokeWidth = strokeWidth }
fun strokeColor(strokeColor: Int) = apply { properties.strokeColor = strokeColor }
private var strokeColorPressed: Int? = null
fun strokeColorPressed(color: Int?) = apply { strokeColorPressed = color }
private var strokeColorDisabled: Int? = null
fun strokeColorDisabled(color: Int?) = apply { strokeColorDisabled = color }
private var strokeColorSelected: Int? = null
fun strokeColorSelected(color: Int?) = apply { strokeColorSelected = color }
fun strokeColorStateList(colorStateList: ColorStateList) =
apply { properties.strokeColorStateList = colorStateList }
fun dashWidth(dashWidth: Int) = apply { properties.dashWidth = dashWidth }
fun dashGap(dashGap: Int) = apply { properties.dashGap = dashGap }
fun hairlineBordered() = apply { strokeWidth(1) }
fun shortDashed() = apply { dashWidth(12).dashGap(12) }
fun mediumDashed() = apply { dashWidth(24).dashGap(24) }
fun longDashed() = apply { dashWidth(36).dashGap(36) }
fun dashed() = apply { mediumDashed() }
// <rotate>
private var rotateOrder = 0
fun rotate(boolean: Boolean = true) = apply {
properties.useRotate = boolean
rotateOrder = if (boolean) {
order.getAndIncrement()
} else {
0
}
}
fun pivotX(pivotX: Float) = apply { properties.pivotX = pivotX }
fun pivotY(pivotY: Float) = apply { properties.pivotY = pivotY }
fun pivot(pivotX: Float, pivotY: Float) = apply { pivotX(pivotX).pivotY(pivotY) }
fun fromDegrees(degrees: Float) = apply { properties.fromDegrees = degrees }
fun toDegrees(degrees: Float) = apply { properties.toDegrees = degrees }
fun degrees(fromDegrees: Float, toDegrees: Float) =
apply { fromDegrees(fromDegrees).toDegrees(toDegrees) }
fun degrees(degrees: Float) = apply { fromDegrees(degrees).toDegrees(degrees) }
fun rotate(fromDegrees: Float, toDegrees: Float) =
apply { rotate().fromDegrees(fromDegrees).toDegrees(toDegrees) }
fun rotate(degrees: Float) = apply { rotate().degrees(degrees) }
// <scale>
private var scaleOrder = 0
fun scale(boolean: Boolean = true) = apply {
properties.useScale = boolean
scaleOrder = if (boolean) {
order.getAndIncrement()
} else {
0
}
}
fun scaleLevel(level: Int) = apply { properties.scaleLevel = level }
fun scaleGravity(gravity: Int) = apply { properties.scaleGravity = gravity }
fun scaleWidth(scale: Float) = apply { properties.scaleWidth = scale }
fun scaleHeight(scale: Float) = apply { properties.scaleHeight = scale }
fun scale(scale: Float) = apply { scale().scaleWidth(scale).scaleHeight(scale) }
fun scale(scaleWidth: Float, scaleHeight: Float) =
apply { scale().scaleWidth(scaleWidth).scaleHeight(scaleHeight) }
// flip
fun flip(boolean: Boolean = true) = apply { properties.useFlip = boolean }
fun orientation(orientation: Int) = apply { properties.orientation = orientation }
fun flipVertical() = apply { flip().orientation(FlipDrawable.ORIENTATION_VERTICAL) }
// <ripple>
fun ripple(boolean: Boolean = true) = apply { properties.useRipple = boolean }
fun rippleColor(color: Int) = apply { properties.rippleColor = color }
fun rippleColorStateList(colorStateList: ColorStateList) =
apply { properties.rippleColorStateList = colorStateList }
fun rippleRadius(radius: Int) = apply { properties.rippleRadius = radius }
fun build(): Drawable {
if (baseDrawable != null) {
return wrap(baseDrawable!!)
}
var drawable: Drawable
// fall back when ripple is unavailable on devices with API < 21
if (shouldFallbackRipple()) {
if (solidColorPressedWhenRippleUnsupported != null) {
solidColorPressed(solidColorPressedWhenRippleUnsupported)
} else {
solidColorPressed(properties.rippleColor)
}
}
if (needStateListDrawable()) {
drawable = StateListDrawableBuilder()
.pressed(buildPressedDrawable())
.disabled(buildDisabledDrawable())
.selected(buildSelectedDrawable())
.normal(buildNormalDrawable())
.build()
} else {
drawable = GradientDrawable()
setupGradientDrawable(drawable)
}
drawable = wrap(drawable)
return drawable
}
private fun getSolidColorStateList(): ColorStateList {
if (properties.solidColorStateList != null) {
return properties.solidColorStateList!!
}
val states = mutableListOf<IntArray>()
val colors = mutableListOf<Int>()
solidColorPressed?.let {
states.add(intArrayOf(android.R.attr.state_pressed))
colors.add(it)
}
solidColorDisabled?.let {
states.add(intArrayOf(-android.R.attr.state_enabled))
colors.add(it)
}
solidColorSelected?.let {
states.add(intArrayOf(android.R.attr.state_selected))
colors.add(it)
}
states.add(StateSet.WILD_CARD)
colors.add(properties.solidColor)
return ColorStateList(states.toTypedArray(), colors.toIntArray())
}
private fun getStrokeColorStateList(): ColorStateList {
if (properties.strokeColorStateList != null) {
return properties.strokeColorStateList!!
}
val states = mutableListOf<IntArray>()
val colors = mutableListOf<Int>()
strokeColorPressed?.let {
states.add(intArrayOf(android.R.attr.state_pressed))
colors.add(it)
}
strokeColorDisabled?.let {
states.add(intArrayOf(-android.R.attr.state_enabled))
colors.add(it)
}
strokeColorSelected?.let {
states.add(intArrayOf(android.R.attr.state_selected))
colors.add(it)
}
states.add(StateSet.WILD_CARD)
colors.add(properties.strokeColor)
return ColorStateList(states.toTypedArray(), colors.toIntArray())
}
private fun buildPressedDrawable(): Drawable? {
if (solidColorPressed == null && strokeColorPressed == null) return null
val pressedDrawable = GradientDrawable()
setupGradientDrawable(pressedDrawable)
solidColorPressed?.let {
pressedDrawable.setColor(it)
}
strokeColorPressed?.let {
setStrokeColor(pressedDrawable, it)
}
return pressedDrawable
}
private fun buildDisabledDrawable(): Drawable? {
if (solidColorDisabled == null && strokeColorDisabled == null) return null
val disabledDrawable = GradientDrawable()
setupGradientDrawable(disabledDrawable)
solidColorDisabled?.let {
disabledDrawable.setColor(it)
}
strokeColorDisabled?.let {
setStrokeColor(disabledDrawable, it)
}
return disabledDrawable
}
private fun buildSelectedDrawable(): Drawable? {
if (solidColorSelected == null && strokeColorSelected == null) return null
val selectedDrawable = GradientDrawable()
setupGradientDrawable(selectedDrawable)
solidColorSelected?.let {
selectedDrawable.setColor(it)
}
strokeColorSelected?.let {
setStrokeColor(selectedDrawable, it)
}
return selectedDrawable
}
private fun buildNormalDrawable(): Drawable {
val pressedDrawable = GradientDrawable()
setupGradientDrawable(pressedDrawable)
return pressedDrawable
}
private fun setupGradientDrawable(drawable: GradientDrawable) {
properties.apply {
drawable.shape = shape
if (shape == GradientDrawable.RING) {
setInnerRadius(drawable, innerRadius)
setInnerRadiusRatio(drawable, innerRadiusRatio)
setThickness(drawable, thickness)
setThicknessRatio(drawable, thicknessRatio)
setUseLevelForShape(drawable, useLevelForRing)
}
drawable.cornerRadii = getCornerRadii()
if (useGradient) {
drawable.gradientType = type
setGradientRadiusType(drawable, gradientRadiusType)
setGradientRadius(drawable, gradientRadius)
drawable.setGradientCenter(centerX, centerY)
setOrientation(drawable, getOrientation())
setColors(drawable, getColors())
drawable.useLevel = useLevelForGradient
} else {
drawable.color = getSolidColorStateList()
}
drawable.setSize(width, height)
drawable.setStroke(
strokeWidth,
getStrokeColorStateList(),
dashWidth.toFloat(),
dashGap.toFloat()
)
}
}
private fun needStateListDrawable(): Boolean {
return (hasStrokeColorStateList() || (!properties.useGradient && hasSolidColorStateList()))
}
private fun needRotateDrawable(): Boolean {
return properties.useRotate &&
!(properties.pivotX == 0.5f && properties.pivotY == 0.5f
&& properties.fromDegrees == 0f && properties.toDegrees == 0f)
}
private fun needScaleDrawable(): Boolean {
return properties.useScale
}
private fun wrap(drawable: Drawable): Drawable {
var wrappedDrawable = drawable
if (rotateOrder > 0) {
transformsMap[rotateOrder] = ::wrapRotateIfNeeded
}
if (scaleOrder > 0) {
transformsMap[scaleOrder] = ::wrapScaleIfNeeded
}
for (action in transformsMap.values) {
wrappedDrawable = action.invoke(wrappedDrawable)
}
if (properties.useFlip) {
wrappedDrawable = FlipDrawableBuilder()
.drawable(wrappedDrawable)
.orientation(properties.orientation)
.build()
}
if (isRippleSupported() && properties.useRipple) {
wrappedDrawable = RippleDrawableBuilder()
.drawable(wrappedDrawable)
.color(properties.rippleColor)
.colorStateList(properties.rippleColorStateList)
.radius(properties.rippleRadius)
.build()
}
return wrappedDrawable
}
private fun shouldFallbackRipple(): Boolean {
return properties.useRipple && !isRippleSupported()
}
private fun isRippleSupported() = true
private fun wrapRotateIfNeeded(drawable: Drawable): Drawable {
if (!needRotateDrawable()) return drawable
with(properties) {
return RotateDrawableBuilder()
.drawable(drawable)
.pivotX(pivotX)
.pivotY(pivotY)
.fromDegrees(fromDegrees)
.toDegrees(toDegrees)
.build()
}
}
private fun wrapScaleIfNeeded(drawable: Drawable): Drawable {
if (!needScaleDrawable()) return drawable
with(properties) {
return ScaleDrawableBuilder()
.drawable(drawable)
.level(scaleLevel)
.scaleGravity(scaleGravity)
.scaleWidth(scaleWidth)
.scaleHeight(scaleHeight)
.build()
}
}
private fun hasSolidColorStateList(): Boolean {
return solidColorPressed != null || solidColorDisabled != null || solidColorSelected != null
}
private fun hasStrokeColorStateList(): Boolean {
return strokeColorPressed != null || strokeColorDisabled != null || strokeColorSelected != null
}
}

View File

@@ -1,221 +0,0 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
@file:Suppress("SetterBackingFieldAssignment", "unused")
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.os.Parcel
import android.os.Parcelable
import android.view.Gravity
import java.io.Serializable
data class DrawableProperties(
// <shape>
@JvmField var shape: Int = GradientDrawable.RECTANGLE,
@JvmField var innerRadius: Int = -1,
@JvmField var innerRadiusRatio: Float = 9f,
@JvmField var thickness: Int = -1,
@JvmField var thicknessRatio: Float = 3f,
@JvmField var useLevelForRing: Boolean = false,
// <corner>
private var _cornerRadius: Int = 0,
@JvmField var topLeftRadius: Int = 0,
@JvmField var topRightRadius: Int = 0,
@JvmField var bottomRightRadius: Int = 0,
@JvmField var bottomLeftRadius: Int = 0,
// <gradient>
@JvmField var useGradient: Boolean = false,
@JvmField var type: Int = GradientDrawable.RADIAL_GRADIENT,
@JvmField var angle: Int = 0,
@JvmField var centerX: Float = 0.5f,
@JvmField var centerY: Float = 0.5f,
@JvmField var useCenterColor: Boolean = false,
@JvmField var startColor: Int = Constants.DEFAULT_COLOR,
@JvmField var centerColor: Int? = null,
@JvmField var endColor: Int = 0x7FFFFFFF,
@JvmField var gradientRadiusType: Int = RADIUS_TYPE_FRACTION,
@JvmField var gradientRadius: Float = 0.5f,
@JvmField var useLevelForGradient: Boolean = false,
// <size>
@JvmField var width: Int = -1,
@JvmField var height: Int = -1,
// <solid>
@JvmField var solidColor: Int = Color.TRANSPARENT,
@JvmField var solidColorStateList: ColorStateList? = null,
// <stroke>
@JvmField var strokeWidth: Int = 0,
@JvmField var strokeColor: Int = Color.DKGRAY,
@JvmField var strokeColorStateList: ColorStateList? = null,
@JvmField var dashWidth: Int = 0,
@JvmField var dashGap: Int = 0,
// <rotate>
@JvmField var useRotate: Boolean = false,
@JvmField var pivotX: Float = 0.5f,
@JvmField var pivotY: Float = 0.5f,
@JvmField var fromDegrees: Float = 0f,
@JvmField var toDegrees: Float = 0f,
// <scale>
@JvmField var useScale: Boolean = false,
@JvmField var scaleLevel: Int = 10000,
@JvmField var scaleGravity: Int = Gravity.CENTER,
@JvmField var scaleWidth: Float = 0f,
@JvmField var scaleHeight: Float = 0f,
// flip
@JvmField var useFlip: Boolean = false,
@JvmField var orientation: Int = FlipDrawable.ORIENTATION_HORIZONTAL,
// ripple
@JvmField var useRipple: Boolean = false,
@JvmField var rippleColor: Int = Constants.DEFAULT_COLOR,
@JvmField var rippleColorStateList: ColorStateList? = null,
@JvmField var rippleRadius: Int = -1
) : Serializable {
companion object {
const val RADIUS_TYPE_PIXELS = 0
const val RADIUS_TYPE_FRACTION = 1
@JvmField
val CREATOR = object : Parcelable.Creator<DrawableProperties> {
override fun createFromParcel(parcel: Parcel): DrawableProperties {
return DrawableProperties(parcel)
}
override fun newArray(size: Int): Array<DrawableProperties?> {
return arrayOfNulls(size)
}
}
}
var cornerRadius: Int = _cornerRadius
set(value) {
_cornerRadius = value
topLeftRadius = value
topRightRadius = value
bottomRightRadius = value
bottomLeftRadius = value
}
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readInt(),
parcel.readFloat(),
parcel.readInt(),
parcel.readFloat(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readInt(),
parcel.readFloat(),
parcel.readFloat(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readInt(),
parcel.readInt(),
parcel.readFloat(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
parcel.readParcelable(ColorStateList::class.java.classLoader),
parcel.readInt(),
parcel.readInt(),
parcel.readParcelable(ColorStateList::class.java.classLoader),
parcel.readInt(),
parcel.readInt(),
parcel.readByte() != 0.toByte(),
parcel.readFloat(),
parcel.readFloat(),
parcel.readFloat(),
parcel.readFloat(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readInt(),
parcel.readFloat(),
parcel.readFloat(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readByte() != 0.toByte(),
parcel.readInt(),
parcel.readParcelable(ColorStateList::class.java.classLoader),
parcel.readInt()
)
fun copy(): DrawableProperties {
val parcel = Parcel.obtain()
parcel.setDataPosition(0)
val properties = CREATOR.createFromParcel(parcel)
parcel.recycle()
return properties
}
fun getCornerRadii(): FloatArray {
return floatArrayOf(
topLeftRadius.toFloat(), topLeftRadius.toFloat(),
topRightRadius.toFloat(), topRightRadius.toFloat(),
bottomRightRadius.toFloat(), bottomRightRadius.toFloat(),
bottomLeftRadius.toFloat(), bottomLeftRadius.toFloat()
)
}
fun getOrientation(): GradientDrawable.Orientation {
val orientation: GradientDrawable.Orientation = when (val angle = this.angle % 360) {
0 -> GradientDrawable.Orientation.LEFT_RIGHT
45 -> GradientDrawable.Orientation.BL_TR
90 -> GradientDrawable.Orientation.BOTTOM_TOP
135 -> GradientDrawable.Orientation.BR_TL
180 -> GradientDrawable.Orientation.RIGHT_LEFT
225 -> GradientDrawable.Orientation.TR_BL
270 -> GradientDrawable.Orientation.TOP_BOTTOM
315 -> GradientDrawable.Orientation.TL_BR
else -> throw IllegalArgumentException("Unsupported angle: $angle")
}
return orientation
}
fun getColors(): IntArray {
return if (useCenterColor && centerColor != null) {
intArrayOf(startColor, centerColor!!, endColor)
} else intArrayOf(startColor, endColor)
}
fun materialization(): Drawable = DrawableBuilder().batch(this).build()
}

View File

@@ -1,34 +0,0 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.drawable.Drawable
abstract class DrawableWrapperBuilder<T: DrawableWrapperBuilder<T>> {
protected var drawable: Drawable? = null
@Suppress("UNCHECKED_CAST")
fun drawable(drawable: Drawable): T = apply { this.drawable = drawable } as T
abstract fun build(): Drawable
}

View File

@@ -1,78 +0,0 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
@file:Suppress("DEPRECATION", "CanvasSize")
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Rect
import android.graphics.drawable.Drawable
class FlipDrawable(
private var drawable: Drawable,
private var orientation: Int = ORIENTATION_HORIZONTAL
) : Drawable() {
companion object {
const val ORIENTATION_HORIZONTAL = 0
const val ORIENTATION_VERTICAL = 1
}
override fun draw(canvas: Canvas) {
val saveCount = canvas.save()
if (orientation == ORIENTATION_VERTICAL) {
canvas.scale(1f, -1f, (canvas.width / 2).toFloat(), (canvas.height / 2).toFloat())
} else {
canvas.scale(-1f, 1f, (canvas.width / 2).toFloat(), (canvas.height / 2).toFloat())
}
drawable.bounds = Rect(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
canvas.restoreToCount(saveCount)
}
override fun onLevelChange(level: Int): Boolean {
drawable.level = level
invalidateSelf()
return true
}
override fun getIntrinsicWidth(): Int {
return drawable.intrinsicWidth
}
override fun getIntrinsicHeight(): Int {
return drawable.intrinsicHeight
}
override fun setAlpha(alpha: Int) {
drawable.alpha = alpha
}
override fun getOpacity(): Int {
return drawable.opacity
}
override fun setColorFilter(colorFilter: ColorFilter?) {
drawable.colorFilter = colorFilter
}
}

View File

@@ -1,35 +0,0 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.drawable.Drawable
class FlipDrawableBuilder : DrawableWrapperBuilder<FlipDrawableBuilder>() {
private var orientation: Int = FlipDrawable.ORIENTATION_HORIZONTAL
fun orientation(orientation: Int) = apply { this.orientation = orientation }
override fun build(): Drawable {
return FlipDrawable(drawable!!, orientation)
}
}

View File

@@ -1,182 +0,0 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.view.Gravity
import androidx.annotation.RequiresApi
import java.util.*
class LayerDrawableBuilder {
companion object {
const val DIMEN_UNDEFINED = Int.MIN_VALUE
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private var paddingMode = LayerDrawable.PADDING_MODE_NEST
private var paddingLeft = 0
private var paddingTop = 0
private var paddingRight = 0
private var paddingBottom = 0
private var paddingStart = 0
private var paddingEnd = 0
private val layers = ArrayList<Layer>()
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun paddingMode(mode: Int) = apply { paddingMode = mode }
fun paddingLeft(padding: Int) = apply { paddingLeft = padding }
fun paddingTop(padding: Int) = apply { paddingTop = padding }
fun paddingRight(padding: Int) = apply { paddingRight = padding }
fun paddingBottom(padding: Int) = apply { paddingBottom = padding }
@RequiresApi(Build.VERSION_CODES.M)
fun paddingStart(padding: Int) = apply { paddingStart = padding }
@RequiresApi(Build.VERSION_CODES.M)
fun paddingEnd(padding: Int) = apply { paddingEnd = padding }
fun padding(padding: Int) = apply {
paddingLeft(padding).paddingTop(padding).paddingRight(padding).paddingBottom(padding)
}
@RequiresApi(Build.VERSION_CODES.M)
fun paddingRelative(padding: Int) = apply {
paddingStart(padding).paddingTop(padding).paddingEnd(padding).paddingBottom(padding)
}
fun add(drawable: Drawable) = apply { layers.add(Layer(drawable)) }
fun width(width: Int) = apply { layers.last().width = width }
fun height(height: Int) = apply { layers.last().height = height }
fun insetLeft(inset: Int) = apply { layers.last().insetLeft = inset }
fun insetTop(inset: Int) = apply { layers.last().insetTop = inset }
fun insetRight(inset: Int) = apply { layers.last().insetRight = inset }
fun insetBottom(inset: Int) = apply { layers.last().insetBottom = inset }
@RequiresApi(Build.VERSION_CODES.M)
fun insetStart(inset: Int) = apply { layers.last().insetStart = inset }
@RequiresApi(Build.VERSION_CODES.M)
fun insetEnd(inset: Int) = apply { layers.last().insetEnd = inset }
fun inset(inset: Int) =
apply { insetLeft(inset).insetTop(inset).insetRight(inset).insetBottom(inset) }
fun inset(insetLeft: Int, insetTop: Int, insetRight: Int, insetBottom: Int) = apply {
insetLeft(insetLeft).insetTop(insetTop).insetRight(insetRight).insetBottom(insetBottom)
}
@RequiresApi(Build.VERSION_CODES.M)
fun insetRelative(inset: Int) =
apply { insetStart(inset).insetTop(inset).insetEnd(inset).insetBottom(inset) }
@RequiresApi(Build.VERSION_CODES.M)
fun insetRelative(insetStart: Int, insetTop: Int, insetEnd: Int, insetBottom: Int) = apply {
insetStart(insetStart).insetTop(insetTop).insetEnd(insetEnd).insetBottom(insetBottom)
}
@RequiresApi(Build.VERSION_CODES.M)
fun gravity(gravity: Int) = apply { layers.last().gravity = gravity }
fun modify(
index: Int, drawable: Drawable,
width: Int = DIMEN_UNDEFINED,
height: Int = DIMEN_UNDEFINED,
insetLeft: Int = DIMEN_UNDEFINED,
insetTop: Int = DIMEN_UNDEFINED,
insetRight: Int = DIMEN_UNDEFINED,
insetBottom: Int = DIMEN_UNDEFINED,
insetStart: Int = DIMEN_UNDEFINED,
insetEnd: Int = DIMEN_UNDEFINED
) =
apply {
val layer = layers[index]
layer.drawable = drawable
if (width != DIMEN_UNDEFINED) layer.width = width
if (height != DIMEN_UNDEFINED) layer.height = height
if (insetLeft != DIMEN_UNDEFINED) layer.insetLeft = insetLeft
if (insetTop != DIMEN_UNDEFINED) layer.insetTop = insetTop
if (insetRight != DIMEN_UNDEFINED) layer.insetRight = insetRight
if (insetBottom != DIMEN_UNDEFINED) layer.insetBottom = insetBottom
if (insetStart != DIMEN_UNDEFINED) layer.insetStart = insetStart
if (insetEnd != DIMEN_UNDEFINED) layer.insetEnd = insetEnd
}
fun build(): LayerDrawable {
val layerDrawable = LayerDrawable(layers.map { it -> it.drawable }.toTypedArray())
for (i in 0 until layers.size) {
val layer = layers[i]
layerDrawable.setLayerInset(
i,
layer.insetLeft,
layer.insetTop,
layer.insetRight,
layer.insetBottom
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (layer.insetStart != DIMEN_UNDEFINED || layer.insetEnd != DIMEN_UNDEFINED) {
layerDrawable.setLayerInsetRelative(
i,
layer.insetStart,
layer.insetTop,
layer.insetEnd,
layer.insetBottom
)
}
}
layerDrawable.setId(i, i)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
layerDrawable.setLayerGravity(i, layer.gravity)
layerDrawable.setLayerInsetStart(i, layer.insetStart)
layerDrawable.setLayerInsetEnd(i, layer.insetEnd)
}
}
layerDrawable.paddingMode = paddingMode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
layerDrawable.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)
if (paddingStart != DIMEN_UNDEFINED || paddingEnd != DIMEN_UNDEFINED) {
layerDrawable.setPaddingRelative(
paddingStart,
paddingTop,
paddingEnd,
paddingBottom
)
}
}
return layerDrawable
}
internal class Layer(var drawable: Drawable) {
var gravity: Int = Gravity.NO_GRAVITY
var width: Int = -1
var height: Int = -1
var insetLeft: Int = 0
var insetTop: Int = 0
var insetRight: Int = 0
var insetBottom: Int = 0
var insetStart: Int = DIMEN_UNDEFINED
var insetEnd: Int = DIMEN_UNDEFINED
}
}

View File

@@ -1,61 +0,0 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.Path
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.PathShape
class PathShapeDrawableBuilder {
private var path: Path? = null
private var pathStandardWidth: Float = 100f
private var pathStandardHeight: Float = 100f
private var width: Int = -1
private var height: Int = -1
fun path(path: Path, pathStandardWidth: Float, pathStandardHeight: Float) = apply {
this.path = path
this.pathStandardWidth = pathStandardWidth
this.pathStandardHeight = pathStandardHeight
}
fun width(width: Int) = apply { this.width = width }
fun height(height: Int) = apply { this.height = height }
fun size(size: Int) = apply { width(size).height(size) }
fun build(custom: ((shapeDrawable: ShapeDrawable) -> Unit)? = null): ShapeDrawable {
val shapeDrawable = ShapeDrawable()
if (path == null || width <= 0 || height <= 0) {
return shapeDrawable
}
val pathShape = PathShape(path!!, pathStandardWidth, pathStandardHeight)
shapeDrawable.shape = pathShape
shapeDrawable.intrinsicWidth = width
shapeDrawable.intrinsicHeight = height
if (custom != null) {
custom(shapeDrawable)
}
return shapeDrawable
}
}

View File

@@ -1,73 +0,0 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.*
import android.util.StateSet
class RippleDrawableBuilder : DrawableWrapperBuilder<RippleDrawableBuilder>() {
private var color: Int = Constants.DEFAULT_COLOR
private var colorStateList: ColorStateList? = null
private var radius: Int = -1
fun color(color: Int) = apply { this.color = color }
fun colorStateList(colorStateList: ColorStateList?) =
apply { this.colorStateList = colorStateList }
fun radius(radius: Int) = apply { this.radius = radius }
override fun build(): Drawable {
var drawable = this.drawable!!
val colorStateList = this.colorStateList ?: ColorStateList(
arrayOf(StateSet.WILD_CARD),
intArrayOf(color)
)
var mask = if (drawable is DrawableContainer) drawable.getCurrent() else drawable
if (mask is ShapeDrawable) {
val state = mask.getConstantState()
if (state != null) {
val temp = state.newDrawable().mutate() as ShapeDrawable
temp.paint.color = Color.BLACK
mask = temp
}
} else if (mask is GradientDrawable) {
val state = mask.getConstantState()
if (state != null) {
val temp = state.newDrawable().mutate() as GradientDrawable
temp.setColor(Color.BLACK)
mask = temp
}
} else {
mask = ColorDrawable(Color.BLACK)
}
val rippleDrawable = RippleDrawable(colorStateList, drawable, mask)
setRadius(rippleDrawable, radius)
rippleDrawable.invalidateSelf()
drawable = rippleDrawable
return drawable
}
}

View File

@@ -1,52 +0,0 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.drawable.Drawable
import android.graphics.drawable.RotateDrawable
class RotateDrawableBuilder : DrawableWrapperBuilder<RotateDrawableBuilder>() {
private var pivotX: Float = 0.5f
private var pivotY: Float = 0.5f
private var fromDegrees: Float = 0f
private var toDegrees: Float = 360f
fun pivotX(x: Float) = apply { pivotX = x }
fun pivotY(y: Float) = apply { pivotY = y }
fun fromDegrees(degree: Float) = apply { fromDegrees = degree }
fun toDegrees(degree: Float) = apply { toDegrees = degree }
override fun build(): Drawable {
val rotateDrawable = RotateDrawable()
drawable?.let {
setDrawable(rotateDrawable, it)
apply {
setPivotX(rotateDrawable, pivotX)
setPivotY(rotateDrawable, pivotY)
setFromDegrees(rotateDrawable, fromDegrees)
setToDegrees(rotateDrawable, toDegrees)
}
}
return rotateDrawable
}
}

View File

@@ -1,45 +0,0 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.drawable.Drawable
import android.graphics.drawable.ScaleDrawable
import android.view.Gravity
class ScaleDrawableBuilder : DrawableWrapperBuilder<ScaleDrawableBuilder>() {
private var level: Int = 10000
private var scaleGravity = Gravity.CENTER
private var scaleWidth: Float = 0f
private var scaleHeight: Float = 0f
fun level(level: Int) = apply { this.level = level }
fun scaleGravity(gravity: Int) = apply { this.scaleGravity = gravity }
fun scaleWidth(scale: Float) = apply { this.scaleWidth = scale }
fun scaleHeight(scale: Float) = apply { this.scaleHeight = scale }
override fun build(): Drawable {
val scaleDrawable = ScaleDrawable(drawable, scaleGravity, scaleWidth, scaleHeight)
scaleDrawable.level = level
return scaleDrawable
}
}

View File

@@ -1,60 +0,0 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/1/8.
*/
package com.fankes.tsbattery.utils.drawable.drawabletoolbox
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.StateListDrawable
import android.util.StateSet
class StateListDrawableBuilder {
private var pressed: Drawable? = null
private var disabled: Drawable? = null
private var selected: Drawable? = null
private var normal: Drawable = ColorDrawable(Color.TRANSPARENT)
fun pressed(pressed: Drawable?) = apply { this.pressed = pressed }
fun disabled(disabled: Drawable?) = apply { this.disabled = disabled }
fun selected(selected: Drawable?) = apply { this.selected = selected }
fun normal(normal: Drawable) = apply { this.normal = normal }
fun build(): StateListDrawable {
val stateListDrawable = StateListDrawable()
setupStateListDrawable(stateListDrawable)
return stateListDrawable
}
private fun setupStateListDrawable(stateListDrawable: StateListDrawable) {
pressed?.let {
stateListDrawable.addState(intArrayOf(android.R.attr.state_pressed), it)
}
disabled?.let {
stateListDrawable.addState(intArrayOf(-android.R.attr.state_enabled), it)
}
selected?.let {
stateListDrawable.addState(intArrayOf(android.R.attr.state_selected), it)
}
stateListDrawable.addState(StateSet.WILD_CARD, normal)
}
}

View File

@@ -28,26 +28,21 @@ 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.CauseProblemsApi
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
/**
* 构造对话框
* @param isUseBlackTheme 是否使用深色主题
* @param it 对话框方法体
* @param initiate 对话框方法体
*/
fun Context.showDialog(isUseBlackTheme: Boolean = false, it: DialogBuilder.() -> Unit) =
DialogBuilder(this, isUseBlackTheme).apply(it).show()
fun Context.showDialog(isUseBlackTheme: Boolean = false, initiate: DialogBuilder.() -> Unit) =
DialogBuilder(context = this, isUseBlackTheme).apply(initiate).show()
/**
* 对话框构造器
@@ -60,9 +55,7 @@ class DialogBuilder(val context: Context, private val isUseBlackTheme: Boolean)
private var instanceAndroid: android.app.AlertDialog.Builder? = null // 实例对象
private var dialogInstance: Dialog? = null // 对话框实例
@CauseProblemsApi
var customLayoutView: View? = null // 自定义布局
private var customLayoutView: View? = null // 自定义布局
/**
* 是否需要使用 AndroidX 风格对话框
@@ -125,49 +118,37 @@ class DialogBuilder(val context: Context, private val isUseBlackTheme: Boolean)
else customLayoutView?.findViewWithTag<TextView>("progressContent")?.text = value
}
/**
* 设置对话框自定义布局
* @return [ViewBinding]
*/
inline fun <reified T : ViewBinding> bind() =
T::class.java.method {
name = "inflate"
param(LayoutInflaterClass)
}.get().invoke<T>(LayoutInflater.from(context))?.apply {
customLayoutView = root
} ?: error("binding failed")
/**
* 设置对话框确定按钮
* @param text 按钮文本内容
* @param it 点击事件
* @param callback 点击事件
*/
fun confirmButton(text: String = "确定", it: () -> Unit = {}) {
fun confirmButton(text: String = "确定", callback: () -> Unit = {}) {
if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setPositiveButton(text) { _, _ -> it() } }
else runInSafe { instanceAndroid?.setPositiveButton(text) { _, _ -> it() } }
runInSafe { instanceAndroidX?.setPositiveButton(text) { _, _ -> callback() } }
else runInSafe { instanceAndroid?.setPositiveButton(text) { _, _ -> callback() } }
}
/**
* 设置对话框取消按钮
* @param text 按钮文本内容
* @param it 点击事件
* @param callback 点击事件
*/
fun cancelButton(text: String = "取消", it: () -> Unit = {}) {
fun cancelButton(text: String = "取消", callback: () -> Unit = {}) {
if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setNegativeButton(text) { _, _ -> it() } }
else runInSafe { instanceAndroid?.setNegativeButton(text) { _, _ -> it() } }
runInSafe { instanceAndroidX?.setNegativeButton(text) { _, _ -> callback() } }
else runInSafe { instanceAndroid?.setNegativeButton(text) { _, _ -> callback() } }
}
/**
* 设置对话框第三个按钮
* @param text 按钮文本内容
* @param it 点击事件
* @param callback 点击事件
*/
fun neutralButton(text: String = "更多", it: () -> Unit = {}) {
fun neutralButton(text: String = "更多", callback: () -> Unit = {}) {
if (isUsingAndroidX)
runInSafe { instanceAndroidX?.setNeutralButton(text) { _, _ -> it() } }
else runInSafe { instanceAndroid?.setNeutralButton(text) { _, _ -> it() } }
runInSafe { instanceAndroidX?.setNeutralButton(text) { _, _ -> callback() } }
else runInSafe { instanceAndroid?.setNeutralButton(text) { _, _ -> callback() } }
}
/** 取消对话框 */

View File

@@ -35,8 +35,8 @@ import android.net.Uri
import android.provider.Settings
import android.widget.Toast
import androidx.core.content.getSystemService
import com.fankes.tsbattery.application.TSApplication.Companion.appContext
import com.google.android.material.snackbar.Snackbar
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication.Companion.appContext
/**
* 系统深色模式是否开启
@@ -137,13 +137,13 @@ fun toast(msg: String) = Toast.makeText(appContext, msg, Toast.LENGTH_SHORT).sho
* 弹出 [Snackbar]
* @param msg 提示内容
* @param actionText 按钮文本 - 不写默认取消按钮
* @param it 按钮事件回调
* @param callback 按钮事件回调
*/
fun Context.snake(msg: String, actionText: String = "", it: () -> Unit = {}) =
fun Context.snake(msg: String, actionText: String = "", callback: () -> Unit = {}) =
Snackbar.make((this as Activity).findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG).apply {
if (actionText.isBlank()) return@apply
setActionTextColor(if (isSystemInDarkMode) Color.BLACK else Color.WHITE)
setAction(actionText) { it() }
setAction(actionText) { callback() }
}.show()
/**

View File

@@ -19,15 +19,21 @@
*
* This file is Created by fankes on 2022/3/20.
*/
@file:Suppress("NewApi")
package com.fankes.tsbattery.utils.tool
import android.app.Activity
import android.content.Context
import android.icu.text.SimpleDateFormat
import android.icu.util.Calendar
import android.icu.util.TimeZone
import com.fankes.tsbattery.utils.factory.*
import okhttp3.*
import org.json.JSONObject
import java.io.IOException
import java.io.Serializable
import java.util.*
/**
* 获取 Github Release 最新版本工具类
@@ -44,9 +50,9 @@ object GithubReleaseTool {
* 获取最新版本信息
* @param context 实例
* @param version 当前版本
* @param it 成功后回调 - ([String] 最新版本,[Function] 更新对话框方法体)
* @param result 成功后回调 - ([String] 最新版本,[Function] 更新对话框方法体)
*/
fun checkingForUpdate(context: Context, version: String, it: (String, () -> Unit) -> Unit) = checkingInternetConnect(context) {
fun checkingForUpdate(context: Context, version: String, result: (String, () -> Unit) -> Unit) = checkingInternetConnect(context) {
OkHttpClient().newBuilder().build().newCall(
Request.Builder()
.url("https://api.github.com/repos/$REPO_AUTHOR/$REPO_NAME/releases/latest")
@@ -56,12 +62,12 @@ object GithubReleaseTool {
override fun onFailure(call: Call, e: IOException) {}
override fun onResponse(call: Call, response: Response) = runInSafe {
JSONObject(response.body?.string() ?: "").apply {
JSONObject(response.body.string()).apply {
GithubReleaseBean(
name = getString("name"),
htmlUrl = getString("html_url"),
content = getString("body"),
date = getString("published_at").replace(oldValue = "T", newValue = " ").replace(oldValue = "Z", newValue = "")
date = getString("published_at").localTime()
).apply {
fun showUpdate() = context.showDialog {
title = "最新版本 $name"
@@ -72,7 +78,7 @@ object GithubReleaseTool {
}
if (name != version) (context as? Activity?)?.runOnUiThread {
showUpdate()
it(name) { showUpdate() }
result(name) { showUpdate() }
}
}
}
@@ -83,9 +89,9 @@ object GithubReleaseTool {
/**
* 检查网络连接情况
* @param context 实例
* @param it 已连接回调
* @param callback 已连接回调
*/
private fun checkingInternetConnect(context: Context, it: () -> Unit) = runInSafe {
private fun checkingInternetConnect(context: Context, callback: () -> Unit) = runInSafe {
if (isNetWorkSuccess)
OkHttpClient().newBuilder().build().newCall(
Request.Builder()
@@ -106,11 +112,23 @@ object GithubReleaseTool {
}
override fun onResponse(call: Call, response: Response) = runInSafe {
(context as? Activity?)?.runOnUiThread { runInSafe { it() } }
(context as? Activity?)?.runOnUiThread { runInSafe { callback() } }
}
})
}
/**
* 格式化时间为本地时区
* @return [String] 本地时区时间
*/
private fun String.localTime() = replace(oldValue = "T", newValue = " ").replace(oldValue = "Z", newValue = "").let {
runCatching {
val local = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT).apply { timeZone = Calendar.getInstance().timeZone }
val current = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT).apply { timeZone = TimeZone.getTimeZone("GMT") }
local.format(current.parse(it))
}.getOrNull() ?: it
}
/**
* Github Release bean
* @param name 版本名称

View File

@@ -0,0 +1,59 @@
/*
* 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
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/30.
*/
package com.fankes.tsbattery.utils.tool
import android.content.Context
import com.fankes.tsbattery.BuildConfig
import com.fankes.tsbattery.utils.factory.openBrowser
import com.fankes.tsbattery.utils.factory.showDialog
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
/**
* [YukiHookAPI] 的自动推广工具类
*/
object YukiPromoteTool {
/** 推广已读存储键值 */
private val YUKI_PROMOTE_READED = PrefsData("yuki_promote_readed_${BuildConfig.VERSION_NAME}", false)
/**
* 显示推广对话框
* @param context 实例
*/
fun promote(context: Context) {
fun saveReaded() = context.modulePrefs.put(YUKI_PROMOTE_READED, value = true)
if (context.modulePrefs.get(YUKI_PROMOTE_READED).not())
context.showDialog {
title = "面向开发者的推广"
msg = "你想快速拥有一个自己的 Xposed 模块吗,你只需要拥有基础的 Android 开发经验以及使用 Kotlin 编程语言即可。\n\n" +
"快来体验 YukiHookAPI这是一个使用 Kotlin 重新构建的高效 Xposed Hook API助你的开发变得更轻松。"
confirmButton(text = "去看看") {
context.openBrowser(url = "https://github.com/fankes/YukiHookAPI")
saveReaded()
}
cancelButton(text = "我不是开发者") { saveReaded() }
noCancelable()
}
}
}

View File

@@ -2,13 +2,13 @@
<!-- Base application theme. -->
<style name="Theme.TSBattery" parent="Theme.Material3.DayNight">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorPrimary">@color/colorPrimaryAccent</item>
<item name="colorPrimaryVariant">@color/colorPrimaryAccent</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<item name="colorSecondary">@color/colorPrimaryAccent</item>
<item name="colorSecondaryVariant">@color/colorPrimaryAccent</item>
<item name="colorOnSecondary">@color/colorPrimaryAccent</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->

View File

@@ -1,10 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#656565</color>
<color name="purple_500">#656565</color>
<color name="purple_700">#656565</color>
<color name="teal_200">#656565</color>
<color name="teal_700">#656565</color>
<color name="colorPrimaryAccent">#656565</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@@ -2,13 +2,13 @@
<!-- Base application theme. -->
<style name="Theme.TSBattery" parent="Theme.Material3.DayNight">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/teal_700</item>
<item name="colorPrimary">@color/colorPrimaryAccent</item>
<item name="colorPrimaryVariant">@color/colorPrimaryAccent</item>
<item name="colorOnPrimary">@color/colorPrimaryAccent</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<item name="colorSecondary">@color/colorPrimaryAccent</item>
<item name="colorSecondaryVariant">@color/colorPrimaryAccent</item>
<item name="colorOnSecondary">@color/colorPrimaryAccent</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->

View File

@@ -1,12 +1,13 @@
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
id 'com.android.application' version '7.2.1' apply false
id 'com.android.library' version '7.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}
ext {
appVersionName = "3.7"
appVersionCode = 15
appVersionName = "3.99.6"
appVersionCode = 24
enableR8 = true
}
task clean(type: Delete) {

View File

@@ -6,7 +6,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.jvmargs=-XX:+UseParallelGC
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
@@ -18,4 +18,6 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
kotlin.code.style=official
# Incremental
kotlin.incremental.useClasspathSnapshot=true

View File

@@ -1,6 +1,6 @@
#Mon Feb 14 23:27:58 CST 2022
#Wed May 25 04:34:58 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@@ -10,6 +10,7 @@ dependencyResolutionManagement {
repositories {
google()
maven { url "https://api.xposed.info/" }
maven { url "https://www.jitpack.io" }
maven { url "https://s01.oss.sonatype.org/content/repositories/releases" }
mavenCentral()
}