From 1834803a5ce43726a7841c3ba8d440b3a2480a79 Mon Sep 17 00:00:00 2001 From: Fankesyooni Date: Sun, 20 Mar 2022 01:11:41 +0800 Subject: [PATCH] Merge code --- app/build.gradle | 13 +- app/proguard-rules.pro | 20 +- .../coloros/notify/param/IconPackParams.kt | 2 +- .../notify/ui/activity/ConfigureActivity.kt | 320 ++----------- .../notify/ui/activity/MainActivity.kt | 115 +++-- .../notify/ui/activity/base/BaseActivity.kt | 25 +- .../utils/factory/DialogBuilderFactory.kt | 150 ++++-- .../notify/utils/factory/FunctionFactory.kt | 40 +- .../notify/utils/tool/ClientRequestTool.kt | 150 ------ .../notify/utils/tool/IconRuleManagerTool.kt | 439 ++++++++++++++++++ .../main/res/drawable/ic_nf_icon_update.xml | 5 + app/src/main/res/layout/activity_main.xml | 18 +- app/src/main/res/values-night/themes.xml | 4 +- app/src/main/res/values/themes.xml | 4 +- 14 files changed, 754 insertions(+), 551 deletions(-) delete mode 100644 app/src/main/java/com/fankes/coloros/notify/utils/tool/ClientRequestTool.kt create mode 100644 app/src/main/java/com/fankes/coloros/notify/utils/tool/IconRuleManagerTool.kt create mode 100644 app/src/main/res/drawable/ic_nf_icon_update.xml diff --git a/app/build.gradle b/app/build.gradle index 8f08314..6c9f733 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,12 +25,20 @@ android { versionCode rootProject.ext.appVersionCode versionName rootProject.ext.appVersionName + kotlinOptions { + freeCompilerArgs = [ + '-Xno-param-assertions', + '-Xno-call-assertions', + '-Xno-receiver-assertions' + ] + } + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { - minifyEnabled true + minifyEnabled false signingConfig signingConfigs.debug proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } @@ -42,6 +50,9 @@ android { kotlinOptions { jvmTarget = '1.8' } + buildFeatures { + viewBinding true + } } /** 移除无效耗时 lint Task */ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 123d549..fb2de18 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -27,24 +27,26 @@ -dontoptimize -verbose -overloadaggressively --repackageclasses o -allowaccessmodification -adaptclassstrings -adaptresourcefilenames -adaptresourcefilecontents -#-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* --renamesourcefileattribute H +-renamesourcefileattribute P -keepattributes SourceFile,LineNumberTable --keep class android.support** --keep class androidx** - --keep class me.weishu**{*;} - -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 +-keep public class * extends android.preference.Preference + +-assumenosideeffects class kotlin.jvm.internal.Intrinsics { + public static *** throwUninitializedProperty(...); + public static *** throwUninitializedPropertyAccessException(...); +} + +-keepclassmembers class * implements androidx.viewbinding.ViewBinding { + *** inflate(android.view.LayoutInflater); +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/param/IconPackParams.kt b/app/src/main/java/com/fankes/coloros/notify/param/IconPackParams.kt index 7dae945..562737c 100644 --- a/app/src/main/java/com/fankes/coloros/notify/param/IconPackParams.kt +++ b/app/src/main/java/com/fankes/coloros/notify/param/IconPackParams.kt @@ -101,7 +101,7 @@ class IconPackParams(private val context: Context? = null, private val param: Pa * 已存储的 JSON 数据 * @return [String] */ - internal val storageDataJson get() = (context?.modulePrefs ?: param?.prefs)?.getString(NOTIFY_ICON_DATAS) + internal val storageDataJson get() = (context?.modulePrefs ?: param?.prefs)?.direct()?.getString(NOTIFY_ICON_DATAS) /** * 获取图标数据 diff --git a/app/src/main/java/com/fankes/coloros/notify/ui/activity/ConfigureActivity.kt b/app/src/main/java/com/fankes/coloros/notify/ui/activity/ConfigureActivity.kt index 1d784d9..bfb0df7 100644 --- a/app/src/main/java/com/fankes/coloros/notify/ui/activity/ConfigureActivity.kt +++ b/app/src/main/java/com/fankes/coloros/notify/ui/activity/ConfigureActivity.kt @@ -24,40 +24,28 @@ package com.fankes.coloros.notify.ui.activity -import android.app.ProgressDialog -import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter -import android.widget.ListView -import android.widget.TextView -import androidx.constraintlayout.utils.widget.ImageFilterView import androidx.core.view.isVisible -import androidx.core.widget.doOnTextChanged import com.fankes.coloros.notify.R import com.fankes.coloros.notify.bean.IconDataBean -import com.fankes.coloros.notify.hook.HookConst.SOURCE_SYNC_WAY -import com.fankes.coloros.notify.hook.HookConst.SOURCE_SYNC_WAY_CUSTOM_URL -import com.fankes.coloros.notify.hook.HookConst.TYPE_SOURCE_SYNC_WAY_1 -import com.fankes.coloros.notify.hook.HookConst.TYPE_SOURCE_SYNC_WAY_2 -import com.fankes.coloros.notify.hook.HookConst.TYPE_SOURCE_SYNC_WAY_3 +import com.fankes.coloros.notify.databinding.ActivityConfigBinding +import com.fankes.coloros.notify.databinding.AdapterConfigBinding +import com.fankes.coloros.notify.databinding.DiaIconFilterBinding import com.fankes.coloros.notify.hook.factory.isAppNotifyHookAllOf import com.fankes.coloros.notify.hook.factory.isAppNotifyHookOf import com.fankes.coloros.notify.hook.factory.putAppNotifyHookAllOf import com.fankes.coloros.notify.hook.factory.putAppNotifyHookOf import com.fankes.coloros.notify.param.IconPackParams import com.fankes.coloros.notify.ui.activity.base.BaseActivity -import com.fankes.coloros.notify.ui.view.MaterialSwitch import com.fankes.coloros.notify.utils.factory.* -import com.fankes.coloros.notify.utils.tool.ClientRequestTool +import com.fankes.coloros.notify.utils.tool.IconRuleManagerTool import com.fankes.coloros.notify.utils.tool.SystemUITool -import com.google.android.material.radiobutton.MaterialRadioButton -import com.google.android.material.textfield.TextInputEditText -import com.highcapable.yukihookapi.hook.factory.modulePrefs import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus -class ConfigureActivity : BaseActivity() { +class ConfigureActivity : BaseActivity() { /** 当前筛选条件 */ private var filterText = "" @@ -71,9 +59,7 @@ class ConfigureActivity : BaseActivity() { /** 全部的通知优化图标数据 */ private var iconAllDatas = ArrayList() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_config) + override fun onCreate() { /** 检查激活状态 */ if (!YukiHookModuleStatus.isActive()) { showDialog { @@ -85,31 +71,28 @@ class ConfigureActivity : BaseActivity() { return } /** 返回按钮点击事件 */ - findViewById(R.id.title_back_icon).setOnClickListener { onBackPressed() } + binding.titleBackIcon.setOnClickListener { onBackPressed() } /** 刷新适配器结果相关 */ refreshAdapterResult() /** 设置上下按钮点击事件 */ - findViewById(R.id.config_title_up).setOnClickListener { + binding.configTitleUp.setOnClickListener { snake(msg = "滚动到顶部") onScrollEvent?.invoke(false) } - findViewById(R.id.config_title_down).setOnClickListener { + binding.configTitleDown.setOnClickListener { snake(msg = "滚动到底部") onScrollEvent?.invoke(true) } /** 设置过滤按钮点击事件 */ - findViewById(R.id.config_title_filter).setOnClickListener { + binding.configTitleFilter.setOnClickListener { showDialog { title = "按条件过滤" - var editText: TextInputEditText - addView(R.layout.dia_icon_filter).apply { - editText = findViewById(R.id.dia_icon_filter_input_edit).apply { - requestFocus() - invalidate() - if (filterText.isNotBlank()) { - setText(filterText) - setSelection(filterText.length) - } + val editText = bind().diaIconFilterInputEdit.apply { + requestFocus() + invalidate() + if (filterText.isNotBlank()) { + setText(filterText) + setSelection(filterText.length) } } confirmButton { @@ -130,13 +113,11 @@ class ConfigureActivity : BaseActivity() { } } /** 设置同步列表按钮点击事件 */ - findViewById(R.id.config_title_sync).setOnClickListener { onStartRefresh() } + binding.configTitleSync.setOnClickListener { onStartRefresh() } /** 设置列表元素和 Adapter */ - findViewById(R.id.config_list_view).apply { + binding.configListView.apply { adapter = object : BaseAdapter() { - private val inflater = LayoutInflater.from(context) - override fun getCount() = iconDatas.size override fun getItem(position: Int) = iconDatas[position] @@ -145,40 +126,33 @@ class ConfigureActivity : BaseActivity() { override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { var cView = convertView - val holder: ViewHolder + val holder: AdapterConfigBinding if (convertView == null) { - holder = ViewHolder() - cView = inflater.inflate(R.layout.adapter_config, null).also { - holder.appIcon = it.findViewById(R.id.adp_app_icon) - holder.appName = it.findViewById(R.id.adp_app_name) - holder.pkgName = it.findViewById(R.id.adp_app_pkg_name) - holder.cbrName = it.findViewById(R.id.adp_cbr_name) - holder.switchOpen = it.findViewById(R.id.adp_app_open_switch) - holder.switchAll = it.findViewById(R.id.adp_app_all_switch) - } + holder = AdapterConfigBinding.inflate(LayoutInflater.from(context)) + cView = holder.root cView.tag = holder - } else holder = convertView.tag as ViewHolder + } else holder = convertView.tag as AdapterConfigBinding getItem(position).also { - holder.appIcon.setImageBitmap(it.iconBitmap) + holder.adpAppIcon.setImageBitmap(it.iconBitmap) (if (it.iconColor != 0) it.iconColor else resources.getColor(R.color.colorTextGray)).also { color -> - holder.appIcon.setColorFilter(color) - holder.appName.setTextColor(color) + holder.adpAppIcon.setColorFilter(color) + holder.adpAppName.setTextColor(color) } - holder.appName.text = it.appName - holder.pkgName.text = it.packageName - holder.cbrName.text = "贡献者:" + it.contributorName + holder.adpAppName.text = it.appName + holder.adpAppPkgName.text = it.packageName + holder.adpCbrName.text = "贡献者:" + it.contributorName isAppNotifyHookOf(it).also { e -> - holder.switchOpen.isChecked = e - holder.switchAll.isEnabled = e + holder.adpAppOpenSwitch.isChecked = e + holder.adpAppAllSwitch.isEnabled = e } - holder.switchOpen.setOnCheckedChangeListener { btn, b -> + holder.adpAppOpenSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener putAppNotifyHookOf(it, b) - holder.switchAll.isEnabled = b + holder.adpAppAllSwitch.isEnabled = b SystemUITool.showNeedRestartSnake(context = this@ConfigureActivity) } - holder.switchAll.isChecked = isAppNotifyHookAllOf(it) - holder.switchAll.setOnCheckedChangeListener { btn, b -> + holder.adpAppAllSwitch.isChecked = isAppNotifyHookAllOf(it) + holder.adpAppAllSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener putAppNotifyHookAllOf(it, b) SystemUITool.showNeedRestartSnake(context = this@ConfigureActivity) @@ -186,15 +160,6 @@ class ConfigureActivity : BaseActivity() { } return cView!! } - - inner class ViewHolder { - lateinit var appIcon: ImageFilterView - lateinit var appName: TextView - lateinit var pkgName: TextView - lateinit var cbrName: TextView - lateinit var switchOpen: MaterialSwitch - lateinit var switchAll: MaterialSwitch - } }.apply { setOnItemLongClickListener { _, _, p, _ -> showDialog { @@ -210,13 +175,22 @@ class ConfigureActivity : BaseActivity() { onScrollEvent = { post { setSelection(if (it) iconDatas.lastIndex else 0) } } } /** 设置点击事件 */ - findViewById(R.id.config_cbr_button).setOnClickListener { + binding.configCbrButton.setOnClickListener { openBrowser(url = "https://github.com/fankes/AndroidNotifyIconAdapt/blob/main/CONTRIBUTING.md") } /** 装载数据 */ mockLocalData() /** 更新数据 */ - onStartRefresh() + if (intent?.getBooleanExtra("isShowUpdDialog", true) == true) onStartRefresh() + } + + /** 开始手动同步 */ + private fun onStartRefresh() { + IconRuleManagerTool.syncByHand(context = this) { + filterText = "" + mockLocalData() + SystemUITool.showNeedUpdateApplySnake(context = this) + } } /** 装载或刷新本地数据 */ @@ -225,213 +199,13 @@ class ConfigureActivity : BaseActivity() { refreshAdapterResult() } - /** 首次进入或更新数据 */ - private fun onStartRefresh() = - showDialog { - title = "同步列表" - var sourceType = modulePrefs.getInt(SOURCE_SYNC_WAY, TYPE_SOURCE_SYNC_WAY_1) - var customUrl = modulePrefs.getString(SOURCE_SYNC_WAY_CUSTOM_URL) - addView(R.layout.dia_source_from).apply { - val radio1 = findViewById(R.id.dia_sf_rd1) - val radio2 = findViewById(R.id.dia_sf_rd2) - val radio3 = findViewById(R.id.dia_sf_rd3) - val edLin = findViewById(R.id.dia_sf_text_lin) - findViewById(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() - neutralButton(text = "自定义规则") { - showDialog { - title = "自定义规则" - var editText: TextInputEditText - addView(R.layout.dia_source_from_string).apply { - editText = findViewById(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 = "取消") - } - } - } - - /** - * 开始更新数据 - * @param url - */ - private fun onRefreshing(url: String) = ClientRequestTool.checkingInternetConnect(context = this) { - ProgressDialog(this).apply { - setDefaultStyle(context = this@ConfigureActivity) - setCancelable(false) - setTitle("同步中") - setMessage("正在同步 OS 数据") - show() - }.also { - ClientRequestTool.wait( - context = this, - url = "$url/OS/ColorOS/NotifyIconsSupportConfig.json" - ) { isDone1, ctOS -> - it.setMessage("正在同步 APP 数据") - ClientRequestTool.wait( - context = this, - url = "$url/APP/NotifyIconsSupportConfig.json" - ) { isDone2, ctAPP -> - it.cancel() - IconPackParams(context = this).also { params -> - if (isDone1 && isDone2) params.splicingJsonArray(ctOS, ctAPP).also { - when { - params.isHackString(it) -> snake(msg = "请求需要验证,请尝试魔法上网或关闭魔法") - params.isNotVaildJson(it) -> snake(msg = "在线规则发生问题,请稍后重试") - params.isCompareDifferent(it) -> { - params.save(it) - filterText = "" - mockLocalData() - SystemUITool.showNeedUpdateApplySnake(context = this) - } - else -> snake(msg = "列表数据已是最新") - } - } else showDialog { - title = "连接失败" - msg = "连接失败,错误如下:\n${if (!isDone1) ctOS else ctAPP}" - confirmButton(text = "解决方案") { - openBrowser(url = "https://www.baidu.com/s?wd=github%2Braw%2B%E6%97%A0%E6%B3%95%E8%AE%BF%E9%97%AE") - } - cancelButton() - } - } - } - } - } - } - - /** - * 开始更新数据 - * @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() { onChanged?.invoke() - findViewById(R.id.config_title_count_text).text = + binding.configTitleCountText.text = if (filterText.isBlank()) "已适配 ${iconDatas.size} 个 APP 的通知图标" else "“${filterText}” 匹配到 ${iconDatas.size} 个结果" - findViewById(R.id.config_list_no_data_view).apply { + binding.configListNoDataView.apply { text = if (iconAllDatas.isEmpty()) "噫,竟然什么都没有~\n请点击右上角同步按钮获取云端数据" else "噫,竟然什么都没找到~" isVisible = iconDatas.isEmpty() } diff --git a/app/src/main/java/com/fankes/coloros/notify/ui/activity/MainActivity.kt b/app/src/main/java/com/fankes/coloros/notify/ui/activity/MainActivity.kt index 0189520..73a15dd 100644 --- a/app/src/main/java/com/fankes/coloros/notify/ui/activity/MainActivity.kt +++ b/app/src/main/java/com/fankes/coloros/notify/ui/activity/MainActivity.kt @@ -24,18 +24,15 @@ package com.fankes.coloros.notify.ui.activity +import android.app.Notification import android.content.ComponentName import android.content.Intent import android.content.pm.PackageManager -import android.os.Bundle -import android.view.View -import android.widget.LinearLayout -import android.widget.TextView -import androidx.appcompat.widget.SwitchCompat -import androidx.constraintlayout.utils.widget.ImageFilterView +import android.provider.Settings import androidx.core.view.isVisible import com.fankes.coloros.notify.BuildConfig import com.fankes.coloros.notify.R +import com.fankes.coloros.notify.databinding.ActivityMainBinding import com.fankes.coloros.notify.hook.HookConst.ENABLE_ANDROID12_STYLE import com.fankes.coloros.notify.hook.HookConst.ENABLE_HIDE_ICON import com.fankes.coloros.notify.hook.HookConst.ENABLE_MODULE @@ -51,7 +48,7 @@ import com.fankes.coloros.notify.utils.tool.SystemUITool import com.highcapable.yukihookapi.hook.factory.modulePrefs import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus -class MainActivity : BaseActivity() { +class MainActivity : BaseActivity() { companion object { @@ -59,12 +56,10 @@ class MainActivity : BaseActivity() { private const val moduleVersion = BuildConfig.VERSION_NAME } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + override fun onCreate() { /** 设置文本 */ - findViewById(R.id.main_text_version).text = "模块版本:$moduleVersion" - findViewById(R.id.main_text_coloros_version).text = "系统版本:$colorOSVersion" + binding.mainTextVersion.text = "模块版本:$moduleVersion" + binding.mainTextColorOsVersion.text = "系统版本:$colorOSVersion" when { /** 判断是否为 ColorOS 系统 */ isNotColorOS -> @@ -77,19 +72,36 @@ class MainActivity : BaseActivity() { } /** 判断是否 Hook */ YukiHookModuleStatus.isActive() -> { - findViewById(R.id.main_lin_status).setBackgroundResource(R.drawable.bg_green_round) - findViewById(R.id.main_img_status).setImageResource(R.mipmap.ic_success) - findViewById(R.id.main_text_status).text = "模块已激活" + binding.mainLinStatus.setBackgroundResource(R.drawable.bg_green_round) + binding.mainImgStatus.setImageResource(R.mipmap.ic_success) + binding.mainTextStatus.text = "模块已激活" if (IconPackParams(context = this).iconDatas.isEmpty() && modulePrefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true) ) showDialog { title = "配置通知图标优化名单" msg = "模块需要获取在线规则以更新“通知图标优化名单”,它现在是空的,这看起来是你第一次使用模块,请首先进行配置才可以使用相关功能。\n" + "你可以随时在本页面下方找到“配置通知图标优化名单”手动前往。" - confirmButton(text = "前往") { startActivity(Intent(this@MainActivity, ConfigureActivity::class.java)) } + confirmButton(text = "前往") { navigate() } cancelButton() noCancelable() } + if (isNotNoificationEnabled && modulePrefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true)) + showDialog { + title = "模块的通知权限已关闭" + msg = "请开启通知权限,以确保你能收到通知优化图标在线规则的更新。" + confirmButton { + runCatching { + Intent().also { intent -> + intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS + intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName) + intent.putExtra(Notification.EXTRA_CHANNEL_ID, applicationInfo.uid) + startActivity(intent) + } + }.onFailure { snake(msg = "跳转通知设置失败") } + } + cancelButton() + noCancelable() + } } else -> showDialog { @@ -102,47 +114,34 @@ class MainActivity : BaseActivity() { noCancelable() } } - /** 初始化 View */ - val moduleEnableSwitch = findViewById(R.id.module_enable_switch) - val moduleEnableLogSwitch = findViewById(R.id.module_enable_log_switch) - val devNotifyConfigItem = findViewById(R.id.config_item_dev) - val a12StyleConfigItem = findViewById(R.id.config_item_a12) - val notifyIconConfigItem = findViewById(R.id.config_item_notify) - val devNotifyConfigSwitch = findViewById(R.id.remove_dev_n_enable_switch) - val crcpNotifyConfigSwitch = findViewById(R.id.remove_chargecp_n_enable_switch) - val dndNotifyConfigSwitch = findViewById(R.id.remove_dndalert_n_enable_switch) - val a12StyleConfigSwitch = findViewById(R.id.a12_style_enable_switch) - val hideIconInLauncherSwitch = findViewById(R.id.hide_icon_in_launcher_switch) - val notifyIconFixSwitch = findViewById(R.id.notify_icon_fix_switch) - val notifyIconFixButton = findViewById(R.id.config_notify_app_button) /** 获取 Sp 存储的信息 */ - devNotifyConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true) - a12StyleConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true) - notifyIconConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true) - notifyIconFixButton.isVisible = modulePrefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true) - devNotifyConfigSwitch.isChecked = modulePrefs.getBoolean(REMOVE_DEV_NOTIFY, default = true) - crcpNotifyConfigSwitch.isChecked = modulePrefs.getBoolean(REMOVE_CHANGECP_NOTIFY, default = false) - dndNotifyConfigSwitch.isChecked = modulePrefs.getBoolean(REMOVE_DNDALERT_NOTIFY, default = false) - a12StyleConfigSwitch.isChecked = modulePrefs.getBoolean(ENABLE_ANDROID12_STYLE, isUpperOfAndroidS) - moduleEnableSwitch.isChecked = modulePrefs.getBoolean(ENABLE_MODULE, default = true) - moduleEnableLogSwitch.isChecked = modulePrefs.getBoolean(ENABLE_MODULE_LOG, default = false) - hideIconInLauncherSwitch.isChecked = modulePrefs.getBoolean(ENABLE_HIDE_ICON) - notifyIconFixSwitch.isChecked = modulePrefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true) - moduleEnableSwitch.setOnCheckedChangeListener { btn, b -> + binding.devNotifyConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true) + binding.a12StyleConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true) + binding.notifyIconConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true) + binding.notifyIconFixButton.isVisible = modulePrefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true) + binding.devNotifyConfigSwitch.isChecked = modulePrefs.getBoolean(REMOVE_DEV_NOTIFY, default = true) + binding.crcpNotifyConfigSwitch.isChecked = modulePrefs.getBoolean(REMOVE_CHANGECP_NOTIFY, default = false) + binding.dndNotifyConfigSwitch.isChecked = modulePrefs.getBoolean(REMOVE_DNDALERT_NOTIFY, default = false) + binding.a12StyleConfigSwitch.isChecked = modulePrefs.getBoolean(ENABLE_ANDROID12_STYLE, isUpperOfAndroidS) + binding.moduleEnableSwitch.isChecked = modulePrefs.getBoolean(ENABLE_MODULE, default = true) + binding.moduleEnableLogSwitch.isChecked = modulePrefs.getBoolean(ENABLE_MODULE_LOG, default = false) + binding.hideIconInLauncherSwitch.isChecked = modulePrefs.getBoolean(ENABLE_HIDE_ICON) + binding.notifyIconFixSwitch.isChecked = modulePrefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true) + binding.moduleEnableSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(ENABLE_MODULE, b) - moduleEnableLogSwitch.isVisible = b - notifyIconConfigItem.isVisible = b - devNotifyConfigItem.isVisible = b - a12StyleConfigItem.isVisible = b + binding.moduleEnableLogSwitch.isVisible = b + binding.notifyIconConfigItem.isVisible = b + binding.devNotifyConfigItem.isVisible = b + binding.a12StyleConfigItem.isVisible = b SystemUITool.showNeedRestartSnake(context = this) } - moduleEnableLogSwitch.setOnCheckedChangeListener { btn, b -> + binding.moduleEnableLogSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(ENABLE_MODULE_LOG, b) SystemUITool.showNeedRestartSnake(context = this) } - hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b -> + binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(ENABLE_HIDE_ICON, b) packageManager.setComponentEnabledSetting( @@ -151,42 +150,42 @@ class MainActivity : BaseActivity() { PackageManager.DONT_KILL_APP ) } - notifyIconFixSwitch.setOnCheckedChangeListener { btn, b -> + binding.notifyIconFixSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(ENABLE_NOTIFY_ICON_FIX, b) - notifyIconFixButton.isVisible = b + binding.notifyIconFixButton.isVisible = b SystemUITool.showNeedRestartSnake(context = this) } - devNotifyConfigSwitch.setOnCheckedChangeListener { btn, b -> + binding.devNotifyConfigSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(REMOVE_DEV_NOTIFY, b) SystemUITool.showNeedRestartSnake(context = this) } - crcpNotifyConfigSwitch.setOnCheckedChangeListener { btn, b -> + binding.crcpNotifyConfigSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(REMOVE_CHANGECP_NOTIFY, b) SystemUITool.showNeedRestartSnake(context = this) } - dndNotifyConfigSwitch.setOnCheckedChangeListener { btn, b -> + binding.dndNotifyConfigSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(REMOVE_DNDALERT_NOTIFY, b) SystemUITool.showNeedRestartSnake(context = this) } - a12StyleConfigSwitch.setOnCheckedChangeListener { btn, b -> + binding.a12StyleConfigSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener modulePrefs.putBoolean(ENABLE_ANDROID12_STYLE, b) SystemUITool.showNeedRestartSnake(context = this) } /** 通知图标优化名单按钮点击事件 */ - notifyIconFixButton.setOnClickListener { startActivity(Intent(this, ConfigureActivity::class.java)) } + binding.notifyIconFixButton.setOnClickListener { navigate() } /** 重启按钮点击事件 */ - findViewById(R.id.title_restart_icon).setOnClickListener { SystemUITool.restartSystemUI(context = this) } + binding.titleRestartIcon.setOnClickListener { SystemUITool.restartSystemUI(context = this) } /** 项目地址按钮点击事件 */ - findViewById(R.id.title_github_icon).setOnClickListener { + binding.titleGithubIcon.setOnClickListener { openBrowser(url = "https://github.com/fankes/ColorOSNotifyIcon") } /** 恰饭! */ - findViewById(R.id.link_with_follow_me).setOnClickListener { + binding.linkWithFollowMe.setOnClickListener { openBrowser(url = "https://www.coolapk.com/u/876977", packageName = "com.coolapk.market") } } diff --git a/app/src/main/java/com/fankes/coloros/notify/ui/activity/base/BaseActivity.kt b/app/src/main/java/com/fankes/coloros/notify/ui/activity/base/BaseActivity.kt index bb78f45..fe319df 100644 --- a/app/src/main/java/com/fankes/coloros/notify/ui/activity/base/BaseActivity.kt +++ b/app/src/main/java/com/fankes/coloros/notify/ui/activity/base/BaseActivity.kt @@ -20,18 +20,36 @@ * * This file is Created by fankes on 2022/1/30. */ +@file:Suppress("UNCHECKED_CAST") + package com.fankes.coloros.notify.ui.activity.base import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import androidx.viewbinding.ViewBinding import com.fankes.coloros.notify.R import com.fankes.coloros.notify.utils.factory.isNotSystemInDarkMode import com.gyf.immersionbar.ktx.immersionBar +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass +import java.lang.reflect.ParameterizedType -abstract class BaseActivity : AppCompatActivity() { +abstract class BaseActivity : AppCompatActivity() { + + /** 获取绑定布局对象 */ + lateinit var binding: VB override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + javaClass.genericSuperclass.also { type -> + if (type is ParameterizedType) { + binding = (type.actualTypeArguments[0] as Class).method { + name = "inflate" + param(LayoutInflaterClass) + }.get().invoke(layoutInflater) ?: error("binding failed") + setContentView(binding.root) + } else error("binding but got wrong type") + } /** 隐藏系统的标题栏 */ supportActionBar?.hide() /** 初始化沉浸状态栏 */ @@ -43,5 +61,10 @@ abstract class BaseActivity : AppCompatActivity() { navigationBarDarkIcon(isNotSystemInDarkMode) fitsSystemWindows(true) } + /** 装载子类 */ + onCreate() } + + /** 回调 [onCreate] 方法 */ + abstract fun onCreate() } \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/factory/DialogBuilderFactory.kt b/app/src/main/java/com/fankes/coloros/notify/utils/factory/DialogBuilderFactory.kt index a6223a7..7c50450 100644 --- a/app/src/main/java/com/fankes/coloros/notify/utils/factory/DialogBuilderFactory.kt +++ b/app/src/main/java/com/fankes/coloros/notify/utils/factory/DialogBuilderFactory.kt @@ -20,94 +20,182 @@ * * This file is Created by fankes on 2022/1/7. */ -@file:Suppress("unused", "DEPRECATION") +@file:Suppress("unused", "OPT_IN_USAGE", "EXPERIMENTAL_API_USAGE") package com.fankes.coloros.notify.utils.factory -import android.app.AlertDialog +import android.app.Dialog import android.content.Context -import android.util.DisplayMetrics +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.view.Gravity import android.view.LayoutInflater import android.view.View -import android.view.WindowManager -import kotlin.math.round +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.ProgressBar +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.viewbinding.ViewBinding +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.highcapable.yukihookapi.annotation.DoNotUseField +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass /** * 构造对话框 + * @param isUseBlackTheme 是否使用深色主题 * @param it 对话框方法体 */ -fun Context.showDialog(it: DialogBuilder.() -> Unit) = DialogBuilder(this).apply(it).show() +fun Context.showDialog(isUseBlackTheme: Boolean = false, it: DialogBuilder.() -> Unit) = + DialogBuilder(this, isUseBlackTheme).apply(it).show() /** * 对话框构造器 * @param context 实例 + * @param isUseBlackTheme 是否使用深色主题 - 对 AndroidX 风格无效 */ -class DialogBuilder(private val context: Context) { +class DialogBuilder(val context: Context, private val isUseBlackTheme: Boolean) { - private var instance: AlertDialog.Builder? = null // 实例对象 + private var instanceAndroidX: androidx.appcompat.app.AlertDialog.Builder? = null // 实例对象 + private var instanceAndroid: android.app.AlertDialog.Builder? = null // 实例对象 - private var customLayoutView: View? = null // 自定义布局 + private var dialogInstance: Dialog? = null // 对话框实例 + + @DoNotUseField + var customLayoutView: View? = null // 自定义布局 + + /** + * 是否需要使用 AndroidX 风格对话框 + * @return [Boolean] + */ + private val isUsingAndroidX get() = runCatching { context is AppCompatActivity }.getOrNull() ?: false init { - instance = AlertDialog.Builder(context, android.R.style.Theme_Material_Light_Dialog) + if (isUsingAndroidX) + runCatching { instanceAndroidX = MaterialAlertDialogBuilder(context) } + else runCatching { + instanceAndroid = android.app.AlertDialog.Builder( + context, + if (isUseBlackTheme) android.R.style.Theme_Material_Dialog else android.R.style.Theme_Material_Light_Dialog + ) + } } /** 设置对话框不可关闭 */ - fun noCancelable() = instance?.setCancelable(false) + fun noCancelable() { + if (isUsingAndroidX) + runCatching { instanceAndroidX?.setCancelable(false) } + else runCatching { instanceAndroid?.setCancelable(false) } + } /** 设置对话框标题 */ var title get() = "" set(value) { - instance?.setTitle(value) + if (isUsingAndroidX) + runCatching { instanceAndroidX?.setTitle(value) } + else runCatching { instanceAndroid?.setTitle(value) } } /** 设置对话框消息内容 */ var msg get() = "" set(value) { - instance?.setMessage(value) + if (isUsingAndroidX) + runCatching { instanceAndroidX?.setMessage(value) } + else runCatching { instanceAndroid?.setMessage(value) } + } + + /** 设置进度条对话框消息内容 */ + var progressContent + get() = "" + set(value) { + if (customLayoutView == null) + customLayoutView = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER or Gravity.START + addView(ProgressBar(context)) + addView(View(context).apply { layoutParams = ViewGroup.LayoutParams(20.dp(context), 5) }) + addView(TextView(context).apply { + tag = "progressContent" + text = value + }) + setPadding(20.dp(context), 20.dp(context), 20.dp(context), 20.dp(context)) + } + else customLayoutView?.findViewWithTag("progressContent")?.text = value } /** * 设置对话框自定义布局 - * @param resId 属性资源 Id - * @return [View] + * @return [ViewBinding] */ - fun addView(resId: Int): View { - customLayoutView = LayoutInflater.from(context).inflate(resId, null) - return customLayoutView ?: error("Inflate $resId failed") - } + inline fun bind() = + T::class.java.method { + name = "inflate" + param(LayoutInflaterClass) + }.get().invoke(LayoutInflater.from(context))?.apply { + customLayoutView = root + } ?: error("binding failed") /** * 设置对话框确定按钮 * @param text 按钮文本内容 * @param it 点击事件 */ - fun confirmButton(text: String = "确定", it: () -> Unit = {}) = - instance?.setPositiveButton(text) { _, _ -> it() } + fun confirmButton(text: String = "确定", it: () -> Unit = {}) { + if (isUsingAndroidX) + runCatching { instanceAndroidX?.setPositiveButton(text) { _, _ -> it() } } + else runCatching { instanceAndroid?.setPositiveButton(text) { _, _ -> it() } } + } /** * 设置对话框取消按钮 * @param text 按钮文本内容 * @param it 点击事件 */ - fun cancelButton(text: String = "取消", it: () -> Unit = {}) = - instance?.setNegativeButton(text) { _, _ -> it() } + fun cancelButton(text: String = "取消", it: () -> Unit = {}) { + if (isUsingAndroidX) + runCatching { instanceAndroidX?.setNegativeButton(text) { _, _ -> it() } } + else runCatching { instanceAndroid?.setNegativeButton(text) { _, _ -> it() } } + } /** * 设置对话框第三个按钮 * @param text 按钮文本内容 * @param it 点击事件 */ - fun neutralButton(text: String = "更多", it: () -> Unit = {}) = - instance?.setNeutralButton(text) { _, _ -> it() } + fun neutralButton(text: String = "更多", it: () -> Unit = {}) { + if (isUsingAndroidX) + runCatching { instanceAndroidX?.setNeutralButton(text) { _, _ -> it() } } + else runCatching { instanceAndroid?.setNeutralButton(text) { _, _ -> it() } } + } + + /** 取消对话框 */ + fun cancel() = dialogInstance?.cancel() /** 显示对话框 */ - internal fun show() = instance?.create()?.apply { - val dm = DisplayMetrics() - (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getMetrics(dm) - customLayoutView?.let { setView(it.apply { minimumWidth = round(x = dm.widthPixels / 1.3).toInt() }) } - setDefaultStyle(context = this@DialogBuilder.context) - }?.show() + internal fun show() { + if (isUsingAndroidX) runCatching { + instanceAndroidX?.create()?.apply { + customLayoutView?.let { setView(it) } + dialogInstance = this + }?.show() + } else runCatching { + instanceAndroid?.create()?.apply { + customLayoutView?.let { setView(it) } + window?.setBackgroundDrawable( + GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, + if (isUseBlackTheme) intArrayOf(0xFF2D2D2D.toInt(), 0xFF2D2D2D.toInt()) + else intArrayOf(Color.WHITE, Color.WHITE) + ).apply { + shape = GradientDrawable.RECTANGLE + gradientType = GradientDrawable.LINEAR_GRADIENT + cornerRadius = 15.dpFloat(this@DialogBuilder.context) + }) + dialogInstance = this + }?.show() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/factory/FunctionFactory.kt b/app/src/main/java/com/fankes/coloros/notify/utils/factory/FunctionFactory.kt index 273f8c0..ced8b35 100644 --- a/app/src/main/java/com/fankes/coloros/notify/utils/factory/FunctionFactory.kt +++ b/app/src/main/java/com/fankes/coloros/notify/utils/factory/FunctionFactory.kt @@ -25,7 +25,6 @@ package com.fankes.coloros.notify.utils.factory import android.app.Activity -import android.app.AlertDialog import android.content.ClipData import android.content.ClipboardManager import android.content.Context @@ -36,11 +35,11 @@ import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Color -import android.graphics.drawable.GradientDrawable import android.net.Uri import android.os.Build import android.util.Base64 import android.widget.Toast +import androidx.core.app.NotificationManagerCompat import com.fankes.coloros.notify.application.CNNApplication.Companion.appContext import com.google.android.material.snackbar.Snackbar import com.highcapable.yukihookapi.hook.factory.classOf @@ -144,6 +143,12 @@ val Context.versionName get() = packageInfo.versionName ?: "" */ val Context.versionCode get() = packageInfo.versionCode +/** + * 是否关闭了通知权限 + * @return [Boolean] + */ +val isNotNoificationEnabled get() = !NotificationManagerCompat.from(appContext).areNotificationsEnabled() + /** * dp 转换为 pxInt * @param context 使用的实例 @@ -158,6 +163,18 @@ fun Number.dp(context: Context) = dpFloat(context).toInt() */ fun Number.dpFloat(context: Context) = toFloat() * context.resources.displayMetrics.density +/** + * 是否为白色 + * @return [Boolean] + */ +val Int.isWhite + get() = safeOfTrue { + val r = this and 0xff0000 shr 16 + val g = this and 0x00ff00 shr 8 + val b = this and 0x0000ff + (0.2126 * r + 0.7152 * g + 0.0722 * b) >= 128 + } + /** * Base64 加密 * @return [String] @@ -193,18 +210,6 @@ val ByteArray.bitmap: Bitmap get() = BitmapFactory.decodeByteArray(this, 0, size */ val String.bitmap: Bitmap get() = unbase64.bitmap -/** - * 设置对话框默认风格 - * @param context 使用的实例 - */ -fun AlertDialog.setDefaultStyle(context: Context) = window?.setBackgroundDrawable(GradientDrawable( - GradientDrawable.Orientation.TOP_BOTTOM, intArrayOf(Color.WHITE, Color.WHITE) -).apply { - shape = GradientDrawable.RECTANGLE - gradientType = GradientDrawable.LINEAR_GRADIENT - cornerRadius = 15.dp(context).toFloat() -}) - /** * 获取系统 Prop 值 * @param key Key @@ -235,6 +240,13 @@ fun execShellSu(cmd: String) = safeOfNothing { */ fun toast(msg: String) = Toast.makeText(appContext, msg, Toast.LENGTH_SHORT).show() +/** + * 跳转到指定页面 + * + * [T] 为指定的 [Activity] + */ +inline fun Context.navigate() = startActivity(Intent(this, T::class.java)) + /** * 弹出 [Snackbar] * @param msg 提示内容 diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/tool/ClientRequestTool.kt b/app/src/main/java/com/fankes/coloros/notify/utils/tool/ClientRequestTool.kt deleted file mode 100644 index 62913ac..0000000 --- a/app/src/main/java/com/fankes/coloros/notify/utils/tool/ClientRequestTool.kt +++ /dev/null @@ -1,150 +0,0 @@ -/* - * ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications. - * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) - * https://github.com/fankes/ColorOSNotifyIcon - * - * This software is non-free but opensource software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - *

- * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * and eula along with this software. If not, see - * - * - * This file is Created by fankes on 2022/2/25. - */ -@file:Suppress("TrustAllX509TrustManager", "CustomX509TrustManager", "DEPRECATION") - -package com.fankes.coloros.notify.utils.tool - -import android.app.Activity -import android.app.ProgressDialog -import android.content.Intent -import android.net.Uri -import android.provider.Settings -import com.fankes.coloros.notify.utils.factory.safeOfNull -import com.fankes.coloros.notify.utils.factory.setDefaultStyle -import com.fankes.coloros.notify.utils.factory.showDialog -import com.fankes.coloros.notify.utils.factory.snake -import com.highcapable.yukihookapi.hook.log.loggerD -import okhttp3.* -import java.io.IOException -import java.security.SecureRandom -import java.security.cert.X509Certificate -import javax.net.ssl.* - -/** - * 网络请求管理类 - */ -object ClientRequestTool { - - /** - * 检查网络连接情况 - * @param context 实例 - * @param it 已连接回调 - */ - fun checkingInternetConnect(context: Activity, it: () -> Unit) = - ProgressDialog(context).apply { - setDefaultStyle(context) - setCancelable(false) - setTitle("准备中") - setMessage("正在检查网络连接情况") - }.apply { - wait(context, url = "https://www.baidu.com") { isDone, _ -> - cancel() - if (isDone) it() else - context.showDialog { - title = "网络不可用" - msg = "无法连接到互联网,请检查你当前的设备是否可以上网,且没有在手机管家中禁用本模块的联网权限。" - confirmButton(text = "检查设置") { - runCatching { - context.startActivity(Intent().apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS - data = Uri.fromParts("package", context.packageName, null) - }) - }.onFailure { context.snake(msg = "启动应用信息页面失败") } - } - cancelButton() - } - } - }.show() - - /** - * 发送 GET 请求内容并等待 - * @param context 实例 - * @param url 请求地址 - * @param it 回调 - ([Boolean] 是否成功,[String] 成功的内容或失败消息) - */ - fun wait(context: Activity, url: String, it: (Boolean, String) -> Unit) = runCatching { - OkHttpClient().newBuilder().apply { - SSLSocketClient.sSLSocketFactory?.let { sslSocketFactory(it, SSLSocketClient.trustManager) } - hostnameVerifier(SSLSocketClient.hostnameVerifier) - }.build().newCall( - Request.Builder() - .url(url) - .get() - .build() - ).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - context.runOnUiThread { it(false, e.toString()) } - } - - override fun onResponse(call: Call, response: Response) { - val bodyString = response.body?.string() ?: "" - context.runOnUiThread { it(true, bodyString) } - } - }) - }.onFailure { it(false, "URL 无效") } - - /** - * 自动信任 SSL 证书 - * - * 放行全部加密 SSL 请求 - */ - object SSLSocketClient { - - /** - * 格式化实例 - * @return [SSLSocketFactory] or null - */ - val sSLSocketFactory - get() = safeOfNull { - SSLContext.getInstance("TLS").let { - it.init(null, arrayOf(trustManager), SecureRandom()) - it.socketFactory - } - } - - /** - * 使用的实例 - * @return [HostnameVerifier] - */ - val hostnameVerifier get() = HostnameVerifier { _, _ -> true } - - /** - * 信任管理者 - * @return [X509TrustManager] - */ - val trustManager - get() = object : X509TrustManager { - - override fun checkClientTrusted(chain: Array?, authType: String?) { - loggerD(msg = "TrustX509 --> $authType") - } - - override fun checkServerTrusted(chain: Array?, authType: String?) { - loggerD(msg = "TrustX509 --> $authType") - } - - override fun getAcceptedIssuers() = arrayOf() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/coloros/notify/utils/tool/IconRuleManagerTool.kt b/app/src/main/java/com/fankes/coloros/notify/utils/tool/IconRuleManagerTool.kt new file mode 100644 index 0000000..628906d --- /dev/null +++ b/app/src/main/java/com/fankes/coloros/notify/utils/tool/IconRuleManagerTool.kt @@ -0,0 +1,439 @@ +/* + * ColorOSNotifyIcon - Optimize notification icons for ColorOS and adapt to native notification icon specifications. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/fankes/ColorOSNotifyIcon + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + *

+ * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/2/25. + */ +@file:Suppress("TrustAllX509TrustManager", "CustomX509TrustManager", "DEPRECATION", "IMPLICIT_CAST_TO_ANY") + +package com.fankes.coloros.notify.utils.tool + +import android.app.Activity +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.Settings +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.NotificationCompat +import androidx.core.content.getSystemService +import androidx.core.view.isVisible +import androidx.core.widget.doOnTextChanged +import com.fankes.coloros.notify.R +import com.fankes.coloros.notify.databinding.DiaSourceFromBinding +import com.fankes.coloros.notify.databinding.DiaSourceFromStringBinding +import com.fankes.coloros.notify.hook.HookConst +import com.fankes.coloros.notify.param.IconPackParams +import com.fankes.coloros.notify.ui.activity.ConfigureActivity +import com.fankes.coloros.notify.utils.factory.openBrowser +import com.fankes.coloros.notify.utils.factory.safeOfNull +import com.fankes.coloros.notify.utils.factory.showDialog +import com.fankes.coloros.notify.utils.factory.snake +import com.highcapable.yukihookapi.hook.factory.modulePrefs +import com.highcapable.yukihookapi.hook.log.loggerD +import okhttp3.* +import java.io.IOException +import java.security.SecureRandom +import java.security.cert.X509Certificate +import javax.net.ssl.* + +/** + * 通知图标在线规则管理类 + */ +object IconRuleManagerTool { + + /** 当前规则的系统名称 */ + private const val OS_TAG = "ColorOS" + + /** 当前规则的通知图标颜色 */ + private const val OS_COLOR = 0xFF4E8A5A + + /** + * 从在线地址手动同步规则 + * @param context 实例 + * @param it 成功后回调 + */ + fun syncByHand(context: Context, it: () -> Unit) = + context.showDialog { + title = "同步列表" + var sourceType = context.modulePrefs.getInt(HookConst.SOURCE_SYNC_WAY, HookConst.TYPE_SOURCE_SYNC_WAY_1) + var customUrl = context.modulePrefs.getString(HookConst.SOURCE_SYNC_WAY_CUSTOM_URL) + bind().apply { + diaSfText.apply { + if (customUrl.isNotBlank()) { + setText(customUrl) + setSelection(customUrl.length) + } + doOnTextChanged { text, _, _, _ -> + customUrl = text.toString() + context.modulePrefs.putString(HookConst.SOURCE_SYNC_WAY_CUSTOM_URL, text.toString()) + } + } + diaSfTextLin.isVisible = sourceType == HookConst.TYPE_SOURCE_SYNC_WAY_3 + diaSfRd1.isChecked = sourceType == HookConst.TYPE_SOURCE_SYNC_WAY_1 + diaSfRd2.isChecked = sourceType == HookConst.TYPE_SOURCE_SYNC_WAY_2 + diaSfRd3.isChecked = sourceType == HookConst.TYPE_SOURCE_SYNC_WAY_3 + diaSfRd1.setOnClickListener { + diaSfRd2.isChecked = false + diaSfRd3.isChecked = false + diaSfTextLin.isVisible = false + sourceType = HookConst.TYPE_SOURCE_SYNC_WAY_1 + context.modulePrefs.putInt(HookConst.SOURCE_SYNC_WAY, HookConst.TYPE_SOURCE_SYNC_WAY_1) + } + diaSfRd2.setOnClickListener { + diaSfRd1.isChecked = false + diaSfRd3.isChecked = false + diaSfTextLin.isVisible = false + sourceType = HookConst.TYPE_SOURCE_SYNC_WAY_2 + context.modulePrefs.putInt(HookConst.SOURCE_SYNC_WAY, HookConst.TYPE_SOURCE_SYNC_WAY_2) + } + diaSfRd3.setOnClickListener { + diaSfRd1.isChecked = false + diaSfRd2.isChecked = false + diaSfTextLin.isVisible = true + sourceType = HookConst.TYPE_SOURCE_SYNC_WAY_3 + context.modulePrefs.putInt(HookConst.SOURCE_SYNC_WAY, HookConst.TYPE_SOURCE_SYNC_WAY_3) + } + } + confirmButton { sync(context, it) } + cancelButton() + neutralButton(text = "自定义规则") { + context.showDialog { + title = "自定义规则" + val editText = bind().diaSfsInputEdit.apply { + requestFocus() + invalidate() + } + IconPackParams(context).also { params -> + confirmButton(text = "合并") { + editText.text.toString().also { jsonString -> + when { + jsonString.isNotBlank() && params.isNotVaildJson(jsonString) -> context.snake(msg = "不是有效的 JSON 数据") + jsonString.isNotBlank() -> { + params.save( + params.splicingJsonArray( + dataJson1 = params.storageDataJson ?: "[]", + dataJson2 = jsonString.takeIf { params.isJsonArray(it) } ?: "[$jsonString]" + ) + ) + it() + } + else -> context.snake(msg = "请输入有效内容") + } + } + } + cancelButton(text = "覆盖") { + editText.text.toString().also { jsonString -> + when { + jsonString.isNotBlank() && params.isNotVaildJson(jsonString) -> context.snake(msg = "不是有效的 JSON 数据") + jsonString.isNotBlank() -> { + params.save(dataJson = jsonString.takeIf { params.isJsonArray(it) } ?: "[$jsonString]") + it() + } + else -> context.snake(msg = "请输入有效内容") + } + } + } + } + neutralButton(text = "取消") + } + } + } + + /** + * 从在线地址同步规则 + * @param context 实例 + * @param it 成功后回调 + */ + fun sync(context: Context, it: () -> Unit) { + val sourceType = context.modulePrefs.getInt(HookConst.SOURCE_SYNC_WAY, HookConst.TYPE_SOURCE_SYNC_WAY_1) + val customUrl = context.modulePrefs.getString(HookConst.SOURCE_SYNC_WAY_CUSTOM_URL) + when (sourceType) { + HookConst.TYPE_SOURCE_SYNC_WAY_1 -> + onRefreshing(context, url = "https://raw.fastgit.org/fankes/AndroidNotifyIconAdapt/main", it) + HookConst.TYPE_SOURCE_SYNC_WAY_2 -> + onRefreshing(context, url = "https://raw.githubusercontent.com/fankes/AndroidNotifyIconAdapt/main", it) + HookConst.TYPE_SOURCE_SYNC_WAY_3 -> + if (customUrl.isNotBlank()) + if (customUrl.startsWith("http://") || customUrl.startsWith("https://")) + onRefreshingCustom(context, customUrl, it) + else context.snakeOrNotify(title = "同步失败", msg = "同步地址不是一个合法的 URL") + else context.snakeOrNotify(title = "同步失败", msg = "同步地址不能为空") + else -> context.snakeOrNotify(title = "同步异常", msg = "同步类型错误") + } + } + + /** + * 开始更新数据 + * @param context 实例 + * @param url + * @param it 成功后回调 + */ + private fun onRefreshing(context: Context, url: String, it: () -> Unit) = checkingInternetConnect(context) { + fun doParse(callback: (Boolean) -> Unit = {}) { + wait(context, url = "$url/OS/$OS_TAG/NotifyIconsSupportConfig.json") { isDone1, ctOS -> + callback(true) + wait(context, url = "$url/APP/NotifyIconsSupportConfig.json") { isDone2, ctAPP -> + callback(false) + IconPackParams(context).also { params -> + when { + isDone1 && isDone2 -> params.splicingJsonArray(ctOS, ctAPP).also { + when { + params.isHackString(it) -> + context.snakeOrNotify(title = "同步错误", msg = "请求需要验证,请尝试魔法上网或关闭魔法") + params.isNotVaildJson(it) -> + context.snakeOrNotify(title = "同步错误", msg = "目标地址不是有效的 JSON 数据") + params.isCompareDifferent(it) -> { + params.save(it) + pushNotify(context, title = "同步完成", msg = "已更新通知图标优化名单,点击查看") + it() + } + else -> (if (context is AppCompatActivity) context.snake(msg = "列表数据已是最新")) + } + } + context is AppCompatActivity -> + context.showDialog { + title = "连接失败" + msg = "连接失败,错误如下:\n${if (!isDone1) ctOS else ctAPP}" + confirmButton(text = "解决方案") { + context.openBrowser(url = "https://www.baidu.com/s?wd=github%2Braw%2B%E6%97%A0%E6%B3%95%E8%AE%BF%E9%97%AE") + } + cancelButton() + } + else -> pushNotify(context, title = "同步地址不可用", msg = if (!isDone1) ctOS else ctAPP) + } + } + } + } + } + if (context is AppCompatActivity) + context.showDialog { + title = "同步中" + progressContent = "正在同步 OS 数据" + noCancelable() + doParse { if (it) progressContent = "正在同步 APP 数据" else cancel() } + } + else doParse() + } + + /** + * 开始更新数据 + * @param context 实例 + * @param url + * @param it 成功后回调 + */ + private fun onRefreshingCustom(context: Context, url: String, it: () -> Unit) = checkingInternetConnect(context) { + fun doParse(callback: () -> Unit = {}) { + wait(context, url) { isDone, content -> + callback() + IconPackParams(context).also { params -> + when { + isDone -> when { + params.isHackString(content) -> + context.snakeOrNotify(title = "同步错误", msg = "请求需要验证,请尝试魔法上网或关闭魔法") + params.isNotVaildJson(content) -> + context.snakeOrNotify(title = "同步错误", msg = "目标地址不是有效的 JSON 数据") + params.isCompareDifferent(content) -> { + params.save(content) + pushNotify(context, title = "同步完成", msg = "已更新通知图标优化名单,点击查看") + it() + } + else -> (if (context is AppCompatActivity) context.snake(msg = "列表数据已是最新")) + } + context is AppCompatActivity -> + context.showDialog { + title = "连接失败" + msg = "连接失败,错误如下:\n$content" + confirmButton(text = "我知道了") + } + else -> pushNotify(context, title = "同步地址不可用", msg = content) + } + } + } + } + if (context is AppCompatActivity) + context.showDialog { + title = "同步中" + progressContent = "正在通过自定义地址同步数据" + noCancelable() + doParse { cancel() } + } + else doParse() + } + + /** + * 检查网络连接情况 + * @param context 实例 + * @param it 已连接回调 + */ + private fun checkingInternetConnect(context: Context, it: () -> Unit) = + if (context is AppCompatActivity) context.showDialog { + title = "准备中" + progressContent = "正在检查网络连接情况" + noCancelable() + baseCheckingInternetConnect(context) { isDone -> + cancel() + if (isDone) it() else + context.showDialog { + title = "网络不可用" + msg = "无法连接到互联网,请检查你当前的设备是否可以上网,且没有在手机管家中禁用本模块的联网权限。" + confirmButton(text = "检查设置") { + runCatching { + context.startActivity(Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + data = Uri.fromParts("package", context.packageName, null) + }) + }.onFailure { context.snake(msg = "启动应用信息页面失败") } + } + cancelButton() + } + } + } else baseCheckingInternetConnect(context) { isDone -> + if (isDone) it() else pushNotify(context, title = "网络不可用", msg = "无法连接到互联网,无法更新通知图标规则") + } + + /** + * 检查网络连接情况 + * @param context 实例 + * @param it 已连接回调 + */ + private fun baseCheckingInternetConnect(context: Context, it: (Boolean) -> Unit) = + wait(context, url = "https://www.baidu.com") { isDone, _ -> it(isDone) } + + /** + * 发送 GET 请求内容并等待 + * @param context 实例 + * @param url 请求地址 + * @param it 回调 - ([Boolean] 是否成功,[String] 成功的内容或失败消息) + */ + private fun wait(context: Context, url: String, it: (Boolean, String) -> Unit) = runCatching { + OkHttpClient().newBuilder().apply { + SSLSocketClient.sSLSocketFactory?.let { sslSocketFactory(it, SSLSocketClient.trustManager) } + hostnameVerifier(SSLSocketClient.hostnameVerifier) + }.build().newCall( + Request.Builder() + .url(url) + .get() + .build() + ).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + (context as? Activity?)?.runOnUiThread { it(false, e.toString()) } ?: it(false, e.toString()) + } + + override fun onResponse(call: Call, response: Response) { + val bodyString = response.body?.string() ?: "" + (context as? Activity?)?.runOnUiThread { it(true, bodyString) } ?: it(true, bodyString) + } + }) + }.onFailure { it(false, "URL 无效") } + + /** + * 根据实例类型决定推送通知还是弹出提示 + * @param title 标题 - 仅对通知生效 + * @param msg 消息内容 + */ + private fun Context.snakeOrNotify(title: String, msg: String) { + if (this !is AppCompatActivity) + pushNotify(context = this, title, msg) + else snake(msg) + } + + /** + * 推送通知 + * @param context 实例 - 类型为 [AppCompatActivity] 时将不会推送通知 + * @param title 通知标题 + * @param msg 通知消息 + */ + private fun pushNotify(context: Context, title: String, msg: String) { + if (context !is AppCompatActivity) + context.getSystemService()?.apply { + areNotificationsEnabled() + createNotificationChannel( + NotificationChannel( + "notifyRuleUpdateId", "通知图标优化规则", + NotificationManager.IMPORTANCE_DEFAULT + ) + ) + notify(0, NotificationCompat.Builder(context, "notifyRuleUpdateId").apply { + setContentTitle(title) + setContentText(msg) + color = OS_COLOR.toInt() + setAutoCancel(true) + setSmallIcon(R.drawable.ic_nf_icon_update) + setSound(null) + setDefaults(NotificationCompat.DEFAULT_ALL) + setContentIntent( + PendingIntent.getActivity( + context, 1, + Intent(context, ConfigureActivity::class.java).apply { putExtra("isShowUpdDialog", false) }, + if (Build.VERSION.SDK_INT < 31) PendingIntent.FLAG_UPDATE_CURRENT else PendingIntent.FLAG_IMMUTABLE + ) + ) + }.build()) + } + } + + /** + * 自动信任 SSL 证书 + * + * 放行全部加密 SSL 请求 + */ + private object SSLSocketClient { + + /** + * 格式化实例 + * @return [SSLSocketFactory] or null + */ + val sSLSocketFactory + get() = safeOfNull { + SSLContext.getInstance("TLS").let { + it.init(null, arrayOf(trustManager), SecureRandom()) + it.socketFactory + } + } + + /** + * 使用的实例 + * @return [HostnameVerifier] + */ + val hostnameVerifier get() = HostnameVerifier { _, _ -> true } + + /** + * 信任管理者 + * @return [X509TrustManager] + */ + val trustManager + get() = object : X509TrustManager { + + override fun checkClientTrusted(chain: Array?, authType: String?) { + loggerD(msg = "TrustX509 --> $authType") + } + + override fun checkServerTrusted(chain: Array?, authType: String?) { + loggerD(msg = "TrustX509 --> $authType") + } + + override fun getAcceptedIssuers() = arrayOf() + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_nf_icon_update.xml b/app/src/main/res/drawable/ic_nf_icon_update.xml new file mode 100644 index 0000000..684336c --- /dev/null +++ b/app/src/main/res/drawable/ic_nf_icon_update.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3ada26d..829845d 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -102,7 +102,7 @@ android:textSize="13sp" /> + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index ea58ec6..b451ac5 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file