13 Commits
1.87 ... 1.95

17 changed files with 449 additions and 96 deletions

6
.idea/misc.xml generated
View File

@@ -10,11 +10,15 @@
<entry key="app/src/main/res/drawable/bg_yellow_round.xml" value="0.255" /> <entry key="app/src/main/res/drawable/bg_yellow_round.xml" value="0.255" />
<entry key="app/src/main/res/drawable/permotion_round.xml" value="0.256" /> <entry key="app/src/main/res/drawable/permotion_round.xml" value="0.256" />
<entry key="app/src/main/res/drawable/white_round.xml" value="0.256" /> <entry key="app/src/main/res/drawable/white_round.xml" value="0.256" />
<entry key="app/src/main/res/layout-w1240dp/dia_source_from.xml" value="0.36484375" />
<entry key="app/src/main/res/layout-w1240dp/dia_source_from_string.xml" value="0.36484375" />
<entry key="app/src/main/res/layout/activity_config.xml" value="0.42168674698795183" /> <entry key="app/src/main/res/layout/activity_config.xml" value="0.42168674698795183" />
<entry key="app/src/main/res/layout/activity_login.xml" value="0.4375" /> <entry key="app/src/main/res/layout/activity_login.xml" value="0.4375" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.36160137752905724" /> <entry key="app/src/main/res/layout/activity_main.xml" value="0.36160137752905724" />
<entry key="app/src/main/res/layout/adapter_config.xml" value="0.4375" /> <entry key="app/src/main/res/layout/adapter_config.xml" value="0.375" />
<entry key="app/src/main/res/layout/dia_icon_filter.xml" value="0.4307692307692308" />
<entry key="app/src/main/res/layout/dia_icon_search.xml" value="0.4307692307692308" /> <entry key="app/src/main/res/layout/dia_icon_search.xml" value="0.4307692307692308" />
<entry key="app/src/main/res/layout/dia_source_from_string.xml" value="0.4307692307692308" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.44871794871794873" /> <entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.44871794871794873" />
</map> </map>
</option> </option>

View File

