Initial commit

This commit is contained in:
2022-02-27 23:42:33 +08:00
commit 73246523e3
97 changed files with 5241 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

20
.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

@@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

20
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.254" />
<entry key="app/src/main/res/layout/activity_config.xml" value="0.4375" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.335" />
<entry key="app/src/main/res/layout/adapter_config.xml" value="0.4375" />
<entry key="app/src/main/res/layout/dia_icon_filter.xml" value="0.4375" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

63
README.md Normal file
View File

@@ -0,0 +1,63 @@
# ColorOS 通知图标增强
![Eclipse Marketplace](https://img.shields.io/badge/build-passing-brightgreen)
![Eclipse Marketplace](https://img.shields.io/badge/license-AGPL3.0-blue)
![Eclipse Marketplace](https://img.shields.io/badge/version-v1.0-green)
<br/><br/>
<img src="https://github.com/fankes/ColorOSNotifyIcon/blob/master/app/src/main/ic_launcher-playstore.png" width = "100" height = "100"/>
<br/>
Optimize notification icons for ColorOS and adapt to native notification icon specifications.<br/>
为 ColorOS 优化通知图标以及适配原生通知图标规范,理论支持 OxygenOS 和 RealmeOS。
# 开始使用
点击下载最新版本
<a href='https://github.com/fankes/ColorOSNotifyIcon/releases'>![Eclipse Marketplace](https://img.shields.io/badge/download-v1.0-green)</a>
<br/><br/>
⚠️ 适配说明<br/>
- 此模块仅支持 LSPosed(作用域“系统界面”)、~~EdXposed(随时停止支持)~~、不支持太极无极
- 目前仅在 ColorOS 12 for OnePlus 上测试通过,如有问题请提交 `issues`
- 建议在不低于 ColorOS 11 的版本上使用
# 请勿用于非法用途
- 本模块完全开源免费,如果好用你可以打赏支持开发,但是请不要用于非法用途。
- 本模块发布地址仅有 [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.coloros.notify/releases)、
[Release](https://github.com/fankes/ColorOSNotifyIcon/releases)
及[蓝奏云](https://fankes.lanzouy.com/b030rvjyf),从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
# 贡献通知图标优化名单
此项目是 `AndroidNotifyIconAdapt` 项目的一部分,详情请参考下方。<br/>
- [Android 通知图标规范适配](https://github.com/fankes/AndroidNotifyIconAdapt)
# 历史背景
继 MIUI 之后的第二大系统 ColorOS 虽然支持原生通知图标,但是第三方推送五颜六色的图标系统并没有做适配,甚至系统自己的图标都是彩色的,极其不友好。<br/><br/>
而且从 ColorOS 12 开始,原生图标丢失了着色属性,这也是一种对原生 Android 生态的破坏。
# 许可证
- [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html)
```
Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
This program is free 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 (at your option) any later version.
This program 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
```
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)<br/><br/>
版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com)

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

80
app/build.gradle Normal file
View File

@@ -0,0 +1,80 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.devtools.ksp' version '1.6.10-1.0.2'
}
android {
compileSdk 31
signingConfigs {
debug {
storeFile file('../keystore/public')
storePassword '123456'
keyAlias 'public'
keyPassword '123456'
v1SigningEnabled true
v2SigningEnabled true
}
}
defaultConfig {
applicationId "com.fankes.coloros.notify"
minSdk 29
targetSdk 31
versionCode rootProject.ext.appVersionCode
versionName rootProject.ext.appVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
/** 移除无效耗时 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
}
dependencies {
implementation "com.github.topjohnwu.libsu:core:3.1.2"
implementation 'androidx.annotation:annotation:1.3.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
compileOnly 'de.robv.android.xposed:api:82'
implementation 'com.highcapable.yukihookapi:api:1.0.2'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.2'
implementation 'com.geyifeng.immersionbar:immersionbar:3.2.0'
implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
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'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

48
app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,48 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontwarn
-ignorewarnings
-optimizationpasses 10
-dontusemixedcaseclassnames
-dontoptimize
-verbose
-overloadaggressively
-repackageclasses o
-allowaccessmodification
-adaptclassstrings
-adaptresourcefilenames
-adaptresourcefilecontents
#-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-renamesourcefileattribute H
-keepattributes SourceFile,LineNumberTable
-keep class android.support**
-keep class androidx**
-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

View File

@@ -0,0 +1,24 @@
package com.fankes.coloros.notify
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.fankes.coloros.notify", appContext.packageName)
}
}

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.fankes.coloros.notify">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".application.CNNApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ColorOSNotifyIcon">
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="为 ColorOS 优化通知图标以及适配原生通知图标规范,理论支持 OxygenOS 和 RealmeOS\n开发者酷安 @星夜不荟" />
<meta-data
android:name="xposedminversion"
android:value="93" />
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:screenOrientation="behind">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
</intent-filter>
</activity>
<activity-alias
android:name=".Home"
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="behind"
android:targetActivity=".ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity
android:name=".ui.ConfigureActivity"
android:exported="false"
android:screenOrientation="behind" />
</application>
</manifest>

View File

@@ -0,0 +1 @@
com.fankes.coloros.notify.hook.HookEntry_YukiHookXposedInit

View File

@@ -0,0 +1 @@
com.fankes.coloros.notify.hook.HookEntry

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,48 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/24.
*/
@file:Suppress("unused")
package com.fankes.coloros.notify.application
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
class CNNApplication : Application() {
companion object {
/** 全局静态实例 */
private var context: CNNApplication? = null
/** 调用全局静态实例 */
val appContext get() = context ?: error("App is death")
}
override fun onCreate() {
super.onCreate()
/** 设置静态实例 */
context = this
/** 跟随系统夜间模式 */
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
}

View File

@@ -0,0 +1,50 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/30.
*/
package com.fankes.coloros.notify.bean
import android.graphics.Bitmap
import com.fankes.coloros.notify.utils.base64
import java.io.Serializable
/**
* 通知栏小图标 bean
* @param appName APP 名称 - 仅限默认语言区域
* @param packageName 包名
* @param iconBitmap 图标位图
* @param iconColor 通知栏中显示的图标颜色 - 设置为 0 使用系统默认颜色
* @param contributorName 贡献者昵称
* @param isEnabled 是否默认启用替换彩色图标 - 关闭后将全部停止替换
* @param isEnabledAll 是否默认启用替换全部图标
*/
data class IconDataBean(
var appName: String,
var packageName: String,
var iconBitmap: Bitmap,
var iconColor: Int = 0,
var contributorName: String,
var isEnabled: Boolean,
var isEnabledAll: Boolean,
) : Serializable {
fun toEnabledName() = ("$appName$packageName").base64 + "_enable"
fun toEnabledAllName() = ("$appName$packageName").base64 + "_enable_all"
}

View File

@@ -0,0 +1,37 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/24.
*/
package com.fankes.coloros.notify.hook
object HookConst {
const val ENABLE_MODULE = "_enable_module"
const val ENABLE_MODULE_LOG = "_enable_module_log"
const val ENABLE_HIDE_ICON = "_hide_icon"
const val ENABLE_ANDROID12_STYLE = "_notify_android12_style"
const val ENABLE_NOTIFY_ICON_FIX = "_notify_icon_fix"
const val REMOVE_DEV_NOTIFY = "_remove_dev_notify"
const val REMOVE_CHANGECP_NOTIFY = "_remove_charge_complete_notify"
const val NOTIFY_ICON_DATAS = "_notify_icon_datas"
const val SYSTEMUI_PACKAGE_NAME = "com.android.systemui"
}

View File

@@ -0,0 +1,373 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/2/26.
*/
package com.fankes.coloros.notify.hook
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.graphics.drawable.VectorDrawable
import android.service.notification.StatusBarNotification
import android.widget.ImageView
import androidx.core.graphics.drawable.toBitmap
import com.fankes.coloros.notify.bean.IconDataBean
import com.fankes.coloros.notify.hook.HookConst.ENABLE_ANDROID12_STYLE
import com.fankes.coloros.notify.hook.HookConst.ENABLE_MODULE
import com.fankes.coloros.notify.hook.HookConst.ENABLE_MODULE_LOG
import com.fankes.coloros.notify.hook.HookConst.ENABLE_NOTIFY_ICON_FIX
import com.fankes.coloros.notify.hook.HookConst.REMOVE_CHANGECP_NOTIFY
import com.fankes.coloros.notify.hook.HookConst.REMOVE_DEV_NOTIFY
import com.fankes.coloros.notify.hook.HookConst.SYSTEMUI_PACKAGE_NAME
import com.fankes.coloros.notify.hook.factory.isAppNotifyHookAllOf
import com.fankes.coloros.notify.hook.factory.isAppNotifyHookOf
import com.fankes.coloros.notify.param.IconPackParams
import com.fankes.coloros.notify.utils.*
import com.fankes.coloros.notify.utils.drawable.drawabletoolbox.DrawableBuilder
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.factory.configs
import com.highcapable.yukihookapi.hook.factory.encase
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.log.loggerD
import com.highcapable.yukihookapi.hook.log.loggerW
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.type.android.ContextClass
import com.highcapable.yukihookapi.hook.type.android.DrawableClass
import com.highcapable.yukihookapi.hook.type.android.IconClass
import com.highcapable.yukihookapi.hook.type.android.ImageViewClass
import com.highcapable.yukihookapi.hook.type.java.BooleanType
import com.highcapable.yukihookapi.hook.type.java.IntType
import com.highcapable.yukihookapi.hook.xposed.proxy.YukiHookXposedInitProxy
@InjectYukiHookWithXposed
class HookEntry : YukiHookXposedInitProxy {
companion object {
/** 原生存在的类 */
private const val ContrastColorUtilClass = "com.android.internal.util.ContrastColorUtil"
/** 原生存在的类 */
private const val NotificationUtilsClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationUtils"
/** 原生存在的类 */
private const val NotificationEntryClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.collection.NotificationEntry"
/** 原生存在的类 */
private const val StatusBarIconClass = "com.android.internal.statusbar.StatusBarIcon"
/** 原生存在的类 */
private const val IconBuilderClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.icon.IconBuilder"
/** 原生存在的类 */
private const val IconManagerClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.icon.IconManager"
/** ColorOS 存在的类 */
private const val OplusContrastColorUtilClass = "com.oplusos.util.OplusContrastColorUtil"
/** ColorOS 存在的类 */
private const val OplusPowerNotificationWarningsClass =
"com.oplusos.systemui.notification.power.OplusPowerNotificationWarnings"
/** ColorOS 存在的类 */
private const val SystemPromptControllerClass = "com.oplusos.systemui.statusbar.policy.SystemPromptController"
/** 根据多个版本存在不同的包名相同的类 */
private val ExpandableNotificationRowClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.ExpandableNotificationRow",
"$SYSTEMUI_PACKAGE_NAME.statusbar.ExpandableNotificationRow"
)
/** 根据多个版本存在不同的包名相同的类 */
private val NotificationViewWrapperClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationViewWrapper",
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationViewWrapper"
)
/** 根据多个版本存在不同的包名相同的类 */
private val NotificationHeaderViewWrapperClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper",
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationHeaderViewWrapper"
)
}
/** 缓存的通知优化图标数组 */
private var iconDatas = ArrayList<IconDataBean>()
/**
* 获取推送通知的应用名称
* @param packageName APP 包名
* @return [String]
*/
private fun Context.findAppName(packageName: String) = safeOf(default = "<unknown>") {
packageManager.getPackageInfo(packageName, 0).applicationInfo.loadLabel(packageManager)
}
/**
* 打印日志
* @param tag 标识
* @param context 实例
* @param packageName APP 包名
* @param isCustom 是否为通知优化生效图标
* @param isGrayscale 是否为灰度图标
*/
private fun PackageParam.printLogcat(
tag: String,
context: Context,
packageName: String,
isCustom: Boolean,
isGrayscale: Boolean
) {
if (prefs.getBoolean(ENABLE_MODULE_LOG)) loggerD(
msg = "$tag --> [${context.findAppName(packageName)}][$packageName] " +
"custom [$isCustom] " +
"grayscale [$isGrayscale]"
)
}
/**
* - 这个是修复彩色图标的关键核心代码判断
*
* 判断是否为灰度图标 - 反射执行系统方法
* @param context 实例
* @param drawable 要判断的图标
* @return [Boolean]
*/
private fun PackageParam.isGrayscaleIcon(context: Context?, drawable: Drawable?) =
ContrastColorUtilClass.clazz.let {
drawable is VectorDrawable || it.method {
name = "isGrayscaleIcon"
param(DrawableClass)
}.get(it.method {
name = "getInstance"
param(ContextClass)
}.get().invoke(context)).invoke<Boolean>(drawable) ?: false
}
/**
* 自动适配状态栏、通知栏自定义小图标
* @param isGrayscaleIcon 是否为灰度图标
* @param packageName APP 包名
* @return [Pair] - ([Bitmap] 位图,[Int] 颜色)
*/
private fun PackageParam.compatCustomIcon(isGrayscaleIcon: Boolean, packageName: String): Pair<Bitmap?, Int> {
var customPair: Pair<Bitmap?, Int>? = null
if (prefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true))
run {
if (iconDatas.isNotEmpty())
iconDatas.forEach {
if (packageName == it.packageName && isAppNotifyHookOf(it)) {
if (!isGrayscaleIcon || isAppNotifyHookAllOf(it))
customPair = Pair(it.iconBitmap, it.iconColor)
return@run
}
}
}
return customPair ?: Pair(null, 0)
}
/**
* 自动适配状态栏小图标
* @param context 实例
* @param isGrayscaleIcon 是否为灰度图标
* @param packageName APP 包名
* @param drawable 原始图标
* @return [Bitmap]
*/
private fun PackageParam.compatStatusIcon(context: Context, isGrayscaleIcon: Boolean, packageName: String, drawable: Drawable) =
compatCustomIcon(isGrayscaleIcon, packageName).first.also {
/** 打印日志 */
printLogcat(tag = "StatusIcon", context, packageName, isCustom = it != null, isGrayscaleIcon)
} ?: drawable.toBitmap()
/**
* 自动适配通知栏小图标
* @param isGrayscaleIcon 是否为灰度图标
* @param packageName APP 包名
* @param drawable 原始图标
* @param iconColor 原生图标颜色
* @param iconView 图标 [ImageView]
*/
private fun PackageParam.compatNotifyIcon(
isGrayscaleIcon: Boolean,
packageName: String,
drawable: Drawable,
iconColor: Int,
iconView: ImageView
) {
compatCustomIcon(isGrayscaleIcon, packageName).also { customPair ->
when {
customPair.first != null || isGrayscaleIcon -> iconView.apply {
setImageBitmap(customPair.first ?: drawable.toBitmap())
/** 是否开启 Android 12 风格 */
val isA12Style = prefs.getBoolean(ENABLE_ANDROID12_STYLE, isUpperOfAndroidS)
/** 旧版风格 */
val oldStyle = (if (context.isSystemInDarkMode) 0xffdcdcdc else 0xff707173).toInt()
/** 新版风格 */
val newStyle = (if (context.isSystemInDarkMode) 0xff2d2d2d else Color.WHITE).toInt()
/** 图标着色 */
val applyColor = customPair.second.takeIf { it != 0 } ?: iconColor.takeIf { it != 0 } ?: oldStyle
/** 判断风格并开始 Hook */
if (isA12Style) {
background = DrawableBuilder().rounded().solidColor(applyColor).build()
setColorFilter(if (isA12Style) newStyle else oldStyle)
setPadding(2.dp(context), 2.dp(context), 2.dp(context), 2.dp(context))
} else {
background = null
setColorFilter(applyColor)
setPadding(0, 0, 0, 0)
}
}
else -> iconView.apply {
setImageDrawable(drawable)
setPadding(0, 0, 0, 0)
background = null
colorFilter = null
}
}
/** 打印日志 */
printLogcat(tag = "NotifyIcon", iconView.context, packageName, isCustom = customPair.first != null, isGrayscaleIcon)
}
}
override fun onHook() = encase {
configs {
debugTag = "ColorOSNotify"
isDebug = false
}
loadApp(SYSTEMUI_PACKAGE_NAME) {
when {
/** 不是 ColorOS 系统停止 Hook */
isNotColorOS -> loggerW(msg = "Aborted Hook -> This System is not ColorOS")
/** Hook 被手动关闭停止 Hook */
!prefs.getBoolean(ENABLE_MODULE, default = true) -> loggerW(msg = "Aborted Hook -> Hook Closed")
/** 开始 Hook */
else -> {
/** 缓存图标数据 */
iconDatas = IconPackParams(param = this).iconDatas
/** 移除开发者警告通知 */
SystemPromptControllerClass.hook {
injectMember {
method { name = "updateDeveloperMode" }
beforeHook {
/** 是否移除 */
if (prefs.getBoolean(REMOVE_DEV_NOTIFY, default = true)) resultNull()
}
}
}
/** 移除充电完成通知 */
OplusPowerNotificationWarningsClass.hook {
injectMember {
method {
name = "showChargeErrorDialog"
param(IntType)
}
beforeHook {
/** 是否移除 */
if (firstArgs as Int == 7 && prefs.getBoolean(REMOVE_CHANGECP_NOTIFY, default = false)) resultNull()
}
}
}
/** 修复并替换 ColorOS 原生灰度图标色彩判断 */
NotificationUtilsClass.hook {
injectMember {
method {
name = "isGrayscaleOplus"
param(ImageViewClass, OplusContrastColorUtilClass.clazz)
}
replaceAny { (firstArgs as? ImageView?)?.let { isGrayscaleIcon(it.context, it.drawable) } }
}
}
/** 替换状态栏图标 */
IconManagerClass.hook {
injectMember {
method {
name = "getIconDescriptor"
param(NotificationEntryClass.clazz, BooleanType)
}
afterHook {
IconBuilderClass.clazz.field { name = "context" }
.of<Context>(field { name = "iconBuilder" }.of(instance))?.also { context ->
NotificationEntryClass.clazz.method {
name = "getSbn"
}.get(firstArgs).invoke<StatusBarNotification>()?.also { nf ->
nf.notification.smallIcon.loadDrawable(context).also { iconDrawable ->
StatusBarIconClass.clazz.field {
name = "icon"
type = IconClass
}.get(result).set(
Icon.createWithBitmap(
compatStatusIcon(
context = context,
isGrayscaleIcon = isGrayscaleIcon(context, iconDrawable),
packageName = nf.packageName,
drawable = iconDrawable
)
)
)
}
}
}
}
}
}
/** 替换通知图标和样式 */
NotificationHeaderViewWrapperClass.hook {
injectMember {
method { name = "resolveHeaderViews" }
afterHook {
NotificationHeaderViewWrapperClass.clazz
.field { name = "mIcon" }.of<ImageView>(instance)?.apply {
ExpandableNotificationRowClass.clazz
.method { name = "getEntry" }
.get(NotificationViewWrapperClass.clazz.field {
name = "mRow"
}.get(instance).self).call()?.let {
it.javaClass.method {
name = "getSbn"
}.get(it).invoke<StatusBarNotification>()
}?.notification?.also { nf ->
nf.smallIcon.loadDrawable(context).also { iconDrawable ->
compatNotifyIcon(
isGrayscaleIcon = isGrayscaleIcon(context, iconDrawable),
packageName = context.packageName,
drawable = iconDrawable,
iconColor = nf.color,
iconView = this
)
}
}
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/2/15.
*/
package com.fankes.coloros.notify.hook.factory
import android.content.Context
import com.fankes.coloros.notify.bean.IconDataBean
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.param.PackageParam
/**
* 获取此 APP 的通知图标是否被 Hook
* @param bean 图标 bean
*/
fun PackageParam.isAppNotifyHookOf(bean: IconDataBean) = prefs.getBoolean(key = bean.toEnabledName(), default = bean.isEnabled)
/**
* 获取此 APP 的通知图标是否被 Hook
* @param bean 图标 bean
*/
fun Context.isAppNotifyHookOf(bean: IconDataBean) = modulePrefs.getBoolean(key = bean.toEnabledName(), default = bean.isEnabled)
/**
* 设置 Hook 此 APP 的通知图标
* @param bean 图标 bean
* @param isHook 是否 Hook
*/
fun Context.putAppNotifyHookOf(bean: IconDataBean, isHook: Boolean) =
modulePrefs.putBoolean(key = bean.toEnabledName(), value = isHook)
/**
* 获取此 APP 的通知图标是否被全部 Hook
* @param bean 图标 bean
*/
fun PackageParam.isAppNotifyHookAllOf(bean: IconDataBean) =
prefs.getBoolean(key = bean.toEnabledAllName(), default = bean.isEnabledAll)
/**
* 获取此 APP 的通知图标是否被全部 Hook
* @param bean 图标 bean
*/
fun Context.isAppNotifyHookAllOf(bean: IconDataBean) =
modulePrefs.getBoolean(key = bean.toEnabledAllName(), default = bean.isEnabledAll)
/**
* 设置全部 Hook 此 APP 的通知图标
* @param bean 图标 bean
* @param isHook 是否 Hook
*/
fun Context.putAppNotifyHookAllOf(bean: IconDataBean, isHook: Boolean) =
modulePrefs.putBoolean(key = bean.toEnabledAllName(), value = isHook)

View File

@@ -0,0 +1,103 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/24.
*/
package com.fankes.coloros.notify.param
import android.content.Context
import android.graphics.Color
import com.fankes.coloros.notify.bean.IconDataBean
import com.fankes.coloros.notify.hook.HookConst.NOTIFY_ICON_DATAS
import com.fankes.coloros.notify.utils.bitmap
import com.fankes.coloros.notify.utils.safeOf
import com.fankes.coloros.notify.utils.safeOfNan
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.param.PackageParam
import org.json.JSONArray
import org.json.JSONObject
/**
* 通知栏小图标适配类
*
* 国内 APP 不规范的图标将由这里完成其自定义单色小图标绘制
* @param context 实例 - 二选一
* @param param 实例 - 二选一
*/
class IconPackParams(private val context: Context? = null, private val param: PackageParam? = null) {
/**
* 已存储的 JSON 数据
* @return [String]
*/
private val storageDataJson get() = (context?.modulePrefs ?: param?.prefs)?.getString(NOTIFY_ICON_DATAS)
/**
* 获取图标数据
* @return [Array] 通知栏小图标数组
*/
val iconDatas
get() = ArrayList<IconDataBean>().apply {
storageDataJson?.also {
if (it.isNotBlank()) runCatching {
JSONArray(it).also { array ->
for (i in 0 until array.length()) runCatching {
(array.get(i) as JSONObject).apply {
add(
IconDataBean(
appName = getString("appName"),
packageName = getString("packageName"),
isEnabled = getBoolean("isEnabled"),
isEnabledAll = getBoolean("isEnabledAll"),
iconBitmap = getString("iconBitmap").bitmap,
iconColor = safeOfNan { Color.parseColor(getString("iconColor")) },
contributorName = getString("contributorName")
)
)
}
}
}
}
}
}
/**
* 拼接图标数组数据
* @param dataJson1 图标数据 JSON
* @param dataJson2 图标数据 JSON
* @return [String] 拼接后的数据
*/
fun splicingJsonArray(dataJson1: String, dataJson2: String) = safeOf(default = "[]") {
dataJson1.replace(oldValue = "]", newValue = "") + "," + dataJson2.replace(oldValue = "[", newValue = "")
}
/**
* 比较图标数据不相等
* @param dataJson 图标数据 JSON
* @return [Boolean] 是否不相等
*/
fun isCompareDifferent(dataJson: String) = storageDataJson?.trim() != dataJson.trim()
/**
* 保存图标数据
* @param dataJson 图标数据 JSON
*/
fun save(dataJson: String) = context?.modulePrefs?.putString(NOTIFY_ICON_DATAS, dataJson)
}

View File

@@ -0,0 +1,289 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/30.
*/
@file:Suppress("SetTextI18n", "InflateParams", "DEPRECATION")
package com.fankes.coloros.notify.ui
import android.app.ProgressDialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ListView
import android.widget.TextView
import androidx.constraintlayout.utils.widget.ImageFilterView
import androidx.core.view.isVisible
import com.fankes.coloros.notify.R
import com.fankes.coloros.notify.bean.IconDataBean
import com.fankes.coloros.notify.hook.factory.isAppNotifyHookAllOf
import com.fankes.coloros.notify.hook.factory.isAppNotifyHookOf
import com.fankes.coloros.notify.hook.factory.putAppNotifyHookAllOf
import com.fankes.coloros.notify.hook.factory.putAppNotifyHookOf
import com.fankes.coloros.notify.param.IconPackParams
import com.fankes.coloros.notify.ui.base.BaseActivity
import com.fankes.coloros.notify.utils.*
import com.fankes.coloros.notify.view.MaterialSwitch
import com.google.android.material.textfield.TextInputEditText
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
class ConfigureActivity : BaseActivity() {
/** 当前筛选条件 */
private var filterText = ""
/** 回调适配器改变 */
private var onChanged: (() -> Unit)? = null
/** 回调滚动事件改变 */
private var onScrollEvent: ((Boolean) -> Unit)? = null
/** 全部的通知优化图标数据 */
private var iconAllDatas = ArrayList<IconDataBean>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_config)
/** 检查激活状态 */
if (!YukiHookModuleStatus.isActive()) {
showDialog {
title = "模块没有激活"
msg = "模块没有激活,你无法使用这里的功能,请先激活模块。"
confirmButton(text = "我知道了") { finish() }
noCancelable()
}
return
}
/** 返回按钮点击事件 */
findViewById<View>(R.id.title_back_icon).setOnClickListener { onBackPressed() }
/** 刷新适配器结果相关 */
refreshAdapterResult()
/** 设置上下按钮点击事件 */
findViewById<View>(R.id.config_title_up).setOnClickListener {
snake(msg = "滚动到顶部")
onScrollEvent?.invoke(false)
}
findViewById<View>(R.id.config_title_down).setOnClickListener {
snake(msg = "滚动到底部")
onScrollEvent?.invoke(true)
}
/** 设置过滤按钮点击事件 */
findViewById<View>(R.id.config_title_filter).setOnClickListener {
showDialog {
title = "按条件过滤"
var editText: TextInputEditText
addView(R.layout.dia_icon_filter).apply {
editText = findViewById<TextInputEditText>(R.id.dia_icon_filter_input_edit).apply {
requestFocus()
invalidate()
if (filterText.isNotBlank()) {
setText(filterText)
setSelection(filterText.length)
}
}
}
confirmButton {
if (editText.text.toString().isNotBlank()) {
filterText = editText.text.toString().trim()
refreshAdapterResult()
} else {
toast(msg = "条件不能为空")
it.performClick()
}
}
cancelButton()
if (filterText.isNotBlank())
neutralButton(text = "清除条件") {
filterText = ""
refreshAdapterResult()
}
}
}
/** 设置同步列表按钮点击事件 */
findViewById<View>(R.id.config_title_sync).setOnClickListener { onStartRefresh() }
/** 设置列表元素和 Adapter */
findViewById<ListView>(R.id.config_list_view).apply {
adapter = object : BaseAdapter() {
private val inflater = LayoutInflater.from(context)
override fun getCount() = iconDatas.size
override fun getItem(position: Int) = iconDatas[position]
override fun getItemId(position: Int) = position.toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var cView = convertView
val holder: ViewHolder
if (convertView == null) {
holder = ViewHolder()
cView = inflater.inflate(R.layout.adapter_config, null).also {
holder.appIcon = it.findViewById(R.id.adp_app_icon)
holder.appName = it.findViewById(R.id.adp_app_name)
holder.pkgName = it.findViewById(R.id.adp_app_pkg_name)
holder.cbrName = it.findViewById(R.id.adp_cbr_name)
holder.switchOpen = it.findViewById(R.id.adp_app_open_switch)
holder.switchAll = it.findViewById(R.id.adp_app_all_switch)
}
cView.tag = holder
} else holder = convertView.tag as ViewHolder
getItem(position).also {
holder.appIcon.setImageBitmap(it.iconBitmap)
(if (it.iconColor != 0) it.iconColor else resources.getColor(R.color.colorTextGray)).also { color ->
holder.appIcon.setColorFilter(color)
holder.appName.setTextColor(color)
}
holder.appName.text = it.appName
holder.pkgName.text = it.packageName
holder.cbrName.text = "贡献者:" + it.contributorName
isAppNotifyHookOf(it).also { e ->
holder.switchOpen.isChecked = e
holder.switchAll.isEnabled = e
}
holder.switchOpen.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
putAppNotifyHookOf(it, b)
holder.switchAll.isEnabled = b
SystemUITool.showNeedRestartSnake(context = this@ConfigureActivity)
}
holder.switchAll.isChecked = isAppNotifyHookAllOf(it)
holder.switchAll.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
putAppNotifyHookAllOf(it, b)
SystemUITool.showNeedRestartSnake(context = this@ConfigureActivity)
}
}
return cView!!
}
inner class ViewHolder {
lateinit var appIcon: ImageFilterView
lateinit var appName: TextView
lateinit var pkgName: TextView
lateinit var cbrName: TextView
lateinit var switchOpen: MaterialSwitch
lateinit var switchAll: MaterialSwitch
}
}.apply { onChanged = { notifyDataSetChanged() } }
onScrollEvent = { post { setSelection(if (it) iconDatas.lastIndex else 0) } }
}
/** 设置点击事件 */
findViewById<View>(R.id.config_cbr_button).setOnClickListener {
runCatching {
startActivity(Intent().apply {
action = "android.intent.action.VIEW"
data = Uri.parse("https://github.com/fankes/AndroidNotifyIconAdapt/blob/main/CONTRIBUTING.md")
/** 防止顶栈一样重叠在自己的 APP 中 */
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}.onFailure {
toast(msg = "无法启动系统默认浏览器")
}
}
/** 装载数据 */
mockLocalData()
/** 更新数据 */
onStartRefresh()
}
/** 装载或刷新本地数据 */
private fun mockLocalData() {
iconAllDatas = IconPackParams(context = this).iconDatas
refreshAdapterResult()
}
/** 首次进入或更新数据 */
private fun onStartRefresh() =
showDialog {
title = if (iconAllDatas.isNotEmpty()) "同步列表" else "初始化"
msg = (if (iconAllDatas.isNotEmpty()) "建议定期从云端拉取数据以获得最新的通知图标优化名单适配数据。\n\n"
else "首次装载需要从云端下载最新适配数据,后续可继续前往这里检查更新。\n\n") +
"通过从 Github 同步最新数据,无法连接可能需要魔法上网。"
confirmButton(text = "开始同步") { onRefreshing() }
cancelButton()
}
/** 开始更新数据 */
private fun onRefreshing() {
ProgressDialog(this).apply {
setDefaultStyle(context = this@ConfigureActivity)
setCancelable(false)
setTitle("同步中")
setMessage("正在同步 OS 数据")
show()
}.also {
ClientRequestTool.wait(
context = this,
url = "https://raw.githubusercontent.com/fankes/AndroidNotifyIconAdapt/main/OS/ColorOS/NotifyIconsSupportConfig.json"
) { isDone1, ctOS ->
it.setMessage("正在同步 APP 数据")
ClientRequestTool.wait(
context = this,
url = "https://raw.githubusercontent.com/fankes/AndroidNotifyIconAdapt/main/APP/NotifyIconsSupportConfig.json"
) { isDone2, ctAPP ->
it.cancel()
IconPackParams(context = this).also { params ->
if (isDone1 && isDone2) params.splicingJsonArray(ctOS, ctAPP).also {
if (params.isCompareDifferent(it)) {
params.save(it)
filterText = ""
mockLocalData()
SystemUITool.showNeedUpdateApplySnake(context = this)
} else snake(msg = "列表数据已是最新")
} else
showDialog {
title = "连接失败"
msg = "连接失败,错误如下:\n${if (!isDone1) ctOS else ctAPP}"
confirmButton(text = "我知道了")
}
}
}
}
}
}
/** 刷新适配器结果相关 */
private fun refreshAdapterResult() {
onChanged?.invoke()
findViewById<TextView>(R.id.config_title_count_text).text =
if (filterText.isBlank()) "已适配 ${iconDatas.size} 个 APP 的通知图标"
else "${filterText}” 匹配到 ${iconDatas.size} 个结果"
findViewById<TextView>(R.id.config_list_no_data_view).apply {
text = if (iconAllDatas.isEmpty()) "噫,竟然什么都没有~\n请点击右上角同步按钮获取云端数据" else "噫,竟然什么都没找到~"
isVisible = iconDatas.isEmpty()
}
}
/**
* 当前结果下的图标数组
* @return [Array]
*/
private val iconDatas
get() = if (filterText.isBlank()) iconAllDatas
else iconAllDatas.filter {
it.appName.lowercase().contains(filterText.lowercase()) || it.packageName.lowercase().contains(filterText.lowercase())
}
}

View File

@@ -0,0 +1,192 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/2/26.
*/
@file:Suppress("SetTextI18n")
package com.fankes.coloros.notify.ui
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 androidx.appcompat.widget.SwitchCompat
import androidx.constraintlayout.utils.widget.ImageFilterView
import androidx.core.view.isVisible
import com.fankes.coloros.notify.BuildConfig
import com.fankes.coloros.notify.R
import com.fankes.coloros.notify.hook.HookConst.ENABLE_ANDROID12_STYLE
import com.fankes.coloros.notify.hook.HookConst.ENABLE_HIDE_ICON
import com.fankes.coloros.notify.hook.HookConst.ENABLE_MODULE
import com.fankes.coloros.notify.hook.HookConst.ENABLE_MODULE_LOG
import com.fankes.coloros.notify.hook.HookConst.ENABLE_NOTIFY_ICON_FIX
import com.fankes.coloros.notify.hook.HookConst.REMOVE_CHANGECP_NOTIFY
import com.fankes.coloros.notify.hook.HookConst.REMOVE_DEV_NOTIFY
import com.fankes.coloros.notify.ui.base.BaseActivity
import com.fankes.coloros.notify.utils.*
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
class MainActivity : BaseActivity() {
companion object {
/** 模块版本 */
private const val moduleVersion = BuildConfig.VERSION_NAME
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/** 设置文本 */
findViewById<TextView>(R.id.main_text_version).text = "当前版本:$moduleVersion"
findViewById<TextView>(R.id.main_text_coloros_version).text = "系统版本:$colorOSVersion"
when {
/** 判断是否为 ColorOS 系统 */
isNotColorOS ->
showDialog {
title = "不是 ColorOS 系统"
msg = "此模块专为 ColorOS 系统打造,当前无法识别你的系统为 ColorOS所以模块无法工作。\n" +
"如有问题请联系 酷安 @星夜不荟"
confirmButton(text = "退出") { finish() }
noCancelable()
}
/** 判断是否 Hook */
YukiHookModuleStatus.isActive() -> {
findViewById<LinearLayout>(R.id.main_lin_status).setBackgroundResource(R.drawable.bg_green_round)
findViewById<ImageFilterView>(R.id.main_img_status).setImageResource(R.mipmap.ic_success)
findViewById<TextView>(R.id.main_text_status).text = "模块已激活"
}
else ->
showDialog {
title = "模块没有激活"
msg = "检测到模块没有激活,模块需要 Xposed 环境依赖," +
"同时需要系统拥有 Root 权限," +
"请自行查看本页面使用帮助与说明第二条。\n" +
"由于需要修改系统应用达到效果,模块不支持太极阴、应用转生。"
confirmButton(text = "我知道了")
noCancelable()
}
}
/** 初始化 View */
val moduleEnableSwitch = findViewById<SwitchCompat>(R.id.module_enable_switch)
val moduleEnableLogSwitch = findViewById<SwitchCompat>(R.id.module_enable_log_switch)
val devNotifyConfigItem = findViewById<View>(R.id.config_item_dev)
val a12StyleConfigItem = findViewById<View>(R.id.config_item_a12)
val notifyIconConfigItem = findViewById<View>(R.id.config_item_notify)
val devNotifyConfigSwitch = findViewById<SwitchCompat>(R.id.remove_dev_n_enable_switch)
val crcpNotifyConfigSwitch = findViewById<SwitchCompat>(R.id.remove_chargecp_n_enable_switch)
val a12StyleConfigSwitch = findViewById<SwitchCompat>(R.id.a12_style_enable_switch)
val hideIconInLauncherSwitch = findViewById<SwitchCompat>(R.id.hide_icon_in_launcher_switch)
val notifyIconFixSwitch = findViewById<SwitchCompat>(R.id.notify_icon_fix_switch)
val notifyIconFixButton = findViewById<View>(R.id.config_notify_app_button)
/** 获取 Sp 存储的信息 */
devNotifyConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true)
a12StyleConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true)
notifyIconConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true)
moduleEnableLogSwitch.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true)
notifyIconFixButton.isVisible = modulePrefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true)
devNotifyConfigSwitch.isChecked = modulePrefs.getBoolean(REMOVE_DEV_NOTIFY, default = true)
crcpNotifyConfigSwitch.isChecked = modulePrefs.getBoolean(REMOVE_CHANGECP_NOTIFY, default = false)
a12StyleConfigSwitch.isChecked = modulePrefs.getBoolean(ENABLE_ANDROID12_STYLE, isUpperOfAndroidS)
moduleEnableSwitch.isChecked = modulePrefs.getBoolean(ENABLE_MODULE, default = true)
moduleEnableLogSwitch.isChecked = modulePrefs.getBoolean(ENABLE_MODULE_LOG, default = false)
hideIconInLauncherSwitch.isChecked = modulePrefs.getBoolean(ENABLE_HIDE_ICON)
notifyIconFixSwitch.isChecked = modulePrefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true)
moduleEnableSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_MODULE, b)
moduleEnableLogSwitch.isVisible = b
notifyIconConfigItem.isVisible = b
devNotifyConfigItem.isVisible = b
a12StyleConfigItem.isVisible = b
SystemUITool.showNeedRestartSnake(context = this)
}
moduleEnableLogSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_MODULE_LOG, b)
SystemUITool.showNeedRestartSnake(context = this)
}
hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_HIDE_ICON, b)
packageManager.setComponentEnabledSetting(
ComponentName(packageName, "com.fankes.coloros.notify.Home"),
if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
}
notifyIconFixSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_NOTIFY_ICON_FIX, b)
notifyIconFixButton.isVisible = b
SystemUITool.showNeedRestartSnake(context = this)
}
devNotifyConfigSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(REMOVE_DEV_NOTIFY, b)
}
crcpNotifyConfigSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(REMOVE_CHANGECP_NOTIFY, b)
}
a12StyleConfigSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener
modulePrefs.putBoolean(ENABLE_ANDROID12_STYLE, b)
SystemUITool.showNeedRestartSnake(context = this)
}
/** 通知图标优化名单按钮点击事件 */
notifyIconFixButton.setOnClickListener { startActivity(Intent(this, ConfigureActivity::class.java)) }
/** 重启按钮点击事件 */
findViewById<View>(R.id.title_restart_icon).setOnClickListener { SystemUITool.restartSystemUI(context = this) }
/** 恰饭! */
findViewById<View>(R.id.link_with_follow_me).setOnClickListener {
runCatching {
startActivity(Intent().apply {
setPackage("com.coolapk.market")
action = "android.intent.action.VIEW"
data = Uri.parse("https://www.coolapk.com/u/876977")
/** 防止顶栈一样重叠在自己的 APP 中 */
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}.onFailure {
toast(msg = "你可能没有安装酷安")
}
}
/** 项目地址点击事件 */
findViewById<View>(R.id.link_with_project_address).setOnClickListener {
runCatching {
startActivity(Intent().apply {
action = "android.intent.action.VIEW"
data = Uri.parse("https://github.com/fankes/ColorOSNotifyIcon")
/** 防止顶栈一样重叠在自己的 APP 中 */
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}.onFailure {
toast(msg = "无法启动系统默认浏览器")
}
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/30.
*/
package com.fankes.coloros.notify.ui.base
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.fankes.coloros.notify.R
import com.fankes.coloros.notify.utils.isNotSystemInDarkMode
import com.gyf.immersionbar.ktx.immersionBar
abstract class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/** 隐藏系统的标题栏 */
supportActionBar?.hide()
/** 初始化沉浸状态栏 */
immersionBar {
statusBarColor(R.color.colorThemeBackground)
autoDarkModeEnable(true)
statusBarDarkFont(isNotSystemInDarkMode)
navigationBarColor(R.color.colorThemeBackground)
navigationBarDarkIcon(isNotSystemInDarkMode)
fitsSystemWindows(true)
}
}
}

View File

@@ -0,0 +1,110 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/2/25.
*/
@file:Suppress("TrustAllX509TrustManager", "CustomX509TrustManager")
package com.fankes.coloros.notify.utils
import android.app.Activity
import com.highcapable.yukihookapi.hook.log.loggerD
import okhttp3.*
import java.io.IOException
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.*
/**
* 网络请求管理类
*/
object ClientRequestTool {
/**
* 发送 GET 请求内容并等待
* @param context 实例
* @param url 请求地址
* @param it 回调 - ([Boolean] 是否成功,[String] 成功的内容或失败消息)
*/
fun wait(context: Activity, url: String, it: (Boolean, String) -> Unit) {
OkHttpClient().newBuilder().apply {
SSLSocketClient.sSLSocketFactory?.let { sslSocketFactory(it, SSLSocketClient.trustManager) }
hostnameVerifier(SSLSocketClient.hostnameVerifier)
}.build().newCall(
Request.Builder()
.url(url)
.get()
.build()
).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
context.runOnUiThread { it(false, e.toString()) }
}
override fun onResponse(call: Call, response: Response) {
val bodyString = response.body?.string() ?: ""
context.runOnUiThread { it(true, bodyString) }
}
})
}
/**
* 自动信任 SSL 证书
*
* 放行全部加密 SSL 请求
*/
object SSLSocketClient {
/**
* 格式化实例
* @return [SSLSocketFactory] or null
*/
val sSLSocketFactory
get() = safeOfNull {
SSLContext.getInstance("TLS").let {
it.init(null, arrayOf<TrustManager>(trustManager), SecureRandom())
it.socketFactory
}
}
/**
* 使用的实例
* @return [HostnameVerifier]
*/
val hostnameVerifier get() = HostnameVerifier { _, _ -> true }
/**
* 信任管理者
* @return [X509TrustManager]
*/
val trustManager
get() = object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) {
loggerD(msg = "TrustX509 --> $authType")
}
override fun checkServerTrusted(chain: Array<X509Certificate?>?, authType: String?) {
loggerD(msg = "TrustX509 --> $authType")
}
override fun getAcceptedIssuers() = arrayOf<X509Certificate>()
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/7.
*/
@file:Suppress("unused", "DEPRECATION")
package com.fankes.coloros.notify.utils
import android.app.AlertDialog
import android.content.Context
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import kotlin.math.round
/**
* 构造对话框
* @param it 对话框方法体
*/
fun Context.showDialog(it: DialogBuilder.() -> Unit) = DialogBuilder(this).apply(it).show()
/**
* 对话框构造器
* @param context 实例
*/
class DialogBuilder(private val context: Context) {
private var instance: AlertDialog.Builder? = null // 实例对象
private var customLayoutView: View? = null // 自定义布局
init {
instance = AlertDialog.Builder(context, android.R.style.Theme_Material_Light_Dialog)
}
/** 设置对话框不可关闭 */
fun noCancelable() = instance?.setCancelable(false)
/** 设置对话框标题 */
var title
get() = ""
set(value) {
instance?.setTitle(value)
}
/** 设置对话框消息内容 */
var msg
get() = ""
set(value) {
instance?.setMessage(value)
}
/**
* 设置对话框自定义布局
* @param resId 属性资源 Id
* @return [View]
*/
fun addView(resId: Int): View {
customLayoutView = LayoutInflater.from(context).inflate(resId, null)
return customLayoutView ?: error("Inflate $resId failed")
}
/**
* 设置对话框确定按钮
* @param text 按钮文本内容
* @param it 点击事件
*/
fun confirmButton(text: String = "确定", it: () -> Unit = {}) =
instance?.setPositiveButton(text) { _, _ -> it() }
/**
* 设置对话框取消按钮
* @param text 按钮文本内容
* @param it 点击事件
*/
fun cancelButton(text: String = "取消", it: () -> Unit = {}) =
instance?.setNegativeButton(text) { _, _ -> it() }
/**
* 设置对话框第三个按钮
* @param text 按钮文本内容
* @param it 点击事件
*/
fun neutralButton(text: String = "更多", it: () -> Unit = {}) =
instance?.setNeutralButton(text) { _, _ -> it() }
/** 显示对话框 */
internal fun show() = instance?.create()?.apply {
val dm = DisplayMetrics()
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getMetrics(dm)
customLayoutView?.let { setView(it.apply { minimumWidth = round(x = dm.widthPixels / 1.3).toInt() }) }
setDefaultStyle(context = this@DialogBuilder.context)
}?.show()
}

View File

@@ -0,0 +1,69 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/2/8.
*/
package com.fankes.coloros.notify.utils
import android.content.Context
import com.google.android.material.snackbar.Snackbar
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
/**
* 系统界面工具
*/
object SystemUITool {
/**
* 重启系统界面
* @param context 实例
*/
fun restartSystemUI(context: Context) =
context.showDialog {
title = "重启系统界面"
msg = "你确定要立即重启系统界面吗?"
confirmButton {
execShellSu(cmd = "pgrep systemui").also { pid ->
if (pid.isNotBlank())
execShellSu(cmd = "kill -9 $pid")
else toast(msg = "ROOT 权限获取失败")
}
}
cancelButton()
}
/**
* 显示需要重启系统界面的 [Snackbar]
* @param context 实例
*/
fun showNeedRestartSnake(context: Context) =
if (YukiHookModuleStatus.isActive())
context.snake(msg = "设置需要重启系统界面才能生效", actionText = "立即重启") { restartSystemUI(context) }
else context.snake(msg = "模块没有激活,更改不会生效")
/**
* 显示更新数据后需要重启系统界面的 [Snackbar]
* @param context 实例
*/
fun showNeedUpdateApplySnake(context: Context) =
if (YukiHookModuleStatus.isActive())
context.snake(msg = "数据已更新,请重启系统界面使更改生效", actionText = "立即重启") { restartSystemUI(context) }
else context.snake(msg = "模块没有激活,更改不会生效")
}

View File

@@ -0,0 +1,331 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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/7.
*/
@file:Suppress("DEPRECATION", "PrivateApi", "unused", "ObsoleteSdkInt")
package com.fankes.coloros.notify.utils
import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.util.Base64
import android.widget.Toast
import com.fankes.coloros.notify.application.CNNApplication.Companion.appContext
import com.google.android.material.snackbar.Snackbar
import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.factory.hasClass
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.log.loggerE
import com.highcapable.yukihookapi.hook.type.java.StringType
import com.topjohnwu.superuser.Shell
/**
* 系统深色模式是否开启
* @return [Boolean] 是否开启
*/
val isSystemInDarkMode
get() = (appContext.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
/**
* 系统深色模式是否没开启
* @return [Boolean] 是否开启
*/
inline val isNotSystemInDarkMode get() = !isSystemInDarkMode
/**
* 系统深色模式是否开启
* @return [Boolean] 是否开启
*/
val Context.isSystemInDarkMode get() = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
/**
* 系统深色模式是否没开启
* @return [Boolean] 是否开启
*/
inline val Context.isNotSystemInDarkMode get() = !isSystemInDarkMode
/**
* 系统版本是否高于或等于 Android 12
* @return [Boolean] 是否符合条件
*/
inline val isUpperOfAndroidS get() = Build.VERSION.SDK_INT > Build.VERSION_CODES.R
/**
* 系统版本是否低于 Android 9
* @return [Boolean] 是否符合条件
*/
inline val isLowerAndroidP get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.P
/**
* 当前设备是否是 ColorOS 定制 Android 系统
* @return [Boolean] 是否符合条件
*/
val isColorOS by lazy { ("oppo.R").hasClass || ("com.color.os.ColorBuild").hasClass || ("oplus.R").hasClass }
/**
* 当前设备是否不是 ColorOS 定制 Android 系统
* @return [Boolean] 是否符合条件
*/
inline val isNotColorOS get() = !isColorOS
/**
* 获取 ColorOS 版本
* @return [String]
*/
val colorOSVersion
get() = safeOf(default = "无法获取") {
findPropString(key = "ro.system.build.fingerprint", default = "无法获取")
.split("ossi:")[1]
.split("/")[0].trim()
}
/**
* 得到安装包信息
* @return [PackageInfo]
*/
val Context.packageInfo get() = packageManager?.getPackageInfo(packageName, 0) ?: PackageInfo()
/**
* 判断应用是否安装
* @return [Boolean]
*/
val String.isInstall
get() = safeOfFalse {
appContext.packageManager.getPackageInfo(
this,
PackageManager.GET_UNINSTALLED_PACKAGES
)
true
}
/**
* 得到版本信息
* @return [String]
*/
val Context.versionName get() = packageInfo.versionName ?: ""
/**
* 得到版本号
* @return [Int]
*/
val Context.versionCode get() = packageInfo.versionCode
/**
* dp 转换为 px
* @return [Int]
*/
val Number.dp get() = (toFloat() * appContext.resources.displayMetrics.density).toInt()
/**
* dp 转换为 px
* @param context 使用的实例
* @return [Int]
*/
fun Number.dp(context: Context) = (toFloat() * context.resources.displayMetrics.density).toInt()
/**
* Base64 加密
* @return [String]
*/
val String.base64: String get() = Base64.encodeToString(toByteArray(), Base64.DEFAULT)
/**
* Base64 解密为字节流
* @return [ByteArray]
*/
val String.unbase64 get() = Base64.decode(this, Base64.DEFAULT) ?: ByteArray(0)
/**
* 字节流解析为位图
* @return [Bitmap]
*/
val ByteArray.bitmap: Bitmap get() = BitmapFactory.decodeByteArray(this, 0, size)
/**
* 字符串解析为位图
* @return [Bitmap]
*/
val String.bitmap: Bitmap get() = unbase64.bitmap
/**
* 设置对话框默认风格
* @param context 使用的实例
*/
fun AlertDialog.setDefaultStyle(context: Context) =
window?.setBackgroundDrawable(
GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
intArrayOf(Color.WHITE, Color.WHITE)
).apply {
shape = GradientDrawable.RECTANGLE
gradientType = GradientDrawable.LINEAR_GRADIENT
cornerRadius = 15.dp(context).toFloat()
})
/**
* 获取系统 Prop 值
* @param key Key
* @param default 默认值
* @return [String]
*/
fun findPropString(key: String, default: String = "") = safeOf(default) {
(classOf(name = "android.os.SystemProperties").method {
name = "get"
param(StringType, StringType)
}.get().invoke(key, default)) ?: default
}
/**
* 执行命令 - su
* @param cmd 命令
* @return [String] 执行结果
*/
fun execShellSu(cmd: String) = safeOfNothing {
Shell.su(cmd).exec().out.let {
if (it.isNotEmpty()) it[0].trim() else ""
}
}
/**
* 弹出 [Toast]
* @param msg 提示内容
*/
fun toast(msg: String) = Toast.makeText(appContext, msg, Toast.LENGTH_SHORT).show()
/**
* 弹出 [Snackbar]
* @param msg 提示内容
* @param actionText 按钮文本 - 不写默认取消按钮
* @param it 按钮事件回调
*/
fun Context.snake(msg: String, actionText: String = "", it: () -> Unit = {}) =
Snackbar.make((this as Activity).findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG)
.apply {
if (actionText.isBlank()) return@apply
setActionTextColor(Color.WHITE)
setAction(actionText) { it() }
}.show()
/**
* 忽略异常返回值
* @param it 回调 - 如果异常为空
* @return [T] 发生异常时返回设定值否则返回正常值
*/
inline fun <T> safeOfNull(it: () -> T): T? = safeOf(null, it)
/**
* 忽略异常返回值
* @param it 回调 - 如果异常为 false
* @return [Boolean] 发生异常时返回设定值否则返回正常值
*/
inline fun safeOfFalse(it: () -> Boolean) = safeOf(default = false, it)
/**
* 忽略异常返回值
* @param it 回调 - 如果异常为 true
* @return [Boolean] 发生异常时返回设定值否则返回正常值
*/
inline fun safeOfTrue(it: () -> Boolean) = safeOf(default = true, it)
/**
* 忽略异常返回值
* @param it 回调 - 如果异常为 false
* @return [String] 发生异常时返回设定值否则返回正常值
*/
inline fun safeOfNothing(it: () -> String) = safeOf(default = "", it)
/**
* 忽略异常返回值
* @param it 回调 - 如果异常为 false
* @return [Int] 发生异常时返回设定值否则返回正常值
*/
inline fun safeOfNan(it: () -> Int) = safeOf(default = 0, it)
/**
* 忽略异常返回值
* @param default 异常返回值
* @param it 正常回调值
* @return [T] 发生异常时返回设定值否则返回正常值
*/
inline fun <T> safeOf(default: T, it: () -> T): T {
return try {
it()
} catch (t: NullPointerException) {
default
} catch (t: UnsatisfiedLinkError) {
default
} catch (t: UnsupportedOperationException) {
default
} catch (t: ClassNotFoundException) {
default
} catch (t: IllegalStateException) {
default
} catch (t: NoSuchMethodError) {
default
} catch (t: NoSuchFieldError) {
default
} catch (t: Error) {
default
} catch (t: Exception) {
default
} catch (t: Throwable) {
default
}
}
/**
* 忽略异常运行
* @param msg 出错输出的消息 - 默认为空
* @param it 正常回调
*/
inline fun safeRun(msg: String = "", it: () -> Unit) {
try {
it()
} catch (e: NullPointerException) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: UnsatisfiedLinkError) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: UnsupportedOperationException) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: ClassNotFoundException) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: IllegalStateException) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: NoSuchMethodError) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: NoSuchFieldError) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: Error) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: Exception) {
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
} catch (e: Throwable) {
}
}

