commit 73246523e3e07b10a605ef8e0a72bce34314c6f4 Author: Fankesyooni Date: Sun Feb 27 23:42:33 2022 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..526b4c2 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..146ab09 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..98f4325 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d74f0d9 --- /dev/null +++ b/README.md @@ -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) +

+ +
+Optimize notification icons for ColorOS and adapt to native notification icon specifications.
+为 ColorOS 优化通知图标以及适配原生通知图标规范,理论支持 OxygenOS 和 RealmeOS。 + +# 开始使用 + +点击下载最新版本 +![Eclipse Marketplace](https://img.shields.io/badge/download-v1.0-green) +

+⚠️ 适配说明
+ +- 此模块仅支持 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` 项目的一部分,详情请参考下方。
+ +- [Android 通知图标规范适配](https://github.com/fankes/AndroidNotifyIconAdapt) + +# 历史背景 + +继 MIUI 之后的第二大系统 ColorOS 虽然支持原生通知图标,但是第三方推送五颜六色的图标系统并没有做适配,甚至系统自己的图标都是彩色的,极其不友好。

+而且从 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 . +``` + +Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)

+版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com) \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..2c7d9bd --- /dev/null +++ b/app/build.gradle @@ -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' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..786522e --- /dev/null +++ b/app/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/app/src/androidTest/java/com/fankes/coloros/notify/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/fankes/coloros/notify/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..19afee1 --- /dev/null +++ b/app/src/androidTest/java/com/fankes/coloros/notify/ExampleInstrumentedTest.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..57b7e6e --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/xposed_init b/app/src/main/assets/xposed_init new file mode 100644 index 0000000..0874363 --- /dev/null +++ b/app/src/main/assets/xposed_init @@ -0,0 +1 @@ +com.fankes.coloros.notify.hook.HookEntry_YukiHookXposedInit \ No newline at end of file diff --git a/app/src/main/assets/yukihookapi_init b/app/src/main/assets/yukihookapi_init new file mode 100644 index 0000000..c5e2fb5 --- /dev/null +++ b/app/src/main/assets/yukihookapi_init @@ -0,0 +1 @@ +com.fankes.coloros.notify.hook.HookEntry \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..44a3a0a Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/fankes/coloros/notify/application/CNNApplication.kt b/app/src/main/java/com/fankes/coloros/notify/application/CNNApplication.kt new file mode 100644 index 0000000..20ea087 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/application/CNNApplication.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/bean/IconDataBean.kt b/app/src/main/java/com/fankes/coloros/notify/bean/IconDataBean.kt new file mode 100644 index 0000000..04da6f0 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/bean/IconDataBean.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/30. + */ +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" +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/hook/HookConst.kt b/app/src/main/java/com/fankes/coloros/notify/hook/HookConst.kt new file mode 100644 index 0000000..fe3cb50 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/hook/HookConst.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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" +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/hook/HookEntry.kt b/app/src/main/java/com/fankes/coloros/notify/hook/HookEntry.kt new file mode 100644 index 0000000..2ed4590 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/hook/HookEntry.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/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() + + /** + * 获取推送通知的应用名称 + * @param packageName APP 包名 + * @return [String] + */ + private fun Context.findAppName(packageName: String) = safeOf(default = "") { + 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(drawable) ?: false + } + + /** + * 自动适配状态栏、通知栏自定义小图标 + * @param isGrayscaleIcon 是否为灰度图标 + * @param packageName APP 包名 + * @return [Pair] - ([Bitmap] 位图,[Int] 颜色) + */ + private fun PackageParam.compatCustomIcon(isGrayscaleIcon: Boolean, packageName: String): Pair { + var customPair: Pair? = 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(field { name = "iconBuilder" }.of(instance))?.also { context -> + NotificationEntryClass.clazz.method { + name = "getSbn" + }.get(firstArgs).invoke()?.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(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() + }?.notification?.also { nf -> + nf.smallIcon.loadDrawable(context).also { iconDrawable -> + compatNotifyIcon( + isGrayscaleIcon = isGrayscaleIcon(context, iconDrawable), + packageName = context.packageName, + drawable = iconDrawable, + iconColor = nf.color, + iconView = this + ) + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/hook/factory/DataFactory.kt b/app/src/main/java/com/fankes/coloros/notify/hook/factory/DataFactory.kt new file mode 100644 index 0000000..ed78bcc --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/hook/factory/DataFactory.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/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) \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/param/IconPackParams.kt b/app/src/main/java/com/fankes/coloros/notify/param/IconPackParams.kt new file mode 100644 index 0000000..007ea20 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/param/IconPackParams.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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().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) +} diff --git a/app/src/main/java/com/fankes/coloros/notify/ui/ConfigureActivity.kt b/app/src/main/java/com/fankes/coloros/notify/ui/ConfigureActivity.kt new file mode 100644 index 0000000..da03b4e --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/ui/ConfigureActivity.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/30. + */ +@file:Suppress("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() + + 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(R.id.title_back_icon).setOnClickListener { onBackPressed() } + /** 刷新适配器结果相关 */ + refreshAdapterResult() + /** 设置上下按钮点击事件 */ + findViewById(R.id.config_title_up).setOnClickListener { + snake(msg = "滚动到顶部") + onScrollEvent?.invoke(false) + } + findViewById(R.id.config_title_down).setOnClickListener { + snake(msg = "滚动到底部") + onScrollEvent?.invoke(true) + } + /** 设置过滤按钮点击事件 */ + findViewById(R.id.config_title_filter).setOnClickListener { + showDialog { + title = "按条件过滤" + var editText: TextInputEditText + addView(R.layout.dia_icon_filter).apply { + editText = findViewById(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(R.id.config_title_sync).setOnClickListener { onStartRefresh() } + /** 设置列表元素和 Adapter */ + findViewById(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(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(R.id.config_title_count_text).text = + if (filterText.isBlank()) "已适配 ${iconDatas.size} 个 APP 的通知图标" + else "“${filterText}” 匹配到 ${iconDatas.size} 个结果" + findViewById(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()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/ui/MainActivity.kt b/app/src/main/java/com/fankes/coloros/notify/ui/MainActivity.kt new file mode 100644 index 0000000..117d5d1 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/ui/MainActivity.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/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(R.id.main_text_version).text = "当前版本:$moduleVersion" + findViewById(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(R.id.main_lin_status).setBackgroundResource(R.drawable.bg_green_round) + findViewById(R.id.main_img_status).setImageResource(R.mipmap.ic_success) + findViewById(R.id.main_text_status).text = "模块已激活" + } + else -> + showDialog { + title = "模块没有激活" + msg = "检测到模块没有激活,模块需要 Xposed 环境依赖," + + "同时需要系统拥有 Root 权限," + + "请自行查看本页面使用帮助与说明第二条。\n" + + "由于需要修改系统应用达到效果,模块不支持太极阴、应用转生。" + confirmButton(text = "我知道了") + noCancelable() + } + } + /** 初始化 View */ + val moduleEnableSwitch = findViewById(R.id.module_enable_switch) + val moduleEnableLogSwitch = findViewById(R.id.module_enable_log_switch) + val devNotifyConfigItem = findViewById(R.id.config_item_dev) + val a12StyleConfigItem = findViewById(R.id.config_item_a12) + val notifyIconConfigItem = findViewById(R.id.config_item_notify) + val devNotifyConfigSwitch = findViewById(R.id.remove_dev_n_enable_switch) + val crcpNotifyConfigSwitch = findViewById(R.id.remove_chargecp_n_enable_switch) + val a12StyleConfigSwitch = findViewById(R.id.a12_style_enable_switch) + val hideIconInLauncherSwitch = findViewById(R.id.hide_icon_in_launcher_switch) + val notifyIconFixSwitch = findViewById(R.id.notify_icon_fix_switch) + val notifyIconFixButton = findViewById(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(R.id.title_restart_icon).setOnClickListener { SystemUITool.restartSystemUI(context = this) } + /** 恰饭! */ + findViewById(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(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 = "无法启动系统默认浏览器") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/ui/base/BaseActivity.kt b/app/src/main/java/com/fankes/coloros/notify/ui/base/BaseActivity.kt new file mode 100644 index 0000000..f1a3308 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/ui/base/BaseActivity.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/30. + */ +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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/ClientRequestTool.kt b/app/src/main/java/com/fankes/coloros/notify/utils/ClientRequestTool.kt new file mode 100644 index 0000000..968c695 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/ClientRequestTool.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/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), SecureRandom()) + it.socketFactory + } + } + + /** + * 使用的实例 + * @return [HostnameVerifier] + */ + val hostnameVerifier get() = HostnameVerifier { _, _ -> true } + + /** + * 信任管理者 + * @return [X509TrustManager] + */ + val trustManager + get() = object : X509TrustManager { + + override fun checkClientTrusted(chain: Array?, authType: String?) { + loggerD(msg = "TrustX509 --> $authType") + } + + override fun checkServerTrusted(chain: Array?, authType: String?) { + loggerD(msg = "TrustX509 --> $authType") + } + + override fun getAcceptedIssuers() = arrayOf() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/DialogBuilder.kt b/app/src/main/java/com/fankes/coloros/notify/utils/DialogBuilder.kt new file mode 100644 index 0000000..1cf1644 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/DialogBuilder.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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() +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/SystemUITool.kt b/app/src/main/java/com/fankes/coloros/notify/utils/SystemUITool.kt new file mode 100644 index 0000000..4c081db --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/SystemUITool.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/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 = "模块没有激活,更改不会生效") +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/Utils.kt b/app/src/main/java/com/fankes/coloros/notify/utils/Utils.kt new file mode 100644 index 0000000..2310977 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/Utils.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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 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 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) { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/Compatible.kt b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/Compatible.kt new file mode 100755 index 0000000..a125822 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/Compatible.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/Constants.kt b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/Constants.kt new file mode 100755 index 0000000..2f9b3ea --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/Constants.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/8. + */ +package com.fankes.coloros.notify.utils.drawable.drawabletoolbox + +class Constants { + + companion object { + const val DEFAULT_COLOR = 0xFFBA68C8.toInt() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/DrawableBuilder.kt b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/DrawableBuilder.kt new file mode 100755 index 0000000..0771683 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/DrawableBuilder.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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 Drawable>() + private var baseDrawable: Drawable? = null + + fun batch(properties: DrawableProperties) = apply { this.properties = properties.copy() } + fun baseDrawable(drawable: Drawable) = apply { baseDrawable = drawable } + + // + 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 } + + // + 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) + } + + // + + 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) } + + // + 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) } + + // + 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 } + + // + 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() } + + // + 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) } + + // + 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) } + + // + + 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() + val colors = mutableListOf() + + 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() + val colors = mutableListOf() + + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/DrawableProperties.kt b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/DrawableProperties.kt new file mode 100755 index 0000000..fc89986 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/DrawableProperties.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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( + + // + @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, + + // + 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, + + // + @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, + + // + @JvmField var width: Int = -1, + @JvmField var height: Int = -1, + + // + @JvmField var solidColor: Int = Color.TRANSPARENT, + @JvmField var solidColorStateList: ColorStateList? = null, + + // + @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, + + // + @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, + + // + @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 { + override fun createFromParcel(parcel: Parcel): DrawableProperties { + return DrawableProperties(parcel) + } + + override fun newArray(size: Int): Array { + 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() +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/DrawableWrapperBuilder.kt b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/DrawableWrapperBuilder.kt new file mode 100755 index 0000000..b79536c --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/DrawableWrapperBuilder.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/8. + */ +package com.fankes.coloros.notify.utils.drawable.drawabletoolbox + +import android.graphics.drawable.Drawable + +abstract class DrawableWrapperBuilder> { + + protected var drawable: Drawable? = null + + @Suppress("UNCHECKED_CAST") + fun drawable(drawable: Drawable): T = apply { this.drawable = drawable } as T + + abstract fun build(): Drawable +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/FlipDrawable.kt b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/FlipDrawable.kt new file mode 100755 index 0000000..6ed6b94 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/FlipDrawable.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/FlipDrawableBuilder.kt b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/FlipDrawableBuilder.kt new file mode 100755 index 0000000..6f01208 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/FlipDrawableBuilder.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/8. + */ +package com.fankes.coloros.notify.utils.drawable.drawabletoolbox + +import android.graphics.drawable.Drawable + +class FlipDrawableBuilder : DrawableWrapperBuilder() { + + private var orientation: Int = FlipDrawable.ORIENTATION_HORIZONTAL + + fun orientation(orientation: Int) = apply { this.orientation = orientation } + + override fun build(): Drawable { + return FlipDrawable(drawable!!, orientation) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/LayerDrawableBuilder.kt b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/LayerDrawableBuilder.kt new file mode 100755 index 0000000..5e68682 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/LayerDrawableBuilder.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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() + + @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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/PathShapeDrawableBuilder.kt b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/PathShapeDrawableBuilder.kt new file mode 100755 index 0000000..783d6da --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/PathShapeDrawableBuilder.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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 + } +} diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/RippleDrawableBuilder.kt b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/RippleDrawableBuilder.kt new file mode 100755 index 0000000..1add1af --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/RippleDrawableBuilder.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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() { + + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/RotateDrawableBuilder.kt b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/RotateDrawableBuilder.kt new file mode 100755 index 0000000..87c0156 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/RotateDrawableBuilder.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/8. + */ +package com.fankes.coloros.notify.utils.drawable.drawabletoolbox + +import android.graphics.drawable.Drawable +import android.graphics.drawable.RotateDrawable + +class RotateDrawableBuilder : DrawableWrapperBuilder() { + + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/ScaleDrawableBuilder.kt b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/ScaleDrawableBuilder.kt new file mode 100755 index 0000000..100065f --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/ScaleDrawableBuilder.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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() { + + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/StateListDrawableBuilder.kt b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/StateListDrawableBuilder.kt new file mode 100755 index 0000000..5fd2d4a --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/drawable/drawabletoolbox/StateListDrawableBuilder.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/view/MaterialSwitch.kt b/app/src/main/java/com/fankes/coloros/notify/view/MaterialSwitch.kt new file mode 100644 index 0000000..86b6423 --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/view/MaterialSwitch.kt @@ -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. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/1/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(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() + ) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-night/bg_dark_round.xml b/app/src/main/res/drawable-night/bg_dark_round.xml new file mode 100755 index 0000000..71baadb --- /dev/null +++ b/app/src/main/res/drawable-night/bg_dark_round.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-night/bg_permotion_round.xml b/app/src/main/res/drawable-night/bg_permotion_round.xml new file mode 100644 index 0000000..09cbc15 --- /dev/null +++ b/app/src/main/res/drawable-night/bg_permotion_round.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_button_round.xml b/app/src/main/res/drawable/bg_button_round.xml new file mode 100644 index 0000000..c7bf8cb --- /dev/null +++ b/app/src/main/res/drawable/bg_button_round.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_dark_round.xml b/app/src/main/res/drawable/bg_dark_round.xml new file mode 100755 index 0000000..ccc45c0 --- /dev/null +++ b/app/src/main/res/drawable/bg_dark_round.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_green_round.xml b/app/src/main/res/drawable/bg_green_round.xml new file mode 100755 index 0000000..5ac75d3 --- /dev/null +++ b/app/src/main/res/drawable/bg_green_round.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_permotion_round.xml b/app/src/main/res/drawable/bg_permotion_round.xml new file mode 100644 index 0000000..b7d8b2b --- /dev/null +++ b/app/src/main/res/drawable/bg_permotion_round.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_yellow_round.xml b/app/src/main/res/drawable/bg_yellow_round.xml new file mode 100755 index 0000000..215bdc4 --- /dev/null +++ b/app/src/main/res/drawable/bg_yellow_round.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_config.xml b/app/src/main/res/layout/activity_config.xml new file mode 100644 index 0000000..3bbd983 --- /dev/null +++ b/app/src/main/res/layout/activity_config.xml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..e0ec5e0 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,576 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_config.xml b/app/src/main/res/layout/adapter_config.xml new file mode 100644 index 0000000..02353d9 --- /dev/null +++ b/app/src/main/res/layout/adapter_config.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dia_icon_filter.xml b/app/src/main/res/layout/dia_icon_filter.xml new file mode 100644 index 0000000..dfdbfbb --- /dev/null +++ b/app/src/main/res/layout/dia_icon_filter.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cf3963f Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..e058920 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..d0c8569 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..aaaf3ac Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..ff543b3 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..24ac32e Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..49ce621 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..fc2b1a9 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..29c0e48 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/bg_qr_pay.png b/app/src/main/res/mipmap-xxhdpi/bg_qr_pay.png new file mode 100755 index 0000000..2036bf9 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/bg_qr_pay.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_about.png b/app/src/main/res/mipmap-xxhdpi/ic_about.png new file mode 100644 index 0000000..12a0dda Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_about.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_back.png b/app/src/main/res/mipmap-xxhdpi/ic_back.png new file mode 100644 index 0000000..49415ff Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_back.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_filter.png b/app/src/main/res/mipmap-xxhdpi/ic_filter.png new file mode 100644 index 0000000..1f8dd3b Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_filter.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..7b51c5e Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..37ec957 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_icon.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_icon.png new file mode 100644 index 0000000..22df8bf Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_icon.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..fb4e2f8 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_page_bottom.png b/app/src/main/res/mipmap-xxhdpi/ic_page_bottom.png new file mode 100644 index 0000000..ac88748 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_page_bottom.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_page_top.png b/app/src/main/res/mipmap-xxhdpi/ic_page_top.png new file mode 100644 index 0000000..7f4adef Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_page_top.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_restart.png b/app/src/main/res/mipmap-xxhdpi/ic_restart.png new file mode 100644 index 0000000..c98cd02 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_restart.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_success.png b/app/src/main/res/mipmap-xxhdpi/ic_success.png new file mode 100644 index 0000000..a3ced9b Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_success.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_sync.png b/app/src/main/res/mipmap-xxhdpi/ic_sync.png new file mode 100644 index 0000000..9c2a67c Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_sync.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_warn.png b/app/src/main/res/mipmap-xxhdpi/ic_warn.png new file mode 100644 index 0000000..5e87f03 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_warn.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..a259c41 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..2930ed3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..1d2512a Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values-night/color.xml b/app/src/main/res/values-night/color.xml new file mode 100644 index 0000000..809ab40 --- /dev/null +++ b/app/src/main/res/values-night/color.xml @@ -0,0 +1,6 @@ + + + #FF2D2D2D + #FFCFCFCF + #FFD3D3D3 + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..3ed2cbd --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/color.xml b/app/src/main/res/values/color.xml new file mode 100644 index 0000000..24917f9 --- /dev/null +++ b/app/src/main/res/values/color.xml @@ -0,0 +1,6 @@ + + + #FFFFFFFF + #FF777777 + #FF323B42 + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..0df5e08 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,11 @@ + + + #656565 + #656565 + #656565 + #656565 + #656565 + #FF000000 + #FFFFFFFF + #00000000 + \ No newline at end of file diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..b7dc6b0 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #4E8A5A + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..63666a5 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + ColorOS 通知图标增强 + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..ea58ec6 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/test/java/com/fankes/coloros/notify/ExampleUnitTest.kt b/app/src/test/java/com/fankes/coloros/notify/ExampleUnitTest.kt new file mode 100644 index 0000000..9d5cf45 --- /dev/null +++ b/app/src/test/java/com/fankes/coloros/notify/ExampleUnitTest.kt @@ -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) + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..e9f6b06 --- /dev/null +++ b/build.gradle @@ -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 +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..cd0519b --- /dev/null +++ b/gradle.properties @@ -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 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..faf773b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -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" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -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 diff --git a/keystore/public b/keystore/public new file mode 100644 index 0000000..4d53697 Binary files /dev/null and b/keystore/public differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..3fff5f0 --- /dev/null +++ b/settings.gradle @@ -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'