commit 14d47ca6a93270ce88a1d24b7340f7f558c395b4 Author: Fankesyooni Date: Mon Jan 24 11:06:47 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/.name b/.idea/.name new file mode 100644 index 0000000..c7f765d --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +MIUINativeNotifyIcon \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..4bec4ea --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,117 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file 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/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..3615609 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..3ff7262 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + \ 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..a458117 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,62 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +android { + compileSdk 31 + + signingConfigs { + debug { + storeFile file('../keystore/public') + storePassword '123456' + keyAlias 'public' + keyPassword '123456' + v1SigningEnabled true + v2SigningEnabled true + } + } + + defaultConfig { + applicationId "com.fankes.miui.notify" + minSdk 29 + targetSdk 26 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled true + 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' + } +} + +dependencies { + compileOnly 'de.robv.android.xposed:api:82' + // 基础依赖包 + implementation 'com.gyf.immersionbar:immersionbar:3.0.0' + // Fragment 快速实现 + implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' + // Kotlin 扩展 + implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0' + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + //noinspection GradleDynamicVersion + testImplementation 'junit:junit:4.+' + 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/miui/sbnvicon/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/fankes/miui/sbnvicon/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..489e777 --- /dev/null +++ b/app/src/androidTest/java/com/fankes/miui/sbnvicon/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.fankes.miui.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.miui.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..654d45f --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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..843d8be --- /dev/null +++ b/app/src/main/assets/xposed_init @@ -0,0 +1 @@ +com.fankes.miui.notify.hook.HookMain \ 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..946aa02 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/fankes/miui/notify/application/MNvApplication.kt b/app/src/main/java/com/fankes/miui/notify/application/MNvApplication.kt new file mode 100644 index 0000000..b5b6dc4 --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/application/MNvApplication.kt @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/01/24. + */ +@file:Suppress("unused") + +package com.fankes.miui.notify.application + +import android.app.Application +import androidx.appcompat.app.AppCompatDelegate + +class MNvApplication : Application() { + + companion object { + + /** 全局静态实例 */ + private var context: MNvApplication? = null + + /** 调用全局静态实例 */ + val appContext get() = context ?: error("App is death") + } + + override fun onCreate() { + super.onCreate() + /** 设置静态实例 */ + context = this + /** 禁止系统夜间模式对自己造成干扰 - 模块要什么夜间模式?😅 (其实是我懒) */ + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/hook/HookMain.kt b/app/src/main/java/com/fankes/miui/notify/hook/HookMain.kt new file mode 100644 index 0000000..0c44dc0 --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/hook/HookMain.kt @@ -0,0 +1,323 @@ +/** + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/01/24. + */ +@file:Suppress("DEPRECATION", "SameParameterValue") + +package com.fankes.miui.notify.hook + +import android.content.Context +import android.graphics.Color +import android.graphics.Outline +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon +import android.os.Build +import android.service.notification.StatusBarNotification +import android.util.Log +import android.view.View +import android.view.ViewOutlineProvider +import android.widget.ImageView +import androidx.annotation.Keep +import androidx.core.graphics.drawable.toBitmap +import com.fankes.miui.notify.hook.HookMedium.QQ_PACKAGE_NAME +import com.fankes.miui.notify.hook.HookMedium.SELF_PACKAGE_NAME +import com.fankes.miui.notify.hook.HookMedium.SYSTEMUI_PACKAGE_NAME +import com.fankes.miui.notify.utils.XPrefUtils +import com.fankes.miui.notify.utils.dp +import com.fankes.miui.notify.utils.isNotMIUI +import com.fankes.miui.notify.utils.round +import de.robv.android.xposed.* +import de.robv.android.xposed.callbacks.XC_LoadPackage + +@Keep +class HookMain : IXposedHookLoadPackage { + + companion object { + + private const val NotificationUtilClass = + "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationUtil" + + private const val NotificationHeaderViewWrapperInjectorClass = + "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationHeaderViewWrapperInjector" + + private const val ExpandedNotificationClass = + "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.ExpandedNotification" + + private const val SystemUIApplicationClass = "$SYSTEMUI_PACKAGE_NAME.SystemUIApplication" + + private const val ContrastColorUtilClass = "com.android.internal.util.ContrastColorUtil" + } + + /** 仅作用于替换的 Hook 方法体 */ + private val replaceToNull = object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam?): Any? { + return null + } + } + + /** 仅作用于替换的 Hook 方法体 */ + private val replaceToTrue = object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam?): Any { + return true + } + } + + /** 仅作用于替换的 Hook 方法体 */ + private val replaceToFalse = object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam?): Any { + return false + } + } + + /** + * 忽略异常运行 + * @param it 正常回调 + */ + private fun runWithoutError(error: String, it: () -> Unit) { + try { + it() + } catch (e: Error) { + logE("hookFailed: $error", e) + } catch (e: Exception) { + logE("hookFailed: $error", e) + } catch (e: Throwable) { + logE("hookFailed: $error", e) + } + } + + /** + * Print the log + * @param content + */ + private fun logD(content: String) { + XposedBridge.log("[MIUINativeNotifyIcon][D]>$content") + Log.d("MIUINativeNotifyIcon", content) + } + + /** + * Print the log + * @param content + */ + private fun logE(content: String, e: Throwable? = null) { + XposedBridge.log("[MIUINativeNotifyIcon][E]>$content") + XposedBridge.log(e) + Log.e("MIUINativeNotifyIcon", content, e) + } + + /** + * 查找目标类 + * @param name 类名 + * @return [Class] + */ + private fun XC_LoadPackage.LoadPackageParam.findClass(name: String) = + classLoader.loadClass(name) + + /** + * ⚠️ 这个是修复彩色图标的关键核心代码判断 + * 判断是否为灰度图标 - 反射执行系统方法 + * @param context 实例 + * @param icon 要判断的图标 + * @return [Boolean] + */ + private fun XC_LoadPackage.LoadPackageParam.isGrayscaleIcon(context: Context, icon: Drawable) = + findClass(ContrastColorUtilClass).let { + val instance = it.getDeclaredMethod("getInstance", Context::class.java) + .apply { isAccessible = true }.invoke(null, context) + it.getDeclaredMethod("isGrayscaleIcon", Drawable::class.java) + .apply { isAccessible = true }.invoke(instance, icon) as Boolean + } + + /** + * 获取全局上下文 + * @return [Context] + */ + private val XC_LoadPackage.LoadPackageParam.globalContext + get() = findClass(SystemUIApplicationClass) + .getDeclaredMethod("getContext").apply { isAccessible = true } + .invoke(null) as Context + + override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) { + if (lpparam == null) return + when (lpparam.packageName) { + /** Hook 自身 */ + SELF_PACKAGE_NAME -> + XposedHelpers.findAndHookMethod( + "$SELF_PACKAGE_NAME.hook.HookMedium", + lpparam.classLoader, + "isHooked", + replaceToTrue + ) + /** Hook 系统 UI */ + SYSTEMUI_PACKAGE_NAME -> { + /** 若不是 MIUI 系统直接停止 Hook */ + if (isNotMIUI) return + /** 若没开启模块直接停止 Hook */ + if (!XPrefUtils.getBoolean(HookMedium.ENABLE_MODULE, default = true)) return + /** 强制回写系统的状态栏图标样式为原生 */ + runWithoutError("SubstituteSmallIcon") { + XposedHelpers.findAndHookMethod( + NotificationUtilClass, + lpparam.classLoader, + "shouldSubstituteSmallIcon", + lpparam.findClass(ExpandedNotificationClass), + replaceToFalse + ) + } + /** 修复通知图标为彩色 */ + runWithoutError("IgnoreStatusBarIconColor") { + XposedHelpers.findAndHookMethod( + NotificationUtilClass, + lpparam.classLoader, + "ignoreStatusBarIconColor", + lpparam.findClass(ExpandedNotificationClass), + object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam) = + if (XPrefUtils.getBoolean(HookMedium.ENABLE_COLOR_ICON_HOOK, default = true)) + try { + /** 获取发送通知的 APP */ + val packageName = (param.args[0] as StatusBarNotification).opPkg + + /** 获取通知小图标 */ + val iconDrawable = (param.args[0] as StatusBarNotification) + .notification.smallIcon.loadDrawable(lpparam.globalContext) + /** 如果开启了修复聊天 APP 的图标 */ + if (packageName == QQ_PACKAGE_NAME && + XPrefUtils.getBoolean(HookMedium.ENABLE_CHAT_ICON_HOOK, default = true) + ) false + /** 只要不是灰度就返回彩色图标 */ + else !lpparam.isGrayscaleIcon(lpparam.globalContext, iconDrawable) + } catch (e: Exception) { + logE("Failed to hook ignoreStatusBarIconColor", e) + false + } + else false + } + ) + } + /** 强制回写系统的状态栏图标样式为原生 */ + runWithoutError("GetSmallIcon") { + XposedHelpers.findAndHookMethod( + NotificationUtilClass, + lpparam.classLoader, + "getSmallIcon", + lpparam.findClass(ExpandedNotificationClass), + Int::class.java, + object : XC_MethodHook() { + + override fun afterHookedMethod(param: MethodHookParam) { + runWithoutError("GetSmallIconOnSet") { + /** 获取通知小图标 */ + val iconDrawable = (param.result as Icon).loadDrawable(lpparam.globalContext) + /** 判断要设置的图标 */ + when { + /** 如果开启了修复聊天 APP 的图标 */ + (param.args[0] as StatusBarNotification).opPkg == QQ_PACKAGE_NAME && + XPrefUtils.getBoolean(HookMedium.ENABLE_CHAT_ICON_HOOK, default = true) -> + param.result = Icon.createWithBitmap(IconPackParams.qqSmallIcon) + /** 若不是灰度图标自动处理为圆角 */ + !lpparam.isGrayscaleIcon(lpparam.globalContext, iconDrawable) -> + param.result = Icon.createWithBitmap( + iconDrawable.toBitmap().round(15.dp(lpparam.globalContext)) + ) + } + } + } + } + ) + } + /** 干掉下拉通知图标自动设置回 APP 图标的方法 - Android 12 */ + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) + runWithoutError("ResetIconBgAndPaddings") { + XposedHelpers.findAndHookMethod( + NotificationHeaderViewWrapperInjectorClass, + lpparam.classLoader, + "resetIconBgAndPaddings", + ImageView::class.java, + lpparam.findClass(ExpandedNotificationClass), + replaceToNull + ) + } + /** 修复下拉通知图标自动设置回 APP 图标的方法 - Android 12 */ + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) + runWithoutError("AutoSetAppIcon") { + XposedHelpers.findAndHookMethod( + NotificationHeaderViewWrapperInjectorClass, + lpparam.classLoader, + "setAppIcon", + Context::class.java, + ImageView::class.java, + lpparam.findClass(ExpandedNotificationClass), + object : XC_MethodReplacement() { + override fun replaceHookedMethod(param: MethodHookParam): Any? { + runWithoutError("AutoSetAppIconOnSet") { + /** 获取 [Context] */ + val context = param.args[0] as Context + + /** 获取图标框 */ + val iconImageView = param.args[1] as ImageView + + /** 获取通知小图标 */ + val iconDrawable = (param.args[2] as StatusBarNotification) + .notification.smallIcon.loadDrawable(context) + + /** 获取发送通知的 APP */ + val packageName = (param.args[2] as StatusBarNotification).opPkg + /** 如果开启了修复聊天 APP 的图标 */ + if (packageName == QQ_PACKAGE_NAME && + XPrefUtils.getBoolean(HookMedium.ENABLE_CHAT_ICON_HOOK, default = true) + ) + iconImageView.apply { + /** 设置自定义小图标 */ + setImageDrawable(BitmapDrawable(IconPackParams.qqSmallIcon)) + /** 上色 */ + setColorFilter(Color.WHITE) + } + else { + /** 重新设置图标 - 防止系统更改它 */ + iconImageView.setImageDrawable(iconDrawable) + /** 判断如果是灰度图标就给他设置一个白色颜色遮罩 */ + if (lpparam.isGrayscaleIcon(context, iconDrawable)) + iconImageView.setColorFilter(Color.WHITE) + else + iconImageView.apply { + clipToOutline = true + /** 设置一个圆角轮廓裁切 */ + outlineProvider = object : ViewOutlineProvider() { + override fun getOutline(view: View, out: Outline) { + out.setRoundRect(0, 0, view.width, view.height, 5.dp(context)) + } + } + /** 清除原生的背景边距设置 */ + setPadding(0, 0, 0, 0) + /** 清除原生的主题色背景圆圈颜色 */ + setBackgroundDrawable(null) + } + } + } + return null + } + } + ) + } + logD("hook Completed!") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/hook/HookMedium.kt b/app/src/main/java/com/fankes/miui/notify/hook/HookMedium.kt new file mode 100644 index 0000000..cba9bd1 --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/hook/HookMedium.kt @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/24. + */ +package com.fankes.miui.notify.hook + +import android.util.Log +import androidx.annotation.Keep + +@Keep +object HookMedium { + + const val ENABLE_MODULE = "_enable_module" + const val ENABLE_HIDE_ICON = "_hide_icon" + const val ENABLE_COLOR_ICON_HOOK = "_color_icon_hook" + const val ENABLE_CHAT_ICON_HOOK = "_chat_icon_hook" + + const val SELF_PACKAGE_NAME = "com.fankes.miui.notify" + const val SYSTEMUI_PACKAGE_NAME = "com.android.systemui" + const val QQ_PACKAGE_NAME = "com.tencent.mobileqq" + + /** + * 判断模块是否激活 + * 在 [HookMain] 中 Hook 掉此方法 + * @return [Boolean] 激活状态 + */ + fun isHooked(): Boolean { + Log.d("MIUINativeNotifyIcon", "isHooked: true") + return false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/hook/IconPackParams.kt b/app/src/main/java/com/fankes/miui/notify/hook/IconPackParams.kt new file mode 100644 index 0000000..f8d8827 --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/hook/IconPackParams.kt @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/24. + */ +package com.fankes.miui.notify.hook + +import android.graphics.Bitmap +import com.fankes.miui.notify.utils.bitmap +import com.fankes.miui.notify.utils.unbase64 + +object IconPackParams { + + /** + * QQ 通知栏小图标 + * @return [Bitmap] + */ + val qqSmallIcon by lazy { + ("iVBORw0KGgoAAAANSUhEUgAAAEwAAABMCAYAAADHl1ErAAAAAXNSR0IArs4c6QAAAARzQklUCAgI\n" + + "CHwIZIgAAATZSURBVHic7ZxNjF5TGMd/T6uliBk1pSgtxQwlqo2gKghiU4ZaCOIjRGwIiY8QCRs7\n" + + "S4suRJBYkUgXMiIipBYTiZmuKtRHW0biK8xrNE2l9be4d5LpeN/33nue877n3tHf5p259zzPfc4/\n" + + "z7kf5wuOUglLeXFJw8BmYD0wApwBrACW5UUOANPAPuAb4HNg0sx29j/aREgakvSUpHGFMyHpBUln\n" + + "p65Pz5C0VNJLkmYcQs3nkKRtkoZS1y8qkkYl/RBRqPlMS3oodT2jIOnlHgo1nzdT19eFpHf6KNYs\n" + + "O1LXO4hEYs0ynrr+lZD0SkKxZtmeWodSSLoztVJzeC52/aK+uEoaBP6I6TMC55nZt7GcLYrlKOeN\n" + + "yP5i8HZMZ9EEk7QRGI3lLyIbJG2N5Sxak1T2ZLoylr/ITJnZWTEcRckwSeuor1gAqyTdHMNRrCb5\n" + + "TCQ/veT5GE7cTVLSUuBP4Fh/OD1npZn97HEQI8O20AyxAO71OoghWB2fjJ1w38diNMk9wBqvnz5x\n" + + "EDjFzPaHOnBlmLIu5qaIBdmtY7PHgbdJXu60T8ElHmOvYCNO+xQkFWzYaZ+CCzzGXsHOddqnYK2k\n" + + "4HoHG+YXPTXUPiHLycY+g/Bk2EnAoMM+FYtxxO0V7ESHfUqODzX0CLbEYZua40INY/e4LniOClYR\n" + + "j2CtaFH0n99DDT2CneawTU3w969HsEcdtql5PNQwqHunYb2snQjqfQ3NsNtptlgAD4YYhWZYnYfU\n" + + "yhI09FY5wyStpfliQTb0dm1Vo5Am+USATV15sqpBpSaZ91C0aO43ZDtWmNlvZQtXzbC7WVhiATxc\n" + + "pXDVDNsBXFMpnPqz18zOKVu4tGDK5sTvCwqp/mwys1LTPKs0yXsCg2kC95UtWCXDdgPnB4VTf/4C\n" + + "Bszsn6KCpTIsn860UMWC7EF2U5mCZZvkXeGxNIb7yxQq1SQXeHOcpWVmhYMjhRmWPx0XulgAA2U+\n" + + "lco0ySZNZ/JyS1GBMoLdECGQpnB9UYGu97D82/EnHCPFDaTrt2VRhl3G/0ssgKu7nSwSbH3EQJrC\n" + + "pd1OFgl2YYfjh4GpoHDqwRTZ9M12dJ0/ViTYRR2OPwK8WmBbZ94j66pqR6ckAYoFW93m2F4ze61M\n" + + "VDVmwMzeBT5tc25VN8Miwdrd8Lfkvz+WCKyufJf/tlu0tbibYZFgy+b9/7SZ7cr//qREYHXlQ4D8\n" + + "9eGOaF515B4T/1kSLGl3j1fU9oIZZQPRc+sxd8n1TDdNijLsQP47aWa3tTn/eoF9HXnLzP6ee8DM\n" + + "HgPez//tOmZR9Ka/DVgH3Dj/Ivn5IeDXSuGmZ8TMvmp3QtIYcMjMbu3Z1VWPHQTKMuatb4y1RieQ\n" + + "jVV2fbrUhNVm9r3HgXsGYr7Q6QGvnz7wolcsiLvmezv17TubNLONMRzFFGwR8CX1651tAWvMbDqG\n" + + "s2iTgvMhqk3U66P8IHBFLLGgR1v6SZoANvTCdwW+Bi5u9zrk4ZiYziRdBVxHPWYnLgGelfSZmX2Q\n" + + "OpgjkLRV0sfpXq8KmVAddrCTNChpLLEYVRhXNoMymOB7mLIX1p3U76lYRAsYDt2/wvOUXEnzxAIY\n" + + "wLH02nPT30O2ccYoWVf2mYR/Hh0m23dsP9myltlXk9PJlhkuB052+v8F+AL4CGjWln9N5l/CCEZS\n" + + "HWRiiAAAAABJRU5ErkJggg==").unbase64.bitmap + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/ui/MainActivity.kt b/app/src/main/java/com/fankes/miui/notify/ui/MainActivity.kt new file mode 100644 index 0000000..432310d --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/ui/MainActivity.kt @@ -0,0 +1,257 @@ +/** + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/01/24. + */ +@file:Suppress("DEPRECATION", "SetWorldReadable", "SetTextI18n") + +package com.fankes.miui.notify.ui + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Bundle +import android.os.Handler +import android.view.View +import android.widget.LinearLayout +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SwitchCompat +import androidx.constraintlayout.utils.widget.ImageFilterView +import com.fankes.miui.notify.BuildConfig +import com.fankes.miui.notify.R +import com.fankes.miui.notify.hook.HookMedium +import com.fankes.miui.notify.utils.* +import com.gyf.immersionbar.ImmersionBar +import java.io.File + +class MainActivity : AppCompatActivity() { + + companion object { + + /** 模块版本 */ + private const val moduleVersion = BuildConfig.VERSION_NAME + + /** MIUI 版本 */ + private val miuiVersion by lazy { + if (isMIUI) + findPropString(key = "ro.miui.ui.version.code", default = "无法获取") + + " " + findPropString(key = "ro.system.build.version.incremental") + else "不是 MIUI 系统" + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + /** 隐藏系统的标题栏 */ + supportActionBar?.hide() + /** 初始化沉浸状态栏 */ + ImmersionBar.with(this) + .statusBarColor(R.color.white) + .autoDarkModeEnable(false) + .statusBarDarkFont(true) + .navigationBarColor(R.color.white) + .navigationBarDarkIcon(true) + .fitsSystemWindows(true) + .init() + /** 设置文本 */ + findViewById(R.id.main_text_version).text = "当前版本:$moduleVersion" + findViewById(R.id.main_text_miui_version).text = "MIUI 版本:$miuiVersion" + /** 判断是否为 MIUI 系统 */ + if (isNotMIUI) { + showDialog { + title = "不是 MIUI 系统" + msg = "此模块专为 MIUI 系统打造,当前无法识别你的系统为 MIUI,所以模块无法工作。\n" + + "如有问题请联系 酷安 @星夜不荟" + confirmButton(text = "退出") { finish() } + noCancelable() + } + return + } + /** 判断 Hook 状态 */ + if (isHooked()) { + findViewById(R.id.main_lin_status).setBackgroundResource(R.drawable.green_round) + findViewById(R.id.main_img_status).setImageResource(R.mipmap.succcess) + 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 hideIconInLauncherSwitch = findViewById(R.id.hide_icon_in_launcher_switch) + val colorIconHookSwitch = findViewById(R.id.color_icon_fix_switch) + val chatIconHookSwitch = findViewById(R.id.chat_icon_fix_switch) + /** 获取 Sp 存储的信息 */ + moduleEnableSwitch.isChecked = getBoolean(HookMedium.ENABLE_MODULE, default = true) + hideIconInLauncherSwitch.isChecked = getBoolean(HookMedium.ENABLE_HIDE_ICON) + colorIconHookSwitch.isChecked = getBoolean(HookMedium.ENABLE_COLOR_ICON_HOOK, default = true) + chatIconHookSwitch.isChecked = getBoolean(HookMedium.ENABLE_CHAT_ICON_HOOK, default = true) + moduleEnableSwitch.setOnCheckedChangeListener { btn, b -> + if (!btn.isPressed) return@setOnCheckedChangeListener + putBoolean(HookMedium.ENABLE_MODULE, b) + } + hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b -> + if (!btn.isPressed) return@setOnCheckedChangeListener + putBoolean(HookMedium.ENABLE_HIDE_ICON, b) + packageManager.setComponentEnabledSetting( + ComponentName(this@MainActivity, "com.fankes.miui.notify.Home"), + if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP + ) + } + colorIconHookSwitch.setOnCheckedChangeListener { btn, b -> + if (!btn.isPressed) return@setOnCheckedChangeListener + putBoolean(HookMedium.ENABLE_COLOR_ICON_HOOK, b) + } + chatIconHookSwitch.setOnCheckedChangeListener { btn, b -> + if (!btn.isPressed) return@setOnCheckedChangeListener + putBoolean(HookMedium.ENABLE_CHAT_ICON_HOOK, b) + } + /** 重启按钮点击事件 */ + findViewById(R.id.title_restart_icon).setOnClickListener { + showDialog { + title = "重启系统界面" + msg = "你确定要立即重启系统界面吗?" + confirmButton { restartSystemUI() } + cancelButton() + } + } + /** 恰饭! */ + findViewById(R.id.link_with_follow_me).setOnClickListener { + try { + 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 + }) + } catch (e: Exception) { + Toast.makeText(this, "你可能没有安装酷安", Toast.LENGTH_SHORT).show() + } + } + /** 项目地址点击事件 */ + findViewById(R.id.link_with_project_address).setOnClickListener { + try { + startActivity(Intent().apply { + action = "android.intent.action.VIEW" + data = Uri.parse("https://github.com/fankes/MIUINativeNotifyIcon") + /** 防止顶栈一样重叠在自己的 APP 中 */ + flags = Intent.FLAG_ACTIVITY_NEW_TASK + }) + } catch (e: Exception) { + Toast.makeText(this, "无法启动系统默认浏览器", Toast.LENGTH_SHORT).show() + } + } + } + + /** + * 判断模块是否激活 + * @return [Boolean] 激活状态 + */ + private fun isHooked() = HookMedium.isHooked() + + /** 重启系统界面 */ + private fun restartSystemUI() = + execShellCmd(cmd = "pgrep systemui").also { pid -> + if (pid.isNotBlank()) + execShellCmd(cmd = "kill -9 $pid") + else Toast.makeText(this, "ROOT 权限获取失败", Toast.LENGTH_SHORT).show() + } + + override fun onResume() { + super.onResume() + setWorldReadable() + } + + override fun onRestart() { + super.onRestart() + setWorldReadable() + } + + override fun onPause() { + super.onPause() + setWorldReadable() + } + + /** + * 获取保存的值 + * @param key 名称 + * @param default 默认值 + * @return [Boolean] 保存的值 + */ + private fun getBoolean(key: String, default: Boolean = false) = + getSharedPreferences( + packageName + "_preferences", + Context.MODE_PRIVATE + ).getBoolean(key, default) + + /** + * 保存值 + * @param key 名称 + * @param bool 值 + */ + private fun putBoolean(key: String, bool: Boolean) { + getSharedPreferences( + packageName + "_preferences", + Context.MODE_PRIVATE + ).edit().putBoolean(key, bool).apply() + setWorldReadable() + /** 延迟继续设置强制允许 SP 可读可写 */ + Handler().postDelayed({ setWorldReadable() }, 500) + Handler().postDelayed({ setWorldReadable() }, 1000) + Handler().postDelayed({ setWorldReadable() }, 1500) + } + + /** + * 强制设置 Sp 存储为全局可读可写 + * 以供模块使用 + */ + private fun setWorldReadable() { + try { + if (FileUtils.getDefaultPrefFile(this).exists()) { + for (file in arrayOf( + FileUtils.getDataDir(this), + FileUtils.getPrefDir(this), + FileUtils.getDefaultPrefFile(this) + )) { + file.setReadable(true, false) + file.setExecutable(true, false) + } + } + } catch (_: Exception) { + Toast.makeText(this, "无法写入模块设置,请检查权限\n如果此提示一直显示,请不要双开模块", Toast.LENGTH_SHORT).show() + } + } + + override fun onBackPressed() { + setWorldReadable() + super.onBackPressed() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/utils/DialogBuilder.kt b/app/src/main/java/com/fankes/miui/notify/utils/DialogBuilder.kt new file mode 100644 index 0000000..7984dda --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/DialogBuilder.kt @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/7. + */ +@file:Suppress("unused") + +package com.fankes.miui.notify.utils + +import android.app.AlertDialog +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.GradientDrawable + + +/** + * 构造对话框 + * @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 // 实例对象 + + 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 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 { + window?.setBackgroundDrawable(GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(Color.WHITE, Color.WHITE) + ).apply { + shape = GradientDrawable.RECTANGLE + gradientType = GradientDrawable.LINEAR_GRADIENT + cornerRadius = 15.dp(this@DialogBuilder.context) + }) + }?.show() +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/utils/FileUtils.java b/app/src/main/java/com/fankes/miui/notify/utils/FileUtils.java new file mode 100755 index 0000000..69fc22e --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/FileUtils.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by zpp0196 on 2019/2/9. + */ +package com.fankes.miui.notify.utils; + +import android.content.Context; + +import com.fankes.miui.notify.BuildConfig; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +@SuppressWarnings("ALL") +public class FileUtils { + + private static final String FILE_PREF_NAME = BuildConfig.APPLICATION_ID + "_preferences.xml"; + + public static boolean copyFile(File srcFile, File targetFile) { + FileInputStream ins = null; + FileOutputStream out = null; + try { + if (targetFile.exists()) { + targetFile.delete(); + } + File targetParent = targetFile.getParentFile(); + if (!targetParent.exists()) { + targetParent.mkdirs(); + } + targetFile.createNewFile(); + ins = new FileInputStream(srcFile); + out = new FileOutputStream(targetFile); + byte[] b = new byte[1024]; + int n; + while ((n = ins.read(b)) != -1) { + out.write(b, 0, n); + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + try { + if (ins != null) { + ins.close(); + } + if (out != null) { + out.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return true; + } + + public static File getDataDir(Context context) { + return new File(context.getApplicationInfo().dataDir); + } + + public static File getPrefDir(Context context) { + return new File(getDataDir(context), "shared_prefs"); + } + + public static File getDefaultPrefFile(Context context) { + return new File(getPrefDir(context), FILE_PREF_NAME); + } +} diff --git a/app/src/main/java/com/fankes/miui/notify/utils/Utils.kt b/app/src/main/java/com/fankes/miui/notify/utils/Utils.kt new file mode 100644 index 0000000..1b6ad67 --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/Utils.kt @@ -0,0 +1,179 @@ +/** + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/7. + */ +@file:Suppress("DEPRECATION", "PrivateApi", "unused") + +package com.fankes.miui.notify.utils + +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.graphics.* +import android.graphics.Bitmap.createBitmap +import android.util.Base64 +import com.fankes.miui.notify.application.MNvApplication.Companion.appContext +import java.io.DataInputStream +import java.io.DataOutputStream + + +/** + * 当前设备是否是 MIUI 定制 Android 系统 + * @return [Boolean] 是否符合条件 + */ +val isMIUI by lazy { + try { + Class.forName("android.miui.R") + true + } catch (_: Exception) { + false + } +} + +/** + * 当前设备是否不是 MIUI 定制 Android 系统 + * @return [Boolean] 是否符合条件 + */ +inline val isNotMIUI get() = !isMIUI + +/** + * 得到安装包信息 + * @return [PackageInfo] + */ +val Context.packageInfo get() = packageManager?.getPackageInfo(packageName, 0) ?: PackageInfo() + +/** + * 判断应用是否安装 + * @return [Boolean] + */ +val String.isInstall + get() = + try { + appContext.packageManager.getPackageInfo( + this, + PackageManager.GET_UNINSTALLED_PACKAGES + ) + true + } catch (e: Exception) { + false + } + +/** + * 得到版本信息 + * @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 [Float] + */ +fun Number.dp(context: Context) = toFloat() * context.resources.displayMetrics.density + +/** + * 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) + +/** + * 圆角图片 + * @param radius 圆角度 + * @return [Bitmap] 圆角后的位图 - 失败会返回处理之前的位图 + */ +fun Bitmap.round(radius: Float): Bitmap = + createBitmap(width, height, Bitmap.Config.ARGB_8888).also { out -> + Canvas(out).also { canvas -> + Paint().also { paint -> + paint.isAntiAlias = true + canvas.drawARGB(0, 0, 0, 0) + paint.color = Color.WHITE + canvas.drawRoundRect(RectF(Rect(0, 0, width, height)), radius, radius, paint) + paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) + canvas.drawBitmap(this, Rect(0, 0, width, height), Rect(0, 0, width, height), paint) + } + } + } + +/** + * 获取系统 Prop 值 + * @param key Key + * @param default 默认值 + * @return [String] + */ +fun findPropString(key: String, default: String = "") = + try { + (Class.forName("android.os.SystemProperties").getDeclaredMethod( + "get", + String::class.java, + String::class.java + ).apply { isAccessible = true }.invoke(null, key, default)) as? String? ?: default + } catch (_: Exception) { + default + } + +/** + * 执行命令 - su + * @param cmd 命令 + * @return [String] 执行结果 + */ +fun execShellCmd(cmd: String): String { + var result = "" + var dos: DataOutputStream? = null + var dis: DataInputStream? = null + try { + val p = Runtime.getRuntime().exec("su") + dos = DataOutputStream(p.outputStream) + dis = DataInputStream(p.inputStream) + dos.writeBytes("$cmd\n") + dos.flush() + dos.writeBytes("exit\n") + dos.flush() + var line: String + while (dis.readLine().also { line = it } != null) result += line + p.waitFor() + } catch (_: Exception) { + } finally { + try { + dos?.close() + dis?.close() + } catch (_: Exception) { + } + } + return result.trim() +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/utils/XPrefUtils.kt b/app/src/main/java/com/fankes/miui/notify/utils/XPrefUtils.kt new file mode 100644 index 0000000..2ca1e6b --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/XPrefUtils.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by zpp0196 on 2018/4/11. + */ +package com.fankes.miui.notify.utils + +import com.fankes.miui.notify.BuildConfig +import de.robv.android.xposed.XSharedPreferences + +object XPrefUtils { + + fun getBoolean(key: String, default: Boolean = false) = pref.getBoolean(key, default) + + private val pref: XSharedPreferences + get() { + val preferences = XSharedPreferences(BuildConfig.APPLICATION_ID) + preferences.makeWorldReadable() + preferences.reload() + return preferences + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/Compatible.kt b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/Compatible.kt new file mode 100755 index 0000000..203a46e --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/Compatible.kt @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ +@file:Suppress("SameParameterValue") + +package com.fankes.miui.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/miui/notify/utils/drawable/drawabletoolbox/Constants.kt b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/Constants.kt new file mode 100755 index 0000000..061c20c --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/Constants.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ + +package com.fankes.miui.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/miui/notify/utils/drawable/drawabletoolbox/DrawableBuilder.kt b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/DrawableBuilder.kt new file mode 100755 index 0000000..d12665e --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/DrawableBuilder.kt @@ -0,0 +1,488 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + +package com.fankes.miui.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/miui/notify/utils/drawable/drawabletoolbox/DrawableProperties.kt b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/DrawableProperties.kt new file mode 100755 index 0000000..0385887 --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/DrawableProperties.kt @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ +@file:Suppress("SetterBackingFieldAssignment", "unused") + +package com.fankes.miui.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/miui/notify/utils/drawable/drawabletoolbox/DrawableWrapperBuilder.kt b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/DrawableWrapperBuilder.kt new file mode 100755 index 0000000..b6ca1f1 --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/DrawableWrapperBuilder.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ +package com.fankes.miui.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/miui/notify/utils/drawable/drawabletoolbox/FlipDrawable.kt b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/FlipDrawable.kt new file mode 100755 index 0000000..c5a2825 --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/FlipDrawable.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ +@file:Suppress("DEPRECATION", "CanvasSize") + +package com.fankes.miui.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/miui/notify/utils/drawable/drawabletoolbox/FlipDrawableBuilder.kt b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/FlipDrawableBuilder.kt new file mode 100755 index 0000000..6bd1200 --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/FlipDrawableBuilder.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ +package com.fankes.miui.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/miui/notify/utils/drawable/drawabletoolbox/LayerDrawableBuilder.kt b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/LayerDrawableBuilder.kt new file mode 100755 index 0000000..672184c --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/LayerDrawableBuilder.kt @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ +package com.fankes.miui.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 +import java.util.* + +class LayerDrawableBuilder { + + companion object { + const val DIMEN_UNDEFINED = Int.MIN_VALUE + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private var paddingMode = LayerDrawable.PADDING_MODE_NEST + private var paddingLeft = 0 + private var paddingTop = 0 + private var paddingRight = 0 + private var paddingBottom = 0 + private var paddingStart = 0 + private var paddingEnd = 0 + private val layers = ArrayList() + + @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/miui/notify/utils/drawable/drawabletoolbox/PathShapeDrawableBuilder.kt b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/PathShapeDrawableBuilder.kt new file mode 100755 index 0000000..433b084 --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/PathShapeDrawableBuilder.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ +package com.fankes.miui.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/miui/notify/utils/drawable/drawabletoolbox/RippleDrawableBuilder.kt b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/RippleDrawableBuilder.kt new file mode 100755 index 0000000..6606d87 --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/RippleDrawableBuilder.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ +package com.fankes.miui.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/miui/notify/utils/drawable/drawabletoolbox/RotateDrawableBuilder.kt b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/RotateDrawableBuilder.kt new file mode 100755 index 0000000..8b563cc --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/RotateDrawableBuilder.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ +package com.fankes.miui.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/miui/notify/utils/drawable/drawabletoolbox/ScaleDrawableBuilder.kt b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/ScaleDrawableBuilder.kt new file mode 100755 index 0000000..14e0ddc --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/ScaleDrawableBuilder.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ +package com.fankes.miui.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/miui/notify/utils/drawable/drawabletoolbox/StateListDrawableBuilder.kt b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/StateListDrawableBuilder.kt new file mode 100755 index 0000000..278c4f0 --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/utils/drawable/drawabletoolbox/StateListDrawableBuilder.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ +package com.fankes.miui.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/miui/notify/view/MaterialSwitch.kt b/app/src/main/java/com/fankes/miui/notify/view/MaterialSwitch.kt new file mode 100644 index 0000000..ce22644 --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/view/MaterialSwitch.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) + * + * This file is part of MIUINativeNotifyIcon. + * + * MIUINativeNotifyIcon is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MIUINativeNotifyIcon 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file is Created by fankes on 2022/1/8. + */ + +@file:Suppress("SameParameterValue") + +package com.fankes.miui.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.miui.notify.utils.dp +import com.fankes.miui.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/dark_round.xml b/app/src/main/res/drawable/dark_round.xml new file mode 100755 index 0000000..ccc45c0 --- /dev/null +++ b/app/src/main/res/drawable/dark_round.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/green_round.xml b/app/src/main/res/drawable/green_round.xml new file mode 100755 index 0000000..5ac75d3 --- /dev/null +++ b/app/src/main/res/drawable/green_round.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/permotion_round.xml b/app/src/main/res/drawable/permotion_round.xml new file mode 100644 index 0000000..b7d8b2b --- /dev/null +++ b/app/src/main/res/drawable/permotion_round.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/red_round.xml b/app/src/main/res/drawable/red_round.xml new file mode 100755 index 0000000..2f8a503 --- /dev/null +++ b/app/src/main/res/drawable/red_round.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/white_round.xml b/app/src/main/res/drawable/white_round.xml new file mode 100755 index 0000000..8b43d04 --- /dev/null +++ b/app/src/main/res/drawable/white_round.xml @@ -0,0 +1,6 @@ + + + + + \ 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..1875ff8 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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..7117e7c 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..135e501 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..49d72a6 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..bd49b5d 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..a62181f 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..63d4fac 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..84ba4dc 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..6e576c0 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..1a76053 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/about.png b/app/src/main/res/mipmap-xxhdpi/about.png new file mode 100644 index 0000000..12a0dda Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/about.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..8c5551f 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..a9dd417 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_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..7b4943a 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/icon_launcher.png b/app/src/main/res/mipmap-xxhdpi/icon_launcher.png new file mode 100644 index 0000000..22df8bf Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/qr_pay.png b/app/src/main/res/mipmap-xxhdpi/qr_pay.png new file mode 100755 index 0000000..2036bf9 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/qr_pay.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/restart.png b/app/src/main/res/mipmap-xxhdpi/restart.png new file mode 100644 index 0000000..8b556fe Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/restart.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/succcess.png b/app/src/main/res/mipmap-xxhdpi/succcess.png new file mode 100644 index 0000000..a3ced9b Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/succcess.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/warn.png b/app/src/main/res/mipmap-xxhdpi/warn.png new file mode 100644 index 0000000..5e87f03 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/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..746ffe1 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..bab5471 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..5f516e2 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/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..347904a --- /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/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..bdc04db --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #656565 + #656565 + #656565 + #656565 + #656565 + #FF000000 + #FFFFFFFF + \ 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..ddc0b5f --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #E06818 + \ 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..ae9b782 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + MIUI 原生通知图标 + \ 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..0f08029 --- /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/miui/sbnvicon/ExampleUnitTest.kt b/app/src/test/java/com/fankes/miui/sbnvicon/ExampleUnitTest.kt new file mode 100644 index 0000000..44c2844 --- /dev/null +++ b/app/src/test/java/com/fankes/miui/sbnvicon/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.fankes.miui.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..185268c --- /dev/null +++ b/build.gradle @@ -0,0 +1,32 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + ext.kotlin_version = "1.6.10" + repositories { + google() + maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } + maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' } + maven { url "https://www.jitpack.io" } + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:7.0.4" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } + maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' } + maven { url "https://www.jitpack.io" } + mavenCentral() + } +} + +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..98bed16 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +# 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 +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official \ 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..dbe6c02 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jan 24 04:31:03 CST 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.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..5ebc41d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = "MIUINativeNotifyIcon" +include ':app'