View File

@@ -0,0 +1,293 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.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

@@ -0,0 +1,30 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.utils.drawable.drawabletoolbox
class Constants {
companion object {
const val DEFAULT_COLOR = 0xFFBA68C8.toInt()
}
}

View File

@@ -0,0 +1,490 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.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

@@ -0,0 +1,222 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.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

@@ -0,0 +1,35 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.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

@@ -0,0 +1,79 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.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

@@ -0,0 +1,36 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.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

@@ -0,0 +1,182 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.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
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

@@ -0,0 +1,62 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.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

@@ -0,0 +1,74 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.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

@@ -0,0 +1,53 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.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

@@ -0,0 +1,46 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.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

@@ -0,0 +1,61 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.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

@@ -0,0 +1,69 @@
/*
* ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/fankes/ColorOSNotifyIcon
*
* 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.
* <p>
*
* 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.coloros.notify.view
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.util.AttributeSet
import androidx.appcompat.widget.SwitchCompat
import com.fankes.coloros.notify.utils.dp
import com.fankes.coloros.notify.utils.drawable.drawabletoolbox.DrawableBuilder
class MaterialSwitch(context: Context, attrs: AttributeSet?) : SwitchCompat(context, attrs) {
private fun toColors(selected: Int, pressed: Int, normal: Int): ColorStateList {
val colors = intArrayOf(selected, pressed, normal)
val states = arrayOfNulls<IntArray>(3)
states[0] = intArrayOf(android.R.attr.state_checked)
states[1] = intArrayOf(android.R.attr.state_pressed)
states[2] = intArrayOf()
return ColorStateList(states, colors)
}
init {
trackDrawable = DrawableBuilder()
.rectangle()
.rounded()
.solidColor(0xFF656565.toInt())
.height(20.dp)
.cornerRadius(15.dp)
.build()
thumbDrawable = DrawableBuilder()
.rectangle()
.rounded()
.solidColor(Color.WHITE)
.size(20.dp, 20.dp)
.cornerRadius(20.dp)
.strokeWidth(2.dp)
.strokeColor(Color.TRANSPARENT)
.build()
trackTintList = toColors(
0xFF656565.toInt(),
0xFFCCCCCC.toInt(),
0xFFCCCCCC.toInt()
)
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#66A6A6A6" />
<corners android:radius="15dp" />
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#666E6E6E" />
<corners android:radius="15dp" />
</shape>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#777777">
<item>
<shape android:shape="rectangle">
<solid android:color="#66DAD9D9" />
<corners android:radius="15dp" />
</shape>
</item>
</ripple>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#661B1B1B" />
<corners android:radius="15dp" />
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#26A69A" />
<corners android:radius="15dp" />
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#66E4E4E4" />
<corners android:radius="15dp" />
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FF9800" />
<corners android:radius="15dp" />
</shape>

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorThemeBackground"
android:orientation="vertical"
tools:context=".ui.MainActivity"
tools:ignore="HardcodedText,UseCompoundDrawables,ContentDescription">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="0dp"
android:gravity="center|start"
android:paddingLeft="15dp"
android:paddingTop="13dp"
android:paddingRight="15dp"
android:paddingBottom="5dp">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/title_back_icon"
style="?android:attr/selectableItemBackgroundBorderless"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="25dp"
android:src="@mipmap/ic_back"
android:tint="@color/colorTextGray"
android:tooltipText="返回" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_weight="1"
android:gravity="center|start"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:singleLine="true"
android:text="通知图标优化名单"
android:textColor="@color/colorTextGray"
android:textSize="19sp"
android:textStyle="bold" />
<TextView
android:id="@+id/config_title_count_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="适配列表正在等待装载"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/config_title_up"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_marginEnd="5dp"
android:src="@mipmap/ic_page_top"
android:tint="@color/colorTextGray"
android:tooltipText="滚动到顶部" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/config_title_down"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_marginEnd="15dp"
android:src="@mipmap/ic_page_bottom"
android:tint="@color/colorTextGray"
android:tooltipText="滚动到底部" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/config_title_filter"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_filter"
android:tint="@color/colorTextGray"
android:tooltipText="按条件过滤" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/config_title_sync"
android:layout_width="23dp"
android:layout_height="23dp"
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_sync"
android:tint="@color/colorTextGray"
android:tooltipText="同步列表" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="10dp"
android:layout_marginRight="15dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="horizontal"
android:padding="15dp">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="10dp"
android:alpha="0.85"
android:src="@mipmap/ic_about"
android:tint="@color/colorTextDark" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.6"
android:lineSpacingExtra="5dp"
android:text="启用替换后 APP 的彩色通知图标将被替换为预设的小图标。\n启用全部替换将忽略 APP 单色图标,强制将全部通知图标替换为列表中预设的小图标。"
android:textColor="@color/colorTextGray"
android:textSize="11sp"
tools:ignore="SmallSp" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<TextView
android:id="@+id/config_list_no_data_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:lineSpacingExtra="6dp"
android:text="没有数据"
android:textColor="@color/colorTextDark"
android:textSize="17sp"
android:visibility="gone" />
<ListView
android:id="@+id/config_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/trans"
android:dividerHeight="15dp"
android:fadingEdgeLength="10dp"
android:listSelector="@null"
android:padding="15dp"
android:requiresFadingEdge="vertical"
android:scrollbars="none" />
</FrameLayout>
<TextView
android:id="@+id/config_cbr_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="15dp"
android:background="@drawable/bg_button_round"
android:gravity="center"
android:padding="10dp"
android:singleLine="true"
android:text="贡献通知图标优化名单"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
</LinearLayout>

View File

@@ -0,0 +1,576 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorThemeBackground"
android:orientation="vertical"
tools:context=".ui.MainActivity"
tools:ignore="HardcodedText,UseCompoundDrawables,ContentDescription">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="0dp"
android:gravity="center|start"
android:paddingLeft="15dp"
android:paddingTop="13dp"
android:paddingRight="15dp"
android:paddingBottom="5dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
android:text="ColorOS 通知图标增强"
android:textColor="@color/colorTextGray"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/title_restart_icon"
style="?android:attr/selectableItemBackgroundBorderless"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginEnd="5dp"
android:alpha="0.85"
android:src="@mipmap/ic_restart"
android:tint="@color/colorTextGray"
android:tooltipText="重启系统界面" />
</LinearLayout>
<LinearLayout
android:id="@+id/main_lin_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="10dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="5dp"
android:background="@drawable/bg_dark_round"
android:elevation="0dp"
android:gravity="center">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/main_img_status"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginStart="25dp"
android:layout_marginEnd="5dp"
android:src="@mipmap/ic_warn"
android:tint="@color/white" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingTop="10dp"
android:paddingRight="20dp"
android:paddingBottom="10dp">
<TextView
android:id="@+id/main_text_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:text="模块未激活"
android:textColor="@color/white"
android:textSize="18sp" />
<TextView
android:id="@+id/main_text_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:alpha="0.8"
android:text="当前版本:%1"
android:textColor="@color/white"
android:textSize="13sp" />
<TextView
android:id="@+id/main_text_coloros_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.8"
android:text="ColorOS 版本:%1"
android:textColor="@color/white"
android:textSize="13sp" />
</LinearLayout>
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
android:fadingEdgeLength="10dp"
android:fillViewport="true"
android:requiresFadingEdge="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="10dp"
android:layout_marginRight="15dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="horizontal"
android:padding="15dp">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="10dp"
android:alpha="0.85"
android:src="@mipmap/ic_about"
android:tint="@color/colorTextDark" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.6"
android:lineSpacingExtra="5dp"
android:text="修改设置或开启关闭模块请点击右上角重启系统界面才能生效。"
android:textColor="@color/colorTextGray"
android:textSize="11sp"
tools:ignore="SmallSp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="10dp"
android:layout_marginRight="15dp"
android:animateLayoutChanges="true"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<com.fankes.coloros.notify.view.MaterialSwitch
android:id="@+id/module_enable_switch"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="5dp"
android:text="启用模块"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<com.fankes.coloros.notify.view.MaterialSwitch
android:id="@+id/module_enable_log_switch"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginBottom="5dp"
android:text="启用调试日志"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="模块关闭后一切功能都将彻底停止工作。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/config_item_dev"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="10dp"
android:layout_marginRight="15dp"
android:animateLayoutChanges="true"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<com.fankes.coloros.notify.view.MaterialSwitch
android:id="@+id/remove_dev_n_enable_switch"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="5dp"
android:text="移除开发者选项警告通知"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="每次重新开机后只要开发者选项开启就会推送警告通知,开启后将直接移除这个“你在教我做事”的通知功能。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<com.fankes.coloros.notify.view.MaterialSwitch
android:id="@+id/remove_chargecp_n_enable_switch"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginBottom="5dp"
android:text="移除充电完成通知"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:id="@+id/color_icon_compat_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="充电完成后总是会推送一条通知,有时候甚至还不是静音状态,开启后将直接移除这个“你在教我做事”的通知功能。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/config_item_a12"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="10dp"
android:layout_marginRight="15dp"
android:animateLayoutChanges="true"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<com.fankes.coloros.notify.view.MaterialSwitch
android:id="@+id/a12_style_enable_switch"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="5dp"
android:text="启用 Android 12 通知栏风格"
android:textAllCaps="false"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="无论任何版本,开启后都将使用 Android 12 原生通知图标方式为通知栏推送的通知图标增加外部圆形轮廓。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/config_item_notify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:animateLayoutChanges="true"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<com.fankes.coloros.notify.view.MaterialSwitch
android:id="@+id/notify_icon_fix_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:text="启用通知图标优化"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:id="@+id/config_notify_app_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:background="@drawable/bg_button_round"
android:gravity="center"
android:padding="10dp"
android:singleLine="true"
android:text="配置通知图标优化名单"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="首次安装请打开名单列表从云端更新数据,后期适配的内容也请手动打开名单列表重新拉取数据以检查更新,数据更新后重启系统界面即可生效。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="此选项默认开启,开启后将对优化名单内的 APP 通知小图标使用单色调进行修复,特别是通过系统级别推送的通知,它们始终是 APP 默认图标(彩色的 APP 图标),修复后使得它们的图标看起来更加符合 Android 原生的统一规范。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<com.fankes.coloros.notify.view.MaterialSwitch
android:id="@+id/hide_icon_in_launcher_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="在桌面隐藏模块图标"
android:textColor="@color/colorTextGray"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="隐藏模块图标后界面可能会被关闭,将不会再在桌面显示,你可以在 EdXposed、LSPosed 中找到模块设置并打开。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.6"
android:lineSpacingExtra="6dp"
android:text="注意:部分机型可能无效,取决于系统。"
android:textColor="#FF5722"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:padding="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:gravity="center|start">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:alpha="0.85"
android:src="@mipmap/ic_about"
android:tint="@color/colorTextGray" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:text="使用帮助与说明"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.这个模块是如何诞生的?\nA.继 MIUI 之后的第二大系统 ColorOS 虽然支持原生通知图标,但是第三方推送五颜六色的图标系统并没有做适配,甚至系统自己的图标都是彩色的,极其不友好,而且从 ColorOS 12 开始,原生图标丢失了着色属性,这也是一种对原生 Android 生态的破坏。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.如何使用?\nA.模块仅支持 LSPosedEdXposed 也可以使用但随时停止支持,由于模块涉及到修改系统应用,不支持其它 Hook 框架,在 LSPosed 的作用域中,只需勾选“系统界面”即可,应用设置后需要重启系统界面。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.哪些是已知问题?\nA.暂时只对 ColorOS 12 做了适配,其它版本情况未知,请自行进行测试。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="Q.如何反馈问题?\nA.酷安关注 @星夜不荟"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/link_with_project_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:padding="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:gravity="center|start">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:alpha="0.85"
android:src="@mipmap/ic_about"
android:tint="@color/colorTextGray" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.85"
android:singleLine="true"
android:text="项目地址"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.8"
android:lineSpacingExtra="6dp"
android:text="本软件是免费开源项目,遵循 AGPL3.0 协议,你可以点击这里前往 Github 查看源码以及获取模块更新。"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="10dp"
android:background="@drawable/bg_permotion_round"
android:elevation="0dp"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<TextView
android:id="@+id/link_with_follow_me"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:gravity="center"
android:lineSpacingExtra="6dp"
android:text="恰饭时间\n点击前往酷安关注我获取我的更多应用"
android:textColor="@color/colorTextGray"
android:textSize="16sp" />
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
app:cardCornerRadius="15dp"
app:cardElevation="0dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/bg_qr_pay" />
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:gravity="center"
android:lineSpacingExtra="6dp"
android:text="开发者 酷安 @星夜不荟\n未经允许不得转载、修改复制我的劳动成果"
android:textColor="@color/colorTextGray"
android:textSize="16sp" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="10dp"
android:autoLink="web"
android:background="@drawable/bg_permotion_round"
android:lineSpacingExtra="6dp"
android:padding="10dp"
android:text="此模块使用 YukiHookAPI 构建。\n点击这里了解更多 https://github.com/fankes/YukiHookAPI"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_permotion_round"
android:baselineAligned="false"
android:gravity="center|start"
android:orientation="horizontal"
android:padding="15dp"
tools:ignore="HardcodedText">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center|start"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:gravity="center|start">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/adp_app_icon"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="8dp"
android:src="@mipmap/ic_about"
android:tint="@color/colorTextGray" />
<TextView
android:id="@+id/adp_app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="APP 名称"
android:textColor="@color/colorTextGray"
android:textSize="14sp" />
</LinearLayout>
<TextView
android:id="@+id/adp_app_pkg_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:alpha="0.75"
android:singleLine="true"
android:text="com.xxx.xxx"
android:textColor="@color/colorTextDark"
android:textSize="12sp" />
<TextView
android:id="@+id/adp_cbr_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="贡献者noname"
android:textColor="@color/colorTextDark"
android:textSize="13sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.fankes.coloros.notify.view.MaterialSwitch
android:id="@+id/adp_app_open_switch"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_marginBottom="5dp"
android:text="启用替换 "
android:textColor="@color/colorTextGray"
android:textSize="13sp" />
<com.fankes.coloros.notify.view.MaterialSwitch
android:id="@+id/adp_app_all_switch"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:text="全部替换 "
android:textColor="@color/colorTextGray"
android:textSize="13sp" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp"
tools:ignore="HardcodedText">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/dia_icon_filter_input_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:hint="可输入 APP 名称、包名"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorThemeBackground">#FF2D2D2D</color>
<color name="colorTextDark">#FFCFCFCF</color>
<color name="colorTextGray">#FFD3D3D3</color>
</resources>

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.ColorOSNotifyIcon" parent="Theme.Material3.DayNight">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</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>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorThemeBackground">#FFFFFFFF</color>
<color name="colorTextDark">#FF777777</color>
<color name="colorTextGray">#FF323B42</color>
</resources>

View File

@@ -0,0 +1,11 @@
<?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="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="trans">#00000000</color>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#4E8A5A</color>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">ColorOS 通知图标增强</string>
</resources>

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.ColorOSNotifyIcon" 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>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,17 @@
package com.fankes.coloros.notify
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

14
build.gradle Normal file
View File

@@ -0,0 +1,14 @@
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
}
ext {
appVersionName = "1.0"
appVersionCode = 1
}
task clean(type: Delete) {
delete rootProject.buildDir
}

23
gradle.properties Normal file
View File

@@ -0,0 +1,23 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# 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
# 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
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Sat Feb 26 13:04:39 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

185
gradlew vendored Executable file
View File

@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

BIN
keystore/public Normal file

Binary file not shown.

19
settings.gradle Normal file
View File

@@ -0,0 +1,19 @@
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
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()
}
}
rootProject.name = "ColorOSNotifyIcon"
include ':app'