@@ -2,7 +2,7 @@
![Eclipse Marketplace](https://img.shields.io/badge/build-passing-brightgreen) ![Eclipse Marketplace](https://img.shields.io/badge/build-passing-brightgreen)
![Eclipse Marketplace](https://img.shields.io/badge/license-AGPL3.0-blue) ![Eclipse Marketplace](https://img.shields.io/badge/license-AGPL3.0-blue)
![Eclipse Marketplace](https://img.shields.io/badge/version-v1.87-green) ![Eclipse Marketplace](https://img.shields.io/badge/version-v1.95-green)
<br/><br/> <br/><br/>
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/app/src/main/ic_launcher-playstore.png" width = "100" height = "100"/> <img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/app/src/main/ic_launcher-playstore.png" width = "100" height = "100"/>
<br/> <br/>
@@ -12,7 +12,7 @@ Fix the native notification bar icon function abandoned by the MIUI development
# 开始使用 # 开始使用
点击下载最新版本 点击下载最新版本
<a href='https://github.com/fankes/MIUINativeNotifyIcon/releases'>![Eclipse Marketplace](https://img.shields.io/badge/download-v1.87-green)</a> <a href='https://github.com/fankes/MIUINativeNotifyIcon/releases'>![Eclipse Marketplace](https://img.shields.io/badge/download-v1.95-green)</a>
<br/><br/> <br/><br/>
⚠️ 适配说明<br/> ⚠️ 适配说明<br/>
@@ -33,7 +33,7 @@ Fix the native notification bar icon function abandoned by the MIUI development
此项目是 `AndroidNotifyIconAdapt` 项目的一部分,详情请参考下方。<br/> 此项目是 `AndroidNotifyIconAdapt` 项目的一部分,详情请参考下方。<br/>
- [Android 通知图标规范适配](https://github.com/fankes/AndroidNotifyIconAdapt) - [Android 通知图标规范适配计划](https://github.com/fankes/AndroidNotifyIconAdapt)
# 历史背景 # 历史背景

View File

@@ -63,13 +63,11 @@ tasks.whenTaskAdded {
} }
dependencies { dependencies {
compileOnly 'de.robv.android.xposed:api:82'
implementation 'com.highcapable.yukihookapi:api:1.0.4'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.4'
implementation "com.github.topjohnwu.libsu:core:3.1.2" implementation "com.github.topjohnwu.libsu:core:3.1.2"
implementation 'androidx.annotation:annotation:1.3.0' implementation 'androidx.annotation:annotation:1.3.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
compileOnly 'de.robv.android.xposed:api:82'
implementation 'com.highcapable.yukihookapi:api:1.0.3'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.3'
implementation 'com.geyifeng.immersionbar:immersionbar:3.2.0' implementation 'com.geyifeng.immersionbar:immersionbar:3.2.0'
implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.0' implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.3' implementation 'com.squareup.okhttp3:okhttp:4.9.3'

View File

@@ -47,4 +47,13 @@ data class IconDataBean(
) : Serializable { ) : Serializable {
fun toEnabledName() = ("$appName$packageName").base64 + "_enable" fun toEnabledName() = ("$appName$packageName").base64 + "_enable"
fun toEnabledAllName() = ("$appName$packageName").base64 + "_enable_all" fun toEnabledAllName() = ("$appName$packageName").base64 + "_enable_all"
override fun toString() = "{\n" +
" \"appName\": \"$appName\",\n" +
" \"packageName\": \"$packageName\",\n" +
" \"iconBitmap\": \"${iconBitmap.base64}\",\n" +
" \"iconColor\": \"#${Integer.toHexString(iconColor)}\",\n" +
" \"contributorName\": \"$contributorName\",\n" +
" \"isEnabled\": $isEnabled,\n" +
" \"isEnabledAll\": $isEnabledAll\n" +
" }"
} }

View File

@@ -34,5 +34,12 @@ object HookConst {
const val ENABLE_NOTIFY_ICON_FIX = "_notify_icon_fix" const val ENABLE_NOTIFY_ICON_FIX = "_notify_icon_fix"
const val NOTIFY_ICON_DATAS = "_notify_icon_datas" const val NOTIFY_ICON_DATAS = "_notify_icon_datas"
const val SOURCE_SYNC_WAY = "_source_sync_way"
const val SOURCE_SYNC_WAY_CUSTOM_URL = "_source_sync_way_custom_url"
const val TYPE_SOURCE_SYNC_WAY_1 = 1000
const val TYPE_SOURCE_SYNC_WAY_2 = 2000
const val TYPE_SOURCE_SYNC_WAY_3 = 3000
const val SYSTEMUI_PACKAGE_NAME = "com.android.systemui" const val SYSTEMUI_PACKAGE_NAME = "com.android.systemui"
} }

View File

@@ -75,6 +75,9 @@ class HookEntry : YukiHookXposedInitProxy {
/** 原生存在的类 */ /** 原生存在的类 */
private const val ContrastColorUtilClass = "com.android.internal.util.ContrastColorUtil" private const val ContrastColorUtilClass = "com.android.internal.util.ContrastColorUtil"
/** 原生存在的类 */
private const val StatusBarIconViewClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.StatusBarIconView"
/** 根据多个版本存在不同的包名相同的类 */ /** 根据多个版本存在不同的包名相同的类 */
private val ExpandableNotificationRowClass = VariousClass( private val ExpandableNotificationRowClass = VariousClass(
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.ExpandableNotificationRow", "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.ExpandableNotificationRow",
@@ -130,17 +133,6 @@ class HookEntry : YukiHookXposedInitProxy {
} }
} else BitmapCompatTool.isGrayscaleDrawable(drawable) } else BitmapCompatTool.isGrayscaleDrawable(drawable)
/**
* 是否为新版本 MIUI 方案
*
* 拥有状态栏图标颜色检查功能
* @return [Boolean]
*/
private val PackageParam.hasIgnoreStatusBarIconColor
get() = safeOfFalse {
NotificationUtilClass.clazz.hasMethod(name = "ignoreStatusBarIconColor", ExpandedNotificationClass.clazz)
}
/** /**
* 是否为旧版本 MIUI 方案 * 是否为旧版本 MIUI 方案
* *
@@ -432,14 +424,14 @@ class HookEntry : YukiHookXposedInitProxy {
} }
/** /**
* Hook 通知栏小图标颜色 * 判断状态栏小图标颜色以及反射的核心方法
* *
* 区分系统版本 - 由于每个系统版本的方法不一样这里单独拿出来进行 Hook * 区分系统版本 - 由于每个系统版本的方法不一样这里单独拿出来进行 Hook
* @param context 实例 * @param context 实例
* @param expandedNf 状态栏实例 * @param expandedNf 状态栏实例
* @return [Boolean] 是否忽略通知图标颜色 * @return [Boolean] 是否忽略通知图标颜色
*/ */
private fun PackageParam.hookIgnoreStatusBarIconColor(context: Context, expandedNf: StatusBarNotification?) = private fun PackageParam.hasIgnoreStatusBarIconColor(context: Context, expandedNf: StatusBarNotification?) =
if (!context.isMiuiNotifyStyle) if (!context.isMiuiNotifyStyle)
if (prefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true)) safeOfFalse { if (prefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true)) safeOfFalse {
/** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */ /** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */
@@ -456,16 +448,26 @@ class HookEntry : YukiHookXposedInitProxy {
* 只要不是灰度就返回彩色图标 * 只要不是灰度就返回彩色图标
* 否则不对颜色进行反色处理防止一些系统图标出现异常 * 否则不对颜色进行反色处理防止一些系统图标出现异常
*/ */
if (isTargetFixApp) false else isNotGrayscaleIcon (if (isTargetFixApp) false else isNotGrayscaleIcon).also {
} ?: true printLogcat(tag = "IconColor", context, expandedNf, isTargetFixApp, !isNotGrayscaleIcon)
} else false }
else true } ?: true.also { printLogcat(tag = "IconColor", context, expandedNf, isCustom = false, isGrayscale = false) }
} else false.also { printLogcat(tag = "IconColor", context, expandedNf, isCustom = false, isGrayscale = true) }
else true.also { printLogcat(tag = "IconColor", context, expandedNf, isCustom = false, isGrayscale = false) }
override fun onHook() = encase { override fun onHook() {
configs { runConfig()
runHook()
}
/** 配置 Hook */
private fun runConfig() = configs {
debugTag = "MIUINativeNotifyIcon" debugTag = "MIUINativeNotifyIcon"
isDebug = false isDebug = false
} }
/** 开始 Hook */
private fun runHook() = encase {
loadApp(SYSTEMUI_PACKAGE_NAME) { loadApp(SYSTEMUI_PACKAGE_NAME) {
when { when {
/** 不是 MIUI 系统停止 Hook */ /** 不是 MIUI 系统停止 Hook */
@@ -494,19 +496,6 @@ class HookEntry : YukiHookXposedInitProxy {
*/ */
replaceAny { globalContext?.isMiuiNotifyStyle ?: isShowMiuiStyle } replaceAny { globalContext?.isMiuiNotifyStyle ?: isShowMiuiStyle }
} }
if (hasIgnoreStatusBarIconColor)
injectMember {
method {
name = "ignoreStatusBarIconColor"
param(ExpandedNotificationClass.clazz)
}
replaceAny {
hookIgnoreStatusBarIconColor(
context = globalContext ?: error("GlobalContext got null"),
expandedNf = firstArgs as? StatusBarNotification?
)
}
}
/** 强制回写系统的状态栏图标样式为原生 */ /** 强制回写系统的状态栏图标样式为原生 */
injectMember { injectMember {
var isUseLegacy = false var isUseLegacy = false
@@ -534,6 +523,19 @@ class HookEntry : YukiHookXposedInitProxy {
} }
} }
} }
StatusBarIconViewClass.hook {
/** Hook 状态栏图标的颜色 */
injectMember {
method { name = "updateIconColor" }
afterHook {
instance<ImageView>().also {
if (hasIgnoreStatusBarIconColor(it.context, field { name = "mNotification" }
.of<StatusBarNotification>(instance))) it.colorFilter = null
else it.setColorFilter(field { name = "mCurrentSetColor" }.of<Int>(instance) ?: 0)
}
}
}
}
NotificationHeaderViewWrapperClass.hook { NotificationHeaderViewWrapperClass.hook {
/** 修复下拉通知图标自动设置回 APP 图标的方法 */ /** 修复下拉通知图标自动设置回 APP 图标的方法 */
injectMember { injectMember {

View File

@@ -20,15 +20,15 @@
* *
* This file is Created by fankes on 2022/1/24. * This file is Created by fankes on 2022/1/24.
*/ */
@file:Suppress("MemberVisibilityCanBePrivate")
package com.fankes.miui.notify.params package com.fankes.miui.notify.params
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import com.fankes.miui.notify.bean.IconDataBean import com.fankes.miui.notify.bean.IconDataBean
import com.fankes.miui.notify.hook.HookConst.NOTIFY_ICON_DATAS import com.fankes.miui.notify.hook.HookConst.NOTIFY_ICON_DATAS
import com.fankes.miui.notify.utils.bitmap import com.fankes.miui.notify.utils.*
import com.fankes.miui.notify.utils.safeOf
import com.fankes.miui.notify.utils.safeOfNan
import com.highcapable.yukihookapi.hook.factory.modulePrefs import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.param.PackageParam import com.highcapable.yukihookapi.hook.param.PackageParam
import org.json.JSONArray import org.json.JSONArray
@@ -47,7 +47,7 @@ class IconPackParams(private val context: Context? = null, private val param: Pa
* 已存储的 JSON 数据 * 已存储的 JSON 数据
* @return [String] * @return [String]
*/ */
private val storageDataJson get() = (context?.modulePrefs ?: param?.prefs)?.getString(NOTIFY_ICON_DATAS) internal val storageDataJson get() = (context?.modulePrefs ?: param?.prefs)?.getString(NOTIFY_ICON_DATAS)
/** /**
* 获取图标数据 * 获取图标数据
@@ -59,22 +59,29 @@ class IconPackParams(private val context: Context? = null, private val param: Pa
if (it.isNotBlank()) runCatching { if (it.isNotBlank()) runCatching {
JSONArray(it).also { array -> JSONArray(it).also { array ->
for (i in 0 until array.length()) runCatching { for (i in 0 until array.length()) runCatching {
(array.get(i) as JSONObject).apply { add(convertToBean(array.get(i) as JSONObject)!!)
add( }.onFailure { context?.snake(msg = "部分规则加载失败") }
}
}.onFailure { context?.snake(msg = "规则加载发生错误") }
}
}
/**
* 转换为 [IconDataBean]
* @param jsonObject Json 实例
* @return [IconDataBean] or null
*/
private fun convertToBean(jsonObject: JSONObject) = safeOfNull {
jsonObject.let {
IconDataBean( IconDataBean(
appName = getString("appName"), appName = it.getString("appName"),
packageName = getString("packageName"), packageName = it.getString("packageName"),
isEnabled = getBoolean("isEnabled"), isEnabled = it.getBoolean("isEnabled"),
isEnabledAll = getBoolean("isEnabledAll"), isEnabledAll = it.getBoolean("isEnabledAll"),
iconBitmap = getString("iconBitmap").bitmap, iconBitmap = it.getString("iconBitmap").bitmap,
iconColor = safeOfNan { Color.parseColor(getString("iconColor")) }, iconColor = safeOfNan { Color.parseColor(it.getString("iconColor")) },
contributorName = getString("contributorName") contributorName = it.getString("contributorName")
) )
)
}
}
}
}
} }
} }
@@ -88,6 +95,34 @@ class IconPackParams(private val context: Context? = null, private val param: Pa
dataJson1.replace(oldValue = "]", newValue = "") + "," + dataJson2.replace(oldValue = "[", newValue = "") dataJson1.replace(oldValue = "]", newValue = "") + "," + dataJson2.replace(oldValue = "[", newValue = "")
} }
/**
* 是否不为合法 JSON
* @param json 数据
* @return [Boolean]
*/
fun isNotVaildJson(json: String) = !isJsonArray(json) && !isJsonObject(json)
/**
* 是否为 JSON 数组
* @param json 数据
* @return [Boolean]
*/
fun isJsonArray(json: String) = json.trim().let { it.startsWith("[") && it.endsWith("]") }
/**
* 是否为 JSON 实例
* @param json 数据
* @return [Boolean]
*/
fun isJsonObject(json: String) = json.trim().let { it.startsWith("{") && it.endsWith("}") }
/**
* 是否为异常地址
* @param json 数据
* @return [Boolean]
*/
fun isHackString(json: String) = json.contains(other = "Checking your browser before accessing")
/** /**
* 比较图标数据不相等 * 比较图标数据不相等
* @param dataJson 图标数据 JSON * @param dataJson 图标数据 JSON

View File

@@ -34,8 +34,14 @@ import android.widget.ListView
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.utils.widget.ImageFilterView import androidx.constraintlayout.utils.widget.ImageFilterView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import com.fankes.miui.notify.R import com.fankes.miui.notify.R
import com.fankes.miui.notify.bean.IconDataBean import com.fankes.miui.notify.bean.IconDataBean
import com.fankes.miui.notify.hook.HookConst.SOURCE_SYNC_WAY
import com.fankes.miui.notify.hook.HookConst.SOURCE_SYNC_WAY_CUSTOM_URL
import com.fankes.miui.notify.hook.HookConst.TYPE_SOURCE_SYNC_WAY_1
import com.fankes.miui.notify.hook.HookConst.TYPE_SOURCE_SYNC_WAY_2
import com.fankes.miui.notify.hook.HookConst.TYPE_SOURCE_SYNC_WAY_3
import com.fankes.miui.notify.hook.factory.isAppNotifyHookAllOf import com.fankes.miui.notify.hook.factory.isAppNotifyHookAllOf
import com.fankes.miui.notify.hook.factory.isAppNotifyHookOf import com.fankes.miui.notify.hook.factory.isAppNotifyHookOf
import com.fankes.miui.notify.hook.factory.putAppNotifyHookAllOf import com.fankes.miui.notify.hook.factory.putAppNotifyHookAllOf
@@ -44,14 +50,13 @@ import com.fankes.miui.notify.params.IconPackParams
import com.fankes.miui.notify.ui.base.BaseActivity import com.fankes.miui.notify.ui.base.BaseActivity
import com.fankes.miui.notify.utils.* import com.fankes.miui.notify.utils.*
import com.fankes.miui.notify.view.MaterialSwitch import com.fankes.miui.notify.view.MaterialSwitch
import com.google.android.material.radiobutton.MaterialRadioButton
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
class ConfigureActivity : BaseActivity() { class ConfigureActivity : BaseActivity() {
/** 访问请求链接 */
private var rawGithubUrl = "https://raw.githubusercontent.com/fankes/AndroidNotifyIconAdapt/main"
/** 当前筛选条件 */ /** 当前筛选条件 */
private var filterText = "" private var filterText = ""
@@ -188,7 +193,18 @@ class ConfigureActivity : BaseActivity() {
lateinit var switchOpen: MaterialSwitch lateinit var switchOpen: MaterialSwitch
lateinit var switchAll: MaterialSwitch lateinit var switchAll: MaterialSwitch
} }
}.apply { onChanged = { notifyDataSetChanged() } } }.apply {
setOnItemLongClickListener { _, _, p, _ ->
showDialog {
title = "复制“${iconDatas[p].appName}”的规则"
msg = "是否复制单条规则到剪贴板?"
confirmButton { copyToClipboard(iconDatas[p].toString()) }
cancelButton()
}
true
}
onChanged = { notifyDataSetChanged() }
}
onScrollEvent = { post { setSelection(if (it) iconDatas.lastIndex else 0) } } onScrollEvent = { post { setSelection(if (it) iconDatas.lastIndex else 0) } }
} }
/** 设置点击事件 */ /** 设置点击事件 */
@@ -210,16 +226,119 @@ class ConfigureActivity : BaseActivity() {
/** 首次进入或更新数据 */ /** 首次进入或更新数据 */
private fun onStartRefresh() = private fun onStartRefresh() =
showDialog { showDialog {
title = if (iconAllDatas.isNotEmpty()) "同步列表" else "初始化" title = "同步列表"
msg = (if (iconAllDatas.isNotEmpty()) "建议定期从云端拉取数据以获得最新的通知图标优化名单适配数据。\n\n" var sourceType = modulePrefs.getInt(SOURCE_SYNC_WAY, TYPE_SOURCE_SYNC_WAY_1)
else "首次装载需要从云端下载最新适配数据,后续可继续前往这里检查更新。\n\n") + var customUrl = modulePrefs.getString(SOURCE_SYNC_WAY_CUSTOM_URL)
"通过从 Github 同步最新数据,无法连接可能需要魔法上网。" addView(R.layout.dia_source_from).apply {
confirmButton(text = "开始同步") { onRefreshing() } val radio1 = findViewById<MaterialRadioButton>(R.id.dia_sf_rd1)
val radio2 = findViewById<MaterialRadioButton>(R.id.dia_sf_rd2)
val radio3 = findViewById<MaterialRadioButton>(R.id.dia_sf_rd3)
val edLin = findViewById<View>(R.id.dia_sf_text_lin)
findViewById<TextInputEditText>(R.id.dia_sf_text).apply {
if (customUrl.isNotBlank()) {
setText(customUrl)
setSelection(customUrl.length)
}
doOnTextChanged { text, _, _, _ ->
customUrl = text.toString()
modulePrefs.putString(SOURCE_SYNC_WAY_CUSTOM_URL, text.toString())
}
}
edLin.isVisible = sourceType == TYPE_SOURCE_SYNC_WAY_3
radio1.isChecked = sourceType == TYPE_SOURCE_SYNC_WAY_1
radio2.isChecked = sourceType == TYPE_SOURCE_SYNC_WAY_2
radio3.isChecked = sourceType == TYPE_SOURCE_SYNC_WAY_3
radio1.setOnClickListener {
radio2.isChecked = false
radio3.isChecked = false
edLin.isVisible = false
sourceType = TYPE_SOURCE_SYNC_WAY_1
modulePrefs.putInt(SOURCE_SYNC_WAY, TYPE_SOURCE_SYNC_WAY_1)
}
radio2.setOnClickListener {
radio1.isChecked = false
radio3.isChecked = false
edLin.isVisible = false
sourceType = TYPE_SOURCE_SYNC_WAY_2
modulePrefs.putInt(SOURCE_SYNC_WAY, TYPE_SOURCE_SYNC_WAY_2)
}
radio3.setOnClickListener {
radio1.isChecked = false
radio2.isChecked = false
edLin.isVisible = true
sourceType = TYPE_SOURCE_SYNC_WAY_3
modulePrefs.putInt(SOURCE_SYNC_WAY, TYPE_SOURCE_SYNC_WAY_3)
}
}
confirmButton {
when (sourceType) {
TYPE_SOURCE_SYNC_WAY_1 -> onRefreshing(url = "https://raw.fastgit.org/fankes/AndroidNotifyIconAdapt/main")
TYPE_SOURCE_SYNC_WAY_2 -> onRefreshing(url = "https://raw.githubusercontent.com/fankes/AndroidNotifyIconAdapt/main")
TYPE_SOURCE_SYNC_WAY_3 ->
if (customUrl.isNotBlank())
if (customUrl.startsWith("http://") || customUrl.startsWith("https://"))
onRefreshingCustom(customUrl)
else snake(msg = "同步地址不是一个合法的 URL")
else snake(msg = "同步地址不能为空")
else -> snake(msg = "同步类型错误")
}
}
cancelButton() cancelButton()
neutralButton(text = "自定义规则") {
showDialog {
title = "自定义规则"
var editText: TextInputEditText
addView(R.layout.dia_source_from_string).apply {
editText = findViewById<TextInputEditText>(R.id.dia_sfs_input_edit).apply {
requestFocus()
invalidate()
}
}
IconPackParams(context = this@ConfigureActivity).also { params ->
confirmButton(text = "合并") {
editText.text.toString().also { jsonString ->
when {
jsonString.isNotBlank() && params.isNotVaildJson(jsonString) -> snake(msg = "不是有效的 JSON 数据")
jsonString.isNotBlank() -> {
params.save(
params.splicingJsonArray(
dataJson1 = params.storageDataJson ?: "[]",
dataJson2 = jsonString.takeIf { params.isJsonArray(it) } ?: "[$jsonString]"
)
)
filterText = ""
mockLocalData()
SystemUITool.showNeedUpdateApplySnake(context = this@ConfigureActivity)
}
else -> snake(msg = "请输入有效内容")
}
}
}
cancelButton(text = "覆盖") {
editText.text.toString().also { jsonString ->
when {
jsonString.isNotBlank() && params.isNotVaildJson(jsonString) -> snake(msg = "不是有效的 JSON 数据")
jsonString.isNotBlank() -> {
params.save(dataJson = jsonString.takeIf { params.isJsonArray(it) } ?: "[$jsonString]")
filterText = ""
mockLocalData()
SystemUITool.showNeedUpdateApplySnake(context = this@ConfigureActivity)
}
else -> snake(msg = "请输入有效内容")
}
}
}
}
neutralButton(text = "取消")
}
}
} }
/** 开始更新数据 */ /**
private fun onRefreshing() = ClientRequestTool.checkingInternetConnect(context = this) { * 开始更新数据
* @param url
*/
private fun onRefreshing(url: String) = ClientRequestTool.checkingInternetConnect(context = this) {
ProgressDialog(this).apply { ProgressDialog(this).apply {
setDefaultStyle(context = this@ConfigureActivity) setDefaultStyle(context = this@ConfigureActivity)
setCancelable(false) setCancelable(false)
@@ -229,22 +348,27 @@ class ConfigureActivity : BaseActivity() {
}.also { }.also {
ClientRequestTool.wait( ClientRequestTool.wait(
context = this, context = this,
url = "$rawGithubUrl/OS/MIUI/NotifyIconsSupportConfig.json" url = "$url/OS/MIUI/NotifyIconsSupportConfig.json"
) { isDone1, ctOS -> ) { isDone1, ctOS ->
it.setMessage("正在同步 APP 数据") it.setMessage("正在同步 APP 数据")
ClientRequestTool.wait( ClientRequestTool.wait(
context = this, context = this,
url = "$rawGithubUrl/APP/NotifyIconsSupportConfig.json" url = "$url/APP/NotifyIconsSupportConfig.json"
) { isDone2, ctAPP -> ) { isDone2, ctAPP ->
it.cancel() it.cancel()
IconPackParams(context = this).also { params -> IconPackParams(context = this).also { params ->
if (isDone1 && isDone2) params.splicingJsonArray(ctOS, ctAPP).also { if (isDone1 && isDone2) params.splicingJsonArray(ctOS, ctAPP).also {
if (params.isCompareDifferent(it)) { when {
params.isHackString(it) -> snake(msg = "请求需要验证,请尝试魔法上网或关闭魔法")
params.isNotVaildJson(it) -> snake(msg = "在线规则发生问题,请稍后重试")
params.isCompareDifferent(it) -> {
params.save(it) params.save(it)
filterText = "" filterText = ""
mockLocalData() mockLocalData()
SystemUITool.showNeedUpdateApplySnake(context = this) SystemUITool.showNeedUpdateApplySnake(context = this)
} else snake(msg = "列表数据已是最新") }
else -> snake(msg = "列表数据已是最新")
}
} else showDialog { } else showDialog {
title = "连接失败" title = "连接失败"
msg = "连接失败,错误如下:\n${if (!isDone1) ctOS else ctAPP}" msg = "连接失败,错误如下:\n${if (!isDone1) ctOS else ctAPP}"
@@ -259,6 +383,46 @@ class ConfigureActivity : BaseActivity() {
} }
} }
/**
* 开始更新数据
* @param url
*/
private fun onRefreshingCustom(url: String) = ClientRequestTool.checkingInternetConnect(context = this) {
ProgressDialog(this).apply {
setDefaultStyle(context = this@ConfigureActivity)
setCancelable(false)
setTitle("同步中")
setMessage("正在通过自定义地址同步数据")
show()
}.also {
ClientRequestTool.wait(
context = this,
url = url
) { isDone, content ->
it.cancel()
IconPackParams(context = this).also { params ->
if (isDone)
when {
params.isHackString(content) -> snake(msg = "请求需要验证,请尝试魔法上网或关闭魔法")
params.isNotVaildJson(content) -> snake(msg = "目标地址不是有效的 JSON 数据")
params.isCompareDifferent(content) -> {
params.save(content)
filterText = ""
mockLocalData()
SystemUITool.showNeedUpdateApplySnake(context = this)
}
else -> snake(msg = "列表数据已是最新")
}
else showDialog {
title = "连接失败"
msg = "连接失败,错误如下:\n$content"
confirmButton(text = "我知道了")
}
}
}
}
}
/** 刷新适配器结果相关 */ /** 刷新适配器结果相关 */
private fun refreshAdapterResult() { private fun refreshAdapterResult() {
onChanged?.invoke() onChanged?.invoke()

View File

@@ -61,8 +61,8 @@ class MainActivity : BaseActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
/** 设置文本 */ /** 设置文本 */
findViewById<TextView>(R.id.main_text_version).text = "当前版本:$moduleVersion" findViewById<TextView>(R.id.main_text_version).text = "模块版本:$moduleVersion"
findViewById<TextView>(R.id.main_text_miui_version).text = "MIUI 版本:$miuiFullVersion" findViewById<TextView>(R.id.main_text_miui_version).text = "系统版本:$miuiFullVersion"
when { when {
/** 判断是否为 MIUI 系统 */ /** 判断是否为 MIUI 系统 */
isNotMIUI -> isNotMIUI ->

View File

@@ -79,7 +79,7 @@ object ClientRequestTool {
* @param url 请求地址 * @param url 请求地址
* @param it 回调 - ([Boolean] 是否成功,[String] 成功的内容或失败消息) * @param it 回调 - ([Boolean] 是否成功,[String] 成功的内容或失败消息)
*/ */
fun wait(context: Activity, url: String, it: (Boolean, String) -> Unit) { fun wait(context: Activity, url: String, it: (Boolean, String) -> Unit) = runCatching {
OkHttpClient().newBuilder().apply { OkHttpClient().newBuilder().apply {
SSLSocketClient.sSLSocketFactory?.let { sslSocketFactory(it, SSLSocketClient.trustManager) } SSLSocketClient.sSLSocketFactory?.let { sslSocketFactory(it, SSLSocketClient.trustManager) }
hostnameVerifier(SSLSocketClient.hostnameVerifier) hostnameVerifier(SSLSocketClient.hostnameVerifier)
@@ -98,7 +98,7 @@ object ClientRequestTool {
context.runOnUiThread { it(true, bodyString) } context.runOnUiThread { it(true, bodyString) }
} }
}) })
} }.onFailure { it(false, "URL 无效") }
/** /**
* 自动信任 SSL 证书 * 自动信任 SSL 证书

View File

@@ -26,6 +26,8 @@ package com.fankes.miui.notify.utils
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
@@ -48,6 +50,7 @@ import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.log.loggerE import com.highcapable.yukihookapi.hook.log.loggerE
import com.highcapable.yukihookapi.hook.type.java.StringType import com.highcapable.yukihookapi.hook.type.java.StringType
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import java.io.ByteArrayOutputStream
/** /**
* 系统深色模式是否开启 * 系统深色模式是否开启
@@ -148,8 +151,35 @@ val miuiVersion
* @return [String] * @return [String]
*/ */
val miuiFullVersion val miuiFullVersion
get() = if (isMIUI) (miuiVersion + " " + findPropString(key = "ro.system.build.version.incremental")) get() = if (isMIUI) findPropString(key = "ro.system.build.version.incremental").let {
else "不是 MIUI 系统" if (it.lowercase().contains("a") ||
it.lowercase().contains("b") ||
it.lowercase().contains("c") ||
it.lowercase().contains("d") ||
it.lowercase().contains("e") ||
it.lowercase().contains("f") ||
it.lowercase().contains("g") ||
it.lowercase().contains("h") ||
it.lowercase().contains("i") ||
it.lowercase().contains("j") ||
it.lowercase().contains("k") ||
it.lowercase().contains("l") ||
it.lowercase().contains("m") ||
it.lowercase().contains("n") ||
it.lowercase().contains("o") ||
it.lowercase().contains("p") ||
it.lowercase().contains("q") ||
it.lowercase().contains("r") ||
it.lowercase().contains("s") ||
it.lowercase().contains("t") ||
it.lowercase().contains("u") ||
it.lowercase().contains("v") ||
it.lowercase().contains("w") ||
it.lowercase().contains("x") ||
it.lowercase().contains("y") ||
it.lowercase().contains("z")
) "$it 稳定版" else "V$miuiVersion $it 开发版"
} else "不是 MIUI 系统"
/** /**
* 得到安装包信息 * 得到安装包信息
@@ -195,6 +225,17 @@ val Number.dp get() = (toFloat() * appContext.resources.displayMetrics.density).
*/ */
fun Number.dp(context: Context) = toFloat() * context.resources.displayMetrics.density fun Number.dp(context: Context) = toFloat() * context.resources.displayMetrics.density
/**
* Base64 加密
* @return [String]
*/
val Bitmap.base64
get() = safeOfNothing {
val baos = ByteArrayOutputStream()
compress(Bitmap.CompressFormat.PNG, 100, baos)
Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT)
}
/** /**
* Base64 加密 * Base64 加密
* @return [String] * @return [String]
@@ -298,6 +339,19 @@ fun Context.openBrowser(url: String, packageName: String = "") =
else snake(msg = "启动系统浏览器失败") else snake(msg = "启动系统浏览器失败")
} }
/**
* 复制到剪贴板
* @param content 要复制的文本
*/
fun Context.copyToClipboard(content: String) = runCatching {
(getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).apply {
setPrimaryClip(ClipData.newPlainText(null, content))
(primaryClip?.getItemAt(0)?.text ?: "").also {
if (it != content) snake(msg = "复制失败") else snake(msg = "已复制")
}
}
}
/** /**
* 忽略异常返回值 * 忽略异常返回值
* @param it 回调 - 如果异常为空 * @param it 回调 - 如果异常为空

View File

@@ -149,7 +149,7 @@
android:divider="@color/trans" android:divider="@color/trans"
android:dividerHeight="15dp" android:dividerHeight="15dp"
android:fadingEdgeLength="10dp" android:fadingEdgeLength="10dp"
android:listSelector="@null" android:listSelector="@color/trans"
android:padding="15dp" android:padding="15dp"
android:requiresFadingEdge="vertical" android:requiresFadingEdge="vertical"
android:scrollbars="none" /> android:scrollbars="none" />

View File

@@ -86,7 +86,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="5dp" android:layout_marginBottom="5dp"
android:alpha="0.8" android:alpha="0.8"
android:text="当前版本:%1" android:text="模块版本:%1"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="13sp" /> android:textSize="13sp" />
@@ -95,7 +95,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:alpha="0.8" android:alpha="0.8"
android:text="MIUI 版本:%1" android:text="系统版本:%1"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="13sp" /> android:textSize="13sp" />
</LinearLayout> </LinearLayout>

View File

@@ -5,6 +5,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/bg_permotion_round" android:background="@drawable/bg_permotion_round"
android:baselineAligned="false" android:baselineAligned="false"
android:descendantFocusability="blocksDescendants"
android:gravity="center|start" android:gravity="center|start"
android:orientation="horizontal" android:orientation="horizontal"
android:padding="15dp" android:padding="15dp"

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp"
tools:ignore="HardcodedText">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8.5dp"
android:layout_marginRight="8.5dp"
android:layout_marginBottom="10dp"
android:lineSpacingExtra="6dp"
android:text="在线规则将不定期更新,建议定期同步列表以适配更多 APP若无法同步请自行寻找解决方法或魔法上网。"
android:textSize="14sp" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/dia_sf_rd1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="从 FastGit 获取" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/dia_sf_rd2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="从 Github Raw 获取" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/dia_sf_rd3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="从自定义地址获取" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/dia_sf_text_lin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/dia_sf_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:hint="请输入在线地址 URL"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp"
tools:ignore="HardcodedText">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/dia_sfs_input_edit"
android:layout_width="match_parent"
android:layout_height="150dp"
android:ellipsize="end"
android:gravity="center|start|top"
android:hint="请粘贴 JSON 规则到此处"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View File

@@ -1,12 +1,12 @@
plugins { plugins {
id 'com.android.application' version '7.1.1' apply false id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.1' apply false id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
} }
ext { ext {
appVersionName = "1.87" appVersionName = "1.95"
appVersionCode = 20 appVersionCode = 22
} }
task clean(type: Delete) { task clean(type: Delete) {