mirror of
https://github.com/fankes/MIUINativeNotifyIcon.git
synced 2025-09-06 02:35:32 +08:00
Compare commits
388 Commits
Author | SHA1 | Date | |
---|---|---|---|
9465286ce1
|
|||
61849cebe0
|
|||
1f71fbda9a
|
|||
8f01e649fe
|
|||
ae15d72923
|
|||
3b82c33c2e
|
|||
621f973a2f
|
|||
ef2c8afe3f
|
|||
3d7e4d8825
|
|||
19b2efbb83
|
|||
d330510788
|
|||
25a23d7e1a
|
|||
db4b418ed4
|
|||
8a97e38d20
|
|||
|
f294120fa8 | ||
|
d244658aa6 | ||
|
445ac85d8d | ||
|
e3b22671c1 | ||
d574449e4a
|
|||
85a2e730e0
|
|||
ca367bde9e
|
|||
03ce7fbbe6
|
|||
88e3f79878
|
|||
ba67bd0a62
|
|||
0c0cf9bfba
|
|||
0e98ec16c7
|
|||
f1fd3f2679
|
|||
97065494b0
|
|||
b172d7a090
|
|||
b86988a310
|
|||
32f8ca9d63
|
|||
3cf67eb7fc
|
|||
9f39f297f9
|
|||
7feab98a1e
|
|||
b297b43dfb
|
|||
c7c03e891f
|
|||
5ddc5de475
|
|||
d09c11f026
|
|||
bf22cae957
|
|||
a08e085b2b
|
|||
9690e27511
|
|||
f9dde5f549
|
|||
8ddb938883
|
|||
8664d7b285
|
|||
d70d01cf6c
|
|||
4703d339b7
|
|||
76b1cc2f0b
|
|||
62ec1d16ae
|
|||
7db010c9a1
|
|||
e6fa34d7fd
|
|||
d4661abb71
|
|||
744d028160
|
|||
3f3a92f192
|
|||
49f18ef8c3
|
|||
73173cd7ab
|
|||
c181530ad7
|
|||
883325dc2f
|
|||
6fe1f0572c
|
|||
be8f558bf5
|
|||
175263c9aa
|
|||
f95fe45ce3
|
|||
42b0fa69b7
|
|||
d0a9554a28
|
|||
c500bdad43
|
|||
9bded26604
|
|||
c687c755f2
|
|||
b9e1d1abe7
|
|||
27cfcbada6
|
|||
f0c6dabf76
|
|||
208862bf62
|
|||
ee948f5327
|
|||
d62d0f99a2
|
|||
0e2b6c3df3
|
|||
b0c1f2bb8f
|
|||
53a7381824
|
|||
fb5f63f248
|
|||
49fa06a64d
|
|||
b17e39e27c
|
|||
3abe4d7800
|
|||
a5586c8505
|
|||
2325f0ea86
|
|||
63e4dd8299
|
|||
4c8b1b0d53
|
|||
881f4a46e5
|
|||
2de7455259
|
|||
d93492be80
|
|||
a7cdda61e9
|
|||
9fda7a975c
|
|||
a1819c6e1d
|
|||
fcd5a59047
|
|||
7e4c20ca05
|
|||
69f3637d22
|
|||
630b803fa4
|
|||
b2bb05c4e7
|
|||
d53c455dd2
|
|||
e0b5b462f9
|
|||
aa10d5491e
|
|||
098f6aaa3d
|
|||
69d66e8982
|
|||
629c402c44
|
|||
4707685cc2
|
|||
fc9d2a0d21
|
|||
d8e549b360
|
|||
741157336c
|
|||
596c4363d0
|
|||
1ffee75977
|
|||
2ff2f735a0
|
|||
74743e8d1e
|
|||
2982db6cc9
|
|||
68ccd4dafa
|
|||
e47359dfe9
|
|||
2289e3a5bf
|
|||
ee0c6a06ae
|
|||
d508dcd1e3
|
|||
241d2e9248
|
|||
0f10fb077f
|
|||
1ca26a76ef
|
|||
22a1fd63e7
|
|||
84ae130bc4
|
|||
6e4999da76
|
|||
54851231eb
|
|||
d5f366a82d
|
|||
b7ee52c65b
|
|||
c39343ba17
|
|||
4f54598ae2
|
|||
33b3cf6abc
|
|||
210c6898ab
|
|||
ec3f07791b
|
|||
0e435cf0e1
|
|||
ce29f65ce8
|
|||
baea7847c8
|
|||
1dd5a899f2
|
|||
3293b0f412
|
|||
7f0a951996
|
|||
50b32f6357
|
|||
1963537c09
|
|||
0a0c3ff28c
|
|||
de25660879
|
|||
3b174899bc
|
|||
d74f5c4654
|
|||
2f49c2bc6b
|
|||
4d4073b200
|
|||
a4081f27d5
|
|||
8499953588
|
|||
207279ff23
|
|||
29646fb127
|
|||
f3f157ea94
|
|||
41136687dd
|
|||
223a185358
|
|||
7c3d3d1bda
|
|||
7a35833bb6
|
|||
e2b274745d
|
|||
edc462e161
|
|||
5888d139b3
|
|||
|
790031dd4e | ||
|
d6cf855277 | ||
|
5a7ba08a27 | ||
|
244a5d2ec6 | ||
|
c7ce4d33bc | ||
2b9433df03
|
|||
f995d2a628
|
|||
0395e5a6de
|
|||
adbc126825
|
|||
1259a35747
|
|||
39fa9c38a8
|
|||
788d4605eb
|
|||
b986c2df18
|
|||
5c088ac243
|
|||
eb75c1c511
|
|||
efc192b0ee
|
|||
7b083daa3d
|
|||
6b18128a57
|
|||
553098f6bf
|
|||
9499727087
|
|||
0036ba0090
|
|||
2e62939181
|
|||
30acbff3d5
|
|||
093529baea
|
|||
d9d40ac08e
|
|||
99bd924c66
|
|||
d14b2cb551
|
|||
a864d4aa32
|
|||
12029cf550
|
|||
ac4544e992
|
|||
7f0597f034
|
|||
a7c02467de
|
|||
107569607d
|
|||
5d099d2ec6
|
|||
45da218cc8
|
|||
9a8cc9378c
|
|||
d7620f62b1
|
|||
cf9f81be23
|
|||
2e1929f36e
|
|||
cb6cbf0398
|
|||
dbdd0fc514
|
|||
21f4ff9ad3
|
|||
1bc79056a9
|
|||
41cb741dff
|
|||
6578027a34
|
|||
01cb647f0c
|
|||
171cd6a6d1
|
|||
02f8852ab0
|
|||
826805c552 | |||
627697d586 | |||
f5615ff89f | |||
dd45e57b00 | |||
d73239de73 | |||
deb0ca794f | |||
84b3648626 | |||
ae8fb6514c | |||
197f427adb | |||
58a23b4537 | |||
72bbdb69a8 | |||
679ae26462 | |||
eefa7ee9e7 | |||
217d5d9bc3 | |||
4d1e383ef5 | |||
25399cd88b | |||
18691d8b8d | |||
7c61b09054 | |||
7a7e179a2b | |||
dbd7dbf172 | |||
502e5dc04b | |||
ff0c57319a | |||
647a39c327 | |||
04e8d43356 | |||
f85d2db36e | |||
2e5284ebe9 | |||
abffc4d853 | |||
5d81b8ff87 | |||
d91d2736e6 | |||
72169989f2 | |||
c85da002f5 | |||
c09a1b5760 | |||
c30e56c5aa | |||
822e988c16 | |||
fc728a75a4 | |||
256e2ebfce | |||
be4b952f62 | |||
c7263c6d94 | |||
4733578912 | |||
87879301f0 | |||
c9be72e5f7 | |||
0b6f55c49a | |||
226d9395b6 | |||
9877bf902f | |||
0e43a7ce8c | |||
d256c8024d | |||
686671d2b8 | |||
b2031f95a4 | |||
d943aa6184 | |||
21aa563a5f | |||
009982c26f | |||
47461809dc | |||
e7dae611ba | |||
da1f390d39 | |||
48ec0f9a72 | |||
9393a87c63 | |||
2fde4ae833 | |||
bee5b21097 | |||
bdda054d3a | |||
66d578f172 | |||
c8e3e67ce0 | |||
378edf92eb | |||
7d4db23346 | |||
8f40f4b30c | |||
442e57fd0a | |||
6e3c627ad2 | |||
299e93b25c | |||
b8894e3bc0 | |||
85a49122d0 | |||
dba85cca89 | |||
6b10523fb0 | |||
048d7e4284 | |||
bc2dd8a973 | |||
646f30d05a | |||
361b07a909 | |||
8e3b575d38 | |||
dff2c4d0c8 | |||
6700ad96ad | |||
cb6fdefee0 | |||
3631d5e8c3 | |||
e5af34d6db | |||
abd7546dfe | |||
12ce701334 | |||
16e3780c97 | |||
ca27c17c46 | |||
cf80becf4e | |||
a3689b4aaa | |||
cd628e9329 | |||
a36b1a8ba2 | |||
5db1671a97 | |||
af3dace9d9 | |||
7616bf9370 | |||
b124355601 | |||
09a8f5644f | |||
387bdd197b | |||
3c8d358c63 | |||
8c9dcc1706 | |||
b9a9979ef8 | |||
8809573353 | |||
0b7cb8f567 | |||
1b49271fd2 | |||
9492ae6022 | |||
e509f0e6db | |||
b610eca3b2 | |||
3aff10fed5 | |||
f29a0a9c64 | |||
44dfb5abfe | |||
e80b95004a | |||
9b90004bff | |||
84bbaa5d8d | |||
2d98fd0e1a | |||
0f2da2077e | |||
b8a843906a | |||
2a76d8e3f0 | |||
f0f3a37402 | |||
3e50ca6db7 | |||
c27f124b25 | |||
|
09be1ea2c2 | ||
f5f90d712a | |||
7a1f139adc | |||
3ed382165b | |||
5173051e66 | |||
c30ee53796 | |||
ae2fcda709 | |||
1a077259d1 | |||
6e95239d94 | |||
c1b08c93d0 | |||
90dda0aa52 | |||
4699eb137e | |||
ef47eef989 | |||
7a5df6457f | |||
3d977998c1 | |||
014e073af6 | |||
0be6b9c910 | |||
3816aab335 | |||
3b77a1cd82 | |||
5c5fae4ef6 | |||
ea665fdc30 | |||
740a250a59 | |||
f433da040c | |||
00bf1acdb8 | |||
099886b0b2 | |||
4109a25b5d | |||
db93f8d48e | |||
e6cff940a7 | |||
3f7ea97812 | |||
8f8810e92a | |||
e65624c044 | |||
eabac2bd2d | |||
bde952c72c | |||
|
3e362591b6 | ||
|
ea29c089f6 | ||
cff1eb1958 | |||
83c98b2ad3 | |||
0d7f323232 | |||
0d63b0a14d | |||
6684d7f376 | |||
89321af1ce | |||
d7a9f1e413 | |||
edf199d9a6 | |||
8d6a05e02c | |||
c7d9b2661b | |||
d0a32e08d9 | |||
b0f9e44f8a | |||
1e749d2f64 | |||
c886bbd6cf | |||
ec45839029 | |||
6fd2294529 | |||
|
5913da8183 | ||
|
fcf2889668 | ||
|
6d784f1283 | ||
cd8ceec765 | |||
5325ac37d6 | |||
07fa6965ad | |||
4c88a5fbc6 | |||
da61a52c13 | |||
3314f9fb57 | |||
ebb1bec36f | |||
|
d5d2e2107c | ||
|
c39d03f8c9 | ||
|
e20dfab5b1 | ||
|
ffc9be3721 | ||
|
126aedabfd | ||
56661b03e6 | |||
|
c45658be68 | ||
e0443ff97c |
17
.editorconfig
Normal file
17
.editorconfig
Normal file
@@ -0,0 +1,17 @@
|
||||
# noinspection EditorConfigKeyCorrectness
|
||||
[{*.kt,*.kts}]
|
||||
ktlint_standard_annotation = disabled
|
||||
ktlint_standard_filename = disabled
|
||||
ktlint_standard_wrapping = disabled
|
||||
ktlint_standard_import-ordering = enabled
|
||||
ktlint_standard_max-line-length = disabled
|
||||
ktlint_standard_multiline-if-else = disabled
|
||||
ktlint_standard_argument-list-wrapping = disabled
|
||||
ktlint_standard_parameter-list-wrapping = disabled
|
||||
ktlint_standard_trailing-comma-on-declaration-site = disabled
|
||||
ktlint_function_signature_body_expression_wrapping = multiline
|
||||
ij_continuation_indent_size = 2
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = false
|
||||
max_line_length = 150
|
29
.github/ISSUE_TEMPLATE/----------.md
vendored
29
.github/ISSUE_TEMPLATE/----------.md
vendored
@@ -1,29 +0,0 @@
|
||||
---
|
||||
name: 通知优化图标适配反馈
|
||||
about: 提交通知图标优化适配必须使用此模板提交
|
||||
title: "[通知优化图标适配反馈]"
|
||||
labels: To be adapted
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**需要适配的 APP 名称/包名/通知图标颜色 (必填)**
|
||||
|
||||
* (示例:小米音乐/com.miui.player/#fff16033)
|
||||
|
||||
*
|
||||
|
||||
**提供相关 APP 的下载渠道截图以及简要说明用途 (必填)**
|
||||
|
||||
*
|
||||
|
||||
**提供相关 APP 的通知单色图标适配素材 大小 50x50 (选填)**
|
||||
|
||||
* (可填写资源下载地址或直接添加附件提交,不接受百度网盘、天翼云盘以及各种快传、私有云盘)
|
||||
|
||||
* (若直接在附件提交这里可不填)
|
||||
|
||||
<!--- 提交时请将括号内容包括括号全部删除,填入你自己的内容 --->
|
||||
<!--- 请保留模板原始标题 --->
|
||||
<!--- 不按规定提交的 issues 将直接被关闭 --->
|
||||
<!--- Create by Template --->
|
52
.github/ISSUE_TEMPLATE/----bug---.md
vendored
52
.github/ISSUE_TEMPLATE/----bug---.md
vendored
@@ -1,52 +0,0 @@
|
||||
---
|
||||
name: 问题与 BUG 反馈
|
||||
about: 问题反馈必须使用此模板进行提交
|
||||
title: "[问题与 BUG 反馈] *简要描述问题原因*"
|
||||
labels: bug
|
||||
assignees: fankes
|
||||
|
||||
---
|
||||
|
||||
**MIUI 版本(必填)**
|
||||
|
||||
*
|
||||
|
||||
**MIUI 版本类型(请保留一个)**
|
||||
|
||||
* 公测版/内测版/稳定版
|
||||
|
||||
**Android 版本(必填)**
|
||||
|
||||
*
|
||||
|
||||
**模块版本(必填)**
|
||||
|
||||
*
|
||||
|
||||
**使用的 Xposed 框架名称与框架版本(必填)**
|
||||
|
||||
*
|
||||
|
||||
**同时使用的带有系统界面作用域的 Xposed 模块(选填)**
|
||||
|
||||
* (没有可空)
|
||||
|
||||
**问题的具体描述**
|
||||
|
||||
* (复现步骤、前提以及详细截图和录屏演示)
|
||||
|
||||
|
||||
**提供模块问题 Log 或必要 Log**
|
||||
|
||||
* (LSPosed 可在日志管理中查看并筛选包含 `MIUINativeNotifyIcon` 的日志)
|
||||
|
||||
<details><summary>展开查看</summary><pre><code>
|
||||
|
||||
(此处粘贴问题Log)
|
||||
|
||||
</code></pre></details>
|
||||
|
||||
<!--- 提交时请将括号内容包括括号全部删除,填入你自己的内容 --->
|
||||
<!--- 请保留模板原始标题 --->
|
||||
<!--- 不按规定提交的 issues 将直接被关闭 --->
|
||||
<!--- Create by Template --->
|
96
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
96
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
name: 问题与 BUG 反馈
|
||||
description: 问题反馈必须使用此模板进行提交
|
||||
labels: [ bug ]
|
||||
title: "[问题与 BUG 反馈] (在这里简要描述问题原因)"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### 请在下方填写问题发生的具体原因和复现步骤。
|
||||
|
||||
我们只接受 MIUI 或 HyperOS 正规官方版本系统,如果你正在使用官改 (第三方修改版) 请不要提交任何 BUG 与问题,我们无义务去解决,请自求多福。
|
||||
|
||||
发生异常、崩溃、闪退或功能性问题,必须提交问题 Log (日志),没有 Log 的 issues 将直接被关闭。
|
||||
- type: input
|
||||
attributes:
|
||||
label: 模块版本
|
||||
description: 请填写当前使用的模块完整版本号,例如:**2.0**
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: 系统版本
|
||||
description: |
|
||||
这里填写当前的系统,例如:**MIUI 13.0.1** 或 **HyperOS 1.0**
|
||||
开发版需要标注具体的版本号,例如:**MIUI 13 22.9.27** 或 **HyperOS 1.0 22.9.27**
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: 系统版本类型
|
||||
description: 请选择你使用的系统版本类型。
|
||||
options:
|
||||
- 稳定版
|
||||
- 稳定内测版
|
||||
- 开发版
|
||||
- 开发内测版
|
||||
- 开发公测版 DEV
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Android 版本
|
||||
options:
|
||||
- 14
|
||||
- 13
|
||||
- 12L/12.1
|
||||
- 12
|
||||
- 11
|
||||
- 10
|
||||
- 9
|
||||
- 8.1
|
||||
- 8.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Xposed 框架名称与版本号
|
||||
description: 请填写当前使用的 Xposed 框架,例如:**LSPosed 1.8.4(次版本号)**
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: 与系统界面(系统 UI)同作用域的 Xposed 模块
|
||||
description: |
|
||||
此模块的作用域为系统界面(系统 UI),为确保非其它模块冲突造成的问题,请一定要填写当前你同时激活的相关模块。
|
||||
若没有,请直接在下方填写“无”。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 详细描述问题发生的具体原因
|
||||
description: 请在下方详细描述问题发生的具体场景、复现步骤和经过,以便我们能够按照你所描述的步骤复现这个问题。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 提供模块问题 Log 或必要 Log
|
||||
description: LSPosed 可在日志管理中查看并筛选包含 `MIUINativeNotifyIcon` 的日志。
|
||||
value: |
|
||||
<details><summary>展开查看</summary><pre><code>
|
||||
|
||||
(此处粘贴问题 Log)
|
||||
|
||||
</code></pre></details>
|
||||
<!-- 提交时请将括号内容包括括号全部删除,粘贴你复制的日志,不要破坏代码格式 -->
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 确认一下你提交的信息
|
||||
description: |
|
||||
为了确保 issues 的质量和避免浪费不必要的时间,未勾选下方选项的 issues 将直接被关闭。
|
||||
请一定确保你已经**勾选下方的选项**后再提交。
|
||||
options:
|
||||
- label: 我确保上述信息准确无误
|
||||
required: false
|
71
.github/ISSUE_TEMPLATE/request_notify_icon_adaption.yml
vendored
Normal file
71
.github/ISSUE_TEMPLATE/request_notify_icon_adaption.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
name: 通知图标优化适配反馈
|
||||
description: 提交通知图标优化适配必须使用此模板提交
|
||||
labels: [To be adapted]
|
||||
title: "[通知图标优化适配反馈]"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### 请在下方填写你需要适配的 APP 通知图标的必要信息。
|
||||
|
||||
**China and Chinese only.**
|
||||
**仅限中国和中文。**
|
||||
|
||||
以下类型的 APP 不予适配:
|
||||
|
||||
- VPN、翻墙软件
|
||||
- 涉嫌色情、赌博类软件
|
||||
- 申请超限权限、涉嫌泄露国家机密行为的软件
|
||||
|
||||
以下类型的 APP 通知图标暂不适配:
|
||||
|
||||
- 多态彩色图标,状态不唯一,例如 360 极速浏览器
|
||||
- 规范的原生图标,但未被通知图标规则适配的 (将稍后加入白名单)
|
||||
- type: input
|
||||
attributes:
|
||||
label: APP 名称
|
||||
description: 这里填写 APP 的名称,例如:**微信**
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: APP 包名
|
||||
description: 这里填写 APP 的包名,例如:**com.tencent.mm**
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: 通知图标颜色 (HEX)
|
||||
description: |
|
||||
这里填写通知图标在下拉通知栏中的图标颜色,要求为 16 进制,例如:**#ff232323**
|
||||
如果不知道什么是 16 进制颜色,可以参考 [这里](https://www.qtccolor.com/tool/hex.aspx) 的取色。
|
||||
留空代表使用系统默认主题色。
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: 下载渠道、来源地址链接
|
||||
description: 请填写我们应该从何处得到你需要适配的这个 APP 的下载链接。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 简单描述适配的通知图标使用场景
|
||||
description: 简单描述一下当前 APP 的通知图标在何时会变成彩色的、不规范的以及可触发推送通知的操作,例如小米推送或 HMS 推送。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 通知单色图标适配素材 (大小 50x50~72x72)
|
||||
description: 请在这里填写我们能够获得这个图标的网址或在下方的文本框粘贴你需要上传的图标。
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 确认一下你提交的信息
|
||||
description: |
|
||||
为了确保 issues 的质量和避免浪费不必要的时间,未勾选下方选项的 issues 将直接被关闭。
|
||||
请一定确保你已经**勾选下方的选项**后再提交。
|
||||
options:
|
||||
- label: 我确保上述信息准确无误
|
||||
required: false
|
88
.github/workflows/commit_ci.yml
vendored
Normal file
88
.github/workflows/commit_ci.yml
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
name: Automatic Build on Commit
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.txt'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build CI
|
||||
if: ${{ success() }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
APK_OUTPUT_PATH: 'app/build/outputs/apk'
|
||||
TG_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TG_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
COMMIT_MESSAGE: |+
|
||||
New push to GitHub\!
|
||||
```
|
||||
${{ github.event.head_commit.message }}
|
||||
```by `${{ github.event.head_commit.author.name }}`
|
||||
See commit detail [here](${{ github.event.head_commit.url }})
|
||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Prepare GitHub Env
|
||||
run: |
|
||||
GITHUB_SHA=${{ github.sha }}
|
||||
GITHUB_CI_COMMIT_ID=${GITHUB_SHA:0:7}
|
||||
echo "GITHUB_CI_COMMIT_ID=$GITHUB_CI_COMMIT_ID" >> $GITHUB_ENV
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1
|
||||
with:
|
||||
cmake-version: '3.22.1'
|
||||
- name: Prepare Java 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 17
|
||||
java-package: jdk
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
- name: Cache Gradle Dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/build-cache-*
|
||||
key: gradle-deps-core-${{ hashFiles('**/build.gradle.kts') }}
|
||||
restore-keys: |
|
||||
gradle-deps
|
||||
- name: Cache Gradle Build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches/build-cache-*
|
||||
key: gradle-builds-core-${{ github.sha }}
|
||||
restore-keys: |
|
||||
gradle-builds
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
./gradlew :app:assembleDebug
|
||||
./gradlew :app:assembleRelease
|
||||
echo "DEBUG_APK_PATH=$(find ${{ env.APK_OUTPUT_PATH }}/debug -name '*.apk')" >> $GITHUB_ENV
|
||||
echo "RELEASE_APK_PATH=$(find ${{ env.APK_OUTPUT_PATH }}/release -name '*.apk')" >> $GITHUB_ENV
|
||||
- name: Upload Artifacts (Debug)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ${{ env.DEBUG_APK_PATH }}
|
||||
name: MIUINativeNotifyIcon-debug-${{ github.event.head_commit.id }}
|
||||
- name: Upload Artifacts (Release)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ${{ env.RELEASE_APK_PATH }}
|
||||
name: MIUINativeNotifyIcon-release-${{ github.event.head_commit.id }}
|
||||
- name: Post Artifacts to Telegram
|
||||
run: |
|
||||
export debug=$(find ${{ env.APK_OUTPUT_PATH }}/debug -name "*.apk")
|
||||
export release=$(find ${{ env.APK_OUTPUT_PATH }}/release -name "*.apk")
|
||||
ESCAPED=`python3 -c 'import json,os,urllib.parse; msg = json.dumps(os.environ["COMMIT_MESSAGE"]); print(urllib.parse.quote(msg if len(msg) <= 1024 else json.dumps(os.environ["COMMIT_URL"])))'`
|
||||
curl -v "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMediaGroup?chat_id=${TG_CHAT_ID}&media=%5B%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Fdebug%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Frelease%22%2C%22parse_mode%22%3A%22MarkdownV2%22%2C%22caption%22:${ESCAPED}%7D%5D" \
|
||||
-F debug="@$debug" \
|
||||
-F release="@$release"
|
70
.github/workflows/pull_request_ci.yml
vendored
Normal file
70
.github/workflows/pull_request_ci.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Pull Request Checker
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.txt'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Pull Request Check
|
||||
if: ${{ success() }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
APK_OUTPUT_PATH: 'app/build/outputs/apk'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Prepare GitHub Env
|
||||
run: |
|
||||
GITHUB_SHA=${{ github.sha }}
|
||||
GITHUB_CI_COMMIT_ID=${GITHUB_SHA:0:7}
|
||||
echo "GITHUB_CI_COMMIT_ID=$GITHUB_CI_COMMIT_ID" >> $GITHUB_ENV
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1
|
||||
with:
|
||||
cmake-version: '3.22.1'
|
||||
- name: Prepare Java 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 17
|
||||
java-package: jdk
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
- name: Cache Gradle Dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/build-cache-*
|
||||
key: gradle-deps-core-${{ hashFiles('**/build.gradle.kts') }}
|
||||
restore-keys: |
|
||||
gradle-deps
|
||||
- name: Cache Gradle Build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches/build-cache-*
|
||||
key: gradle-builds-core-${{ github.sha }}
|
||||
restore-keys: |
|
||||
gradle-builds
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
./gradlew :app:assembleDebug
|
||||
./gradlew :app:assembleRelease
|
||||
echo "DEBUG_APK_PATH=$(find ${{ env.APK_OUTPUT_PATH }}/debug -name '*.apk')" >> $GITHUB_ENV
|
||||
echo "RELEASE_APK_PATH=$(find ${{ env.APK_OUTPUT_PATH }}/release -name '*.apk')" >> $GITHUB_ENV
|
||||
- name: Upload Artifacts (Debug)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ${{ env.DEBUG_APK_PATH }}
|
||||
name: MIUINativeNotifyIcon-debug-${{ github.event.head_commit.id }}
|
||||
- name: Upload Artifacts (Release)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ${{ env.RELEASE_APK_PATH }}
|
||||
name: MIUINativeNotifyIcon-release-${{ github.event.head_commit.id }}
|
2
.idea/.gitignore
generated
vendored
2
.idea/.gitignore
generated
vendored
@@ -1,3 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
/gradle.xml
|
||||
/misc.xml
|
1
.idea/.name
generated
1
.idea/.name
generated
@@ -1 +0,0 @@
|
||||
MIUINativeNotifyIcon
|
26
.idea/appInsightsSettings.xml
generated
Normal file
26
.idea/appInsightsSettings.xml
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AppInsightsSettings">
|
||||
<option name="tabSettings">
|
||||
<map>
|
||||
<entry key="Firebase Crashlytics">
|
||||
<value>
|
||||
<InsightsFilterSettings>
|
||||
<option name="connection">
|
||||
<ConnectionSetting>
|
||||
<option name="appId" value="PLACEHOLDER" />
|
||||
<option name="mobileSdkAppId" value="" />
|
||||
<option name="projectId" value="" />
|
||||
<option name="projectNumber" value="" />
|
||||
</ConnectionSetting>
|
||||
</option>
|
||||
<option name="signal" value="SIGNAL_UNSPECIFIED" />
|
||||
<option name="timeIntervalDays" value="THIRTY_DAYS" />
|
||||
<option name="visibilityType" value="ALL" />
|
||||
</InsightsFilterSettings>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
10
.idea/deploymentTargetDropDown.xml
generated
Normal file
10
.idea/deploymentTargetDropDown.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<value>
|
||||
<entry key="app">
|
||||
<State />
|
||||
</entry>
|
||||
</value>
|
||||
</component>
|
||||
</project>
|
20
.idea/gradle.xml
generated
20
.idea/gradle.xml
generated
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
BIN
.idea/icon.png
generated
Normal file
BIN
.idea/icon.png
generated
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -6,5 +6,6 @@
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="YAMLSchemaValidation" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
6
.idea/kotlinc.xml
generated
Normal file
6
.idea/kotlinc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.9.10" />
|
||||
</component>
|
||||
</project>
|
6
.idea/ktlint.xml
generated
Normal file
6
.idea/ktlint.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KtlintProjectConfiguration">
|
||||
<treatAsErrors>false</treatAsErrors>
|
||||
</component>
|
||||
</project>
|
10
.idea/migrations.xml
generated
Normal file
10
.idea/migrations.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectMigrations">
|
||||
<option name="MigrateToGradleLocalJavaHome">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
26
.idea/misc.xml
generated
26
.idea/misc.xml
generated
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DesignSurface">
|
||||
<option name="filePathToZoomLevelMap">
|
||||
<map>
|
||||
<entry key="app/src/main/res/drawable-night/dark_round.xml" value="0.256" />
|
||||
<entry key="app/src/main/res/drawable-night/permotion_round.xml" value="0.256" />
|
||||
<entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.44871794871794873" />
|
||||
<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/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_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/dia_icon_search.xml" value="0.4307692307692308" />
|
||||
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.44871794871794873" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
@@ -1,44 +0,0 @@
|
||||
# 开始贡献
|
||||
|
||||
欢迎为通知图标优化名单贡献宝贵资源!<br/>
|
||||
|
||||
## 分支规定
|
||||
|
||||
不管是直接 Push 代码还是提交 Pull Request,都必须使 commit 指向 master 分支。
|
||||
|
||||
## 代码格式规范
|
||||
|
||||
- 1.全部提交代码必须使用 IDE(Android Studio 或 IDEA) 进行格式化,未经格式化的代码将拒绝合并提交请求
|
||||
- 2.代码必须使用 4 spaces 缩进格式化
|
||||
|
||||
## 贡献方法
|
||||
|
||||
- 在下方的类中添加新的 APP 通知图标适配条目
|
||||
- [IconPackParams.kt](https://github.com/fankes/MIUINativeNotifyIcon/blob/master/app/src/main/java/com/fankes/miui/notify/params/IconPackParams.kt)
|
||||
- 使用灰度位图转 Base64 来得到 Base64 的位图数据字符串
|
||||
- [BitmapToBase64](https://github.com/fankes/BitmapToBase64)
|
||||
- 新增条目的模板如下所示
|
||||
|
||||
```kotlin
|
||||
IconDataBean(
|
||||
isEnabled = true, // 是否默认启用替换彩色图标 - 关闭后将全部停止替换
|
||||
isEnabledAll = false, // 是否默认启用替换全部图标
|
||||
appName = "", // APP 名称
|
||||
packageName = "", // APP 包名
|
||||
iconBitmap = ("").bitmap, // 位图数据 Base64
|
||||
iconColor = 0, // 通知栏中显示的图标颜色 - 设置为 0 使用系统默认颜色 (不设置颜色可不写)
|
||||
contributorName = "" // 贡献者昵称
|
||||
)
|
||||
```
|
||||
|
||||
- 图标大小建议保持在 50x50
|
||||
- 提交时请将后方的注释删除,否则不予合并代码
|
||||
|
||||
## 其它要求
|
||||
|
||||
- 1.调试性质或大批量注释代码,禁止提交
|
||||
- 2.类名和方法名仅能由开发者进行修改和提交,禁止随意修改项目名称、方法名称以及类名
|
||||
- 3.禁止随意更新项目依赖以及增加新的依赖,有问题请提前提交到 issues 进行说明
|
||||
- 4.禁止更新项目版本号,版本号交由开发者合并代码并发布 release 版本
|
||||
- 5.代码语言要求,请统一使用 Kotlin,除特殊情况外,不接受其他语言的提交
|
||||
- 6.以上
|
74
EXPLORE_HISTORY.md
Normal file
74
EXPLORE_HISTORY.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# 历史背景
|
||||
|
||||
这个模块诞生来源于 MIUI 的乱改和不规范,本来 MIUI 9 之后,官方给出了原生通知图标样式,后面由于用户反应通知栏经常出现黑白块。
|
||||
|
||||
这当然不是系统的错,而是国内 APP 和 `MIPUSH` 的通知极其不规范的通知图标设计。
|
||||
|
||||
但是呢,接到反馈后 MIUI 开发组选择直接忽略这个问题,在 `2021-5-18` 的开发版开始,把全部通知图标都改成了 APP 的彩色图标,使得之前拥有自有样式的原生图标也被破坏。
|
||||
|
||||
对于 Android 开发者来说,官方文档中的 `setSmallIcon` 不再适用于魔改后的 MIUI,这将会严重破坏非常多的状态图标。
|
||||
|
||||
当然,国内的手机生态除了 `MIPUSH` 的营销通知就是社交软件的通知,可能大部分人都不会在意这件事情。
|
||||
|
||||
但是,这个模块就是为了修复被 MIUI 开发组忽略的图标问题才诞生的,并完美地给 MIUI 修复了黑白块图标的问题。
|
||||
|
||||
补充:现在这个 [开发文档](https://dev.mi.com/distribute/doc/details?pId=1582) 竟然还在,那就放在这里鞭个尸吧。
|
||||
|
||||
## 效果展示
|
||||
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/img-src/style.png?raw=true" alt="Screenshot"/>
|
||||
|
||||
## 探索历程
|
||||
|
||||
原生 Android 的小图标和通知图标具有状态性。
|
||||
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/img-src/native.jpg?raw=true" height = "35" alt="Screenshot"/>
|
||||
|
||||
而 MIUI 最近的版本直接破坏了这一状态性,全部设置为 APP 的图标,不仅难看而且你无法下拉通知栏区别这些图标代表什么。
|
||||
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/img-src/miui.jpg?raw=true" height = "40" alt="Screenshot"/>
|
||||
|
||||
同样地,通知面板的图标同样遵守这一状态性。
|
||||
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/img-src/native_n_1.jpg?raw=true" height = "100" alt="Screenshot"/>
|
||||
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/img-src/native_n_2.jpg?raw=true" height = "100" alt="Screenshot"/>
|
||||
|
||||
而 MIUI 做了什么呢?
|
||||
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/img-src/miui_n_1.jpg?raw=true" height = "100" alt="Screenshot"/>
|
||||
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/img-src/miui_n_2.jpg?raw=true" height = "100" alt="Screenshot"/>
|
||||
|
||||
不曾记得是什么版本开始,MIUI 把通知图标改成了这个鬼样子,寻找开发组提案也是无人问津,最后转念一想,自己干吧。
|
||||
|
||||
由于目前大量通知图标都来自 `MIPUSH` 发出的营销通知,而 `MIPUSH` 的图标都是统一的彩色应用图标,很多应用也没有适配这一特性, 在通知栏广告满天飞的情况下,MIUI
|
||||
选择放弃原生通知功能,而做出这种违反原生通知规则的做法,而这些彩色图标被设置为单色调图标,也确实会发生黑白块的问题,但是同时又会破坏遵守规范的图标。
|
||||
|
||||
真的没有办法了吗?在不断探索下,我找到了原生支持色彩判断的类。
|
||||
|
||||
```kotlin
|
||||
com.android.internal.util.ContrastColorUtil
|
||||
```
|
||||
|
||||
这个类中有一个方法可以拿出来判断图标的灰度效果。
|
||||
|
||||
```kotlin
|
||||
ContrastColorUtil.getInstance().isGrayscaleIcon(drawable)
|
||||
```
|
||||
|
||||
问题就被解决了,顺便修了一下被 MIUI 破坏的通知图标以及优化了一下应用本身方块图标的圆角......
|
||||
|
||||
最后,我想大声问一句 MIUI 开发组:“就这?” 就这么简单的问题为什么拖了这么长时间也没有结论,还要交给用户去修复,这真的是一种负责任的表现吗?
|
||||
|
||||
后来一想,也是啊,被国内生态毒害的用户,怎么可能会去想到这些问题呢,最后只能是我自作多情,还对 MIUI 留有一点情怀吧。
|
||||
|
||||
——来自一个无可奈何的 MIUI 老用户
|
||||
|
||||
## 后记
|
||||
|
||||
近期重新适配了 MIUI 11、12、12.5、13、14 版本,每个版本的图标设置方法都不一样,而且改的乱七八糟的,我都要无语了,只能用了很多折中方案,毕竟我也没有那么大精力每个版本去修复,实在是累了。
|
||||
|
||||
特地的把自己能有的小米手机刷成各种 MIUI 版本去为酷友做专项适配,我也是很累了,也希望你们能够多多支持,也能让 MIUI 做得更好。
|
||||
|
||||
MIUI 再不重写,怕是永远会变成安卓之光。雷军,金凡!!
|
359
PRIVACY.md
Normal file
359
PRIVACY.md
Normal file
@@ -0,0 +1,359 @@
|
||||
### MIUI 原生通知图标 隐私政策
|
||||
|
||||
> 简体中文
|
||||
|
||||
版本:1.0
|
||||
|
||||
修订与生效日期:2022年4月25日
|
||||
|
||||
#### 前言
|
||||
|
||||
为帮助您使用 MIUI 原生通知图标产品(以下简称“本产品”)或服务,我们可能收集与提供服务相关的设备信息或申请设备权限,您有权拒绝或撤回授权;
|
||||
|
||||
我们采取了互联网业内标准的技术措施和数据安全措施来保护您的设备信息安全;
|
||||
|
||||
除非再次征得您的同意,我们不会将您的设备信息用于本政策未载明的其他目的;
|
||||
|
||||
在阅读本协议前,请确认您已年满 18 岁,否则请在父母的陪同下阅读本协议。
|
||||
|
||||
#### 目录
|
||||
|
||||
```
|
||||
1. 引言
|
||||
2. 本产品处理设备信息的法律依据
|
||||
3. 我们如何收集和使用您的设备信息
|
||||
3.1 向您提供本产品和/或服务的核心业务功能
|
||||
3.2 征得授权同意的例外
|
||||
4. 我们如何转让、公开披露您的设备信息
|
||||
4.1 转让您的设备信息
|
||||
4.2 公开披露
|
||||
4.3 共享、转让、公开披露设备信息授权同意的例外
|
||||
5. 您管理设备信息的权利
|
||||
5.1 设备权限调用
|
||||
6. 如何更新与修改本政策
|
||||
7. 争议解决
|
||||
8. 名词解释
|
||||
```
|
||||
|
||||
1. 引言
|
||||
|
||||
【特别提示】
|
||||
|
||||
请您在使用我们的各项产品和/或服务前,仔细阅读并充分理解本政策。一旦您使用或继续使用本产品/服务,即表示您同意我们按照本政策处理您的相关信息。
|
||||
|
||||
2. 本产品处理设备信息的法律依据
|
||||
|
||||
如果您是中华人民共和国大陆地区的用户,我们将依据《中华人民共和国网络安全法》、《信息安全技术 个人信息安全规范》(GB/T 35273-2017)以及其他相关法律法规收集和使用您的设备信息或申请设备权限,为您提供本产品或服务。 我们通常只会在征得您同意的情况下收集您的设备信息。
|
||||
|
||||
3. 我们如何收集和使用您的设备信息
|
||||
|
||||
我们会遵循正当、合法、必要的原则,出于本政策所述的以下目的,收集和使用您在使用服务过程中主动提供或因使用本产品和/或服务而产生的设备信息。如果我们要将您的设备信息用于本政策未载明的其它用途,或基于特定目的将收集而来的信息用于其他目的,我们将以合理的方式向您告知,并在使用前再次征得您的同意。
|
||||
|
||||
3.1 向您提供本产品和/或服务的核心业务功能
|
||||
|
||||
为实现 本产品的核心业务功能,我们可能需要向您收集设备信息。以下将详细列出 本产品的核心业务功能及为实现该功能所需收集的设备信息,若您拒绝收集,则无法使用该服务。
|
||||
|
||||
查看设备中的所有应用信息。 本产品会读取您的设备应用列表;
|
||||
|
||||
搜索功能。 当您使用 本产品提供的搜索功能时,我们可能会使用应用数据缓存,收集您设备上的信息并进行本地存储。 该信息通常无法单独识别您的个人身份。
|
||||
|
||||
3.2 征得授权同意的例外
|
||||
|
||||
根据相关法律法规的规定,在以下情形中,我们可以在不征得您的授权同意的情况下收集、使用一些必要的设备信息:
|
||||
|
||||
```
|
||||
a. 与国家安全、国防安全直接相关的;
|
||||
b. 与公共安全、公共卫生、重大公共利益直接相关的;
|
||||
c. 与犯罪侦查、起诉、审判和判决执行等直接相关的;
|
||||
d. 出于维护您或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;
|
||||
e. 所收集的设备信息是您自行向社会公众公开的;
|
||||
f. 从合法公开披露的信息中收集到您的设备信息,如从合法的新闻报道、政府信息公开等渠道;
|
||||
g. 根据您的要求签订和履行合同所必需的;
|
||||
h. 用于维护 本产品的产品和/或服务的安全稳定运行所必需的,例如发现、处置产品或服务的故障;
|
||||
i. 法律法规规定的其他情形。
|
||||
```
|
||||
|
||||
4. 我们如何共享、转让、公开披露您的设备信息
|
||||
|
||||
4.1 转让您的设备信息
|
||||
|
||||
除非获取您的明确同意,我们不会将您的设备信息转让给任何公司、组织或个人。
|
||||
|
||||
4.2 公开披露
|
||||
|
||||
除非获取您的明确同意,我们不会公开披露您的设备信息。
|
||||
|
||||
4.3 共享、转让、公开披露设备信息授权同意的例外
|
||||
|
||||
根据相关法律法规的规定,在以下情形中,我们可以在不征得您的授权同意的情况下共享、转让、公开披露您的设备信息:
|
||||
|
||||
```
|
||||
A. 与国家安全、国防安全有关的;
|
||||
B. 与公共安全、公共卫生、重大公共利益有关的;
|
||||
C. 与犯罪侦查、起诉、审判和判决执行等有关的;
|
||||
D. 出于维护您或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;
|
||||
E. 您自行向社会公众公开的设备信息;
|
||||
F. 从合法公开披露的信息中收集到的设备信息的,如合法的新闻报道、政府信息公开等渠道。
|
||||
G. 法律法规规定的其他情形。
|
||||
```
|
||||
|
||||
根据法律规定,共享、转让经去标识化处理的设备信息,且确保数据接收方无法复原并重新识别设备信息主体的,不属于设备信息的对外共享、转让及公开披露行为,对此类数据的保存及处理将无需另行向您通知并征得您的同意。
|
||||
|
||||
5. 您管理设备信息的权利
|
||||
|
||||
5.1 设备权限调用
|
||||
|
||||
我们在提供服务的过程中,可能需要您开通一些设备权限,例如网络连接、查看设备应用列表等访问权限。 您也可以在设备的【设置】功能中随时选择关闭部分或者全部权限,从而拒绝我们收集您相应的设备信息。在不同设备中,权限显示方式及关闭方式可能有所不同,具体请参考设备及系统开发方说明或指引。
|
||||
|
||||
6. 如何更新与修改本政策
|
||||
|
||||
6.1 本政策为本产品的重要组成部分。 本产品保留不时更新或修改本政策的权利。
|
||||
|
||||
6.2 未经您明确同意,我们不会削减您按照本政策所应享有的权利。我们会通过 App 客户端推送通知、弹窗形式等合理方式通知您,以便您能及时了解本政策所做的任何变更。
|
||||
|
||||
6.3 对于重大变更,视具体情况我们 可能还会提供更为显著的通知 说明本政策的具体变更内容。 重大变更包括但不限于:
|
||||
|
||||
```
|
||||
A. 我们的服务模式发生重大变化。如处理设备信息的目的、处理的设备信息类型、设备信息的使用方式等;
|
||||
B. 我们在所有权结构、组织架构等方面发生重大变化。如业务调整、破产并购等引起的所有者变更等;
|
||||
C. 设备信息共享、转让或公开披露的主要对象发生变化;
|
||||
D. 您参与设备信息处理方面的权利及其行使方式发生重大变化;
|
||||
E. 我们负责处理设备信息安全的责任部门、联络方式及投诉渠道发生变化;
|
||||
F. 设备信息安全影响评估报告表明存在高风险时。
|
||||
```
|
||||
|
||||
6.4 若您不同意修改后的隐私政策,您有权并应立即停止使用 本产品的服务。如果您继续使用 本产品的服务,则视为您接受 本产品对本政策相关条款所做的修改。
|
||||
|
||||
7. 争议解决
|
||||
|
||||
7.1 如果您认为我们的设备信息处理行为损害了您的合法权益,您也可向有关政府部门进行反映。
|
||||
|
||||
8. 名词解释
|
||||
|
||||
本隐私政策中使用到的名词,在通常意义中有如下定义:
|
||||
|
||||
设备信息: 设备信息是指以电子或者其他方式记录的能够单独或者与其他信息结合识别设备身份的各种信息,包括但不限于设备的品牌、型号、系统版本、 唯一设备标识符、 网络 IP 地址、Cookie等;
|
||||
|
||||
设备: 设备是指可用于访问本产品和/或服务的装置,例如台式计算机、笔记本电脑、平板电脑或智能手机。 唯一设备标识符: 唯一设备标识符(专属 ID 或 UUID)是指由设备制造商编入到设备中的一串字符,可用于以独有方式标识相应设备(例如手机的 IMEI
|
||||
号)。唯一设备标识符有多种用途,其中可在不能使用 Cookie(例如在移动应用程序中)时用以提供广告。
|
||||
|
||||
IP 地址: 每台上网的设备都会指定一个编号,称为互联网协议 ( IP ) 地址。这些编号通常都是根据地理区域指定的。IP 地址通常可用于识别设备连接至互联网时所在的位置。
|
||||
|
||||
信息收集技术: 我们在《MIUI 原生通知图标 隐私政策》中所述的“自动收集”包括以下方式: A. Cookie(曲奇档案) Cookie 是您浏览网页时,网站服务器放在客户端(您的计算机、移动电话或其他智能终端内)里面的一个小小的文本文件,当您再次访问相应网站时,网站就可通过
|
||||
Cookie 识别您的浏览器。Cookie 可能会存储用户偏好及其他信息。您可以将浏览器配置为拒绝所有 Cookie 或在网站发送 Cookie 时显示提示。不过,如果没有 Cookie,某些网站功能或服务可能无法正常工作。 B. Web beacon(网络信标) Web beacon
|
||||
是装嵌在网站或电邮内的电子图像文件案或其他技术,可用于计算访客数目、记录您是否及何时阅览电邮或网站,或用以使用某些 Cookie。 C. Log files(日志文件) Log files 储存自动收集的若干数据。该等数据包括互联网协议( IP )地址、浏览器类型、互联网服务提供商(
|
||||
ISP )、引用/退出页面、操作系统、日期/时间戳和点击流数据等。 D. ET Tag(实体标签) ET Tag 是在互联网浏览器与互联网服务器之间背后传送的 HTTP 协议标头,可代替 Cookie,用以追踪个别使用者,使我们可更深入地了解和改善我们的服务。 E. JavaScript
|
||||
JavaScript 是一种编程语言,用于制作更具互动性和动态的网页。JavaScript 可以设定 Cookie、阅读 Cookie 及删除 Cookie。
|
||||
|
||||
算法: 计算机在执行解题运算时遵循的流程或一系列规则。
|
||||
|
||||
应用数据缓存: 应用数据缓存是指设备上的一种数据存储机制。使用它有很多好处,例如,可让网络应用在未连接互联网的情况下运行,以及可通过提高内容加载速度来改善相关应用的性能。
|
||||
|
||||
非个人身份信息: 记录的与用户相关的信息,但实际上不可直接或间接识别您身份的信息,包括经过去标识化、匿名化处理或化名方式提供的设备信息。
|
||||
|
||||
去标识化: 指通过对设备信息的技术处理,使其在不借助额外信息的情况下,无法识别设备信息主体的过程。
|
||||
|
||||
匿名化: 指通过对设备信息的技术处理,使得设备信息主体无法被识别,且处理后的信息不能被复原的过程。
|
||||
|
||||
服务器日志: 与大多数网站一样,我们的服务器会自动记录您在访问网站时所发出的网页请求。这些“服务器日志”通常包括您的网络请求、互联网协议地址、浏览器类型、浏览器语言、请求的日期和时间及可以唯一识别您的浏览器的一个或多个 Cookie。
|
||||
|
||||
### MIUINativeNotifyIcon Privacy Policy
|
||||
|
||||
> English
|
||||
|
||||
Version: 1.0
|
||||
|
||||
Amendment and effective date: April 25, 2022
|
||||
|
||||
#### Preface
|
||||
|
||||
In order to help you use MIUINativeNotifyIcon products (hereinafter referred to as "this product") or services, we may collect device information
|
||||
related to the provision of services or apply for device permissions, and you have the right to refuse or withdraw authorization;
|
||||
|
||||
We have adopted technical measures and data security measures that are standard in the Internet industry to protect the security of your device
|
||||
information;
|
||||
|
||||
Unless we obtain your consent again, we will not use your device information for other purposes not specified in this policy;
|
||||
|
||||
Before reading this agreement, please confirm that you are at least 18 years old, otherwise please read this agreement with your parents.
|
||||
|
||||
#### content
|
||||
|
||||
````
|
||||
1 Introduction
|
||||
2. Legal basis for this product to process device information
|
||||
3. How we collect and use your device information
|
||||
3.1 Provide you with the core business functions of this product and/or service
|
||||
3.2 Exceptions to Authorized Consent
|
||||
4. How we transfer and publicly disclose your device information
|
||||
4.1 Transfer your device information
|
||||
4.2 Public disclosure
|
||||
4.3 Exceptions to Authorization and Consent for Sharing, Transfer, and Public Disclosure of Device Information
|
||||
5. Your Right to Manage Device Information
|
||||
5.1 Device permission call
|
||||
6. How to update and modify this policy
|
||||
7. Dispute Resolution
|
||||
8. Glossary
|
||||
````
|
||||
|
||||
1 Introduction
|
||||
|
||||
【Special Note】
|
||||
|
||||
Please read and fully understand this policy before using our products and/or services. Once you use or continue to use this product/service, you
|
||||
agree that we will process your relevant information in accordance with this policy.
|
||||
|
||||
2. Legal basis for this product to process device information
|
||||
|
||||
If you are a user in the mainland of the People's Republic of China, we will collect and use your Device information or apply for device
|
||||
permissions to provide you with this product or service. We generally only collect your device information with your consent.
|
||||
|
||||
3. How we collect and use your device information
|
||||
|
||||
We will follow the principles of legitimacy, lawfulness, and necessity, and for the following purposes described in this policy, to collect and
|
||||
use the device information that you actively provide during the use of the service or that arises from the use of this product and/or service. If
|
||||
we want to use your device information for other purposes not specified in this policy, or use the collected information for other purposes based
|
||||
on a specific purpose, we will inform you in a reasonable manner and ask again before use with your consent.
|
||||
|
||||
3.1 Provide you with the core business functions of this product and/or service
|
||||
|
||||
In order to implement the core business functions of this product, we may need to collect device information from you. The following will list in
|
||||
detail the core business functions of this product and the device information that needs to be collected to achieve this function. If you refuse
|
||||
to collect, you cannot use the service.
|
||||
|
||||
View all app information on the device. This product will read your device application list;
|
||||
|
||||
searching feature. When you use the search function provided by this product, we may use application data cache, collect information on your
|
||||
device and store it locally. This information generally does not individually identify you as an individual.
|
||||
|
||||
3.2 Exceptions to Authorized Consent
|
||||
|
||||
According to relevant laws and regulations, we may collect and use some necessary device information without your authorization and consent under
|
||||
the following circumstances:
|
||||
|
||||
````
|
||||
a. Directly related to national security and national defense security;
|
||||
b. Directly related to public safety, public health, and major public interests;
|
||||
c. Directly related to criminal investigation, prosecution, trial and execution of judgments;
|
||||
d. In order to protect your or other personal life, property and other major legitimate rights and interests, but it is difficult to obtain my consent;
|
||||
e. The collected device information is disclosed to the public by you;
|
||||
f. Your device information is collected from legally publicly disclosed information, such as legal news reports, government information disclosure and other channels;
|
||||
g. Necessary to enter into and perform a contract at your request;
|
||||
h. Necessary to maintain the safe and stable operation of the products and/or services of this product, such as finding and disposing of faults in the products or services;
|
||||
i. Other situations stipulated by laws and regulations.
|
||||
````
|
||||
|
||||
4. How we share, transfer, and publicly disclose your device information
|
||||
|
||||
4.1 Transfer your device information
|
||||
|
||||
We will not transfer your device information to any company, organization or individual without your express consent.
|
||||
|
||||
4.2 Public disclosure
|
||||
|
||||
We will not publicly disclose your device information without your express consent.
|
||||
|
||||
4.3 Exceptions to Authorization and Consent for Sharing, Transfer, and Public Disclosure of Device Information
|
||||
|
||||
According to relevant laws and regulations, we may share, transfer and publicly disclose your device information without your authorization and
|
||||
consent in the following circumstances:
|
||||
|
||||
````
|
||||
A. Related to national security and national defense security;
|
||||
B. Related to public safety, public health, and major public interests;
|
||||
C. Related to criminal investigation, prosecution, trial and execution of judgments;
|
||||
D. In order to protect your or other personal life, property and other major legitimate rights and interests, but it is difficult to obtain my consent;
|
||||
E. The device information that you disclose to the public by yourself;
|
||||
F. Device information collected from legally publicly disclosed information, such as legal news reports, government information disclosure and other channels.
|
||||
G. Other situations stipulated by laws and regulations.
|
||||
````
|
||||
|
||||
According to the law, sharing and transferring de-identified device information and ensuring that the data recipient cannot restore and
|
||||
re-identify the subject of the device information is not an act of external sharing, transfer and public disclosure of device information.
|
||||
Storage and processing will not be required to notify you and obtain your consent.
|
||||
|
||||
5. Your Right to Manage Device Information
|
||||
|
||||
5.1 Device permission call
|
||||
|
||||
In the process of providing services, we may require you to activate some device permissions, such as network connection, viewing device
|
||||
application list and other access permissions. You can also choose to turn off some or all permissions at any time in the [Settings] function of
|
||||
the device, thereby refusing us to collect your corresponding device information. In different devices, the display method and closing method of
|
||||
permissions may be different. For details, please refer to the instructions or guidelines of the device and system developers.
|
||||
|
||||
6. How to update and modify this policy
|
||||
|
||||
6.1 This policy is an important part of this product. This product reserves the right to update or modify this policy from time to time.
|
||||
|
||||
6.2 We will not reduce your rights under this Policy without your express consent. We will notify you through reasonable means such as app client
|
||||
push notifications and pop-up windows, so that you can keep abreast of any changes to this policy.
|
||||
|
||||
6.3 For major changes, we may also provide a more prominent notice to explain the specific changes to this policy as the case may be. Significant
|
||||
changes include, but are not limited to:
|
||||
|
||||
````
|
||||
A. Significant changes to our service model. Such as the purpose of processing device information, the type of device information processed, the way of using device information, etc.;
|
||||
B. We have made significant changes in our ownership structure, organizational structure, etc. Such as changes in owners caused by business adjustments, bankruptcy mergers and acquisitions, etc.;
|
||||
C. The main object of equipment information sharing, transfer or public disclosure has changed;
|
||||
D. Significant changes in your rights to participate in the processing of device information and the way you exercise it;
|
||||
E. The responsible department, contact information and complaint channel that we are responsible for handling equipment information security have changed;
|
||||
F. When the equipment information security impact assessment report indicates that there is a high risk.
|
||||
````
|
||||
|
||||
6.4 If you do not agree with the revised Privacy Policy, you have the right and should immediately stop using the services of this product. If
|
||||
you continue to use the services of this product, it is deemed that you accept the modifications made by this product to the relevant terms of
|
||||
this policy.
|
||||
|
||||
7. Dispute Resolution
|
||||
|
||||
7.1 If you believe that our device information processing has damaged your legitimate rights and interests, you can also report to the relevant
|
||||
government departments.
|
||||
|
||||
8. Glossary
|
||||
|
||||
Terms used in this Privacy Policy have the following definitions in their usual meanings:
|
||||
|
||||
Device information: Device information refers to various information recorded electronically or in other ways that can identify the identity of
|
||||
the device alone or in combination with other information, including but not limited to the brand, model, system version, unique device
|
||||
identifier, network IP address of the device , Cookies, etc.;
|
||||
|
||||
Device: A device is a device that can be used to access the Product and/or Service, such as a desktop computer, laptop, tablet or smartphone.
|
||||
Unique Device Identifier: A Unique Device Identifier (Unique ID or UUID) is a string of characters programmed into a device by the device
|
||||
manufacturer that can be used to uniquely identify the device (such as the IMEI of a mobile phone)
|
||||
No). Unique device identifiers are used for a variety of purposes, including serving advertisements when cookies cannot be used, such as in
|
||||
mobile applications.
|
||||
|
||||
IP Address: Every device that goes online is assigned a number called an Internet Protocol (IP) address. These numbers are usually assigned by
|
||||
geographic area. An IP address can often be used to identify where a device is connected to the Internet.
|
||||
|
||||
Information collection technology: The "automatic collection" described in the "MIUINativeNotifyIcon Privacy Policy" includes the following
|
||||
methods: A. Cookie (cookie file) Cookie is when you browse the web, the website server places it on the client (your A small text file in a
|
||||
computer, mobile phone or other intelligent terminal), when you visit the corresponding website again, the website can pass Cookies identify your
|
||||
browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies or to display a prompt
|
||||
when a website sends a cookie. However, some website features or services may not function properly without cookies. B. Web beacon (web beacon)
|
||||
Web beacon An electronic image file or other technology embedded in a website or email that can be used to count visitors, record whether and
|
||||
when you view an email or website, or to use certain cookies. C. Log files Log files store certain data collected automatically. This data
|
||||
includes Internet Protocol (IP) address, browser type, Internet Service Provider (
|
||||
ISP), referring/exit pages, operating system, date/time stamp and clickstream data, etc. D. ET Tag (Entity Tag) ET Tag is an HTTP protocol header
|
||||
transmitted behind the Internet browser and Internet server, which can replace cookies to track individual users, so that we can better
|
||||
understand and improve our Serve. E. JavaScript JavaScript is a programming language used to make more interactive and dynamic web pages.
|
||||
JavaScript can set cookies, read cookies and delete cookies.
|
||||
|
||||
Algorithm: A process or set of rules that a computer follows when performing a problem-solving operation.
|
||||
|
||||
App data cache: App data cache refers to a data storage mechanism on the device. There are many benefits to using it, such as allowing web
|
||||
applications to run without an internet connection, and improving the performance of related applications by increasing the speed of content
|
||||
loading.
|
||||
|
||||
Non-Personally Identifiable Information: Information that is recorded about a user but does not actually identify you directly or indirectly,
|
||||
including device information that has been de-identified, anonymized, or provided under a pseudonym.
|
||||
|
||||
De-identification: refers to the process of technical processing of device information to make it impossible to identify the subject of device
|
||||
information without the aid of additional information.
|
||||
|
||||
Anonymization: refers to the process in which the subject of the device information cannot be identified and the processed information cannot be
|
||||
recovered through the technical processing of the device information.
|
||||
|
||||
Server Logs: Like most websites, our servers automatically log the web page requests you make while visiting the website. These "server logs"
|
||||
typically include your web request, internet protocol address, browser type, browser language, date and time of the request, and one or more
|
||||
cookies that uniquely identify your browser.
|
161
README.md
161
README.md
@@ -1,98 +1,110 @@
|
||||
# MIUI 原生通知图标
|
||||
|
||||

|
||||

|
||||

|
||||
<br/><br/>
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/app/src/main/ic_launcher-playstore.png" width = "100" height = "100"/>
|
||||
<br/>
|
||||
Fix the native notification bar icon function abandoned by the MIUI development team.<br/>
|
||||
修复被 MIUI 开发组丢弃的原生通知图标,支持 MIUI 12、12.5、13 以及最新版本。
|
||||
[](https://github.com/fankes/MIUINativeNotifyIcon/blob/master/LICENSE)
|
||||
[](https://github.com/fankes/MIUINativeNotifyIcon/actions/workflows/commit_ci.yml)
|
||||
[](https://github.com/fankes/MIUINativeNotifyIcon/releases)
|
||||

|
||||

|
||||
|
||||
# 开始使用
|
||||
[](https://t.me/MIUINativeNotifyIcon_CI)
|
||||
[](https://t.me/XiaofangInternet)
|
||||
[](https://qm.qq.com/cgi-bin/qm/qr?k=dp2h5YhWiga9WWb_Oh7kSHmx01X8I8ii&jump_from=webapi&authKey=Za5CaFP0lk7+Zgsk2KpoBD7sSaYbeXbsDgFjiWelOeH4VSionpxFJ7V0qQBSqvFM)
|
||||
[](https://pd.qq.com/s/44gcy28h)
|
||||
|
||||
点击下载最新版本
|
||||
<a href='https://github.com/fankes/MIUINativeNotifyIcon/releases'></a>
|
||||
<br/><br/>
|
||||
⚠️ 适配说明<br/>
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/img-src/icon.png?raw=true" width = "100" height = "100" alt="LOGO"/>
|
||||
|
||||
- 此模块仅支持 LSPosed(作用域“系统界面”)、~~EdXposed(随时停止支持)~~、不支持太极无极
|
||||
- 目前最低支持基于 Android 9 版本的 MIUI 12 或 MIUI 12.5(最低建议)
|
||||
- 建议最低从 MIUI 12.5 `2021-5-18` 开发版以后开始使用模块,之前的版本可能或多或少存在 MIUI 自身 BUG 不生效、黑白块的问题
|
||||
- 请始终保持最新版本的 LSPosed,旧版本可能会出现 Hook 不生效的问题
|
||||
Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
|
||||
# 请勿用于非法用途
|
||||
修复被 MIUI 开发组丢弃的原生通知图标,支持 MIUI 11~14 以及 HyperOS 1.0。
|
||||
|
||||
## For Non-Chinese Users
|
||||
|
||||
This project will not be adapted i18n, please stay tuned for my new projects in the future.
|
||||
|
||||
## 项目迁移公告
|
||||
|
||||
由于本人同时维护 **MIUI** 与 **ColorOS** 两个系统需要同时维护两个模块,十分不方便,所以我决定在后期逐渐合并两个项目并解耦合为一个新项目并计划适配更多系统与设备,例如原生与类原生系统。
|
||||
|
||||
在新的项目确定后,会在这里添加新项目的链接,届时我会终止维护这个项目并建议大家转移到新项目。
|
||||
|
||||
## 适配说明
|
||||
|
||||
- 此模块仅支持 **LSPosed** (作用域“系统界面”)、**~~EdXposed(随时停止支持)~~**、不支持**太极、无极**
|
||||
|
||||
- 请确保你使用的是 MIUI 官方版本,任何第三方官改包发生的问题,开发者没有义务去解决和修复,请自求多福
|
||||
|
||||
- 目前最低支持基于 Android 9 版本的 MIUI 11 或 MIUI 12、12.5 (最低建议)
|
||||
|
||||
- 建议最低从 MIUI 12.5 `2021-5-18` 开发版以后开始使用模块,之前的版本可能或多或少存在 MIUI 自身 BUG 不生效、图标黑白块的问题
|
||||
|
||||
- 请始终保持最新版本的 **LSPosed**,旧版本可能会出现 Hook 不生效的问题,若最新版本依然不生效请在作用域中长按“系统界面” (“系统 UI”) 选择重新优化
|
||||
|
||||
## 历史背景
|
||||
|
||||
点击下方的链接查看此模块的历史背景与探索历程。
|
||||
|
||||
- [EXPLORE_HISTORY](https://github.com/fankes/MIUINativeNotifyIcon/blob/master/EXPLORE_HISTORY.md)
|
||||
|
||||
## 贡献通知图标优化名单
|
||||
|
||||
此项目是 `AndroidNotifyIconAdapt` 项目的一部分,详情请参考下方。
|
||||
|
||||
- [Android 通知图标规范适配计划](https://github.com/fankes/AndroidNotifyIconAdapt)
|
||||
|
||||
## 发行渠道
|
||||
|
||||
| <img src="https://avatars.githubusercontent.com/in/15368?s=64&v=4" width = "30" height = "30" alt="LOGO"/> | [GitHub CI](https://github.com/fankes/MIUINativeNotifyIcon/actions/workflows/commit_ci.yml) | CI 自动构建 (测试版) |
|
||||
|------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|---------------|
|
||||
|
||||
| <img src="https://github.com/peter-iakovlev/Telegram/blob/public/Icon.png?raw=true" width = "30" height = "30" alt="LOGO"/> | [Telegram CI 频道](https://t.me/MIUINativeNotifyIcon_CI) | CI 自动构建 (测试版) |
|
||||
|-----------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|---------------|
|
||||
|
||||
| <img src="https://avatars.githubusercontent.com/in/15368?s=64&v=4" width = "30" height = "30" alt="LOGO"/> | [GitHub Releases](https://github.com/fankes/MIUINativeNotifyIcon/releases) | 正式版 (稳定版) |
|
||||
|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|-----------|
|
||||
|
||||
| <img src="https://avatars.githubusercontent.com/u/78217009?s=200&v=4?raw=true" width = "30" height = "30" alt="LOGO"/> | [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.miui.notify/releases) | 正式版 (稳定版) |
|
||||
|------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|-----------|
|
||||
|
||||
| <img src="https://github.com/fankes/fankes/assets/37344460/82113d3c-aa7b-4dd1-95c7-cda650065c12" width = "30" height = "30" alt="LOGO"/> | [123 云盘 **(密码:62ll)**](https://www.123pan.com/s/5SlUVv-W8DBh.html) | 正式版 (稳定版) |
|
||||
|------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|-----------|
|
||||
|
||||
| <img src="https://github.com/fankes/fankes/assets/37344460/3cd43efd-785e-411d-a5c3-a8c9dc02308a" width = "30" height = "30" alt="LOGO"/> | [酷安应用市场](https://www.coolapk.com/apk/com.fankes.miui.notify) | 正式版 (稳定版) |
|
||||
|------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------|-----------|
|
||||
|
||||
本模块发布地址仅限于上述所列出的地址,从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
|
||||
|
||||
## 请勿用于非法用途
|
||||
|
||||
- 本模块完全开源免费,如果好用你可以打赏支持开发,但是请不要用于非法用途。
|
||||
- 本模块发布地址仅有 [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.miui.notify/releases)、
|
||||
[Release](https://github.com/fankes/MIUINativeNotifyIcon/releases)
|
||||
及[蓝奏云](https://fankes.lanzouy.com/b030o2e8h),从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
|
||||
|
||||
# 开始贡献
|
||||
## 项目推广
|
||||
|
||||
由于国内厂商 APP 的不规范彩色图标影响整体图标的美观,现在开放第三方 APP 的通知图标适配。<br/>
|
||||
欢迎为通知图标优化名单贡献宝贵资源!<br/>
|
||||
如果你正在寻找一个可以自动管理 Gradle 项目依赖的 Gradle 插件,你可以了解一下 [SweetDependency](https://github.com/HighCapable/SweetDependency) 项目。
|
||||
|
||||
- [CONTRIBUTING](https://github.com/fankes/MIUINativeNotifyIcon/blob/master/CONTRIBUTING.md)
|
||||
如果你正在寻找一个可以自动生成属性键值的 Gradle 插件,你可以了解一下 [SweetProperty](https://github.com/HighCapable/SweetProperty) 项目。
|
||||
|
||||
# 通知测试
|
||||
本项目同样使用了 **SweetDependency** 和 **SweetProperty**。
|
||||
|
||||
你可以 [点击这里下载](https://github.com/fankes/MIUINativeNotifyIcon/blob/master/tool/NotifyTester.apk) 工具测试通知图标是否生效。
|
||||
## 捐赠支持
|
||||
|
||||
# 历史背景
|
||||
工作不易,无意外情况此项目将继续维护下去,提供更多可能,欢迎打赏。
|
||||
|
||||
这个模块诞生来源于 MIUI 的乱改和不规范,本来 MIUI 9 之后,官方给出了原生通知图标样式,后面由于用户反应通知栏经常出现黑白块。<br/><br/>
|
||||
这当然不是系统的错,而是国内 APP 和 `MIPUSH` 的通知极其不规范的通知图标设计。<br/><br/>
|
||||
但是呢,接到反馈后 MIUI 开发组选择直接忽略这个问题,在 `2021-5-18` 的开发版开始,把全部通知图标都改成了 APP 的彩色图标,使得之前拥有自有样式的原生图标也被破坏。<br/><br/>
|
||||
对于 Android 开发者来说,官方文档中的 `setSmallIcon` 不再适用于魔改后的 MIUI,这将会严重破坏非常多的状态图标。<br/><br/>
|
||||
当然,国内的手机生态除了 `MIPUSH` 的营销通知就是社交软件的通知,可能大部分人都不会在意这件事情。<br/><br/>
|
||||
但是,这个模块就是为了修复被 MIUI 开发组忽略的图标问题才诞生的,并完美地给 MIUI 修复了黑白块图标的问题。
|
||||
<br/>
|
||||
<img src="https://github.com/fankes/fankes/blob/main/img-src/payment_code.jpg?raw=true" width = "500" alt="Payment Code"/>
|
||||
|
||||
# 探索历程
|
||||
## Star History
|
||||
|
||||
原生 Android 的小图标和通知图标具有状态性。<br/><br/>
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/images/native.jpg" height = "35"/><br/><br/>
|
||||
而 MIUI 最近的版本直接破坏了这一状态性,全部设置为 APP 的图标,不仅难看而且你无法下拉通知栏区别这些图标代表什么。<br/><br/>
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/images/miui.jpg" height = "40"/><br/><br/>
|
||||
同样地,通知面板的图标同样遵守这一状态性。<br/><br/>
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/images/native_n_1.jpg" height = "100"/><br/>
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/images/native_n_2.jpg" height = "100"/><br/><br/>
|
||||
而 MIUI 做了什么呢?<br/><br/>
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/images/miui_n_1.jpg" height = "100"/><br/>
|
||||
<img src="https://github.com/fankes/MIUINativeNotifyIcon/blob/master/images/miui_n_2.jpg" height = "100"/><br/><br/>
|
||||
不曾记得是什么版本开始,MIUI 把通知图标改成了这个鬼样子,寻找开发组提案也是无人问津,最后转念一想,自己干吧。<br/><br/>
|
||||
由于目前大量通知图标都来自 `MIPUSH` 发出的营销通知,而 `MIPUSH` 的图标都是统一的彩色应用图标,很多应用也没有适配这一特性, 在通知栏广告满天飞的情况下,MIUI
|
||||
选择放弃原生通知功能,而做出这种违反原生通知规则的做法,而这些彩色图标被设置为单色调图标,也确实会发生黑白块的问题,但是同时又会破坏遵守规范的图标。<br/><br/>
|
||||
真的没有办法了吗?在不断探索下,我找到了原生支持色彩判断的类。
|
||||

|
||||
|
||||
```
|
||||
com.android.internal.util.ContrastColorUtil
|
||||
```
|
||||
## 隐私政策
|
||||
|
||||
这个类中有一个方法可以拿出来判断图标的灰度效果。
|
||||
- [PRIVACY](https://github.com/fankes/MIUINativeNotifyIcon/blob/master/PRIVACY.md)
|
||||
|
||||
```
|
||||
ContrastColorUtil.getInstance().isGrayscaleIcon(drawable)
|
||||
```
|
||||
|
||||
问题就被解决了,顺便修了一下被 MIUI 破坏的通知图标以及优化了一下应用本身方块图标的圆角......<br/><br/>
|
||||
最后,我想大声问一句 MIUI 开发组:“就这?” 就这么简单的问题为什么拖了这么长时间也没有结论,还要交给用户去修复,这真的是一种负责任的表现吗?<br/><br/>
|
||||
后来一想,也是啊,被国内生态毒害的用户,怎么可能会去想到这些问题呢,最后只能是我自作多情,还对 MIUI 留有一点情怀吧。<br/><br/>
|
||||
——来自一个无可奈何的 MIUI 老用户
|
||||
|
||||
# 后记
|
||||
|
||||
近期重新适配了 MIUI 12、12.5、13 版本,每个版本的图标设置方法都不一样,而且改的乱七八糟的,我都要无语了,只能用了很多折中方案,毕竟我也没有那么大精力每个版本去修复,实在是累了。<br/><br/>
|
||||
特地的把自己能有的小米手机刷成各种 MIUI 版本去为酷友做专项适配,我也是很累了,也希望你们能够多多支持,也能让 MIUI 做得更好。<br/><br/>
|
||||
MIUI 再不重写,怕是永远会变成安卓之光。雷军,金凡!!
|
||||
|
||||
# 许可证
|
||||
## 许可证
|
||||
|
||||
- [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html)
|
||||
|
||||
```
|
||||
Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
@@ -105,8 +117,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
```
|
||||
|
||||
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)<br/><br/>
|
||||
版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
Powered by [YukiHookAPI](https://github.com/HighCapable/YukiHookAPI)
|
||||
|
||||
版权所有 © 2017-2023 Fankes Studio(qzmmcn@163.com)
|
4
app/.gitignore
vendored
4
app/.gitignore
vendored
@@ -1 +1,3 @@
|
||||
/build
|
||||
/build
|
||||
/src/main/assets/xposed_init
|
||||
/src/main/resources/META-INF/yukihookapi_init
|
@@ -1,82 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.devtools.ksp' version '1.6.10-1.0.2'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 31
|
||||
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('../keystore/public')
|
||||
storePassword '123456'
|
||||
keyAlias 'public'
|
||||
keyPassword '123456'
|
||||
v1SigningEnabled true
|
||||
v2SigningEnabled true
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.fankes.miui.notify"
|
||||
minSdk 28
|
||||
targetSdk 31
|
||||
versionCode rootProject.ext.appVersionCode
|
||||
versionName rootProject.ext.appVersionName
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
signingConfig signingConfigs.debug
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除无效耗时 lint Task */
|
||||
tasks.whenTaskAdded {
|
||||
task -> if (task.name == "lintVitalRelease") task.enabled = false
|
||||
}
|
||||
|
||||
/** 移除无效耗时 lint Task */
|
||||
tasks.whenTaskAdded {
|
||||
task -> if (task.name == "lintVitalAnalyzeRelease") task.enabled = false
|
||||
}
|
||||
|
||||
/** 移除无效耗时 lint Task */
|
||||
tasks.whenTaskAdded {
|
||||
task -> if (task.name == "lintVitalReportRelease") task.enabled = false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "com.github.topjohnwu.libsu:core:3.1.2"
|
||||
implementation 'androidx.annotation:annotation:1.3.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
|
||||
compileOnly 'de.robv.android.xposed:api:82'
|
||||
implementation 'com.highcapable.yukihookapi:api:1.0.2'
|
||||
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.2'
|
||||
implementation 'com.geyifeng.immersionbar:immersionbar:3.2.0'
|
||||
implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.0'
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
}
|
84
app/build.gradle.kts
Normal file
84
app/build.gradle.kts
Normal file
@@ -0,0 +1,84 @@
|
||||
plugins {
|
||||
autowire(libs.plugins.android.application)
|
||||
autowire(libs.plugins.kotlin.android)
|
||||
autowire(libs.plugins.kotlin.ksp)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = property.project.app.packageName
|
||||
compileSdk = property.project.android.compileSdk
|
||||
|
||||
signingConfigs {
|
||||
create("universal") {
|
||||
keyAlias = property.project.app.signing.keyAlias
|
||||
keyPassword = property.project.app.signing.keyPassword
|
||||
storeFile = rootProject.file(property.project.app.signing.storeFilePath)
|
||||
storePassword = property.project.app.signing.storePassword
|
||||
enableV1Signing = true
|
||||
enableV2Signing = true
|
||||
}
|
||||
}
|
||||
defaultConfig {
|
||||
applicationId = property.project.app.packageName
|
||||
minSdk = property.project.android.minSdk
|
||||
targetSdk = property.project.android.targetSdk
|
||||
versionName = property.project.app.versionName
|
||||
versionCode = property.project.app.versionCode
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
all { signingConfig = signingConfigs.getByName("universal") }
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
freeCompilerArgs = listOf(
|
||||
"-Xno-param-assertions",
|
||||
"-Xno-call-assertions",
|
||||
"-Xno-receiver-assertions"
|
||||
)
|
||||
}
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
viewBinding = true
|
||||
}
|
||||
lint { checkReleaseBuilds = false }
|
||||
androidResources.additionalParameters += listOf("--allow-reserved-package-id", "--package-id", "0x37")
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
onVariants(selector().all()) {
|
||||
it.outputs.forEach { output ->
|
||||
val currentType = it.buildType
|
||||
val currentSuffix = property.github.ci.commit.id.let { suffix -> if (suffix.isNotBlank()) "-$suffix" else "" }
|
||||
val currentVersion = "${output.versionName.get()}$currentSuffix(${output.versionCode.get()})"
|
||||
if (output is com.android.build.api.variant.impl.VariantOutputImpl)
|
||||
output.outputFileName.set("${property.project.name}-v$currentVersion-$currentType.apk")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(de.robv.android.xposed.api)
|
||||
implementation(com.highcapable.yukihookapi.api)
|
||||
ksp(com.highcapable.yukihookapi.ksp.xposed)
|
||||
implementation(com.fankes.projectpromote.project.promote)
|
||||
implementation(com.github.topjohnwu.libsu.core)
|
||||
implementation(com.github.duanhong169.drawabletoolbox)
|
||||
implementation(com.squareup.okhttp3.okhttp)
|
||||
implementation(androidx.core.core.ktx)
|
||||
implementation(androidx.appcompat.appcompat)
|
||||
implementation(com.google.android.material.material)
|
||||
implementation(androidx.constraintlayout.constraintlayout)
|
||||
testImplementation(junit.junit)
|
||||
androidTestImplementation(androidx.test.ext.junit)
|
||||
androidTestImplementation(androidx.test.espresso.espresso.core)
|
||||
}
|
24
app/proguard-rules.pro
vendored
24
app/proguard-rules.pro
vendored
@@ -20,29 +20,27 @@
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-dontwarn
|
||||
-ignorewarnings
|
||||
-optimizationpasses 10
|
||||
-dontusemixedcaseclassnames
|
||||
-dontoptimize
|
||||
-verbose
|
||||
-overloadaggressively
|
||||
-repackageclasses o
|
||||
-allowaccessmodification
|
||||
-adaptclassstrings
|
||||
-adaptresourcefilenames
|
||||
-adaptresourcefilecontents
|
||||
|
||||
#-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
|
||||
-renamesourcefileattribute H
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-renamesourcefileattribute P
|
||||
-keepattributes SourceFile,Signature,LineNumberTable
|
||||
|
||||
-keep class android.support**
|
||||
-keep class androidx**
|
||||
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||
public static *** throwUninitializedProperty(...);
|
||||
public static *** throwUninitializedPropertyAccessException(...);
|
||||
}
|
||||
|
||||
-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
|
||||
-keep class * extends android.app.Activity
|
||||
-keep class * implements androidx.viewbinding.ViewBinding {
|
||||
<init>();
|
||||
*** inflate(android.view.LayoutInflater);
|
||||
}
|
@@ -1,7 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.fankes.miui.notify">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-permission
|
||||
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
|
||||
tools:node="remove" />
|
||||
<permission
|
||||
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
|
||||
tools:node="remove" />
|
||||
|
||||
<application
|
||||
android:name=".application.MNNApplication"
|
||||
@@ -11,22 +21,27 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.MIUINativeNotifyIcon"
|
||||
tools:ignore="AllowBackup">
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="AllowBackup,ExportedService">
|
||||
|
||||
<meta-data
|
||||
android:name="xposedmodule"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="xposeddescription"
|
||||
android:value="MIUI 状态栏原生图标,修复 12.5、13 后期被破坏的彩色图标。\n开发者:酷安 @星夜不荟" />
|
||||
android:value="为金凡教我做事的 MIUI 修复自 12.5 开始后期被破坏的单色图标。\n开发者:酷安 @星夜不荟" />
|
||||
<meta-data
|
||||
android:name="xposedminversion"
|
||||
android:value="93" />
|
||||
<meta-data
|
||||
android:name="xposedscope"
|
||||
android:resource="@array/module_scope" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:name=".ui.activity.MainActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="behind">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -40,7 +55,8 @@
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="behind"
|
||||
android:targetActivity=".ui.MainActivity">
|
||||
android:targetActivity=".ui.activity.MainActivity">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -49,8 +65,26 @@
|
||||
</activity-alias>
|
||||
|
||||
<activity
|
||||
android:name=".ui.ConfigureActivity"
|
||||
android:exported="false"
|
||||
android:name=".ui.activity.ConfigureActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="behind" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.auto.NotifyIconRuleUpdateActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||
|
||||
<service
|
||||
android:name=".service.QuickStartTileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_notify_icon"
|
||||
android:label="@string/tile_name"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
@@ -1 +0,0 @@
|
||||
com.fankes.miui.notify.hook.HookEntry_YukiHookXposedInit
|
@@ -1 +0,0 @@
|
||||
com.fankes.miui.notify.hook.HookEntry
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
@@ -18,36 +18,23 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/24.
|
||||
* This file is created by fankes on 2022/1/24.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.fankes.miui.notify.application
|
||||
|
||||
import android.app.Application
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.fankes.miui.notify.data.ConfigData
|
||||
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
|
||||
|
||||
class MNNApplication : Application() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 全局静态实例 */
|
||||
private var context: MNNApplication? = null
|
||||
|
||||
/** 调用全局静态实例 */
|
||||
val appContext get() = context ?: error("App is death")
|
||||
|
||||
/** 自身 APP 是否已启动 */
|
||||
var isMineStarted = false
|
||||
}
|
||||
class MNNApplication : ModuleApplication() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
/** 设置状态 */
|
||||
isMineStarted = true
|
||||
/** 设置静态实例 */
|
||||
context = this
|
||||
/** 跟随系统夜间模式 */
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
/** 装载存储控制类 */
|
||||
ConfigData.init(instance = this)
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
@@ -18,17 +18,17 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/30.
|
||||
* This file is created by fankes on 2022/1/30.
|
||||
*/
|
||||
package com.fankes.miui.notify.bean
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.fankes.miui.notify.utils.base64
|
||||
import com.fankes.miui.notify.utils.factory.base64
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* 通知栏小图标 bean
|
||||
* @param appName APP 名称 - 仅限默认语言区域
|
||||
* @param appName APP 名称
|
||||
* @param packageName 包名
|
||||
* @param iconBitmap 图标位图
|
||||
* @param iconColor 通知栏中显示的图标颜色 - 设置为 0 使用系统默认颜色
|
||||
@@ -47,4 +47,15 @@ data class IconDataBean(
|
||||
) : Serializable {
|
||||
fun toEnabledName() = ("$appName$packageName").base64 + "_enable"
|
||||
fun toEnabledAllName() = ("$appName$packageName").base64 + "_enable_all"
|
||||
override fun toString() = """
|
||||
{
|
||||
"appName": "$appName",
|
||||
"packageName": "$packageName",
|
||||
"iconBitmap": "${iconBitmap.base64}",
|
||||
"iconColor": "#${Integer.toHexString(iconColor)}",
|
||||
"contributorName": "$contributorName",
|
||||
"isEnabled": $isEnabled,
|
||||
"isEnabledAll": $isEnabledAll
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2023/2/2.
|
||||
*/
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.fankes.miui.notify.const
|
||||
|
||||
import com.fankes.miui.notify.generated.AppProperties
|
||||
import com.fankes.miui.notify.wrapper.BuildConfigWrapper
|
||||
|
||||
/**
|
||||
* 包名常量定义类
|
||||
*/
|
||||
object PackageName {
|
||||
|
||||
/** 系统界面 (系统 UI) */
|
||||
const val SYSTEMUI = "com.android.systemui"
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知图标优化名单同步方式定义类
|
||||
*/
|
||||
object IconRuleSourceSyncType {
|
||||
|
||||
/** GitHub Raw (代理 - GitHub Proxy) */
|
||||
const val GITHUB_RAW_PROXY_1 = 500
|
||||
|
||||
/** GitHub Raw (代理 - 7ED Services) */
|
||||
const val GITHUB_RAW_PROXY_2 = 1000
|
||||
|
||||
/** GitHub Raw (直连) */
|
||||
const val GITHUB_RAW_DIRECT = 2000
|
||||
|
||||
/** 自定义地址 */
|
||||
const val CUSTOM_URL = 3000
|
||||
}
|
||||
|
||||
/**
|
||||
* 模块版本常量定义类
|
||||
*/
|
||||
object ModuleVersion {
|
||||
|
||||
/** 当前 GitHub 提交的 ID (CI 自动构建) */
|
||||
const val GITHUB_COMMIT_ID = AppProperties.GITHUB_CI_COMMIT_ID
|
||||
|
||||
/** 版本名称 */
|
||||
const val NAME = BuildConfigWrapper.VERSION_NAME
|
||||
|
||||
/** 版本号 */
|
||||
const val CODE = BuildConfigWrapper.VERSION_CODE
|
||||
|
||||
/** 是否为 CI 自动构建版本 */
|
||||
val isCiMode = GITHUB_COMMIT_ID.isNotBlank()
|
||||
|
||||
/** 当前版本名称后缀 */
|
||||
val suffix = GITHUB_COMMIT_ID.let { if (it.isNotBlank()) "-$it" else "" }
|
||||
|
||||
override fun toString() = "$NAME$suffix($CODE)"
|
||||
}
|
375
app/src/main/java/com/fankes/miui/notify/data/ConfigData.kt
Normal file
375
app/src/main/java/com/fankes/miui/notify/data/ConfigData.kt
Normal file
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2023/2/2.
|
||||
*/
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.fankes.miui.notify.data
|
||||
|
||||
import android.content.Context
|
||||
import com.fankes.miui.notify.const.IconRuleSourceSyncType
|
||||
import com.highcapable.yukihookapi.hook.factory.prefs
|
||||
import com.highcapable.yukihookapi.hook.log.YLog
|
||||
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
|
||||
|
||||
/**
|
||||
* 全局配置存储控制类
|
||||
*/
|
||||
object ConfigData {
|
||||
|
||||
/** 启用模块 */
|
||||
val ENABLE_MODULE = PrefsData("_enable_module", true)
|
||||
|
||||
/** 启用模块日志 */
|
||||
val ENABLE_MODULE_LOG = PrefsData("_enable_module_log", false)
|
||||
|
||||
/** 解除状态栏通知图标个数限制 */
|
||||
val ENABLE_LIFTED_STATUS_ICON_COUNT = PrefsData("_enable_hook_status_icon_count", true)
|
||||
|
||||
/** 解除后的状态栏通知图标最大个数 */
|
||||
val LIFTED_STATUS_ICON_COUNT = PrefsData("_hook_status_icon_count", 5)
|
||||
|
||||
/** 启用通知图标兼容模式 */
|
||||
val ENABLE_COLOR_ICON_COMPAT = PrefsData("_color_icon_compat", false)
|
||||
|
||||
/** 状态栏中的通知图标暗色透明度 */
|
||||
val STATUS_ICON_DARK_ALPHA_LEVEL = PrefsData("_status_icon_dark_alpha", 75)
|
||||
|
||||
/** 状态栏中的通知图标亮色透明度 */
|
||||
val STATUS_ICON_LIGHT_ALPHA_LEVEL = PrefsData("_status_icon_light_alpha", 95)
|
||||
|
||||
/** 通知栏中的通知图标圆角程度 */
|
||||
val NOTIFY_ICON_CORNER_SIZE = PrefsData("_notify_icon_corner", 15)
|
||||
|
||||
/** 替换 MIUI 样式通知栏的通知图标 */
|
||||
val ENABLE_REPLACE_MIUI_STYLE_NOTIFY_ICON = PrefsData("_replace_miui_style_notify_icon", true)
|
||||
|
||||
/** 强制通知栏中的通知图标使用系统着色 */
|
||||
val ENABLE_NOTIFY_ICON_FORCE_SYSTEM_COLOR = PrefsData("_notify_icon_force_system_color", false)
|
||||
|
||||
/** 强制通知栏中的通知图标为 APP 图标 */
|
||||
val ENABLE_NOTIFY_ICON_FORCE_APP_ICON = PrefsData("_notify_icon_force_app_icon", false)
|
||||
|
||||
/** 启用通知图标优化 */
|
||||
val ENABLE_NOTIFY_ICON_FIX = PrefsData("_notify_icon_fix", true)
|
||||
|
||||
/** 使用占位符修补未适配的通知图标 */
|
||||
val ENABLE_NOTIFY_ICON_FIX_PLACEHOLDER = PrefsData("_notify_icon_fix_placeholder", false)
|
||||
|
||||
/** 提醒未适配通知图标的新安装应用 */
|
||||
val ENABLE_NOTIFY_ICON_FIX_NOTIFY = PrefsData("_notify_icon_fix_notify", true)
|
||||
|
||||
/** 启用通知图标优化名单自动更新 */
|
||||
val ENABLE_NOTIFY_ICON_FIX_AUTO = PrefsData("_enable_notify_icon_fix_auto", true)
|
||||
|
||||
/** 通知图标优化名单自动更新时间 */
|
||||
val NOTIFY_ICON_FIX_AUTO_TIME = PrefsData("_notify_icon_fix_auto_time", "07:00")
|
||||
|
||||
/** 通知图标优化适配数据 */
|
||||
val NOTIFY_ICONS_DATA = PrefsData("_notify_icon_datas", "")
|
||||
|
||||
/** 通知图标优化名单同步方式 */
|
||||
val ICON_RULE_SOURCE_SYNC_TYPE = PrefsData("_rule_source_sync_way", IconRuleSourceSyncType.GITHUB_RAW_PROXY_1)
|
||||
|
||||
/** 通知图标优化名单同步地址 */
|
||||
val ICON_RULE_SOURCE_SYNC_CUSTOM_URL = PrefsData("_rule_source_sync_way_custom_url", "")
|
||||
|
||||
/** 忽略 Android 版本过低提示 */
|
||||
val IGNORED_ANDROID_VERSION_TO_LOW = PrefsData("_ignored_android_version_to_low", false)
|
||||
|
||||
/** 当前实例 - [Context] or [PackageParam] */
|
||||
private var instance: Any? = null
|
||||
|
||||
/**
|
||||
* 初始化存储控制类
|
||||
* @param instance 实例 - 只能是 [Context] or [PackageParam]
|
||||
* @throws IllegalStateException 如果类型错误
|
||||
*/
|
||||
fun init(instance: Any) {
|
||||
when (instance) {
|
||||
is Context, is PackageParam -> this.instance = instance
|
||||
else -> error("Unknown type for init ConfigData")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 [String] 数据
|
||||
* @param data 键值数据模板
|
||||
* @return [String]
|
||||
*/
|
||||
private fun getString(data: PrefsData<String>) = when (instance) {
|
||||
is Context -> (instance as Context).prefs().get(data)
|
||||
is PackageParam -> (instance as PackageParam).prefs.get(data)
|
||||
else -> error("Unknown type for get prefs data")
|
||||
}
|
||||
|
||||
/**
|
||||
* 存入 [String] 数据
|
||||
* @param data 键值数据模板
|
||||
* @param value 键值内容
|
||||
*/
|
||||
private fun putString(data: PrefsData<String>, value: String) {
|
||||
when (instance) {
|
||||
is Context -> (instance as Context).prefs().edit { put(data, value) }
|
||||
is PackageParam -> YLog.warn("Not support for this method")
|
||||
else -> error("Unknown type for put prefs data")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 [Int] 数据
|
||||
* @param data 键值数据模板
|
||||
* @return [Int]
|
||||
*/
|
||||
internal fun getInt(data: PrefsData<Int>) = when (instance) {
|
||||
is Context -> (instance as Context).prefs().get(data)
|
||||
is PackageParam -> (instance as PackageParam).prefs.get(data)
|
||||
else -> error("Unknown type for get prefs data")
|
||||
}
|
||||
|
||||
/**
|
||||
* 存入 [Int] 数据
|
||||
* @param data 键值数据模板
|
||||
* @param value 键值内容
|
||||
*/
|
||||
internal fun putInt(data: PrefsData<Int>, value: Int) {
|
||||
when (instance) {
|
||||
is Context -> (instance as Context).prefs().edit { put(data, value) }
|
||||
is PackageParam -> YLog.warn("Not support for this method")
|
||||
else -> error("Unknown type for put prefs data")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 [Boolean] 数据
|
||||
* @param data 键值数据模板
|
||||
* @return [Boolean]
|
||||
*/
|
||||
internal fun getBoolean(data: PrefsData<Boolean>) = when (instance) {
|
||||
is Context -> (instance as Context).prefs().get(data)
|
||||
is PackageParam -> (instance as PackageParam).prefs.get(data)
|
||||
else -> error("Unknown type for get prefs data")
|
||||
}
|
||||
|
||||
/**
|
||||
* 存入 [Boolean] 数据
|
||||
* @param data 键值数据模板
|
||||
* @param value 键值内容
|
||||
*/
|
||||
internal fun putBoolean(data: PrefsData<Boolean>, value: Boolean) {
|
||||
when (instance) {
|
||||
is Context -> (instance as Context).prefs().edit { put(data, value) }
|
||||
is PackageParam -> YLog.warn("Not support for this method")
|
||||
else -> error("Unknown type for put prefs data")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用模块
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableModule
|
||||
get() = getBoolean(ENABLE_MODULE)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_MODULE, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用模块日志
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableModuleLog
|
||||
get() = getBoolean(ENABLE_MODULE_LOG)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_MODULE_LOG, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否解除状态栏通知图标个数限制
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableLiftedStatusIconCount
|
||||
get() = getBoolean(ENABLE_LIFTED_STATUS_ICON_COUNT)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_LIFTED_STATUS_ICON_COUNT, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除后的状态栏通知图标最大个数
|
||||
* @return [Int]
|
||||
*/
|
||||
var liftedStatusIconCount
|
||||
get() = getInt(LIFTED_STATUS_ICON_COUNT)
|
||||
set(value) {
|
||||
putInt(LIFTED_STATUS_ICON_COUNT, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用通知图标兼容模式
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableColorIconCompat
|
||||
get() = getBoolean(ENABLE_COLOR_ICON_COMPAT)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_COLOR_ICON_COMPAT, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态栏中的通知图标暗色透明度
|
||||
* @return [Int]
|
||||
*/
|
||||
var statusIconDarkAlphaLevel
|
||||
get() = getInt(STATUS_ICON_DARK_ALPHA_LEVEL)
|
||||
set(value) {
|
||||
putInt(STATUS_ICON_DARK_ALPHA_LEVEL, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态栏中的通知图标亮色透明度
|
||||
* @return [Int]
|
||||
*/
|
||||
var statusIconLightAlphaLevel
|
||||
get() = getInt(STATUS_ICON_LIGHT_ALPHA_LEVEL)
|
||||
set(value) {
|
||||
putInt(STATUS_ICON_LIGHT_ALPHA_LEVEL, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知栏中的通知图标圆角程度
|
||||
* @return [Int]
|
||||
*/
|
||||
var notifyIconCornerSize
|
||||
get() = getInt(NOTIFY_ICON_CORNER_SIZE)
|
||||
set(value) {
|
||||
putInt(NOTIFY_ICON_CORNER_SIZE, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否替换 MIUI 样式通知栏的通知图标
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableReplaceMiuiStyleNotifyIcon
|
||||
get() = getBoolean(ENABLE_REPLACE_MIUI_STYLE_NOTIFY_ICON)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_REPLACE_MIUI_STYLE_NOTIFY_ICON, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否强制通知栏中的通知图标使用系统着色
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableNotifyIconForceSystemColor
|
||||
get() = getBoolean(ENABLE_NOTIFY_ICON_FORCE_SYSTEM_COLOR)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_NOTIFY_ICON_FORCE_SYSTEM_COLOR, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否强制通知栏中的通知图标为 APP 图标
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableNotifyIconForceAppIcon
|
||||
get() = getBoolean(ENABLE_NOTIFY_ICON_FORCE_APP_ICON)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_NOTIFY_ICON_FORCE_APP_ICON, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用通知图标优化
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableNotifyIconFix
|
||||
get() = getBoolean(ENABLE_NOTIFY_ICON_FIX)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_NOTIFY_ICON_FIX, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否使用占位符修补未适配的通知图标
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableNotifyIconFixPlaceholder
|
||||
get() = getBoolean(ENABLE_NOTIFY_ICON_FIX_PLACEHOLDER)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_NOTIFY_ICON_FIX_PLACEHOLDER, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否提醒未适配通知图标的新安装应用
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableNotifyIconFixNotify
|
||||
get() = getBoolean(ENABLE_NOTIFY_ICON_FIX_NOTIFY)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_NOTIFY_ICON_FIX_NOTIFY, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用通知图标优化名单自动更新
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isEnableNotifyIconFixAuto
|
||||
get() = getBoolean(ENABLE_NOTIFY_ICON_FIX_AUTO)
|
||||
set(value) {
|
||||
putBoolean(ENABLE_NOTIFY_ICON_FIX_AUTO, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知图标优化名单自动更新时间
|
||||
* @return [String]
|
||||
*/
|
||||
var notifyIconFixAutoTime
|
||||
get() = getString(NOTIFY_ICON_FIX_AUTO_TIME)
|
||||
set(value) {
|
||||
putString(NOTIFY_ICON_FIX_AUTO_TIME, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知图标优化名单同步方式
|
||||
* @return [Int]
|
||||
*/
|
||||
var iconRuleSourceSyncType
|
||||
get() = getInt(ICON_RULE_SOURCE_SYNC_TYPE)
|
||||
set(value) {
|
||||
putInt(ICON_RULE_SOURCE_SYNC_TYPE, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知图标优化名单同步地址
|
||||
* @return [String]
|
||||
*/
|
||||
var iconRuleSourceSyncCustomUrl
|
||||
get() = getString(ICON_RULE_SOURCE_SYNC_CUSTOM_URL)
|
||||
set(value) {
|
||||
putString(ICON_RULE_SOURCE_SYNC_CUSTOM_URL, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否忽略 Android 版本过低提示
|
||||
* @return [Boolean]
|
||||
*/
|
||||
var isIgnoredAndroidVersionToLow
|
||||
get() = getBoolean(IGNORED_ANDROID_VERSION_TO_LOW)
|
||||
set(value) {
|
||||
putBoolean(IGNORED_ANDROID_VERSION_TO_LOW, value)
|
||||
}
|
||||
}
|
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2023/2/3.
|
||||
*/
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.fankes.miui.notify.data.factory
|
||||
|
||||
import android.widget.CompoundButton
|
||||
import com.fankes.miui.notify.data.ConfigData
|
||||
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
|
||||
|
||||
/**
|
||||
* 绑定到 [CompoundButton] 自动设置选中状态
|
||||
* @param data 键值数据模板
|
||||
* @param initiate 方法体
|
||||
*/
|
||||
fun CompoundButton.bind(data: PrefsData<Boolean>, initiate: CompoundButtonDataBinder.(CompoundButton) -> Unit = {}) {
|
||||
val binder = CompoundButtonDataBinder(button = this).also { initiate(it, this) }
|
||||
isChecked = ConfigData.getBoolean(data).also { binder.initializeCallback?.invoke(it) }
|
||||
binder.applyChangesCallback = { ConfigData.putBoolean(data, it) }
|
||||
setOnCheckedChangeListener { button, isChecked ->
|
||||
if (button.isPressed) {
|
||||
if (binder.isAutoApplyChanges) binder.applyChangesCallback?.invoke(isChecked)
|
||||
binder.changedCallback?.invoke(isChecked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [CompoundButton] 数据绑定管理器实例
|
||||
* @param button 当前实例
|
||||
*/
|
||||
class CompoundButtonDataBinder(private val button: CompoundButton) {
|
||||
|
||||
/** 状态初始化回调事件 */
|
||||
internal var initializeCallback: ((Boolean) -> Unit)? = null
|
||||
|
||||
/** 状态改变回调事件 */
|
||||
internal var changedCallback: ((Boolean) -> Unit)? = null
|
||||
|
||||
/** 应用更改回调事件 */
|
||||
internal var applyChangesCallback: ((Boolean) -> Unit)? = null
|
||||
|
||||
/** 是否启用自动应用更改 */
|
||||
var isAutoApplyChanges = true
|
||||
|
||||
/**
|
||||
* 监听状态初始化
|
||||
* @param result 回调结果
|
||||
*/
|
||||
fun onInitialize(result: (Boolean) -> Unit) {
|
||||
initializeCallback = result
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听状态改变
|
||||
* @param result 回调结果
|
||||
*/
|
||||
fun onChanged(result: (Boolean) -> Unit) {
|
||||
changedCallback = result
|
||||
}
|
||||
|
||||
/** 重新初始化 */
|
||||
fun reinitialize() {
|
||||
initializeCallback?.invoke(button.isChecked)
|
||||
}
|
||||
|
||||
/** 应用更改并重新初始化 */
|
||||
fun applyChangesAndReinitialize() {
|
||||
applyChanges()
|
||||
reinitialize()
|
||||
}
|
||||
|
||||
/** 应用更改 */
|
||||
fun applyChanges() {
|
||||
applyChangesCallback?.invoke(button.isChecked)
|
||||
}
|
||||
|
||||
/** 取消更改 */
|
||||
fun cancelChanges() {
|
||||
button.isChecked = button.isChecked.not()
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2023/2/4.
|
||||
*/
|
||||
@file:Suppress("SetTextI18n")
|
||||
|
||||
package com.fankes.miui.notify.data.factory
|
||||
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import com.fankes.miui.notify.data.ConfigData
|
||||
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
|
||||
|
||||
/**
|
||||
* 绑定到 [SeekBar] 自动设置进度与 [TextView]
|
||||
* @param data 键值数据模板
|
||||
* @param textView 文本框
|
||||
* @param suffix 文本显示的后缀 - 默认空
|
||||
* @param onChange 当改变停止时回调
|
||||
*/
|
||||
fun SeekBar.bind(data: PrefsData<Int>, textView: TextView, suffix: String = "", onChange: (Int) -> Unit = {}) {
|
||||
ConfigData.getInt(data).also { value ->
|
||||
textView.text = "$value$suffix"
|
||||
progress = value
|
||||
}
|
||||
setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
|
||||
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
|
||||
textView.text = "$progress$suffix"
|
||||
}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||
ConfigData.putInt(data, seekBar.progress)
|
||||
onChange(seekBar.progress)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
|
||||
})
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/24.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "SetWorldReadable")
|
||||
|
||||
package com.fankes.miui.notify.hook
|
||||
|
||||
object HookConst {
|
||||
|
||||
const val ENABLE_MODULE = "_enable_module"
|
||||
const val ENABLE_MODULE_LOG = "_enable_module_log"
|
||||
const val ENABLE_HIDE_ICON = "_hide_icon"
|
||||
const val ENABLE_COLOR_ICON_HOOK = "_color_icon_hook"
|
||||
const val ENABLE_COLOR_ICON_COMPAT = "_color_icon_compat"
|
||||
const val ENABLE_NOTIFY_ICON_FIX = "_notify_icon_fix"
|
||||
|
||||
const val SYSTEMUI_PACKAGE_NAME = "com.android.systemui"
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
@@ -18,537 +18,44 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/2/15.
|
||||
* This file is created by fankes on 2022/2/15.
|
||||
*/
|
||||
package com.fankes.miui.notify.hook
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.Outline
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import android.service.notification.StatusBarNotification
|
||||
import android.view.View
|
||||
import android.view.ViewOutlineProvider
|
||||
import android.widget.ImageView
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.fankes.miui.notify.hook.HookConst.ENABLE_COLOR_ICON_COMPAT
|
||||
import com.fankes.miui.notify.hook.HookConst.ENABLE_COLOR_ICON_HOOK
|
||||
import com.fankes.miui.notify.hook.HookConst.ENABLE_MODULE
|
||||
import com.fankes.miui.notify.hook.HookConst.ENABLE_MODULE_LOG
|
||||
import com.fankes.miui.notify.hook.HookConst.ENABLE_NOTIFY_ICON_FIX
|
||||
import com.fankes.miui.notify.hook.HookConst.SYSTEMUI_PACKAGE_NAME
|
||||
import com.fankes.miui.notify.hook.factory.isAppNotifyHookAllOf
|
||||
import com.fankes.miui.notify.hook.factory.isAppNotifyHookOf
|
||||
import com.fankes.miui.notify.params.IconPackParams
|
||||
import com.fankes.miui.notify.utils.*
|
||||
import com.fankes.miui.notify.utils.drawable.drawabletoolbox.DrawableBuilder
|
||||
import com.fankes.miui.notify.const.PackageName
|
||||
import com.fankes.miui.notify.data.ConfigData
|
||||
import com.fankes.miui.notify.hook.entity.SystemUIHooker
|
||||
import com.fankes.miui.notify.utils.factory.isLowerAndroidP
|
||||
import com.fankes.miui.notify.utils.factory.isNotMiSystem
|
||||
import com.fankes.miui.notify.utils.factory.isNotSupportMiSystemVersion
|
||||
import com.fankes.miui.notify.utils.factory.miSystemVersion
|
||||
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
|
||||
import com.highcapable.yukihookapi.hook.bean.VariousClass
|
||||
import com.highcapable.yukihookapi.hook.factory.*
|
||||
import com.highcapable.yukihookapi.hook.log.loggerD
|
||||
import com.highcapable.yukihookapi.hook.log.loggerW
|
||||
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||
import com.highcapable.yukihookapi.hook.type.android.ContextClass
|
||||
import com.highcapable.yukihookapi.hook.type.android.DrawableClass
|
||||
import com.highcapable.yukihookapi.hook.type.android.ImageViewClass
|
||||
import com.highcapable.yukihookapi.hook.type.java.IntType
|
||||
import com.highcapable.yukihookapi.hook.xposed.proxy.YukiHookXposedInitProxy
|
||||
import com.highcapable.yukihookapi.hook.factory.configs
|
||||
import com.highcapable.yukihookapi.hook.factory.encase
|
||||
import com.highcapable.yukihookapi.hook.log.YLog
|
||||
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
|
||||
|
||||
@InjectYukiHookWithXposed
|
||||
class HookEntry : YukiHookXposedInitProxy {
|
||||
object HookEntry : IYukiHookXposedInit {
|
||||
|
||||
companion object {
|
||||
|
||||
/** MIUI 新版本存在的类 */
|
||||
private const val SystemUIApplicationClass = "$SYSTEMUI_PACKAGE_NAME.SystemUIApplication"
|
||||
|
||||
/** MIUI 新版本存在的类 */
|
||||
private const val NotificationHeaderViewWrapperInjectorClass =
|
||||
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationHeaderViewWrapperInjector"
|
||||
|
||||
/** MIUI 新版本存在的类 */
|
||||
private const val NotificationHeaderViewWrapperClass =
|
||||
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationHeaderViewWrapper"
|
||||
|
||||
/** MIUI 新版本存在的类 */
|
||||
private const val NotificationViewWrapperClass =
|
||||
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationViewWrapper"
|
||||
|
||||
/** 未确定是否只有旧版本存在的类 */
|
||||
private const val ExpandableNotificationRowClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.ExpandableNotificationRow"
|
||||
|
||||
/** 原生存在的类 */
|
||||
private const val ContrastColorUtilClass = "com.android.internal.util.ContrastColorUtil"
|
||||
|
||||
/** 根据多个版本存在不同的包名相同的类 */
|
||||
private val NotificationUtilClass = VariousClass(
|
||||
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationUtil",
|
||||
"$SYSTEMUI_PACKAGE_NAME.miui.statusbar.notification.NotificationUtil"
|
||||
)
|
||||
|
||||
/** 根据多个版本存在不同的包名相同的类 */
|
||||
private val ExpandedNotificationClass = VariousClass(
|
||||
"$SYSTEMUI_PACKAGE_NAME.statusbar.notification.ExpandedNotification",
|
||||
"$SYSTEMUI_PACKAGE_NAME.miui.statusbar.ExpandedNotification"
|
||||
)
|
||||
override fun onInit() = configs {
|
||||
debugLog {
|
||||
tag = "MIUINativeNotifyIcon"
|
||||
isRecord = true
|
||||
elements(PRIORITY)
|
||||
}
|
||||
isDebug = false
|
||||
}
|
||||
|
||||
/**
|
||||
* - 这个是修复彩色图标的关键核心代码判断
|
||||
*
|
||||
* 判断是否为灰度图标 - 反射执行系统方法
|
||||
* @param context 实例
|
||||
* @param drawable 要判断的图标
|
||||
* @return [Boolean]
|
||||
*/
|
||||
private fun PackageParam.isGrayscaleIcon(context: Context, drawable: Drawable) =
|
||||
if (!prefs.getBoolean(ENABLE_COLOR_ICON_COMPAT)) safeOfFalse {
|
||||
ContrastColorUtilClass.clazz.let {
|
||||
it.method {
|
||||
name = "isGrayscaleIcon"
|
||||
param(DrawableClass)
|
||||
}.get(it.method {
|
||||
name = "getInstance"
|
||||
param(ContextClass)
|
||||
}.get().invoke(context)).invoke<Boolean>(drawable) ?: false
|
||||
}
|
||||
} else BitmapCompatTool.isGrayscaleDrawable(drawable)
|
||||
|
||||
/**
|
||||
* 是否为新版本 MIUI 方案
|
||||
*
|
||||
* 拥有状态栏图标颜色检查功能
|
||||
* @return [Boolean]
|
||||
*/
|
||||
private val PackageParam.hasIgnoreStatusBarIconColor
|
||||
get() = safeOfFalse {
|
||||
NotificationUtilClass.clazz.hasMethod(name = "ignoreStatusBarIconColor", ExpandedNotificationClass.clazz)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前通知栏的样式
|
||||
* @return [Boolean]
|
||||
*/
|
||||
private val PackageParam.isShowMiuiStyle
|
||||
get() = safeOfFalse {
|
||||
NotificationUtilClass.clazz.method { name = "showMiuiStyle" }.get().invoke() ?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 [ExpandedNotificationClass] 的应用名称
|
||||
* @param instance 通知实例
|
||||
* @return [String]
|
||||
*/
|
||||
private fun PackageParam.findAppName(instance: Any?) = safeOf(default = "<unknown>") {
|
||||
ExpandedNotificationClass.clazz.method { name = "getAppName" }.get(instance).invoke() ?: "<empty>"
|
||||
}
|
||||
|
||||
/**
|
||||
* 适配通知栏、状态栏图标
|
||||
*
|
||||
* 适配第三方图标包对系统包管理器更换图标后的彩色图标
|
||||
*
|
||||
* 自动识别 MIPUSH 图标
|
||||
* @param context 实例
|
||||
* @param iconDrawable 原始图标
|
||||
* @return [Drawable] 适配的图标
|
||||
*/
|
||||
private fun StatusBarNotification.compatNotifyIcon(context: Context, iconDrawable: Drawable) = safeOf(iconDrawable) {
|
||||
/** 给 MIPUSH 设置 APP 自己的图标 */
|
||||
if (isXmsf && opPkgName.isNotBlank())
|
||||
context.packageManager.getPackageInfo(opPkgName, 0).applicationInfo.loadIcon(context.packageManager)
|
||||
else iconDrawable
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推送通知的包名
|
||||
*
|
||||
* 自动兼容旧版本系统
|
||||
* @return [String]
|
||||
*/
|
||||
private val StatusBarNotification.compatOpPkgName
|
||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) opPkg else packageName ?: ""
|
||||
|
||||
/**
|
||||
* 判断通知是否来自 MIPUSH
|
||||
* @return [Boolean]
|
||||
*/
|
||||
private val StatusBarNotification.isXmsf get() = compatOpPkgName == "com.xiaomi.xmsf"
|
||||
|
||||
/**
|
||||
* 获取推送通知的包名
|
||||
*
|
||||
* 自动判断 MIPUSH
|
||||
* @return [String]
|
||||
*/
|
||||
private val StatusBarNotification.opPkgName get() = if (isXmsf) xmsfPkgName else compatOpPkgName
|
||||
|
||||
/**
|
||||
* 获取 MIPUSH 通知真实包名
|
||||
* @return [String]
|
||||
*/
|
||||
private val StatusBarNotification.xmsfPkgName: String
|
||||
get() {
|
||||
val xmsfPkg = notification.extras.getString("xmsf_target_package") ?: ""
|
||||
val targetPkg = notification.extras.getString("target_package") ?: ""
|
||||
return xmsfPkg.ifBlank { targetPkg.ifBlank { compatOpPkgName } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局上下文
|
||||
* @return [Context] or null
|
||||
*/
|
||||
private val PackageParam.globalContext
|
||||
get() = safeOfNull {
|
||||
if (SystemUIApplicationClass.clazz.hasMethod(name = "getContext"))
|
||||
SystemUIApplicationClass.clazz.method { name = "getContext" }.get().invoke<Context>()
|
||||
else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook 状态栏小图标
|
||||
*
|
||||
* 区分系统版本 - 由于每个系统版本的方法不一样这里单独拿出来进行 Hook
|
||||
* @param context 实例
|
||||
* @param expandedNf 通知实例
|
||||
* @param iconDrawable 小图标 [Drawable]
|
||||
* @param it 回调小图标 - ([Bitmap] 小图标)
|
||||
*/
|
||||
private fun PackageParam.hookSmallIconOnSet(
|
||||
context: Context,
|
||||
expandedNf: StatusBarNotification?,
|
||||
iconDrawable: Drawable?,
|
||||
it: (Bitmap) -> Unit
|
||||
) = safeRun(msg = "GetSmallIconOnSet") {
|
||||
if (iconDrawable == null) return@safeRun
|
||||
/** 如果没开启修复 APP 的彩色图标 */
|
||||
if (!prefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true)) return@safeRun
|
||||
/** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */
|
||||
expandedNf?.also { notifyInstance ->
|
||||
/** 判断是否不是灰度图标 */
|
||||
val isNotGrayscaleIcon = notifyInstance.isXmsf || !isGrayscaleIcon(context, iconDrawable)
|
||||
|
||||
/** 目标彩色通知 APP 图标 */
|
||||
var customIcon: Bitmap? = null
|
||||
if (prefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true))
|
||||
run {
|
||||
IconPackParams.iconDatas.forEach {
|
||||
if ((notifyInstance.opPkgName == it.packageName ||
|
||||
findAppName(notifyInstance) == it.appName) &&
|
||||
isAppNotifyHookOf(it)
|
||||
) {
|
||||
if (isNotGrayscaleIcon || isAppNotifyHookAllOf(it))
|
||||
customIcon = it.iconBitmap
|
||||
return@run
|
||||
}
|
||||
}
|
||||
}
|
||||
/** 打印日志 */
|
||||
if (prefs.getBoolean(ENABLE_MODULE_LOG))
|
||||
loggerD(msg = "Icon --> [${findAppName(notifyInstance)}][${notifyInstance.opPkgName}] custom [${customIcon != null}] grayscale [${!isNotGrayscaleIcon}] xmsf [${notifyInstance.isXmsf}]")
|
||||
when {
|
||||
/** 处理自定义通知图标优化 */
|
||||
customIcon != null -> it(customIcon!!)
|
||||
/** 若不是灰度图标自动处理为圆角 */
|
||||
isNotGrayscaleIcon -> it(notifyInstance.compatNotifyIcon(context, iconDrawable).toBitmap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook 通知栏小图标
|
||||
*
|
||||
* 区分系统版本 - 由于每个系统版本的方法不一样这里单独拿出来进行 Hook
|
||||
* @param context 实例
|
||||
* @param expandedNf 通知实例
|
||||
* @param iconImageView 通知图标实例
|
||||
*/
|
||||
private fun PackageParam.hookNotifyIconOnSet(context: Context, expandedNf: StatusBarNotification?, iconImageView: ImageView) =
|
||||
safeRun(msg = "AutoSetAppIconOnSet") {
|
||||
/** 如果没开启修复 APP 的彩色图标 */
|
||||
if (!prefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true)) return@safeRun
|
||||
/** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */
|
||||
expandedNf?.let { notifyInstance ->
|
||||
/** 是否开启修复 APP 的彩色图标 */
|
||||
val isNotifyIconFix = prefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true)
|
||||
|
||||
/** 新版风格反色 */
|
||||
val newStyle = if (context.isSystemInDarkMode) 0xFF2D2D2D.toInt() else Color.WHITE
|
||||
|
||||
/** 旧版风格反色 */
|
||||
val oldStyle = if (context.isNotSystemInDarkMode) 0xFF707070.toInt() else Color.WHITE
|
||||
|
||||
/** 通知图标原始颜色 */
|
||||
val iconColor = notifyInstance.notification.color
|
||||
|
||||
/** 是否有通知栏图标颜色 */
|
||||
val hasIconColor = iconColor != 0
|
||||
|
||||
/** 通知图标适配颜色 */
|
||||
val supportColor = iconColor.let {
|
||||
when {
|
||||
isUpperOfAndroidS -> newStyle
|
||||
it == 0 -> oldStyle
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取通知小图标 */
|
||||
val iconDrawable = notifyInstance.notification.smallIcon.loadDrawable(context)
|
||||
|
||||
/** 判断图标风格 */
|
||||
val isGrayscaleIcon = !notifyInstance.isXmsf && isGrayscaleIcon(context, iconDrawable)
|
||||
|
||||
/** 自定义默认小图标 */
|
||||
var customIcon: Bitmap? = null
|
||||
|
||||
/** 自定义默认小图标颜色 */
|
||||
var customIconColor = 0
|
||||
|
||||
if (isNotifyIconFix) run {
|
||||
IconPackParams.iconDatas.forEach {
|
||||
if ((notifyInstance.opPkgName == it.packageName ||
|
||||
findAppName(notifyInstance) == it.appName) &&
|
||||
isAppNotifyHookOf(it)
|
||||
) {
|
||||
if (!isGrayscaleIcon || isAppNotifyHookAllOf(it)) {
|
||||
customIcon = it.iconBitmap
|
||||
customIconColor = it.iconColor
|
||||
return@run
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** 处理自定义通知图标优化 */
|
||||
if (customIcon != null)
|
||||
iconImageView.apply {
|
||||
/** 设置自定义小图标 */
|
||||
setImageBitmap(customIcon)
|
||||
/** 上色 */
|
||||
setColorFilter(if (isUpperOfAndroidS || customIconColor == 0) supportColor else customIconColor)
|
||||
/** Android 12 设置图标外圈颜色 */
|
||||
if (isUpperOfAndroidS && customIconColor != 0)
|
||||
background = DrawableBuilder().rounded().solidColor(customIconColor).build()
|
||||
}
|
||||
else {
|
||||
/** 重新设置图标 - 防止系统更改它 */
|
||||
iconImageView.setImageDrawable(iconDrawable)
|
||||
/** 判断如果是灰度图标就给他设置一个白色颜色遮罩 */
|
||||
if (isGrayscaleIcon) iconImageView.apply {
|
||||
/** 设置图标着色 */
|
||||
setColorFilter(supportColor)
|
||||
/** Android 12 设置图标外圈颜色 */
|
||||
if (isUpperOfAndroidS && hasIconColor)
|
||||
background = DrawableBuilder().rounded().solidColor(iconColor).build()
|
||||
} else iconImageView.apply {
|
||||
/** 重新设置图标 */
|
||||
setImageDrawable(notifyInstance.compatNotifyIcon(context, iconDrawable))
|
||||
/** 设置裁切到边界 */
|
||||
clipToOutline = true
|
||||
/** 设置一个圆角轮廓裁切 */
|
||||
outlineProvider = object : ViewOutlineProvider() {
|
||||
override fun getOutline(view: View, out: Outline) {
|
||||
out.setRoundRect(
|
||||
0, 0,
|
||||
view.width, view.height, 5.dp(context)
|
||||
)
|
||||
}
|
||||
}
|
||||
/** 清除原生的背景边距设置 */
|
||||
if (isUpperOfAndroidS) setPadding(0, 0, 0, 0)
|
||||
/** 清除原生的主题色背景圆圈颜色 */
|
||||
if (isUpperOfAndroidS) background = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook 通知栏小图标颜色
|
||||
*
|
||||
* 区分系统版本 - 由于每个系统版本的方法不一样这里单独拿出来进行 Hook
|
||||
* @param context 实例
|
||||
* @param expandedNf 状态栏实例
|
||||
* @return [Boolean] 是否忽略通知图标颜色
|
||||
*/
|
||||
private fun PackageParam.hookIgnoreStatusBarIconColor(context: Context, expandedNf: StatusBarNotification?) =
|
||||
if (prefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true)) safeOfFalse {
|
||||
/** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */
|
||||
expandedNf?.let { notifyInstance ->
|
||||
/** 获取通知小图标 */
|
||||
val iconDrawable =
|
||||
notifyInstance.notification.smallIcon.loadDrawable(context)
|
||||
|
||||
/** 判断是否不是灰度图标 */
|
||||
val isNotGrayscaleIcon = notifyInstance.isXmsf || !isGrayscaleIcon(context, iconDrawable)
|
||||
|
||||
/** 获取目标修复彩色图标的 APP */
|
||||
var isTargetFixApp = false
|
||||
/** 如果开启了自定义通知图标优化 */
|
||||
if (prefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true))
|
||||
run {
|
||||
IconPackParams.iconDatas.forEach {
|
||||
if ((notifyInstance.opPkgName == it.packageName ||
|
||||
findAppName(notifyInstance) == it.appName) &&
|
||||
isAppNotifyHookOf(it)
|
||||
) {
|
||||
if (isNotGrayscaleIcon || isAppNotifyHookAllOf(it)) isTargetFixApp = true
|
||||
return@run
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 只要不是灰度就返回彩色图标
|
||||
* 否则不对颜色进行反色处理防止一些系统图标出现异常
|
||||
*/
|
||||
if (isTargetFixApp) false else isNotGrayscaleIcon
|
||||
} ?: true
|
||||
} else false
|
||||
|
||||
override fun onHook() = encase {
|
||||
configs {
|
||||
debugTag = "MIUINativeNotifyIcon"
|
||||
isDebug = false
|
||||
}
|
||||
loadApp(SYSTEMUI_PACKAGE_NAME) {
|
||||
loadApp(PackageName.SYSTEMUI) {
|
||||
ConfigData.init(instance = this)
|
||||
when {
|
||||
/** 不是 MIUI 系统停止 Hook */
|
||||
isNotMIUI -> loggerW(msg = "Aborted Hook -> This System is not MIUI")
|
||||
/** 系统版本低于 Android P 停止 Hook */
|
||||
isLowerAndroidP -> loggerW(msg = "Aborted Hook -> This System is lower than Android P")
|
||||
/** 不是支持的 MIUI 系统停止 Hook */
|
||||
isNotSupportMiuiVersion -> loggerW(msg = "Aborted Hook -> This MIUI Version $miuiVersion not supported")
|
||||
/** Hook 被手动关闭停止 Hook */
|
||||
!prefs.getBoolean(ENABLE_MODULE, default = true) -> loggerW(msg = "Aborted Hook -> Hook Closed")
|
||||
/** 开始 Hook */
|
||||
else -> {
|
||||
NotificationUtilClass.hook {
|
||||
/** 强制回写系统的状态栏图标样式为原生 */
|
||||
injectMember {
|
||||
method {
|
||||
name = "shouldSubstituteSmallIcon"
|
||||
param(ExpandedNotificationClass.clazz)
|
||||
}
|
||||
/**
|
||||
* 因为之前的 MIUI 版本的状态栏图标颜色会全部设置为白色的 - 找不到修复的地方就直接判断版本了
|
||||
* 对于之前没有通知图标色彩判断功能的版本判断是 MIUI 样式就停止 Hook
|
||||
*/
|
||||
replaceAny { if (hasIgnoreStatusBarIconColor) false else isShowMiuiStyle }
|
||||
}
|
||||
if (hasIgnoreStatusBarIconColor)
|
||||
injectMember {
|
||||
method {
|
||||
name = "ignoreStatusBarIconColor"
|
||||
param(ExpandedNotificationClass.clazz)
|
||||
}
|
||||
replaceAny {
|
||||
hookIgnoreStatusBarIconColor(
|
||||
context = globalContext ?: error("GlobalContext got null"),
|
||||
expandedNf = args[0] as? StatusBarNotification?
|
||||
)
|
||||
}
|
||||
}
|
||||
/** 强制回写系统的状态栏图标样式为原生 */
|
||||
injectMember {
|
||||
var isUseLegacy = false
|
||||
method {
|
||||
name = "getSmallIcon"
|
||||
param(ExpandedNotificationClass.clazz, IntType)
|
||||
}.remedys {
|
||||
method {
|
||||
name = "getSmallIcon"
|
||||
param(ExpandedNotificationClass.clazz)
|
||||
}
|
||||
method {
|
||||
name = "getSmallIcon"
|
||||
param(ContextClass, ExpandedNotificationClass.clazz)
|
||||
}.onFind { isUseLegacy = true }
|
||||
}
|
||||
afterHook {
|
||||
/** 对于之前没有通知图标色彩判断功能的版本判断是 MIUI 样式就停止 Hook */
|
||||
if (hasIgnoreStatusBarIconColor || !isShowMiuiStyle)
|
||||
(globalContext ?: args[0] as Context).also { context ->
|
||||
hookSmallIconOnSet(
|
||||
context = context,
|
||||
args[if (isUseLegacy) 1 else 0] as? StatusBarNotification?,
|
||||
(result as Icon).loadDrawable(context)
|
||||
) { icon -> result = Icon.createWithBitmap(icon) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (NotificationHeaderViewWrapperInjectorClass.hasClass)
|
||||
NotificationHeaderViewWrapperInjectorClass.hook {
|
||||
/** 修复下拉通知图标自动设置回 APP 图标的方法 */
|
||||
injectMember {
|
||||
var isUseLegacy = false
|
||||
method {
|
||||
name = "setAppIcon"
|
||||
param(ContextClass, ImageViewClass, ExpandedNotificationClass.clazz)
|
||||
}.remedys {
|
||||
method {
|
||||
name = "setAppIcon"
|
||||
param(ImageViewClass, ExpandedNotificationClass.clazz)
|
||||
}.onFind { isUseLegacy = true }
|
||||
}
|
||||
replaceUnit {
|
||||
if (isUseLegacy)
|
||||
hookNotifyIconOnSet(
|
||||
context = globalContext ?: error("GlobalContext got null"),
|
||||
args[1] as? StatusBarNotification?,
|
||||
args[0] as ImageView
|
||||
)
|
||||
else
|
||||
hookNotifyIconOnSet(
|
||||
context = args[0] as? Context ?: globalContext ?: error("GlobalContext got null"),
|
||||
args[2] as? StatusBarNotification?,
|
||||
args[1] as ImageView
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
/** 干掉下拉通知图标自动设置回 APP 图标的方法 - Android 12 */
|
||||
if (isUpperOfAndroidS)
|
||||
injectMember {
|
||||
method {
|
||||
name = "resetIconBgAndPaddings"
|
||||
param(ImageViewClass, ExpandedNotificationClass.clazz)
|
||||
}
|
||||
intercept()
|
||||
}
|
||||
}
|
||||
else
|
||||
NotificationHeaderViewWrapperClass.hook {
|
||||
/** 之前的版本解决方案 */
|
||||
injectMember {
|
||||
method { name = "handleHeaderViews" }
|
||||
afterHook {
|
||||
/** 对于之前没有通知图标色彩判断功能的版本判断是 MIUI 样式就停止 Hook */
|
||||
if (!hasIgnoreStatusBarIconColor && isShowMiuiStyle) return@afterHook
|
||||
|
||||
/** 获取小图标 */
|
||||
val iconImageView =
|
||||
NotificationHeaderViewWrapperClass.clazz
|
||||
.field { name = "mIcon" }.of<ImageView>(instance) ?: return@afterHook
|
||||
|
||||
/**
|
||||
* 从父类中得到 mRow 变量 - [ExpandableNotificationRowClass]
|
||||
* 获取其中的得到通知方法
|
||||
*/
|
||||
val expandedNf =
|
||||
ExpandableNotificationRowClass.clazz
|
||||
.method { name = "getStatusBarNotification" }
|
||||
.get(NotificationViewWrapperClass.clazz.field { name = "mRow" }.get(instance).self)
|
||||
.invoke<StatusBarNotification>()
|
||||
/** 执行 Hook */
|
||||
hookNotifyIconOnSet(iconImageView.context, expandedNf, iconImageView)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
isNotMiSystem -> YLog.warn("Aborted Hook -> This System is not MIUI or HyperOS")
|
||||
isLowerAndroidP -> YLog.warn("Aborted Hook -> This System is lower than Android P")
|
||||
isNotSupportMiSystemVersion -> YLog.warn("Aborted Hook -> This System Version ${miSystemVersion.ifBlank { "unknown" }} not supported")
|
||||
ConfigData.isEnableModule.not() -> YLog.warn("Aborted Hook -> Hook Closed")
|
||||
else -> loadHooker(SystemUIHooker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
@@ -18,53 +18,50 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/2/15.
|
||||
* This file is created by fankes on 2022/2/15.
|
||||
* This file is Modified by fankes on 2023/2/3.
|
||||
*/
|
||||
package com.fankes.miui.notify.hook.factory
|
||||
package com.fankes.miui.notify.params.factory
|
||||
|
||||
import android.content.Context
|
||||
import com.fankes.miui.notify.bean.IconDataBean
|
||||
import com.highcapable.yukihookapi.hook.factory.modulePrefs
|
||||
import com.highcapable.yukihookapi.hook.factory.prefs
|
||||
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||
|
||||
/**
|
||||
* 获取此 APP 的通知图标是否被 Hook
|
||||
* @param bean 图标 bean
|
||||
*/
|
||||
fun PackageParam.isAppNotifyHookOf(bean: IconDataBean) = prefs.getBoolean(key = bean.toEnabledName(), default = bean.isEnabled)
|
||||
fun PackageParam.isAppNotifyHookOf(bean: IconDataBean) = prefs.getBoolean(bean.toEnabledName(), bean.isEnabled)
|
||||
|
||||
/**
|
||||
* 获取此 APP 的通知图标是否被 Hook
|
||||
* @param bean 图标 bean
|
||||
*/
|
||||
fun Context.isAppNotifyHookOf(bean: IconDataBean) = modulePrefs.getBoolean(key = bean.toEnabledName(), default = bean.isEnabled)
|
||||
fun Context.isAppNotifyHookOf(bean: IconDataBean) = prefs().getBoolean(bean.toEnabledName(), bean.isEnabled)
|
||||
|
||||
/**
|
||||
* 设置 Hook 此 APP 的通知图标
|
||||
* @param bean 图标 bean
|
||||
* @param isHook 是否 Hook
|
||||
*/
|
||||
fun Context.putAppNotifyHookOf(bean: IconDataBean, isHook: Boolean) =
|
||||
modulePrefs.putBoolean(key = bean.toEnabledName(), value = isHook)
|
||||
fun Context.putAppNotifyHookOf(bean: IconDataBean, isHook: Boolean) = prefs().edit { putBoolean(bean.toEnabledName(), isHook) }
|
||||
|
||||
/**
|
||||
* 获取此 APP 的通知图标是否被全部 Hook
|
||||
* @param bean 图标 bean
|
||||
*/
|
||||
fun PackageParam.isAppNotifyHookAllOf(bean: IconDataBean) =
|
||||
prefs.getBoolean(key = bean.toEnabledAllName(), default = bean.isEnabledAll)
|
||||
fun PackageParam.isAppNotifyHookAllOf(bean: IconDataBean) = prefs.getBoolean(bean.toEnabledAllName(), bean.isEnabledAll)
|
||||
|
||||
/**
|
||||
* 获取此 APP 的通知图标是否被全部 Hook
|
||||
* @param bean 图标 bean
|
||||
*/
|
||||
fun Context.isAppNotifyHookAllOf(bean: IconDataBean) =
|
||||
modulePrefs.getBoolean(key = bean.toEnabledAllName(), default = bean.isEnabledAll)
|
||||
fun Context.isAppNotifyHookAllOf(bean: IconDataBean) = prefs().getBoolean(bean.toEnabledAllName(), bean.isEnabledAll)
|
||||
|
||||
/**
|
||||
* 设置全部 Hook 此 APP 的通知图标
|
||||
* @param bean 图标 bean
|
||||
* @param isHook 是否 Hook
|
||||
*/
|
||||
fun Context.putAppNotifyHookAllOf(bean: IconDataBean, isHook: Boolean) =
|
||||
modulePrefs.putBoolean(key = bean.toEnabledAllName(), value = isHook)
|
||||
fun Context.putAppNotifyHookAllOf(bean: IconDataBean, isHook: Boolean) = prefs().edit { putBoolean(bean.toEnabledAllName(), isHook) }
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
@@ -18,13 +18,19 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
* This file is created by fankes on 2022/3/26.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.drawable.drawabletoolbox
|
||||
package com.fankes.miui.notify.service
|
||||
|
||||
class Constants {
|
||||
import android.service.quicksettings.TileService
|
||||
import com.fankes.miui.notify.ui.activity.ConfigureActivity
|
||||
import com.fankes.miui.notify.utils.factory.navigate
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_COLOR = 0xFFBA68C8.toInt()
|
||||
class QuickStartTileService : TileService() {
|
||||
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
/** 启动通知图标优化列表窗口 */
|
||||
navigate<ConfigureActivity>()
|
||||
}
|
||||
}
|
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/30.
|
||||
*/
|
||||
@file:Suppress("SetTextI18n", "InflateParams", "DEPRECATION")
|
||||
|
||||
package com.fankes.miui.notify.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.ListView
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.miui.notify.R
|
||||
import com.fankes.miui.notify.hook.factory.isAppNotifyHookAllOf
|
||||
import com.fankes.miui.notify.hook.factory.isAppNotifyHookOf
|
||||
import com.fankes.miui.notify.hook.factory.putAppNotifyHookAllOf
|
||||
import com.fankes.miui.notify.hook.factory.putAppNotifyHookOf
|
||||
import com.fankes.miui.notify.params.IconPackParams
|
||||
import com.fankes.miui.notify.ui.base.BaseActivity
|
||||
import com.fankes.miui.notify.utils.SystemUITool
|
||||
import com.fankes.miui.notify.utils.showDialog
|
||||
import com.fankes.miui.notify.utils.snake
|
||||
import com.fankes.miui.notify.utils.toast
|
||||
import com.fankes.miui.notify.view.MaterialSwitch
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
|
||||
class ConfigureActivity : BaseActivity() {
|
||||
|
||||
/** 当前筛选条件 */
|
||||
private var filterText = ""
|
||||
|
||||
/** 回调适配器改变 */
|
||||
private var onChanged: (() -> Unit)? = null
|
||||
|
||||
/** 回调滚动事件改变 */
|
||||
private var onScrollEvent: ((Boolean) -> Unit)? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_config)
|
||||
/** 返回按钮点击事件 */
|
||||
findViewById<View>(R.id.title_back_icon).setOnClickListener { onBackPressed() }
|
||||
/** 刷新适配器结果相关 */
|
||||
refreshAdapterResult()
|
||||
/** 设置上下按钮点击事件 */
|
||||
findViewById<View>(R.id.config_title_up).setOnClickListener {
|
||||
snake(msg = "滚动到顶部")
|
||||
onScrollEvent?.invoke(false)
|
||||
}
|
||||
findViewById<View>(R.id.config_title_down).setOnClickListener {
|
||||
snake(msg = "滚动到底部")
|
||||
onScrollEvent?.invoke(true)
|
||||
}
|
||||
/** 设置过滤按钮点击事件 */
|
||||
findViewById<View>(R.id.config_title_filter).setOnClickListener {
|
||||
showDialog {
|
||||
title = "按条件过滤"
|
||||
var editText: TextInputEditText
|
||||
addView(R.layout.dia_icon_filter).apply {
|
||||
editText = findViewById<TextInputEditText>(R.id.dia_icon_filter_input_edit).apply {
|
||||
requestFocus()
|
||||
invalidate()
|
||||
if (filterText.isNotBlank()) {
|
||||
setText(filterText)
|
||||
setSelection(filterText.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
confirmButton {
|
||||
if (editText.text.toString().isNotBlank()) {
|
||||
filterText = editText.text.toString().trim()
|
||||
onChanged?.invoke()
|
||||
refreshAdapterResult()
|
||||
} else {
|
||||
toast(msg = "条件不能为空")
|
||||
it.performClick()
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
if (filterText.isNotBlank())
|
||||
neutralButton(text = "清除条件") {
|
||||
filterText = ""
|
||||
onChanged?.invoke()
|
||||
refreshAdapterResult()
|
||||
}
|
||||
}
|
||||
}
|
||||
/** 设置列表元素和 Adapter */
|
||||
findViewById<ListView>(R.id.config_list_view).apply {
|
||||
adapter = object : BaseAdapter() {
|
||||
|
||||
private val inflater = LayoutInflater.from(context)
|
||||
|
||||
override fun getCount() = iconDatas.size
|
||||
|
||||
override fun getItem(position: Int) = iconDatas[position]
|
||||
|
||||
override fun getItemId(position: Int) = position.toLong()
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
var cView = convertView
|
||||
val holder: ViewHolder
|
||||
if (convertView == null) {
|
||||
holder = ViewHolder()
|
||||
cView = inflater.inflate(R.layout.adapter_config, null).also {
|
||||
holder.appIcon = it.findViewById(R.id.adp_app_icon)
|
||||
holder.appName = it.findViewById(R.id.adp_app_name)
|
||||
holder.pkgName = it.findViewById(R.id.adp_app_pkg_name)
|
||||
holder.cbrName = it.findViewById(R.id.adp_cbr_name)
|
||||
holder.switchOpen = it.findViewById(R.id.adp_app_open_switch)
|
||||
holder.switchAll = it.findViewById(R.id.adp_app_all_switch)
|
||||
}
|
||||
cView.tag = holder
|
||||
} else holder = convertView.tag as ViewHolder
|
||||
getItem(position).also {
|
||||
holder.appIcon.setImageBitmap(it.iconBitmap)
|
||||
(if (it.iconColor != 0) it.iconColor else resources.getColor(R.color.colorTextGray)).also { color ->
|
||||
holder.appIcon.setColorFilter(color)
|
||||
holder.appName.setTextColor(color)
|
||||
}
|
||||
holder.appName.text = it.appName
|
||||
holder.pkgName.text = it.packageName
|
||||
holder.cbrName.text = "贡献者:" + it.contributorName
|
||||
isAppNotifyHookOf(it).also { e ->
|
||||
holder.switchOpen.isChecked = e
|
||||
holder.switchAll.isEnabled = e
|
||||
}
|
||||
holder.switchOpen.setOnCheckedChangeListener { btn, b ->
|
||||
if (!btn.isPressed) return@setOnCheckedChangeListener
|
||||
putAppNotifyHookOf(it, b)
|
||||
holder.switchAll.isEnabled = b
|
||||
SystemUITool.showNeedRestartSnake(context = this@ConfigureActivity)
|
||||
}
|
||||
holder.switchAll.isChecked = isAppNotifyHookAllOf(it)
|
||||
holder.switchAll.setOnCheckedChangeListener { btn, b ->
|
||||
if (!btn.isPressed) return@setOnCheckedChangeListener
|
||||
putAppNotifyHookAllOf(it, b)
|
||||
SystemUITool.showNeedRestartSnake(context = this@ConfigureActivity)
|
||||
}
|
||||
}
|
||||
return cView!!
|
||||
}
|
||||
|
||||
inner class ViewHolder {
|
||||
lateinit var appIcon: ImageFilterView
|
||||
lateinit var appName: TextView
|
||||
lateinit var pkgName: TextView
|
||||
lateinit var cbrName: TextView
|
||||
lateinit var switchOpen: MaterialSwitch
|
||||
lateinit var switchAll: MaterialSwitch
|
||||
}
|
||||
}.apply { onChanged = { notifyDataSetChanged() } }
|
||||
onScrollEvent = { post { setSelection(if (it) iconDatas.lastIndex else 0) } }
|
||||
}
|
||||
/** 设置点击事件 */
|
||||
findViewById<View>(R.id.config_cbr_button).setOnClickListener {
|
||||
runCatching {
|
||||
startActivity(Intent().apply {
|
||||
action = "android.intent.action.VIEW"
|
||||
data = Uri.parse("https://github.com/fankes/MIUINativeNotifyIcon")
|
||||
/** 防止顶栈一样重叠在自己的 APP 中 */
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
}.onFailure {
|
||||
toast(msg = "无法启动系统默认浏览器")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 刷新适配器结果相关 */
|
||||
private fun refreshAdapterResult() {
|
||||
findViewById<TextView>(R.id.config_title_count_text).text =
|
||||
if (filterText.isBlank()) "已适配 ${iconDatas.size} 个 APP 的通知图标"
|
||||
else "“${filterText}” 匹配到 ${iconDatas.size} 个结果"
|
||||
findViewById<View>(R.id.config_list_no_data_view).isVisible = iconDatas.isEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前结果下的图标数组
|
||||
* @return [Array]
|
||||
*/
|
||||
private val iconDatas
|
||||
get() = if (filterText.isBlank()) IconPackParams.iconDatas
|
||||
else IconPackParams.iconDatas.filter {
|
||||
it.appName.lowercase().contains(filterText.lowercase()) || it.packageName.lowercase().contains(filterText.lowercase())
|
||||
}.toTypedArray()
|
||||
}
|
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/24.
|
||||
*/
|
||||
@file:Suppress("SetTextI18n")
|
||||
|
||||
package com.fankes.miui.notify.ui
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.miui.notify.BuildConfig
|
||||
import com.fankes.miui.notify.R
|
||||
import com.fankes.miui.notify.hook.HookConst.ENABLE_COLOR_ICON_COMPAT
|
||||
import com.fankes.miui.notify.hook.HookConst.ENABLE_COLOR_ICON_HOOK
|
||||
import com.fankes.miui.notify.hook.HookConst.ENABLE_HIDE_ICON
|
||||
import com.fankes.miui.notify.hook.HookConst.ENABLE_MODULE
|
||||
import com.fankes.miui.notify.hook.HookConst.ENABLE_MODULE_LOG
|
||||
import com.fankes.miui.notify.hook.HookConst.ENABLE_NOTIFY_ICON_FIX
|
||||
import com.fankes.miui.notify.ui.base.BaseActivity
|
||||
import com.fankes.miui.notify.utils.*
|
||||
import com.highcapable.yukihookapi.hook.factory.modulePrefs
|
||||
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
|
||||
|
||||
class MainActivity : BaseActivity() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 模块版本 */
|
||||
private const val moduleVersion = BuildConfig.VERSION_NAME
|
||||
}
|
||||
|
||||
private var isWarnDialogShowing = false // 警告对话框是否显示
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
/** 设置文本 */
|
||||
findViewById<TextView>(R.id.main_text_version).text = "当前版本:$moduleVersion"
|
||||
findViewById<TextView>(R.id.main_text_miui_version).text = "MIUI 版本:$miuiFullVersion"
|
||||
when {
|
||||
/** 判断是否为 MIUI 系统 */
|
||||
isNotMIUI ->
|
||||
showDialog {
|
||||
title = "不是 MIUI 系统"
|
||||
msg = "此模块专为 MIUI 系统打造,当前无法识别你的系统为 MIUI,所以模块无法工作。\n" +
|
||||
"如有问题请联系 酷安 @星夜不荟"
|
||||
confirmButton(text = "退出") { finish() }
|
||||
noCancelable()
|
||||
}
|
||||
/** 判断最低 Android 系统版本 */
|
||||
isLowerAndroidP ->
|
||||
showDialog {
|
||||
title = "Android 系统版本过低"
|
||||
msg = "此模块最低支持基于 Android 9 的 MIUI 系统,你的系统版本过低不再进行适配。\n" +
|
||||
"如有问题请联系 酷安 @星夜不荟"
|
||||
confirmButton(text = "退出") { finish() }
|
||||
noCancelable()
|
||||
}
|
||||
/** 判断最低 MIUI 版本 */
|
||||
isNotSupportMiuiVersion ->
|
||||
showDialog {
|
||||
title = "MIUI 版本过低"
|
||||
msg = "此模块最低支持 MIUI 12 系统,你的 MIUI 版本为 ${miuiVersion},不再进行适配。\n" +
|
||||
"如有问题请联系 酷安 @星夜不荟"
|
||||
confirmButton(text = "退出") { finish() }
|
||||
noCancelable()
|
||||
}
|
||||
/** 判断是否 Hook */
|
||||
YukiHookModuleStatus.isActive() -> {
|
||||
findViewById<LinearLayout>(R.id.main_lin_status).setBackgroundResource(R.drawable.bg_green_round)
|
||||
findViewById<ImageFilterView>(R.id.main_img_status).setImageResource(R.mipmap.ic_success)
|
||||
findViewById<TextView>(R.id.main_text_status).text = "模块已激活"
|
||||
}
|
||||
else ->
|
||||
showDialog {
|
||||
title = "模块没有激活"
|
||||
msg = "检测到模块没有激活,模块需要 Xposed 环境依赖," +
|
||||
"同时需要系统拥有 Root 权限," +
|
||||
"请自行查看本页面使用帮助与说明第二条。\n" +
|
||||
"由于需要修改系统应用达到效果,模块不支持太极阴、应用转生。"
|
||||
confirmButton(text = "我知道了")
|
||||
noCancelable()
|
||||
}
|
||||
}
|
||||
/** 初始化 View */
|
||||
val moduleEnableSwitch = findViewById<SwitchCompat>(R.id.module_enable_switch)
|
||||
val moduleEnableLogSwitch = findViewById<SwitchCompat>(R.id.module_enable_log_switch)
|
||||
val colorIconHookItem = findViewById<View>(R.id.config_item_color_hook)
|
||||
val notifyIconConfigItem = findViewById<View>(R.id.config_item_notify)
|
||||
val hideIconInLauncherSwitch = findViewById<SwitchCompat>(R.id.hide_icon_in_launcher_switch)
|
||||
val colorIconHookSwitch = findViewById<SwitchCompat>(R.id.color_icon_fix_switch)
|
||||
val colorIconCompatSwitch = findViewById<SwitchCompat>(R.id.color_icon_compat_switch)
|
||||
val colorIconCompatText = findViewById<View>(R.id.color_icon_compat_text)
|
||||
val notifyIconFixSwitch = findViewById<SwitchCompat>(R.id.notify_icon_fix_switch)
|
||||
val notifyIconFixButton = findViewById<View>(R.id.config_notify_app_button)
|
||||
/** 获取 Sp 存储的信息 */
|
||||
colorIconHookItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true)
|
||||
colorIconCompatSwitch.isVisible = modulePrefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true)
|
||||
colorIconCompatText.isVisible = modulePrefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true)
|
||||
notifyIconConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true) &&
|
||||
modulePrefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true)
|
||||
moduleEnableLogSwitch.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true)
|
||||
notifyIconFixButton.isVisible = modulePrefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true)
|
||||
moduleEnableSwitch.isChecked = modulePrefs.getBoolean(ENABLE_MODULE, default = true)
|
||||
moduleEnableLogSwitch.isChecked = modulePrefs.getBoolean(ENABLE_MODULE_LOG, default = false)
|
||||
hideIconInLauncherSwitch.isChecked = modulePrefs.getBoolean(ENABLE_HIDE_ICON)
|
||||
colorIconHookSwitch.isChecked = modulePrefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true)
|
||||
colorIconCompatSwitch.isChecked = modulePrefs.getBoolean(ENABLE_COLOR_ICON_COMPAT)
|
||||
notifyIconFixSwitch.isChecked = modulePrefs.getBoolean(ENABLE_NOTIFY_ICON_FIX, default = true)
|
||||
moduleEnableSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (!btn.isPressed) return@setOnCheckedChangeListener
|
||||
modulePrefs.putBoolean(ENABLE_MODULE, b)
|
||||
moduleEnableLogSwitch.isVisible = b
|
||||
colorIconHookItem.isVisible = b
|
||||
notifyIconConfigItem.isVisible = b && colorIconHookSwitch.isChecked
|
||||
SystemUITool.showNeedRestartSnake(context = this)
|
||||
}
|
||||
moduleEnableLogSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (!btn.isPressed) return@setOnCheckedChangeListener
|
||||
modulePrefs.putBoolean(ENABLE_MODULE_LOG, b)
|
||||
SystemUITool.showNeedRestartSnake(context = this)
|
||||
}
|
||||
hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (!btn.isPressed) return@setOnCheckedChangeListener
|
||||
modulePrefs.putBoolean(ENABLE_HIDE_ICON, b)
|
||||
packageManager.setComponentEnabledSetting(
|
||||
ComponentName(this@MainActivity, "com.fankes.miui.notify.Home"),
|
||||
if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
}
|
||||
colorIconHookSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (!btn.isPressed) return@setOnCheckedChangeListener
|
||||
modulePrefs.putBoolean(ENABLE_COLOR_ICON_HOOK, b)
|
||||
notifyIconConfigItem.isVisible = b
|
||||
colorIconCompatSwitch.isVisible = b
|
||||
colorIconCompatText.isVisible = b
|
||||
SystemUITool.showNeedRestartSnake(context = this)
|
||||
}
|
||||
colorIconCompatSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (!btn.isPressed) return@setOnCheckedChangeListener
|
||||
modulePrefs.putBoolean(ENABLE_COLOR_ICON_COMPAT, b)
|
||||
SystemUITool.showNeedRestartSnake(context = this)
|
||||
}
|
||||
notifyIconFixSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (!btn.isPressed) return@setOnCheckedChangeListener
|
||||
modulePrefs.putBoolean(ENABLE_NOTIFY_ICON_FIX, b)
|
||||
notifyIconFixButton.isVisible = b
|
||||
SystemUITool.showNeedRestartSnake(context = this)
|
||||
}
|
||||
/** 通知图标优化名单按钮点击事件 */
|
||||
notifyIconFixButton.setOnClickListener { startActivity(Intent(this, ConfigureActivity::class.java)) }
|
||||
/** 重启按钮点击事件 */
|
||||
findViewById<View>(R.id.title_restart_icon).setOnClickListener { SystemUITool.restartSystemUI(context = this) }
|
||||
/** 恰饭! */
|
||||
findViewById<View>(R.id.link_with_follow_me).setOnClickListener {
|
||||
runCatching {
|
||||
startActivity(Intent().apply {
|
||||
setPackage("com.coolapk.market")
|
||||
action = "android.intent.action.VIEW"
|
||||
data = Uri.parse("https://www.coolapk.com/u/876977")
|
||||
/** 防止顶栈一样重叠在自己的 APP 中 */
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
}.onFailure {
|
||||
toast(msg = "你可能没有安装酷安")
|
||||
}
|
||||
}
|
||||
/** 项目地址点击事件 */
|
||||
findViewById<View>(R.id.link_with_project_address).setOnClickListener {
|
||||
runCatching {
|
||||
startActivity(Intent().apply {
|
||||
action = "android.intent.action.VIEW"
|
||||
data = Uri.parse("https://github.com/fankes/MIUINativeNotifyIcon")
|
||||
/** 防止顶栈一样重叠在自己的 APP 中 */
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
}.onFailure {
|
||||
toast(msg = "无法启动系统默认浏览器")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
/** MIUI 12 的版本特殊 - 所以给出提示 */
|
||||
if (!isWarnDialogShowing && YukiHookModuleStatus.isActive() && miuiVersion == "12" && isMiuiNotifyStyle)
|
||||
showDialog {
|
||||
isWarnDialogShowing = true
|
||||
title = "经典通知栏样式已启用"
|
||||
msg = "在 MIUI 12 中启用了经典通知栏样式后状态栏图标将不再做原生处理,模块停止工作," +
|
||||
"这取决于系统设置,你应当在 设置>通知管理>通知显示设置 中将样式设置为“原生样式”。"
|
||||
confirmButton(text = "我知道了") { isWarnDialogShowing = false }
|
||||
noCancelable()
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2022/1/30.
|
||||
*/
|
||||
@file:Suppress("SetTextI18n", "InflateParams")
|
||||
|
||||
package com.fankes.miui.notify.ui.activity
|
||||
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.miui.notify.R
|
||||
import com.fankes.miui.notify.bean.IconDataBean
|
||||
import com.fankes.miui.notify.data.ConfigData
|
||||
import com.fankes.miui.notify.databinding.ActivityConfigBinding
|
||||
import com.fankes.miui.notify.databinding.AdapterConfigBinding
|
||||
import com.fankes.miui.notify.databinding.DiaIconFilterBinding
|
||||
import com.fankes.miui.notify.params.IconPackParams
|
||||
import com.fankes.miui.notify.params.factory.isAppNotifyHookAllOf
|
||||
import com.fankes.miui.notify.params.factory.isAppNotifyHookOf
|
||||
import com.fankes.miui.notify.params.factory.putAppNotifyHookAllOf
|
||||
import com.fankes.miui.notify.params.factory.putAppNotifyHookOf
|
||||
import com.fankes.miui.notify.ui.activity.base.BaseActivity
|
||||
import com.fankes.miui.notify.utils.factory.addOnBackPressedEvent
|
||||
import com.fankes.miui.notify.utils.factory.bindAdapter
|
||||
import com.fankes.miui.notify.utils.factory.callOnBackPressed
|
||||
import com.fankes.miui.notify.utils.factory.colorOf
|
||||
import com.fankes.miui.notify.utils.factory.copyToClipboard
|
||||
import com.fankes.miui.notify.utils.factory.navigate
|
||||
import com.fankes.miui.notify.utils.factory.openBrowser
|
||||
import com.fankes.miui.notify.utils.factory.showDialog
|
||||
import com.fankes.miui.notify.utils.factory.snake
|
||||
import com.fankes.miui.notify.utils.factory.toast
|
||||
import com.fankes.miui.notify.utils.tool.IconRuleManagerTool
|
||||
import com.fankes.miui.notify.utils.tool.SystemUITool
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
|
||||
class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
|
||||
|
||||
/** 当前筛选条件 */
|
||||
private var filterText = ""
|
||||
|
||||
/** 回调适配器改变 */
|
||||
private var onChanged: (() -> Unit)? = null
|
||||
|
||||
/** 回调滚动事件改变 */
|
||||
private var onScrollEvent: ((Boolean) -> Unit)? = null
|
||||
|
||||
/** 全部的通知图标优化数据 */
|
||||
private var iconAllDatas = ArrayList<IconDataBean>()
|
||||
|
||||
override fun onCreate() {
|
||||
/** 检查激活和启用状态 */
|
||||
if (YukiHookAPI.Status.isXposedModuleActive.not() || ConfigData.isEnableModule.not()) {
|
||||
showDialog {
|
||||
title = "模块不可用"
|
||||
msg = "模块没有激活或已被停用,你无法使用这里的功能,请先激活或启用模块。"
|
||||
confirmButton(text = "我知道了") { finish() }
|
||||
noCancelable()
|
||||
}
|
||||
return
|
||||
}
|
||||
/** 返回按钮点击事件 */
|
||||
binding.titleBackIcon.setOnClickListener { callOnBackPressed() }
|
||||
/** 刷新适配器结果相关 */
|
||||
refreshAdapterResult()
|
||||
/** 设置上下按钮点击事件 */
|
||||
binding.configTitleUp.setOnClickListener {
|
||||
snake(msg = "滚动到顶部")
|
||||
onScrollEvent?.invoke(false)
|
||||
}
|
||||
binding.configTitleDown.setOnClickListener {
|
||||
snake(msg = "滚动到底部")
|
||||
onScrollEvent?.invoke(true)
|
||||
}
|
||||
/** 设置过滤按钮点击事件 */
|
||||
binding.configTitleFilter.setOnClickListener {
|
||||
showDialog<DiaIconFilterBinding> {
|
||||
title = "按条件过滤"
|
||||
binding.iconFiltersEdit.apply {
|
||||
requestFocus()
|
||||
invalidate()
|
||||
if (filterText.isNotBlank()) {
|
||||
setText(filterText)
|
||||
setSelection(filterText.length)
|
||||
}
|
||||
}
|
||||
confirmButton {
|
||||
if (binding.iconFiltersEdit.text.toString().isNotBlank()) {
|
||||
filterText = binding.iconFiltersEdit.text.toString().trim()
|
||||
refreshAdapterResult()
|
||||
} else {
|
||||
toast(msg = "条件不能为空")
|
||||
it.performClick()
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
if (filterText.isNotBlank())
|
||||
neutralButton(text = "清除条件") {
|
||||
filterText = ""
|
||||
refreshAdapterResult()
|
||||
}
|
||||
}
|
||||
}
|
||||
/** 设置同步列表按钮点击事件 */
|
||||
binding.configTitleSync.setOnClickListener { onStartRefresh() }
|
||||
/** 设置列表元素和 Adapter */
|
||||
binding.configListView.apply {
|
||||
bindAdapter {
|
||||
onBindDatas { iconDatas }
|
||||
onBindViews<AdapterConfigBinding> { binding, position ->
|
||||
iconDatas[position].also { bean ->
|
||||
binding.adpAppIcon.setImageBitmap(bean.iconBitmap)
|
||||
(if (bean.iconColor != 0) bean.iconColor else resources.colorOf(R.color.colorTextGray)).also { color ->
|
||||
binding.adpAppIcon.setColorFilter(color)
|
||||
binding.adpAppName.setTextColor(color)
|
||||
}
|
||||
binding.adpAppName.text = bean.appName
|
||||
binding.adpAppPkgName.text = bean.packageName
|
||||
binding.adpCbrName.text = "贡献者:" + bean.contributorName
|
||||
isAppNotifyHookOf(bean).also { e ->
|
||||
binding.adpAppOpenSwitch.isChecked = e
|
||||
binding.adpAppAllSwitch.isEnabled = e
|
||||
}
|
||||
binding.adpAppOpenSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
putAppNotifyHookOf(bean, b)
|
||||
binding.adpAppAllSwitch.isEnabled = b
|
||||
SystemUITool.refreshSystemUI(context = this@ConfigureActivity)
|
||||
}
|
||||
binding.adpAppAllSwitch.isChecked = isAppNotifyHookAllOf(bean)
|
||||
binding.adpAppAllSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
fun saveState() {
|
||||
putAppNotifyHookAllOf(bean, b)
|
||||
SystemUITool.refreshSystemUI(context = this@ConfigureActivity)
|
||||
}
|
||||
if (b) showDialog {
|
||||
title = "全部替换"
|
||||
msg = "此功能仅针对严重不遵守规范的 APP 通知图标才需要开启,例如:APP 推送通知后无法识别出现的黑白块图标。\n\n" +
|
||||
"此功能在一般情况下请保持关闭并跟随在线规则的配置,并不要随意改变此配置," +
|
||||
"开启后 APP 的通知图标可能会被规则破坏,你确定还要开启吗?"
|
||||
confirmButton { saveState() }
|
||||
cancelButton { btn.isChecked = btn.isChecked.not() }
|
||||
noCancelable()
|
||||
} else saveState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.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) } }
|
||||
}
|
||||
/** 设置点击事件 */
|
||||
binding.configCbrButton.setOnClickListener {
|
||||
showDialog {
|
||||
title = "感谢你的贡献"
|
||||
msg = "通知图标优化名单需要大家的共同维护才能得以完善,请选择你的贡献方式。"
|
||||
confirmButton(text = "贡献规则") { openBrowser(IconRuleManagerTool.RULES_CONTRIBUTING_URL) }
|
||||
cancelButton(text = "请求适配") { openBrowser(IconRuleManagerTool.RULES_FEEDBACK_URL) }
|
||||
neutralButton(text = "暂时不用")
|
||||
}
|
||||
}
|
||||
/** 装载数据 */
|
||||
mockLocalData()
|
||||
/** 更新数据 */
|
||||
when {
|
||||
intent?.getBooleanExtra("isNewAppSupport", false) == true ->
|
||||
showDialog {
|
||||
val appName = intent?.getStringExtra("appName") ?: ""
|
||||
val pkgName = intent?.getStringExtra("pkgName") ?: ""
|
||||
title = "新安装应用通知图标适配"
|
||||
msg = "你已安装 $appName($pkgName)\n\n" +
|
||||
"此应用未在通知优化名单中发现适配数据,若此应用发送的通知为彩色图标," +
|
||||
"可随时点击本页面下方的“贡献通知图标优化名单”按钮提交贡献或请求适配。\n\n" +
|
||||
"若你已知晓此应用会遵守原生通知图标规范,可忽略此提示。\n\n" +
|
||||
"你可以现在立即同步适配列表,以获取最新的适配数据。"
|
||||
confirmButton(text = "同步列表") { onStartRefresh() }
|
||||
cancelButton(text = "复制名称+包名") { copyToClipboard(content = "$appName($pkgName)") }
|
||||
neutralButton(text = "取消")
|
||||
noCancelable()
|
||||
}
|
||||
intent?.getBooleanExtra("isDirectUpdate", false) == true -> onStartRefresh(isByHand = false)
|
||||
intent?.getBooleanExtra("isShowUpdDialog", true) == true -> onStartRefresh()
|
||||
}
|
||||
/** 清除数据 */
|
||||
intent?.apply {
|
||||
removeExtra("isNewAppSupport")
|
||||
removeExtra("isDirectUpdate")
|
||||
removeExtra("isShowUpdDialog")
|
||||
}
|
||||
/** 设置返回监听事件 */
|
||||
addOnBackPressedEvent {
|
||||
if (MainActivity.isActivityLive.not())
|
||||
showDialog {
|
||||
title = "提示"
|
||||
msg = "要返回模块主页吗?"
|
||||
confirmButton {
|
||||
releaseEventAndBack()
|
||||
navigate<MainActivity>()
|
||||
}
|
||||
cancelButton { releaseEventAndBack() }
|
||||
}
|
||||
else releaseEventAndBack()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始同步
|
||||
* @param isByHand 是否手动同步 - 默认是
|
||||
*/
|
||||
private fun onStartRefresh(isByHand: Boolean = true) {
|
||||
if (isByHand)
|
||||
IconRuleManagerTool.syncByHand(context = this) {
|
||||
filterText = ""
|
||||
mockLocalData()
|
||||
}
|
||||
else
|
||||
IconRuleManagerTool.sync(context = this) {
|
||||
filterText = ""
|
||||
mockLocalData()
|
||||
}
|
||||
}
|
||||
|
||||
/** 装载或刷新本地数据 */
|
||||
private fun mockLocalData() {
|
||||
iconAllDatas = IconPackParams(context = this).iconDatas
|
||||
refreshAdapterResult()
|
||||
}
|
||||
|
||||
/** 刷新适配器结果相关 */
|
||||
private fun refreshAdapterResult() {
|
||||
onChanged?.invoke()
|
||||
binding.configTitleCountText.text =
|
||||
if (filterText.isBlank()) "已适配 ${iconDatas.size} 个 APP 的通知图标"
|
||||
else "“$filterText” 匹配到 ${iconDatas.size} 个结果"
|
||||
binding.configListNoDataView.apply {
|
||||
text = if (iconAllDatas.isEmpty()) "噫,竟然什么都没有~\n请点击右上角同步按钮获取云端数据" else "噫,竟然什么都没找到~"
|
||||
isVisible = iconDatas.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前结果下的图标数组
|
||||
* @return [Array]
|
||||
*/
|
||||
private val iconDatas
|
||||
get() = if (filterText.isBlank()) iconAllDatas
|
||||
else iconAllDatas.filter {
|
||||
it.appName.lowercase().contains(filterText.lowercase()) || it.packageName.lowercase().contains(filterText.lowercase())
|
||||
}
|
||||
}
|
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2022/1/24.
|
||||
*/
|
||||
@file:Suppress("SetTextI18n")
|
||||
|
||||
package com.fankes.miui.notify.ui.activity
|
||||
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.miui.notify.R
|
||||
import com.fankes.miui.notify.const.ModuleVersion
|
||||
import com.fankes.miui.notify.data.ConfigData
|
||||
import com.fankes.miui.notify.data.factory.bind
|
||||
import com.fankes.miui.notify.databinding.ActivityMainBinding
|
||||
import com.fankes.miui.notify.databinding.DiaStatusIconCountBinding
|
||||
import com.fankes.miui.notify.params.IconPackParams
|
||||
import com.fankes.miui.notify.ui.activity.base.BaseActivity
|
||||
import com.fankes.miui.notify.utils.factory.androidVersionCodeName
|
||||
import com.fankes.miui.notify.utils.factory.hideOrShowLauncherIcon
|
||||
import com.fankes.miui.notify.utils.factory.isLauncherIconShowing
|
||||
import com.fankes.miui.notify.utils.factory.isLowerAndroidP
|
||||
import com.fankes.miui.notify.utils.factory.isLowerAndroidR
|
||||
import com.fankes.miui.notify.utils.factory.isMIOS
|
||||
import com.fankes.miui.notify.utils.factory.isNotMiSystem
|
||||
import com.fankes.miui.notify.utils.factory.isNotNoificationEnabled
|
||||
import com.fankes.miui.notify.utils.factory.isNotSupportMiSystemVersion
|
||||
import com.fankes.miui.notify.utils.factory.miSystemVersion
|
||||
import com.fankes.miui.notify.utils.factory.miuiVersionCode
|
||||
import com.fankes.miui.notify.utils.factory.navigate
|
||||
import com.fankes.miui.notify.utils.factory.openBrowser
|
||||
import com.fankes.miui.notify.utils.factory.openNotifySetting
|
||||
import com.fankes.miui.notify.utils.factory.showDialog
|
||||
import com.fankes.miui.notify.utils.factory.showTimePicker
|
||||
import com.fankes.miui.notify.utils.factory.snake
|
||||
import com.fankes.miui.notify.utils.factory.systemFullVersion
|
||||
import com.fankes.miui.notify.utils.tool.GithubReleaseTool
|
||||
import com.fankes.miui.notify.utils.tool.I18nWarnTool
|
||||
import com.fankes.miui.notify.utils.tool.SystemUITool
|
||||
import com.fankes.projectpromote.ProjectPromote
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 窗口是否启动 */
|
||||
internal var isActivityLive = false
|
||||
|
||||
/** 模块是否可用 */
|
||||
internal var isModuleRegular = false
|
||||
|
||||
/** 模块是否有效 */
|
||||
internal var isModuleValied = false
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
/** 设置可用性 */
|
||||
isActivityLive = true
|
||||
/** 检查更新 */
|
||||
GithubReleaseTool.checkingForUpdate(context = this, ModuleVersion.NAME) { version, function ->
|
||||
binding.mainTextReleaseVersion.apply {
|
||||
text = "点击更新 $version"
|
||||
isVisible = true
|
||||
setOnClickListener { function() }
|
||||
}
|
||||
}
|
||||
when {
|
||||
/** 判断是否为小米系统 */
|
||||
isNotMiSystem ->
|
||||
showDialog {
|
||||
title = "不是 MIUI 或 HyperOS 系统"
|
||||
msg = "此模块专为 MIUI、HyperOS 系统打造,当前无法识别你的系统为其中任意之一,所以模块无法工作。"
|
||||
confirmButton(text = "查看支持的模块") {
|
||||
openBrowser(url = "https://github.com/fankes/AndroidNotifyIconAdapt")
|
||||
finish()
|
||||
}
|
||||
cancelButton(text = "退出") { finish() }
|
||||
noCancelable()
|
||||
}
|
||||
/** 判断最低 Android 系统版本 */
|
||||
isLowerAndroidP ->
|
||||
showDialog {
|
||||
title = "Android 系统版本过低"
|
||||
msg = "此模块最低支持基于 Android 9 的 MIUI 系统,你的系统版本过低不再进行适配。\n\n" +
|
||||
"若有其它疑问,你可以点击下方按钮前往项目地址进行反馈。"
|
||||
confirmButton(text = "前往项目地址") {
|
||||
openProjectUrl()
|
||||
finish()
|
||||
}
|
||||
cancelButton(text = "退出") { finish() }
|
||||
noCancelable()
|
||||
}
|
||||
/** 判断支持的系统版本 */
|
||||
isNotSupportMiSystemVersion ->
|
||||
showDialog {
|
||||
title = "不支持的系统版本"
|
||||
msg = (if (miSystemVersion.isNotBlank())
|
||||
"此模块目前支持 MIUI 11~14 和 HyperOS 1.0 系统,你的系统版本为 $miSystemVersion,暂不支持。\n\n" +
|
||||
"如果你的系统版本识别有误,请检查是否有相关插件修改了系统版本。\n\n"
|
||||
else "无法获取系统版本,请检查你是否修改了系统参数或使用非官方系统。\n\n") + "若有其它疑问,你可以点击下方按钮前往项目地址进行反馈。"
|
||||
confirmButton(text = "前往项目地址") {
|
||||
openProjectUrl()
|
||||
finish()
|
||||
}
|
||||
cancelButton(text = "退出") { finish() }
|
||||
noCancelable()
|
||||
}
|
||||
/** 判断是否 Hook */
|
||||
YukiHookAPI.Status.isXposedModuleActive -> {
|
||||
if (IconPackParams(context = this).iconDatas.isEmpty() && ConfigData.isEnableNotifyIconFix)
|
||||
showDialog {
|
||||
title = "配置通知图标优化名单"
|
||||
msg = "模块需要获取在线规则以更新“通知图标优化名单”,它现在是空的,这看起来是你第一次使用模块,请首先进行配置才可以使用相关功能。\n" +
|
||||
"你可以随时在本页面下方找到“配置通知图标优化名单”手动前往。"
|
||||
confirmButton(text = "前往") { navigate<ConfigureActivity>() }
|
||||
cancelButton()
|
||||
noCancelable()
|
||||
}
|
||||
if (isNotNoificationEnabled && ConfigData.isEnableNotifyIconFix)
|
||||
showDialog {
|
||||
title = "模块的通知权限已关闭"
|
||||
msg = "请开启通知权限,以确保你能收到通知图标优化在线规则的更新。"
|
||||
confirmButton { openNotifySetting() }
|
||||
cancelButton()
|
||||
noCancelable()
|
||||
}
|
||||
if (isLowerAndroidR && ConfigData.isIgnoredAndroidVersionToLow.not())
|
||||
showDialog {
|
||||
title = "Android 版本过低"
|
||||
msg = "你当前使用的 Android 版本过低,模块的部分功能可能会发生问题," +
|
||||
"由于设备有限,无法逐一调试,若有好的建议可向我们贡献代码提交适配请求,建议在 Android 11 及以上版本中使用效果最佳。"
|
||||
confirmButton(text = "我知道了") { ConfigData.isIgnoredAndroidVersionToLow = true }
|
||||
noCancelable()
|
||||
}
|
||||
/** 推广、恰饭 */
|
||||
ProjectPromote.show(activity = this, ModuleVersion.toString())
|
||||
}
|
||||
else ->
|
||||
showDialog {
|
||||
title = "模块没有激活"
|
||||
msg = "检测到模块没有激活,模块需要 Xposed 环境依赖," +
|
||||
"同时需要系统拥有 Root 权限," +
|
||||
"请自行查看本页面使用帮助与说明第二条。\n" +
|
||||
"由于需要修改系统应用达到效果,模块不支持太极阴、应用转生。"
|
||||
confirmButton(text = "我知道了")
|
||||
noCancelable()
|
||||
}
|
||||
}
|
||||
I18nWarnTool.checkingOrShowing(context = this)
|
||||
if (isMIOS) binding.mainTitle.text = binding.mainTitle.text.toString().replace("MIUI", "HyperOS")
|
||||
binding.mainTextVersion.text = "模块版本:${ModuleVersion.NAME}"
|
||||
/** 设置 CI 自动构建标识 */
|
||||
if (ModuleVersion.isCiMode)
|
||||
binding.mainTextReleaseVersion.apply {
|
||||
text = "CI ${ModuleVersion.GITHUB_COMMIT_ID}"
|
||||
isVisible = true
|
||||
setOnClickListener {
|
||||
showDialog {
|
||||
title = "CI 自动构建说明"
|
||||
msg = """
|
||||
你正在使用的是 CI 自动构建版本,Commit ID 为 ${ModuleVersion.GITHUB_COMMIT_ID}。
|
||||
|
||||
它是由代码提交后自动触发并构建、自动编译发布的,并未经任何稳定性测试,使用风险自负。
|
||||
""".trimIndent()
|
||||
confirmButton(text = "我知道了")
|
||||
noCancelable()
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.mainTextMiuiVersion.text = "系统版本:[$androidVersionCodeName] $systemFullVersion"
|
||||
binding.warnSCountDisTip.isGone = miuiVersionCode > 12.5
|
||||
binding.warnMiuiNotifyStyleTip.isGone = miuiVersionCode > 11
|
||||
binding.statusIconCountText.text = ConfigData.liftedStatusIconCount.toString()
|
||||
binding.notifyIconAutoSyncText.text = ConfigData.notifyIconFixAutoTime
|
||||
binding.moduleEnableSwitch.bind(ConfigData.ENABLE_MODULE) {
|
||||
onInitialize {
|
||||
binding.moduleEnableLogItem.isVisible = it
|
||||
binding.colorIconHookItem.isVisible = it
|
||||
binding.statusIconCountItem.isVisible = isLowerAndroidR.not() && it
|
||||
binding.notifyStyleConfigItem.isVisible = it
|
||||
binding.notifyIconConfigItem.isVisible = it
|
||||
}
|
||||
onChanged {
|
||||
reinitialize()
|
||||
refreshModuleStatus()
|
||||
SystemUITool.showNeedRestartSnake(context = this@MainActivity)
|
||||
}
|
||||
}
|
||||
binding.moduleEnableLogSwitch.bind(ConfigData.ENABLE_MODULE_LOG) {
|
||||
onInitialize { binding.expAllDebugLogButton.isVisible = it }
|
||||
onChanged {
|
||||
reinitialize()
|
||||
SystemUITool.refreshSystemUI(context = this@MainActivity, isRefreshCacheOnly = true)
|
||||
}
|
||||
}
|
||||
binding.statusIconCountSwitch.bind(ConfigData.ENABLE_LIFTED_STATUS_ICON_COUNT) {
|
||||
onInitialize { binding.statusIconCountChildItem.isVisible = it }
|
||||
onChanged {
|
||||
reinitialize()
|
||||
SystemUITool.refreshSystemUI(context = this@MainActivity) { snake(msg = "设置将在新通知推送或状态栏刷新后自动生效") }
|
||||
}
|
||||
}
|
||||
binding.colorIconCompatSwitch.bind(ConfigData.ENABLE_COLOR_ICON_COMPAT) {
|
||||
isAutoApplyChanges = false
|
||||
onChanged {
|
||||
/** 应用更改并刷新系统界面 */
|
||||
fun applyChangesAndRefresh() {
|
||||
applyChanges()
|
||||
SystemUITool.refreshSystemUI(context = this@MainActivity)
|
||||
}
|
||||
if (it) showDialog {
|
||||
title = "启用兼容模式"
|
||||
msg = "启用兼容模式可修复部分系统版本可能出现无法判定通知图标反色的问题," +
|
||||
"但是这也可能会导致新的问题,一般情况下不建议开启,确定要继续吗?\n\n" +
|
||||
"如果系统界面刷新后通知图标颜色发生错误,请尝试重启一次系统界面。"
|
||||
confirmButton { applyChangesAndRefresh() }
|
||||
cancelButton { cancelChanges() }
|
||||
noCancelable()
|
||||
} else applyChangesAndRefresh()
|
||||
}
|
||||
}
|
||||
binding.miuiNotifyIconReplacementSwitch.bind(ConfigData.ENABLE_REPLACE_MIUI_STYLE_NOTIFY_ICON) {
|
||||
onChanged { SystemUITool.refreshSystemUI(context = this@MainActivity) }
|
||||
}
|
||||
binding.notifyIconForceSystemColorSwitch.bind(ConfigData.ENABLE_NOTIFY_ICON_FORCE_SYSTEM_COLOR) {
|
||||
isAutoApplyChanges = false
|
||||
onChanged {
|
||||
/** 应用更改并刷新系统界面 */
|
||||
fun applyChangesAndRefresh() {
|
||||
applyChangesAndReinitialize()
|
||||
SystemUITool.refreshSystemUI(context = this@MainActivity)
|
||||
}
|
||||
if (it) showDialog {
|
||||
title = "破坏性功能警告"
|
||||
msg = "开启这个功能后,任何通知栏中的通知图标都会忽略图标自身的着色属性,全部使用系统默认颜色 (系统提供的统一色调) 着色。\n\n" +
|
||||
"此功能仅面向一些追求图标美观度的用户,我们不推荐开启这个功能,且发生任何 BUG 都不会去修复,仍然继续开启吗?"
|
||||
confirmButton { applyChangesAndRefresh() }
|
||||
cancelButton { cancelChanges() }
|
||||
noCancelable()
|
||||
} else applyChangesAndRefresh()
|
||||
}
|
||||
}
|
||||
binding.notifyIconForceAppIconSwitch.bind(ConfigData.ENABLE_NOTIFY_ICON_FORCE_APP_ICON) {
|
||||
isAutoApplyChanges = false
|
||||
onInitialize {
|
||||
arrayOf(
|
||||
binding.notifyIconCustomCornerItem,
|
||||
binding.notifyIconForceSystemColorItem
|
||||
).forEach { e -> e.isVisible = isLowerAndroidR.not() && it.not() }
|
||||
binding.miuiNotifyIconReplacementItem.isVisible = it.not()
|
||||
}
|
||||
onChanged {
|
||||
/** 应用更改并刷新系统界面 */
|
||||
fun applyChangesAndRefresh() {
|
||||
applyChangesAndReinitialize()
|
||||
SystemUITool.refreshSystemUI(context = this@MainActivity)
|
||||
}
|
||||
if (it) showDialog {
|
||||
title = "破坏性功能警告"
|
||||
msg = "开启这个功能后,任何通知栏中的通知图标都会被强制替换为当前推送通知的 APP 的图标," +
|
||||
"某些系统级别的 APP 通知图标可能会显示异常或发生图标丢失。\n\n" +
|
||||
"此功能仅面向一些追求图标美观度的用户,我们不推荐开启这个功能,且发生任何 BUG 都不会去修复,仍然继续开启吗?"
|
||||
confirmButton { applyChangesAndRefresh() }
|
||||
cancelButton { cancelChanges() }
|
||||
noCancelable()
|
||||
} else applyChangesAndRefresh()
|
||||
}
|
||||
}
|
||||
binding.notifyIconFixSwitch.bind(ConfigData.ENABLE_NOTIFY_ICON_FIX) {
|
||||
onInitialize {
|
||||
binding.notifyIconFixButton.isVisible = it
|
||||
binding.notifyIconFixPlaceholderItem.isVisible = it
|
||||
binding.notifyIconFixNotifyItem.isVisible = it
|
||||
binding.notifyIconAutoSyncItem.isVisible = it
|
||||
}
|
||||
onChanged {
|
||||
reinitialize()
|
||||
SystemUITool.refreshSystemUI(context = this@MainActivity)
|
||||
}
|
||||
}
|
||||
binding.notifyIconFixPlaceholderSwitch.bind(ConfigData.ENABLE_NOTIFY_ICON_FIX_PLACEHOLDER) {
|
||||
isAutoApplyChanges = false
|
||||
onChanged {
|
||||
/** 应用更改并刷新系统界面 */
|
||||
fun applyChangesAndRefresh() {
|
||||
applyChanges()
|
||||
SystemUITool.refreshSystemUI(context = this@MainActivity)
|
||||
}
|
||||
if (it) showDialog {
|
||||
title = "注意"
|
||||
msg = "开启这个功能后,当发现未适配的彩色通知图标时," +
|
||||
"状态栏中显示的通知图标将会使用预置的占位符图标进行修补," +
|
||||
"通知栏中显示的通知图标保持原始图标不变。\n\n" +
|
||||
"此功能的作用仅为临时修复破坏规范的通知图标,仍然继续开启吗?"
|
||||
confirmButton { applyChangesAndRefresh() }
|
||||
cancelButton { cancelChanges() }
|
||||
noCancelable()
|
||||
} else applyChangesAndRefresh()
|
||||
}
|
||||
}
|
||||
binding.notifyIconFixNotifySwitch.bind(ConfigData.ENABLE_NOTIFY_ICON_FIX_NOTIFY) {
|
||||
onChanged { SystemUITool.refreshSystemUI(context = this@MainActivity, isRefreshCacheOnly = true) }
|
||||
}
|
||||
binding.notifyIconAutoSyncSwitch.bind(ConfigData.ENABLE_NOTIFY_ICON_FIX_AUTO) {
|
||||
onInitialize { binding.notifyIconAutoSyncChildItem.isVisible = it }
|
||||
onChanged {
|
||||
reinitialize()
|
||||
SystemUITool.refreshSystemUI(context = this@MainActivity, isRefreshCacheOnly = true)
|
||||
}
|
||||
}
|
||||
binding.statusDarkIconCustomAlphaSeekbar.bind(
|
||||
ConfigData.STATUS_ICON_DARK_ALPHA_LEVEL, binding.statusDarkIconCustomAlphaText, suffix = "%"
|
||||
) { SystemUITool.refreshSystemUI(context = this) }
|
||||
binding.statusLightIconCustomAlphaSeekbar.bind(
|
||||
ConfigData.STATUS_ICON_LIGHT_ALPHA_LEVEL, binding.statusLightIconCustomAlphaText, suffix = "%"
|
||||
) { SystemUITool.refreshSystemUI(context = this) }
|
||||
binding.notifyIconCustomCornerSeekbar.bind(ConfigData.NOTIFY_ICON_CORNER_SIZE, binding.notifyIconCustomCornerText, suffix = " dp") {
|
||||
SystemUITool.refreshSystemUI(context = this)
|
||||
}
|
||||
/** 导出全部日志按钮点击事件 */
|
||||
binding.expAllDebugLogButton.setOnClickListener { SystemUITool.obtainAndExportDebugLogs(context = this) }
|
||||
/** MIUI 通知显示设置按钮点击事件 */
|
||||
binding.miuiNotifyStyleButton.setOnClickListener { SystemUITool.openMiuiNotificationDisplaySettings(context = this) }
|
||||
/** 通知图标优化名单按钮点击事件 */
|
||||
binding.notifyIconFixButton.setOnClickListener { navigate<ConfigureActivity>() }
|
||||
/** 修改状态栏通知图标个数按钮点击事件 */
|
||||
binding.statusIconCountButton.setOnClickListener {
|
||||
showDialog<DiaStatusIconCountBinding> {
|
||||
title = "设置最多显示的图标个数"
|
||||
binding.iconCountEdit.apply {
|
||||
requestFocus()
|
||||
invalidate()
|
||||
setText(ConfigData.liftedStatusIconCount.toString())
|
||||
setSelection(ConfigData.liftedStatusIconCount.toString().length)
|
||||
}
|
||||
confirmButton {
|
||||
when {
|
||||
(runCatching { binding.iconCountEdit.text.toString().toInt() }.getOrNull() ?: -1)
|
||||
!in 0..100 -> snake(msg = "请输入有效数值")
|
||||
binding.iconCountEdit.text.toString().isNotBlank() -> runCatching {
|
||||
ConfigData.liftedStatusIconCount = binding.iconCountEdit.text.toString().trim().toInt()
|
||||
this@MainActivity.binding.statusIconCountText.text = ConfigData.liftedStatusIconCount.toString()
|
||||
SystemUITool.refreshSystemUI(context) { context.snake(msg = "设置将在新通知推送或状态栏刷新后自动生效") }
|
||||
}.onFailure { snake(msg = "数值格式无效") }
|
||||
else -> snake(msg = "请输入有效数值")
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
/** 自动更新在线规则修改时间按钮点击事件 */
|
||||
binding.notifyIconAutoSyncButton.setOnClickListener {
|
||||
showTimePicker(ConfigData.notifyIconFixAutoTime) {
|
||||
showDialog {
|
||||
title = "每天 $it 自动更新"
|
||||
msg = "设置保存后将在每天 $it 自动同步名单到最新云端数据,若数据已是最新则不会显示任何提示,否则会发送一条通知。\n\n" +
|
||||
"请确保:\n\n" +
|
||||
"1.模块没有被禁止前台以及后台联网权限\n" +
|
||||
"2.模块没有被禁止被其它 APP 关联唤醒\n" +
|
||||
"3.模块的系统通知权限已开启\n\n" +
|
||||
"模块无需保持在后台运行,到达同步时间后会自动启动,如果到达时间后模块正在运行则会自动取消本次计划任务。"
|
||||
confirmButton(text = "保存设置") {
|
||||
ConfigData.notifyIconFixAutoTime = it
|
||||
this@MainActivity.binding.notifyIconAutoSyncText.text = it
|
||||
SystemUITool.refreshSystemUI(context, isRefreshCacheOnly = true)
|
||||
}
|
||||
cancelButton()
|
||||
noCancelable()
|
||||
}
|
||||
}
|
||||
}
|
||||
/** 重启按钮点击事件 */
|
||||
binding.titleRestartIcon.setOnClickListener { SystemUITool.restartSystemUI(context = this) }
|
||||
/** 项目地址按钮点击事件 */
|
||||
binding.titleGithubIcon.setOnClickListener { openProjectUrl() }
|
||||
/** 恰饭! */
|
||||
binding.linkWithFollowMe.setOnClickListener {
|
||||
openBrowser(url = "https://www.coolapk.com/u/876977", packageName = "com.coolapk.market")
|
||||
}
|
||||
/** 设置桌面图标显示隐藏 */
|
||||
binding.hideIconInLauncherSwitch.isChecked = isLauncherIconShowing.not()
|
||||
binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
hideOrShowLauncherIcon(b)
|
||||
}
|
||||
/** 注册导出调试日志启动器 */
|
||||
SystemUITool.registerExportDebugLogsLauncher(activity = this)
|
||||
}
|
||||
|
||||
/** 前往项目地址 */
|
||||
private fun openProjectUrl() {
|
||||
openBrowser(url = "https://github.com/fankes/MIUINativeNotifyIcon")
|
||||
}
|
||||
|
||||
/** 刷新模块状态 */
|
||||
private fun refreshModuleStatus() {
|
||||
binding.mainLinStatus.setBackgroundResource(
|
||||
when {
|
||||
YukiHookAPI.Status.isXposedModuleActive &&
|
||||
(isModuleRegular.not() || isModuleValied.not() || ConfigData.isEnableModule.not()) -> R.drawable.bg_yellow_round
|
||||
YukiHookAPI.Status.isXposedModuleActive -> R.drawable.bg_green_round
|
||||
else -> R.drawable.bg_dark_round
|
||||
}
|
||||
)
|
||||
binding.mainImgStatus.setImageResource(
|
||||
when {
|
||||
YukiHookAPI.Status.isXposedModuleActive && ConfigData.isEnableModule -> R.drawable.ic_success
|
||||
else -> R.drawable.ic_warn
|
||||
}
|
||||
)
|
||||
binding.mainTextStatus.text = when {
|
||||
YukiHookAPI.Status.isXposedModuleActive && ConfigData.isEnableModule.not() -> "模块已停用"
|
||||
YukiHookAPI.Status.isXposedModuleActive && isModuleRegular.not() -> "模块已激活,请重启系统界面"
|
||||
YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> "模块已更新,请重启系统界面"
|
||||
YukiHookAPI.Status.isXposedModuleActive -> "模块已激活"
|
||||
else -> "模块未激活"
|
||||
}
|
||||
binding.mainTextApiWay.isVisible = YukiHookAPI.Status.isXposedModuleActive
|
||||
binding.mainTextApiWay.text = "Activated by ${YukiHookAPI.Status.Executor.name} API ${YukiHookAPI.Status.Executor.apiLevel}"
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
/** 刷新模块状态 */
|
||||
refreshModuleStatus()
|
||||
/** 检查模块激活状态 */
|
||||
SystemUITool.checkingActivated(context = this) { isValied ->
|
||||
isModuleRegular = true
|
||||
isModuleValied = isValied
|
||||
refreshModuleStatus()
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2022/3/26.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.fankes.miui.notify.ui.activity.auto
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import com.fankes.miui.notify.ui.activity.base.BaseActivity
|
||||
import com.fankes.miui.notify.utils.factory.delayedRun
|
||||
import com.fankes.miui.notify.utils.tool.IconRuleManagerTool
|
||||
import com.fankes.miui.notify.utils.tool.SystemUITool
|
||||
|
||||
class NotifyIconRuleUpdateActivity : Activity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
/** 设置透明窗口 */
|
||||
window?.decorView?.systemUiVisibility =
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
window?.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
window?.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
|
||||
/** 检测运行状态 */
|
||||
if (BaseActivity.isMainThreadRunning) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
/** 拉取云端数据 */
|
||||
IconRuleManagerTool.sync(context = this) {
|
||||
/** 刷新系统界面 */
|
||||
SystemUITool.refreshSystemUI()
|
||||
/** 结束当前窗口 */
|
||||
runOnUiThread { delayedRun(ms = 1000) { finish() } }
|
||||
}
|
||||
/** 切换到后台 */
|
||||
moveTaskToBack(true)
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2022/1/30.
|
||||
*/
|
||||
@file:Suppress("UNCHECKED_CAST")
|
||||
|
||||
package com.fankes.miui.notify.ui.activity.base
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.fankes.miui.notify.R
|
||||
import com.fankes.miui.notify.utils.factory.isNotSystemInDarkMode
|
||||
import com.highcapable.yukihookapi.hook.factory.current
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
|
||||
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 应用是否正在运行 */
|
||||
var isMainThreadRunning = false
|
||||
}
|
||||
|
||||
/** 获取绑定布局对象 */
|
||||
lateinit var binding: VB
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
isMainThreadRunning = true
|
||||
binding = current().generic()?.argument()?.method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}?.get()?.invoke<VB>(layoutInflater) ?: error("binding failed")
|
||||
setContentView(binding.root)
|
||||
/** 隐藏系统的标题栏 */
|
||||
supportActionBar?.hide()
|
||||
/** 初始化沉浸状态栏 */
|
||||
WindowCompat.getInsetsController(window, window.decorView).apply {
|
||||
isAppearanceLightStatusBars = isNotSystemInDarkMode
|
||||
isAppearanceLightNavigationBars = isNotSystemInDarkMode
|
||||
}
|
||||
ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also {
|
||||
window?.statusBarColor = it
|
||||
window?.navigationBarColor = it
|
||||
window?.navigationBarDividerColor = it
|
||||
}
|
||||
/** 装载子类 */
|
||||
onCreate()
|
||||
}
|
||||
|
||||
/** 回调 [onCreate] 方法 */
|
||||
abstract fun onCreate()
|
||||
}
|
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/30.
|
||||
*/
|
||||
package com.fankes.miui.notify.ui.base
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.fankes.miui.notify.R
|
||||
import com.fankes.miui.notify.utils.isNotSystemInDarkMode
|
||||
import com.gyf.immersionbar.ktx.immersionBar
|
||||
|
||||
abstract class BaseActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
/** 隐藏系统的标题栏 */
|
||||
supportActionBar?.hide()
|
||||
/** 初始化沉浸状态栏 */
|
||||
immersionBar {
|
||||
statusBarColor(R.color.colorThemeBackground)
|
||||
autoDarkModeEnable(true)
|
||||
statusBarDarkFont(isNotSystemInDarkMode)
|
||||
navigationBarColor(R.color.colorThemeBackground)
|
||||
navigationBarDarkIcon(isNotSystemInDarkMode)
|
||||
fitsSystemWindows(true)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
@@ -18,19 +18,21 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
* This file is created by fankes on 2022/1/8.
|
||||
*/
|
||||
@file:Suppress("SameParameterValue")
|
||||
|
||||
package com.fankes.miui.notify.view
|
||||
package com.fankes.miui.notify.ui.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import com.fankes.miui.notify.utils.dp
|
||||
import com.fankes.miui.notify.utils.drawable.drawabletoolbox.DrawableBuilder
|
||||
import com.fankes.miui.notify.utils.factory.dp
|
||||
import com.fankes.miui.notify.utils.factory.isSystemInDarkMode
|
||||
import top.defaults.drawabletoolbox.DrawableBuilder
|
||||
|
||||
class MaterialSwitch(context: Context, attrs: AttributeSet?) : SwitchCompat(context, attrs) {
|
||||
|
||||
@@ -43,27 +45,31 @@ class MaterialSwitch(context: Context, attrs: AttributeSet?) : SwitchCompat(cont
|
||||
return ColorStateList(states, colors)
|
||||
}
|
||||
|
||||
private val thumbColor get() = if (context.isSystemInDarkMode) 0xFF7C7C7C else 0xFFCCCCCC
|
||||
|
||||
init {
|
||||
trackDrawable = DrawableBuilder()
|
||||
.rectangle()
|
||||
.rounded()
|
||||
.solidColor(0xFF656565.toInt())
|
||||
.height(20.dp)
|
||||
.cornerRadius(15.dp)
|
||||
.height(20.dp(context))
|
||||
.cornerRadius(15.dp(context))
|
||||
.build()
|
||||
thumbDrawable = DrawableBuilder()
|
||||
.rectangle()
|
||||
.rounded()
|
||||
.solidColor(Color.WHITE)
|
||||
.size(20.dp, 20.dp)
|
||||
.cornerRadius(20.dp)
|
||||
.strokeWidth(2.dp)
|
||||
.size(20.dp(context), 20.dp(context))
|
||||
.cornerRadius(20.dp(context))
|
||||
.strokeWidth(8.dp(context))
|
||||
.strokeColor(Color.TRANSPARENT)
|
||||
.build()
|
||||
trackTintList = toColors(
|
||||
0xFF656565.toInt(),
|
||||
0xFFCCCCCC.toInt(),
|
||||
0xFFCCCCCC.toInt()
|
||||
thumbColor.toInt(),
|
||||
thumbColor.toInt()
|
||||
)
|
||||
isSingleLine = true
|
||||
ellipsize = TextUtils.TruncateAt.END
|
||||
}
|
||||
}
|
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/7.
|
||||
*/
|
||||
@file:Suppress("unused", "DEPRECATION")
|
||||
|
||||
package com.fankes.miui.notify.utils
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import kotlin.math.round
|
||||
|
||||
/**
|
||||
* 构造对话框
|
||||
* @param it 对话框方法体
|
||||
*/
|
||||
fun Context.showDialog(it: DialogBuilder.() -> Unit) = DialogBuilder(this).apply(it).show()
|
||||
|
||||
/**
|
||||
* 对话框构造器
|
||||
* @param context 实例
|
||||
*/
|
||||
class DialogBuilder(private val context: Context) {
|
||||
|
||||
private var instance: AlertDialog.Builder? = null // 实例对象
|
||||
|
||||
private var customLayoutView: View? = null // 自定义布局
|
||||
|
||||
init {
|
||||
instance = AlertDialog.Builder(context, android.R.style.Theme_Material_Light_Dialog)
|
||||
}
|
||||
|
||||
/** 设置对话框不可关闭 */
|
||||
fun noCancelable() = instance?.setCancelable(false)
|
||||
|
||||
/** 设置对话框标题 */
|
||||
var title
|
||||
get() = ""
|
||||
set(value) {
|
||||
instance?.setTitle(value)
|
||||
}
|
||||
|
||||
/** 设置对话框消息内容 */
|
||||
var msg
|
||||
get() = ""
|
||||
set(value) {
|
||||
instance?.setMessage(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框自定义布局
|
||||
* @param resId 属性资源 Id
|
||||
* @return [View]
|
||||
*/
|
||||
fun addView(resId: Int): View {
|
||||
customLayoutView = LayoutInflater.from(context).inflate(resId, null)
|
||||
return customLayoutView ?: error("Inflate $resId failed")
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框确定按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param it 点击事件
|
||||
*/
|
||||
fun confirmButton(text: String = "确定", it: () -> Unit = {}) =
|
||||
instance?.setPositiveButton(text) { _, _ -> it() }
|
||||
|
||||
/**
|
||||
* 设置对话框取消按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param it 点击事件
|
||||
*/
|
||||
fun cancelButton(text: String = "取消", it: () -> Unit = {}) =
|
||||
instance?.setNegativeButton(text) { _, _ -> it() }
|
||||
|
||||
/**
|
||||
* 设置对话框第三个按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param it 点击事件
|
||||
*/
|
||||
fun neutralButton(text: String = "更多", it: () -> Unit = {}) =
|
||||
instance?.setNeutralButton(text) { _, _ -> it() }
|
||||
|
||||
/** 显示对话框 */
|
||||
internal fun show() = instance?.create()?.apply {
|
||||
val dm = DisplayMetrics()
|
||||
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getMetrics(dm)
|
||||
customLayoutView?.let { setView(it.apply { minimumWidth = round(dm.widthPixels / 1.3).toInt() }) }
|
||||
window?.setBackgroundDrawable(GradientDrawable(
|
||||
GradientDrawable.Orientation.TOP_BOTTOM,
|
||||
intArrayOf(Color.WHITE, Color.WHITE)
|
||||
).apply {
|
||||
shape = GradientDrawable.RECTANGLE
|
||||
gradientType = GradientDrawable.LINEAR_GRADIENT
|
||||
cornerRadius = 15.dp(this@DialogBuilder.context)
|
||||
})
|
||||
}?.show()
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/2/8.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils
|
||||
|
||||
import android.content.Context
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
|
||||
|
||||
/**
|
||||
* 系统界面工具
|
||||
*/
|
||||
object SystemUITool {
|
||||
|
||||
/**
|
||||
* 重启系统界面
|
||||
* @param context 实例
|
||||
*/
|
||||
fun restartSystemUI(context: Context) =
|
||||
context.showDialog {
|
||||
title = "重启系统界面"
|
||||
msg = "你确定要立即重启系统界面吗?\n\n" +
|
||||
"部分 MIUI 系统使用了状态栏主题可能会发生主题失效的情况,这种情况请再重启一次即可。"
|
||||
confirmButton {
|
||||
execShellSu(cmd = "pgrep systemui").also { pid ->
|
||||
if (pid.isNotBlank())
|
||||
execShellSu(cmd = "kill -9 $pid")
|
||||
else toast(msg = "ROOT 权限获取失败")
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示需要重启系统界面的 [Snackbar]
|
||||
* @param context 实例
|
||||
*/
|
||||
fun showNeedRestartSnake(context: Context) =
|
||||
if (YukiHookModuleStatus.isActive())
|
||||
context.snake(msg = "设置需要重启系统界面才能生效", actionText = "立即重启") { restartSystemUI(context) }
|
||||
else context.snake(msg = "模块没有激活,更改不会生效")
|
||||
}
|
@@ -1,357 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/7.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "PrivateApi", "unused", "ObsoleteSdkInt")
|
||||
|
||||
package com.fankes.miui.notify.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.util.Base64
|
||||
import android.widget.Toast
|
||||
import com.fankes.miui.notify.application.MNNApplication.Companion.appContext
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.highcapable.yukihookapi.hook.factory.classOf
|
||||
import com.highcapable.yukihookapi.hook.factory.hasClass
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.log.loggerE
|
||||
import com.highcapable.yukihookapi.hook.type.java.StringType
|
||||
import com.topjohnwu.superuser.Shell
|
||||
|
||||
/**
|
||||
* 系统深色模式是否开启
|
||||
* @return [Boolean] 是否开启
|
||||
*/
|
||||
val isSystemInDarkMode
|
||||
get() = (appContext.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
|
||||
/**
|
||||
* 系统深色模式是否没开启
|
||||
* @return [Boolean] 是否开启
|
||||
*/
|
||||
inline val isNotSystemInDarkMode get() = !isSystemInDarkMode
|
||||
|
||||
/**
|
||||
* 系统深色模式是否开启
|
||||
* @return [Boolean] 是否开启
|
||||
*/
|
||||
val Context.isSystemInDarkMode get() = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
|
||||
/**
|
||||
* 通知栏是否为 MIUI 样式
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
val Context.isMiuiNotifyStyle get() = safeOfFalse { Settings.System.getInt(contentResolver, "status_bar_notification_style") == 0 }
|
||||
|
||||
/**
|
||||
* 系统深色模式是否没开启
|
||||
* @return [Boolean] 是否开启
|
||||
*/
|
||||
inline val Context.isNotSystemInDarkMode get() = !isSystemInDarkMode
|
||||
|
||||
/**
|
||||
* 系统版本是否高于或等于 Android 12
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
inline val isUpperOfAndroidS get() = Build.VERSION.SDK_INT > Build.VERSION_CODES.R
|
||||
|
||||
/**
|
||||
* 系统版本是否低于 Android 9
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
inline val isLowerAndroidP get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.P
|
||||
|
||||
/**
|
||||
* 当前设备是否是 MIUI 定制 Android 系统
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
val isMIUI by lazy { ("android.miui.R").hasClass }
|
||||
|
||||
/**
|
||||
* 当前设备是否不是 MIUI 定制 Android 系统
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
inline val isNotMIUI get() = !isMIUI
|
||||
|
||||
/**
|
||||
* 是否为支持的 MIUI 版本
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isSupportMiuiVersion
|
||||
get() = when (miuiVersion) {
|
||||
"12" -> true
|
||||
"12.5" -> true
|
||||
"13" -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否不为支持的 MIUI 版本
|
||||
* @return [Boolean]
|
||||
*/
|
||||
inline val isNotSupportMiuiVersion get() = !isSupportMiuiVersion
|
||||
|
||||
/**
|
||||
* 获取 MIUI 版本
|
||||
* @return [String]
|
||||
*/
|
||||
val miuiVersion
|
||||
get() =
|
||||
if (isMIUI)
|
||||
findPropString(key = "ro.miui.ui.version.name", default = "V无法获取").let {
|
||||
when (it) {
|
||||
"V110" -> "11"
|
||||
"V11" -> "11"
|
||||
"V120" -> "12"
|
||||
"V12" -> "12"
|
||||
"V125" -> "12.5"
|
||||
"V130" -> "13"
|
||||
"V13" -> "13"
|
||||
else -> it.replace(oldValue = "V", newValue = "")
|
||||
}
|
||||
}.trim()
|
||||
else "NULL"
|
||||
|
||||
/**
|
||||
* 获取 MIUI 完全版本
|
||||
* @return [String]
|
||||
*/
|
||||
val miuiFullVersion
|
||||
get() = if (isMIUI) (miuiVersion + " " + findPropString(key = "ro.system.build.version.incremental"))
|
||||
else "不是 MIUI 系统"
|
||||
|
||||
/**
|
||||
* 得到安装包信息
|
||||
* @return [PackageInfo]
|
||||
*/
|
||||
val Context.packageInfo get() = packageManager?.getPackageInfo(packageName, 0) ?: PackageInfo()
|
||||
|
||||
/**
|
||||
* 判断应用是否安装
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val String.isInstall
|
||||
get() = safeOfFalse {
|
||||
appContext.packageManager.getPackageInfo(
|
||||
this,
|
||||
PackageManager.GET_UNINSTALLED_PACKAGES
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到版本信息
|
||||
* @return [String]
|
||||
*/
|
||||
val Context.versionName get() = packageInfo.versionName ?: ""
|
||||
|
||||
/**
|
||||
* 得到版本号
|
||||
* @return [Int]
|
||||
*/
|
||||
val Context.versionCode get() = packageInfo.versionCode
|
||||
|
||||
/**
|
||||
* dp 转换为 px
|
||||
* @return [Int]
|
||||
*/
|
||||
val Number.dp get() = (toFloat() * appContext.resources.displayMetrics.density).toInt()
|
||||
|
||||
/**
|
||||
* dp 转换为 px
|
||||
* @param context 使用的实例
|
||||
* @return [Float]
|
||||
*/
|
||||
fun Number.dp(context: Context) = toFloat() * context.resources.displayMetrics.density
|
||||
|
||||
/**
|
||||
* Base64 加密
|
||||
* @return [String]
|
||||
*/
|
||||
val String.base64: String get() = Base64.encodeToString(toByteArray(), Base64.DEFAULT)
|
||||
|
||||
/**
|
||||
* Base64 解密为字节流
|
||||
* @return [ByteArray]
|
||||
*/
|
||||
val String.unbase64 get() = Base64.decode(this, Base64.DEFAULT) ?: ByteArray(0)
|
||||
|
||||
/**
|
||||
* 字节流解析为位图
|
||||
* @return [Bitmap]
|
||||
*/
|
||||
val ByteArray.bitmap: Bitmap get() = BitmapFactory.decodeByteArray(this, 0, size)
|
||||
|
||||
/**
|
||||
* 字符串解析为位图
|
||||
* @return [Bitmap]
|
||||
*/
|
||||
val String.bitmap: Bitmap get() = unbase64.bitmap
|
||||
|
||||
/**
|
||||
* 获取系统 Prop 值
|
||||
* @param key Key
|
||||
* @param default 默认值
|
||||
* @return [String]
|
||||
*/
|
||||
fun findPropString(key: String, default: String = "") = safeOf(default) {
|
||||
(classOf(name = "android.os.SystemProperties").method {
|
||||
name = "get"
|
||||
param(StringType, StringType)
|
||||
}.get().invoke(key, default)) ?: default
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令 - su
|
||||
* @param cmd 命令
|
||||
* @return [String] 执行结果
|
||||
*/
|
||||
fun execShellSu(cmd: String) = safeOfNothing {
|
||||
Shell.su(cmd).exec().out.let {
|
||||
if (it.isNotEmpty()) it[0].trim() else ""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出 [Toast]
|
||||
* @param msg 提示内容
|
||||
*/
|
||||
fun toast(msg: String) = Toast.makeText(appContext, msg, Toast.LENGTH_SHORT).show()
|
||||
|
||||
/**
|
||||
* 弹出 [Snackbar]
|
||||
* @param msg 提示内容
|
||||
* @param actionText 按钮文本 - 不写默认取消按钮
|
||||
* @param it 按钮事件回调
|
||||
*/
|
||||
fun Context.snake(msg: String, actionText: String = "", it: () -> Unit = {}) =
|
||||
Snackbar.make((this as Activity).findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG)
|
||||
.apply {
|
||||
if (actionText.isBlank()) return@apply
|
||||
setActionTextColor(Color.WHITE)
|
||||
setAction(actionText) { it() }
|
||||
}.show()
|
||||
|
||||
/**
|
||||
* 忽略异常返回值
|
||||
* @param it 回调 - 如果异常为空
|
||||
* @return [T] 发生异常时返回设定值否则返回正常值
|
||||
*/
|
||||
inline fun <T> safeOfNull(it: () -> T): T? = safeOf(null, it)
|
||||
|
||||
/**
|
||||
* 忽略异常返回值
|
||||
* @param it 回调 - 如果异常为 false
|
||||
* @return [Boolean] 发生异常时返回设定值否则返回正常值
|
||||
*/
|
||||
inline fun safeOfFalse(it: () -> Boolean) = safeOf(default = false, it)
|
||||
|
||||
/**
|
||||
* 忽略异常返回值
|
||||
* @param it 回调 - 如果异常为 true
|
||||
* @return [Boolean] 发生异常时返回设定值否则返回正常值
|
||||
*/
|
||||
inline fun safeOfTrue(it: () -> Boolean) = safeOf(default = true, it)
|
||||
|
||||
/**
|
||||
* 忽略异常返回值
|
||||
* @param it 回调 - 如果异常为 false
|
||||
* @return [String] 发生异常时返回设定值否则返回正常值
|
||||
*/
|
||||
inline fun safeOfNothing(it: () -> String) = safeOf(default = "", it)
|
||||
|
||||
/**
|
||||
* 忽略异常返回值
|
||||
* @param it 回调 - 如果异常为 false
|
||||
* @return [Int] 发生异常时返回设定值否则返回正常值
|
||||
*/
|
||||
inline fun safeOfNan(it: () -> Int) = safeOf(default = 0, it)
|
||||
|
||||
/**
|
||||
* 忽略异常返回值
|
||||
* @param default 异常返回值
|
||||
* @param it 正常回调值
|
||||
* @return [T] 发生异常时返回设定值否则返回正常值
|
||||
*/
|
||||
inline fun <T> safeOf(default: T, it: () -> T): T {
|
||||
return try {
|
||||
it()
|
||||
} catch (t: NullPointerException) {
|
||||
default
|
||||
} catch (t: UnsatisfiedLinkError) {
|
||||
default
|
||||
} catch (t: UnsupportedOperationException) {
|
||||
default
|
||||
} catch (t: ClassNotFoundException) {
|
||||
default
|
||||
} catch (t: IllegalStateException) {
|
||||
default
|
||||
} catch (t: NoSuchMethodError) {
|
||||
default
|
||||
} catch (t: NoSuchFieldError) {
|
||||
default
|
||||
} catch (t: Error) {
|
||||
default
|
||||
} catch (t: Exception) {
|
||||
default
|
||||
} catch (t: Throwable) {
|
||||
default
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略异常运行
|
||||
* @param msg 出错输出的消息 - 默认为空
|
||||
* @param it 正常回调
|
||||
*/
|
||||
inline fun safeRun(msg: String = "", it: () -> Unit) {
|
||||
try {
|
||||
it()
|
||||
} catch (e: NullPointerException) {
|
||||
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
|
||||
} catch (e: ClassNotFoundException) {
|
||||
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
|
||||
} catch (e: IllegalStateException) {
|
||||
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
|
||||
} catch (e: NoSuchMethodError) {
|
||||
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
|
||||
} catch (e: NoSuchFieldError) {
|
||||
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
|
||||
} catch (e: Error) {
|
||||
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
|
||||
} catch (e: Exception) {
|
||||
if (msg.isNotBlank()) loggerE(msg = msg, e = e)
|
||||
} catch (e: Throwable) {
|
||||
}
|
||||
}
|
@@ -1,293 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
*/
|
||||
@file:Suppress("SameParameterValue")
|
||||
|
||||
package com.fankes.miui.notify.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.graphics.drawable.RippleDrawable
|
||||
import android.graphics.drawable.RotateDrawable
|
||||
import android.os.Build
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
|
||||
private val gradientState = resolveGradientState()
|
||||
|
||||
private fun resolveGradientState(): Class<*> {
|
||||
val classes = GradientDrawable::class.java.declaredClasses
|
||||
for (singleClass in classes) {
|
||||
if (singleClass.simpleName == "GradientState") return singleClass
|
||||
}
|
||||
throw RuntimeException("GradientState could not be found in thisAny GradientDrawable implementation")
|
||||
}
|
||||
|
||||
private val rotateState = resolveRotateState()
|
||||
|
||||
private fun resolveRotateState(): Class<*> {
|
||||
val classes = RotateDrawable::class.java.declaredClasses
|
||||
for (singleClass in classes) {
|
||||
if (singleClass.simpleName == "RotateState") return singleClass
|
||||
}
|
||||
throw RuntimeException("RotateState could not be found in thisAny RotateDrawable implementation")
|
||||
}
|
||||
|
||||
@Throws(SecurityException::class, NoSuchFieldException::class)
|
||||
private fun resolveField(source: Class<*>, fieldName: String): Field {
|
||||
val field = source.getDeclaredField(fieldName)
|
||||
field.isAccessible = true
|
||||
return field
|
||||
}
|
||||
|
||||
@Throws(SecurityException::class, NoSuchMethodException::class)
|
||||
private fun resolveMethod(
|
||||
source: Class<*>,
|
||||
methodName: String,
|
||||
vararg parameterTypes: Class<*>
|
||||
): Method {
|
||||
val method = source.getDeclaredMethod(methodName, *parameterTypes)
|
||||
method.isAccessible = true
|
||||
return method
|
||||
}
|
||||
|
||||
fun setInnerRadius(drawable: GradientDrawable, value: Int) {
|
||||
try {
|
||||
val innerRadius = resolveField(gradientState, "mInnerRadius")
|
||||
innerRadius.setInt(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setInnerRadiusRatio(drawable: GradientDrawable, value: Float) {
|
||||
try {
|
||||
val innerRadius = resolveField(gradientState, "mInnerRadiusRatio")
|
||||
innerRadius.setFloat(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setThickness(drawable: GradientDrawable, value: Int) {
|
||||
try {
|
||||
val innerRadius = resolveField(gradientState, "mThickness")
|
||||
innerRadius.setInt(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setThicknessRatio(drawable: GradientDrawable, value: Float) {
|
||||
try {
|
||||
val innerRadius = resolveField(gradientState, "mThicknessRatio")
|
||||
innerRadius.setFloat(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setUseLevelForShape(drawable: GradientDrawable, value: Boolean) {
|
||||
try {
|
||||
val useLevelForShape = resolveField(gradientState, "mUseLevelForShape")
|
||||
useLevelForShape.setBoolean(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
fun setOrientation(drawable: GradientDrawable, value: GradientDrawable.Orientation) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
drawable.orientation = value
|
||||
} else {
|
||||
try {
|
||||
val orientation = resolveField(gradientState, "mOrientation")
|
||||
orientation.set(drawable.constantState, value)
|
||||
val rectIdDirty = resolveField(GradientDrawable::class.java, "mRectIsDirty")
|
||||
rectIdDirty.setBoolean(drawable, true)
|
||||
drawable.invalidateSelf()
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
fun setColors(drawable: GradientDrawable, value: IntArray) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
drawable.colors = value
|
||||
} else {
|
||||
try {
|
||||
val colors = resolveField(gradientState, "mColors")
|
||||
colors.set(drawable.constantState, value)
|
||||
drawable.invalidateSelf()
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setGradientRadiusType(drawable: GradientDrawable, value: Int) {
|
||||
try {
|
||||
val type = resolveField(gradientState, "mGradientRadiusType")
|
||||
type.setInt(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setGradientRadius(drawable: GradientDrawable, value: Float) {
|
||||
try {
|
||||
val gradientRadius = resolveField(gradientState, "mGradientRadius")
|
||||
gradientRadius.setFloat(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setStrokeColor(drawable: GradientDrawable, value: Int) {
|
||||
try {
|
||||
val type = resolveField(gradientState, "mStrokeColor")
|
||||
type.setInt(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setDrawable(rotateDrawable: RotateDrawable, drawable: Drawable) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
rotateDrawable.drawable = drawable
|
||||
} else {
|
||||
try {
|
||||
val drawableField = resolveField(rotateState, "mDrawable")
|
||||
val stateField = resolveField(RotateDrawable::class.java, "mState")
|
||||
drawableField.set(stateField.get(rotateDrawable), drawable)
|
||||
drawable.callback = rotateDrawable
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setPivotX(rotateDrawable: RotateDrawable, value: Float) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
rotateDrawable.pivotX = value
|
||||
} else {
|
||||
try {
|
||||
val pivotXField = resolveField(rotateState, "mPivotX")
|
||||
pivotXField.setFloat(rotateDrawable.constantState, value)
|
||||
val pivotXRelField = resolveField(rotateState, "mPivotXRel")
|
||||
pivotXRelField.setBoolean(rotateDrawable.constantState, true)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setPivotY(rotateDrawable: RotateDrawable, value: Float) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
rotateDrawable.pivotY = value
|
||||
} else {
|
||||
try {
|
||||
val pivotYField = resolveField(rotateState, "mPivotY")
|
||||
pivotYField.setFloat(rotateDrawable.constantState, value)
|
||||
val pivotYRelField = resolveField(rotateState, "mPivotYRel")
|
||||
pivotYRelField.setBoolean(rotateDrawable.constantState, true)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setFromDegrees(rotateDrawable: RotateDrawable, value: Float) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
rotateDrawable.fromDegrees = value
|
||||
} else {
|
||||
try {
|
||||
val fromDegreesField = resolveField(rotateState, "mFromDegrees")
|
||||
fromDegreesField.setFloat(rotateDrawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setToDegrees(rotateDrawable: RotateDrawable, value: Float) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
rotateDrawable.toDegrees = value
|
||||
} else {
|
||||
try {
|
||||
val toDegreesField = resolveField(rotateState, "mToDegrees")
|
||||
toDegreesField.setFloat(rotateDrawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setRadius(rippleDrawable: RippleDrawable, value: Int) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
rippleDrawable.radius = value
|
||||
} else {
|
||||
try {
|
||||
val setRadiusMethod =
|
||||
resolveMethod(RippleDrawable::class.java, "setMaxRadius", Int::class.java)
|
||||
setRadiusMethod.invoke(rippleDrawable, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,490 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
*/
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.fankes.miui.notify.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.util.StateSet
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class DrawableBuilder {
|
||||
|
||||
private var properties = DrawableProperties()
|
||||
private var order: AtomicInteger = AtomicInteger(1)
|
||||
private var transformsMap = TreeMap<Int, (Drawable) -> Drawable>()
|
||||
private var baseDrawable: Drawable? = null
|
||||
|
||||
fun batch(properties: DrawableProperties) = apply { this.properties = properties.copy() }
|
||||
fun baseDrawable(drawable: Drawable) = apply { baseDrawable = drawable }
|
||||
|
||||
// <shape>
|
||||
fun shape(shape: Int) = apply { properties.shape = shape }
|
||||
|
||||
fun rectangle() = apply { shape(GradientDrawable.RECTANGLE) }
|
||||
fun oval() = apply { shape(GradientDrawable.OVAL) }
|
||||
fun line() = apply { shape(GradientDrawable.LINE) }
|
||||
fun ring() = apply { shape(GradientDrawable.RING) }
|
||||
fun innerRadius(innerRadius: Int) = apply { properties.innerRadius = innerRadius }
|
||||
fun innerRadiusRatio(innerRadiusRatio: Float) =
|
||||
apply { properties.innerRadiusRatio = innerRadiusRatio }
|
||||
|
||||
fun thickness(thickness: Int) = apply { properties.thickness = thickness }
|
||||
fun thicknessRatio(thicknessRatio: Float) = apply { properties.thicknessRatio = thicknessRatio }
|
||||
|
||||
fun useLevelForRing(use: Boolean = true) = apply { properties.useLevelForRing = use }
|
||||
|
||||
// <corner>
|
||||
fun cornerRadius(cornerRadius: Int) = apply { properties.cornerRadius = cornerRadius }
|
||||
|
||||
fun topLeftRadius(topLeftRadius: Int) = apply { properties.topLeftRadius = topLeftRadius }
|
||||
fun topRightRadius(topRightRadius: Int) = apply { properties.topRightRadius = topRightRadius }
|
||||
fun bottomRightRadius(bottomRightRadius: Int) =
|
||||
apply { properties.bottomRightRadius = bottomRightRadius }
|
||||
|
||||
fun bottomLeftRadius(bottomLeftRadius: Int) =
|
||||
apply { properties.bottomLeftRadius = bottomLeftRadius }
|
||||
|
||||
fun rounded() = apply { cornerRadius(Int.MAX_VALUE) }
|
||||
fun cornerRadii(
|
||||
topLeftRadius: Int,
|
||||
topRightRadius: Int,
|
||||
bottomRightRadius: Int,
|
||||
bottomLeftRadius: Int
|
||||
) = apply {
|
||||
topLeftRadius(topLeftRadius); topRightRadius(topRightRadius); bottomRightRadius(
|
||||
bottomRightRadius
|
||||
); bottomLeftRadius(bottomLeftRadius)
|
||||
}
|
||||
|
||||
// <gradient>
|
||||
|
||||
fun gradient(useGradient: Boolean = true) = apply { properties.useGradient = useGradient }
|
||||
|
||||
fun gradientType(type: Int) = apply { properties.type = type }
|
||||
fun linearGradient() = apply { gradientType(GradientDrawable.LINEAR_GRADIENT) }
|
||||
fun radialGradient() = apply { gradientType(GradientDrawable.RADIAL_GRADIENT) }
|
||||
fun sweepGradient() = apply { gradientType(GradientDrawable.SWEEP_GRADIENT) }
|
||||
fun angle(angle: Int) = apply { properties.angle = angle }
|
||||
fun centerX(centerX: Float) = apply { properties.centerX = centerX }
|
||||
fun centerY(centerY: Float) = apply { properties.centerY = centerY }
|
||||
fun center(centerX: Float, centerY: Float) = apply { centerX(centerX); centerY(centerY) }
|
||||
|
||||
fun useCenterColor(useCenterColor: Boolean = true) =
|
||||
apply { properties.useCenterColor = useCenterColor }
|
||||
|
||||
fun startColor(startColor: Int) = apply { properties.startColor = startColor }
|
||||
fun centerColor(centerColor: Int) = apply {
|
||||
properties.centerColor = centerColor
|
||||
useCenterColor(true)
|
||||
}
|
||||
|
||||
fun endColor(endColor: Int) = apply { properties.endColor = endColor }
|
||||
fun gradientColors(startColor: Int, endColor: Int, centerColor: Int?) = apply {
|
||||
startColor(startColor); endColor(endColor)
|
||||
useCenterColor(centerColor != null)
|
||||
centerColor?.let {
|
||||
centerColor(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun gradientRadiusType(gradientRadiusType: Int) =
|
||||
apply { properties.gradientRadiusType = gradientRadiusType }
|
||||
|
||||
fun gradientRadius(gradientRadius: Float) = apply { properties.gradientRadius = gradientRadius }
|
||||
fun gradientRadius(radius: Float, type: Int) =
|
||||
apply { gradientRadius(radius); gradientRadiusType(type) }
|
||||
|
||||
fun gradientRadiusInPixel(radius: Float) =
|
||||
apply { gradientRadius(radius); gradientRadiusType(DrawableProperties.RADIUS_TYPE_PIXELS) }
|
||||
|
||||
fun gradientRadiusInFraction(radius: Float) =
|
||||
apply { gradientRadius(radius); gradientRadiusType(DrawableProperties.RADIUS_TYPE_FRACTION) }
|
||||
|
||||
fun useLevelForGradient(use: Boolean) = apply { properties.useLevelForGradient = use }
|
||||
fun useLevelForGradient() = apply { useLevelForGradient(true) }
|
||||
|
||||
// <size>
|
||||
fun width(width: Int) = apply { properties.width = width }
|
||||
|
||||
fun height(height: Int) = apply { properties.height = height }
|
||||
fun size(width: Int, height: Int) = apply { width(width); height(height) }
|
||||
fun size(size: Int) = apply { width(size).height(size) }
|
||||
|
||||
// <solid>
|
||||
fun solidColor(solidColor: Int) = apply { properties.solidColor = solidColor }
|
||||
|
||||
private var solidColorPressed: Int? = null
|
||||
fun solidColorPressed(color: Int?) = apply { solidColorPressed = color }
|
||||
private var solidColorPressedWhenRippleUnsupported: Int? = null
|
||||
fun solidColorPressedWhenRippleUnsupported(color: Int?) =
|
||||
apply { solidColorPressedWhenRippleUnsupported = color }
|
||||
|
||||
private var solidColorDisabled: Int? = null
|
||||
fun solidColorDisabled(color: Int?) = apply { solidColorDisabled = color }
|
||||
private var solidColorSelected: Int? = null
|
||||
fun solidColorSelected(color: Int?) = apply { solidColorSelected = color }
|
||||
fun solidColorStateList(colorStateList: ColorStateList) =
|
||||
apply { properties.solidColorStateList = colorStateList }
|
||||
|
||||
// <stroke>
|
||||
fun strokeWidth(strokeWidth: Int) = apply { properties.strokeWidth = strokeWidth }
|
||||
|
||||
fun strokeColor(strokeColor: Int) = apply { properties.strokeColor = strokeColor }
|
||||
private var strokeColorPressed: Int? = null
|
||||
fun strokeColorPressed(color: Int?) = apply { strokeColorPressed = color }
|
||||
private var strokeColorDisabled: Int? = null
|
||||
fun strokeColorDisabled(color: Int?) = apply { strokeColorDisabled = color }
|
||||
private var strokeColorSelected: Int? = null
|
||||
fun strokeColorSelected(color: Int?) = apply { strokeColorSelected = color }
|
||||
fun strokeColorStateList(colorStateList: ColorStateList) =
|
||||
apply { properties.strokeColorStateList = colorStateList }
|
||||
|
||||
fun dashWidth(dashWidth: Int) = apply { properties.dashWidth = dashWidth }
|
||||
fun dashGap(dashGap: Int) = apply { properties.dashGap = dashGap }
|
||||
fun hairlineBordered() = apply { strokeWidth(1) }
|
||||
fun shortDashed() = apply { dashWidth(12).dashGap(12) }
|
||||
fun mediumDashed() = apply { dashWidth(24).dashGap(24) }
|
||||
fun longDashed() = apply { dashWidth(36).dashGap(36) }
|
||||
fun dashed() = apply { mediumDashed() }
|
||||
|
||||
// <rotate>
|
||||
private var rotateOrder = 0
|
||||
|
||||
|
||||
fun rotate(boolean: Boolean = true) = apply {
|
||||
properties.useRotate = boolean
|
||||
rotateOrder = if (boolean) {
|
||||
order.getAndIncrement()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fun pivotX(pivotX: Float) = apply { properties.pivotX = pivotX }
|
||||
fun pivotY(pivotY: Float) = apply { properties.pivotY = pivotY }
|
||||
fun pivot(pivotX: Float, pivotY: Float) = apply { pivotX(pivotX).pivotY(pivotY) }
|
||||
fun fromDegrees(degrees: Float) = apply { properties.fromDegrees = degrees }
|
||||
fun toDegrees(degrees: Float) = apply { properties.toDegrees = degrees }
|
||||
fun degrees(fromDegrees: Float, toDegrees: Float) =
|
||||
apply { fromDegrees(fromDegrees).toDegrees(toDegrees) }
|
||||
|
||||
fun degrees(degrees: Float) = apply { fromDegrees(degrees).toDegrees(degrees) }
|
||||
fun rotate(fromDegrees: Float, toDegrees: Float) =
|
||||
apply { rotate().fromDegrees(fromDegrees).toDegrees(toDegrees) }
|
||||
|
||||
fun rotate(degrees: Float) = apply { rotate().degrees(degrees) }
|
||||
|
||||
// <scale>
|
||||
private var scaleOrder = 0
|
||||
|
||||
|
||||
fun scale(boolean: Boolean = true) = apply {
|
||||
properties.useScale = boolean
|
||||
scaleOrder = if (boolean) {
|
||||
order.getAndIncrement()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fun scaleLevel(level: Int) = apply { properties.scaleLevel = level }
|
||||
fun scaleGravity(gravity: Int) = apply { properties.scaleGravity = gravity }
|
||||
fun scaleWidth(scale: Float) = apply { properties.scaleWidth = scale }
|
||||
fun scaleHeight(scale: Float) = apply { properties.scaleHeight = scale }
|
||||
fun scale(scale: Float) = apply { scale().scaleWidth(scale).scaleHeight(scale) }
|
||||
fun scale(scaleWidth: Float, scaleHeight: Float) =
|
||||
apply { scale().scaleWidth(scaleWidth).scaleHeight(scaleHeight) }
|
||||
|
||||
// flip
|
||||
|
||||
fun flip(boolean: Boolean = true) = apply { properties.useFlip = boolean }
|
||||
|
||||
fun orientation(orientation: Int) = apply { properties.orientation = orientation }
|
||||
fun flipVertical() = apply { flip().orientation(FlipDrawable.ORIENTATION_VERTICAL) }
|
||||
|
||||
// <ripple>
|
||||
|
||||
fun ripple(boolean: Boolean = true) = apply { properties.useRipple = boolean }
|
||||
|
||||
fun rippleColor(color: Int) = apply { properties.rippleColor = color }
|
||||
fun rippleColorStateList(colorStateList: ColorStateList) =
|
||||
apply { properties.rippleColorStateList = colorStateList }
|
||||
|
||||
fun rippleRadius(radius: Int) = apply { properties.rippleRadius = radius }
|
||||
|
||||
fun build(): Drawable {
|
||||
if (baseDrawable != null) {
|
||||
return wrap(baseDrawable!!)
|
||||
}
|
||||
|
||||
var drawable: Drawable
|
||||
|
||||
// fall back when ripple is unavailable on devices with API < 21
|
||||
if (shouldFallbackRipple()) {
|
||||
if (solidColorPressedWhenRippleUnsupported != null) {
|
||||
solidColorPressed(solidColorPressedWhenRippleUnsupported)
|
||||
} else {
|
||||
solidColorPressed(properties.rippleColor)
|
||||
}
|
||||
}
|
||||
|
||||
if (needStateListDrawable()) {
|
||||
drawable = StateListDrawableBuilder()
|
||||
.pressed(buildPressedDrawable())
|
||||
.disabled(buildDisabledDrawable())
|
||||
.selected(buildSelectedDrawable())
|
||||
.normal(buildNormalDrawable())
|
||||
.build()
|
||||
} else {
|
||||
drawable = GradientDrawable()
|
||||
setupGradientDrawable(drawable)
|
||||
}
|
||||
drawable = wrap(drawable)
|
||||
return drawable
|
||||
}
|
||||
|
||||
private fun getSolidColorStateList(): ColorStateList {
|
||||
if (properties.solidColorStateList != null) {
|
||||
return properties.solidColorStateList!!
|
||||
}
|
||||
|
||||
val states = mutableListOf<IntArray>()
|
||||
val colors = mutableListOf<Int>()
|
||||
|
||||
solidColorPressed?.let {
|
||||
states.add(intArrayOf(android.R.attr.state_pressed))
|
||||
colors.add(it)
|
||||
}
|
||||
solidColorDisabled?.let {
|
||||
states.add(intArrayOf(-android.R.attr.state_enabled))
|
||||
colors.add(it)
|
||||
}
|
||||
solidColorSelected?.let {
|
||||
states.add(intArrayOf(android.R.attr.state_selected))
|
||||
colors.add(it)
|
||||
}
|
||||
states.add(StateSet.WILD_CARD)
|
||||
colors.add(properties.solidColor)
|
||||
|
||||
return ColorStateList(states.toTypedArray(), colors.toIntArray())
|
||||
}
|
||||
|
||||
private fun getStrokeColorStateList(): ColorStateList {
|
||||
if (properties.strokeColorStateList != null) {
|
||||
return properties.strokeColorStateList!!
|
||||
}
|
||||
|
||||
val states = mutableListOf<IntArray>()
|
||||
val colors = mutableListOf<Int>()
|
||||
|
||||
strokeColorPressed?.let {
|
||||
states.add(intArrayOf(android.R.attr.state_pressed))
|
||||
colors.add(it)
|
||||
}
|
||||
strokeColorDisabled?.let {
|
||||
states.add(intArrayOf(-android.R.attr.state_enabled))
|
||||
colors.add(it)
|
||||
}
|
||||
strokeColorSelected?.let {
|
||||
states.add(intArrayOf(android.R.attr.state_selected))
|
||||
colors.add(it)
|
||||
}
|
||||
states.add(StateSet.WILD_CARD)
|
||||
colors.add(properties.strokeColor)
|
||||
|
||||
return ColorStateList(states.toTypedArray(), colors.toIntArray())
|
||||
}
|
||||
|
||||
private fun buildPressedDrawable(): Drawable? {
|
||||
if (solidColorPressed == null && strokeColorPressed == null) return null
|
||||
|
||||
val pressedDrawable = GradientDrawable()
|
||||
setupGradientDrawable(pressedDrawable)
|
||||
solidColorPressed?.let {
|
||||
pressedDrawable.setColor(it)
|
||||
}
|
||||
strokeColorPressed?.let {
|
||||
setStrokeColor(pressedDrawable, it)
|
||||
}
|
||||
return pressedDrawable
|
||||
}
|
||||
|
||||
private fun buildDisabledDrawable(): Drawable? {
|
||||
if (solidColorDisabled == null && strokeColorDisabled == null) return null
|
||||
|
||||
val disabledDrawable = GradientDrawable()
|
||||
setupGradientDrawable(disabledDrawable)
|
||||
solidColorDisabled?.let {
|
||||
disabledDrawable.setColor(it)
|
||||
}
|
||||
strokeColorDisabled?.let {
|
||||
setStrokeColor(disabledDrawable, it)
|
||||
}
|
||||
return disabledDrawable
|
||||
}
|
||||
|
||||
private fun buildSelectedDrawable(): Drawable? {
|
||||
if (solidColorSelected == null && strokeColorSelected == null) return null
|
||||
|
||||
val selectedDrawable = GradientDrawable()
|
||||
setupGradientDrawable(selectedDrawable)
|
||||
solidColorSelected?.let {
|
||||
selectedDrawable.setColor(it)
|
||||
}
|
||||
strokeColorSelected?.let {
|
||||
setStrokeColor(selectedDrawable, it)
|
||||
}
|
||||
return selectedDrawable
|
||||
}
|
||||
|
||||
private fun buildNormalDrawable(): Drawable {
|
||||
val pressedDrawable = GradientDrawable()
|
||||
setupGradientDrawable(pressedDrawable)
|
||||
return pressedDrawable
|
||||
}
|
||||
|
||||
private fun setupGradientDrawable(drawable: GradientDrawable) {
|
||||
properties.apply {
|
||||
drawable.shape = shape
|
||||
if (shape == GradientDrawable.RING) {
|
||||
setInnerRadius(drawable, innerRadius)
|
||||
setInnerRadiusRatio(drawable, innerRadiusRatio)
|
||||
setThickness(drawable, thickness)
|
||||
setThicknessRatio(drawable, thicknessRatio)
|
||||
setUseLevelForShape(drawable, useLevelForRing)
|
||||
}
|
||||
drawable.cornerRadii = getCornerRadii()
|
||||
if (useGradient) {
|
||||
drawable.gradientType = type
|
||||
setGradientRadiusType(drawable, gradientRadiusType)
|
||||
setGradientRadius(drawable, gradientRadius)
|
||||
drawable.setGradientCenter(centerX, centerY)
|
||||
setOrientation(drawable, getOrientation())
|
||||
setColors(drawable, getColors())
|
||||
drawable.useLevel = useLevelForGradient
|
||||
} else {
|
||||
drawable.color = getSolidColorStateList()
|
||||
}
|
||||
drawable.setSize(width, height)
|
||||
drawable.setStroke(
|
||||
strokeWidth,
|
||||
getStrokeColorStateList(),
|
||||
dashWidth.toFloat(),
|
||||
dashGap.toFloat()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun needStateListDrawable(): Boolean {
|
||||
return (hasStrokeColorStateList() || (!properties.useGradient && hasSolidColorStateList()))
|
||||
}
|
||||
|
||||
private fun needRotateDrawable(): Boolean {
|
||||
return properties.useRotate &&
|
||||
!(properties.pivotX == 0.5f && properties.pivotY == 0.5f
|
||||
&& properties.fromDegrees == 0f && properties.toDegrees == 0f)
|
||||
}
|
||||
|
||||
private fun needScaleDrawable(): Boolean {
|
||||
return properties.useScale
|
||||
}
|
||||
|
||||
private fun wrap(drawable: Drawable): Drawable {
|
||||
var wrappedDrawable = drawable
|
||||
|
||||
if (rotateOrder > 0) {
|
||||
transformsMap[rotateOrder] = ::wrapRotateIfNeeded
|
||||
}
|
||||
if (scaleOrder > 0) {
|
||||
transformsMap[scaleOrder] = ::wrapScaleIfNeeded
|
||||
}
|
||||
|
||||
for (action in transformsMap.values) {
|
||||
wrappedDrawable = action.invoke(wrappedDrawable)
|
||||
}
|
||||
|
||||
if (properties.useFlip) {
|
||||
wrappedDrawable = FlipDrawableBuilder()
|
||||
.drawable(wrappedDrawable)
|
||||
.orientation(properties.orientation)
|
||||
.build()
|
||||
}
|
||||
|
||||
if (isRippleSupported() && properties.useRipple) {
|
||||
wrappedDrawable = RippleDrawableBuilder()
|
||||
.drawable(wrappedDrawable)
|
||||
.color(properties.rippleColor)
|
||||
.colorStateList(properties.rippleColorStateList)
|
||||
.radius(properties.rippleRadius)
|
||||
.build()
|
||||
}
|
||||
|
||||
return wrappedDrawable
|
||||
}
|
||||
|
||||
private fun shouldFallbackRipple(): Boolean {
|
||||
return properties.useRipple && !isRippleSupported()
|
||||
}
|
||||
|
||||
private fun isRippleSupported() = true
|
||||
|
||||
private fun wrapRotateIfNeeded(drawable: Drawable): Drawable {
|
||||
if (!needRotateDrawable()) return drawable
|
||||
|
||||
with(properties) {
|
||||
return RotateDrawableBuilder()
|
||||
.drawable(drawable)
|
||||
.pivotX(pivotX)
|
||||
.pivotY(pivotY)
|
||||
.fromDegrees(fromDegrees)
|
||||
.toDegrees(toDegrees)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private fun wrapScaleIfNeeded(drawable: Drawable): Drawable {
|
||||
if (!needScaleDrawable()) return drawable
|
||||
|
||||
with(properties) {
|
||||
return ScaleDrawableBuilder()
|
||||
.drawable(drawable)
|
||||
.level(scaleLevel)
|
||||
.scaleGravity(scaleGravity)
|
||||
.scaleWidth(scaleWidth)
|
||||
.scaleHeight(scaleHeight)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasSolidColorStateList(): Boolean {
|
||||
return solidColorPressed != null || solidColorDisabled != null || solidColorSelected != null
|
||||
}
|
||||
|
||||
private fun hasStrokeColorStateList(): Boolean {
|
||||
return strokeColorPressed != null || strokeColorDisabled != null || strokeColorSelected != null
|
||||
}
|
||||
}
|
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
*/
|
||||
@file:Suppress("SetterBackingFieldAssignment", "unused")
|
||||
|
||||
package com.fankes.miui.notify.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.view.Gravity
|
||||
import java.io.Serializable
|
||||
|
||||
data class DrawableProperties(
|
||||
|
||||
// <shape>
|
||||
@JvmField var shape: Int = GradientDrawable.RECTANGLE,
|
||||
@JvmField var innerRadius: Int = -1,
|
||||
@JvmField var innerRadiusRatio: Float = 9f,
|
||||
@JvmField var thickness: Int = -1,
|
||||
@JvmField var thicknessRatio: Float = 3f,
|
||||
@JvmField var useLevelForRing: Boolean = false,
|
||||
|
||||
// <corner>
|
||||
private var _cornerRadius: Int = 0,
|
||||
@JvmField var topLeftRadius: Int = 0,
|
||||
@JvmField var topRightRadius: Int = 0,
|
||||
@JvmField var bottomRightRadius: Int = 0,
|
||||
@JvmField var bottomLeftRadius: Int = 0,
|
||||
|
||||
// <gradient>
|
||||
@JvmField var useGradient: Boolean = false,
|
||||
@JvmField var type: Int = GradientDrawable.RADIAL_GRADIENT,
|
||||
@JvmField var angle: Int = 0,
|
||||
@JvmField var centerX: Float = 0.5f,
|
||||
@JvmField var centerY: Float = 0.5f,
|
||||
@JvmField var useCenterColor: Boolean = false,
|
||||
@JvmField var startColor: Int = Constants.DEFAULT_COLOR,
|
||||
@JvmField var centerColor: Int? = null,
|
||||
@JvmField var endColor: Int = 0x7FFFFFFF,
|
||||
@JvmField var gradientRadiusType: Int = RADIUS_TYPE_FRACTION,
|
||||
@JvmField var gradientRadius: Float = 0.5f,
|
||||
@JvmField var useLevelForGradient: Boolean = false,
|
||||
|
||||
// <size>
|
||||
@JvmField var width: Int = -1,
|
||||
@JvmField var height: Int = -1,
|
||||
|
||||
// <solid>
|
||||
@JvmField var solidColor: Int = Color.TRANSPARENT,
|
||||
@JvmField var solidColorStateList: ColorStateList? = null,
|
||||
|
||||
// <stroke>
|
||||
@JvmField var strokeWidth: Int = 0,
|
||||
@JvmField var strokeColor: Int = Color.DKGRAY,
|
||||
@JvmField var strokeColorStateList: ColorStateList? = null,
|
||||
@JvmField var dashWidth: Int = 0,
|
||||
@JvmField var dashGap: Int = 0,
|
||||
|
||||
// <rotate>
|
||||
@JvmField var useRotate: Boolean = false,
|
||||
@JvmField var pivotX: Float = 0.5f,
|
||||
@JvmField var pivotY: Float = 0.5f,
|
||||
@JvmField var fromDegrees: Float = 0f,
|
||||
@JvmField var toDegrees: Float = 0f,
|
||||
|
||||
// <scale>
|
||||
@JvmField var useScale: Boolean = false,
|
||||
@JvmField var scaleLevel: Int = 10000,
|
||||
@JvmField var scaleGravity: Int = Gravity.CENTER,
|
||||
@JvmField var scaleWidth: Float = 0f,
|
||||
@JvmField var scaleHeight: Float = 0f,
|
||||
|
||||
// flip
|
||||
@JvmField var useFlip: Boolean = false,
|
||||
@JvmField var orientation: Int = FlipDrawable.ORIENTATION_HORIZONTAL,
|
||||
|
||||
// ripple
|
||||
@JvmField var useRipple: Boolean = false,
|
||||
@JvmField var rippleColor: Int = Constants.DEFAULT_COLOR,
|
||||
@JvmField var rippleColorStateList: ColorStateList? = null,
|
||||
@JvmField var rippleRadius: Int = -1
|
||||
) : Serializable {
|
||||
|
||||
companion object {
|
||||
const val RADIUS_TYPE_PIXELS = 0
|
||||
const val RADIUS_TYPE_FRACTION = 1
|
||||
|
||||
@JvmField
|
||||
val CREATOR = object : Parcelable.Creator<DrawableProperties> {
|
||||
override fun createFromParcel(parcel: Parcel): DrawableProperties {
|
||||
return DrawableProperties(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<DrawableProperties?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cornerRadius: Int = _cornerRadius
|
||||
set(value) {
|
||||
_cornerRadius = value
|
||||
topLeftRadius = value
|
||||
topRightRadius = value
|
||||
bottomRightRadius = value
|
||||
bottomLeftRadius = value
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readFloat(),
|
||||
parcel.readInt(),
|
||||
parcel.readFloat(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readFloat(),
|
||||
parcel.readFloat(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readValue(Int::class.java.classLoader) as? Int,
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readFloat(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readParcelable(ColorStateList::class.java.classLoader),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readParcelable(ColorStateList::class.java.classLoader),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readFloat(),
|
||||
parcel.readFloat(),
|
||||
parcel.readFloat(),
|
||||
parcel.readFloat(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readFloat(),
|
||||
parcel.readFloat(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readParcelable(ColorStateList::class.java.classLoader),
|
||||
parcel.readInt()
|
||||
)
|
||||
|
||||
fun copy(): DrawableProperties {
|
||||
val parcel = Parcel.obtain()
|
||||
parcel.setDataPosition(0)
|
||||
val properties = CREATOR.createFromParcel(parcel)
|
||||
parcel.recycle()
|
||||
return properties
|
||||
}
|
||||
|
||||
fun getCornerRadii(): FloatArray {
|
||||
return floatArrayOf(
|
||||
topLeftRadius.toFloat(), topLeftRadius.toFloat(),
|
||||
topRightRadius.toFloat(), topRightRadius.toFloat(),
|
||||
bottomRightRadius.toFloat(), bottomRightRadius.toFloat(),
|
||||
bottomLeftRadius.toFloat(), bottomLeftRadius.toFloat()
|
||||
)
|
||||
}
|
||||
|
||||
fun getOrientation(): GradientDrawable.Orientation {
|
||||
val orientation: GradientDrawable.Orientation = when (val angle = this.angle % 360) {
|
||||
0 -> GradientDrawable.Orientation.LEFT_RIGHT
|
||||
45 -> GradientDrawable.Orientation.BL_TR
|
||||
90 -> GradientDrawable.Orientation.BOTTOM_TOP
|
||||
135 -> GradientDrawable.Orientation.BR_TL
|
||||
180 -> GradientDrawable.Orientation.RIGHT_LEFT
|
||||
225 -> GradientDrawable.Orientation.TR_BL
|
||||
270 -> GradientDrawable.Orientation.TOP_BOTTOM
|
||||
315 -> GradientDrawable.Orientation.TL_BR
|
||||
else -> throw IllegalArgumentException("Unsupported angle: $angle")
|
||||
}
|
||||
return orientation
|
||||
}
|
||||
|
||||
fun getColors(): IntArray {
|
||||
return if (useCenterColor && centerColor != null) {
|
||||
intArrayOf(startColor, centerColor!!, endColor)
|
||||
} else intArrayOf(startColor, endColor)
|
||||
}
|
||||
|
||||
fun materialization(): Drawable = DrawableBuilder().batch(this).build()
|
||||
}
|
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "CanvasSize")
|
||||
|
||||
package com.fankes.miui.notify.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
class FlipDrawable(
|
||||
private var drawable: Drawable,
|
||||
private var orientation: Int = ORIENTATION_HORIZONTAL
|
||||
) : Drawable() {
|
||||
|
||||
companion object {
|
||||
const val ORIENTATION_HORIZONTAL = 0
|
||||
const val ORIENTATION_VERTICAL = 1
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
val saveCount = canvas.save()
|
||||
if (orientation == ORIENTATION_VERTICAL) {
|
||||
canvas.scale(1f, -1f, (canvas.width / 2).toFloat(), (canvas.height / 2).toFloat())
|
||||
} else {
|
||||
canvas.scale(-1f, 1f, (canvas.width / 2).toFloat(), (canvas.height / 2).toFloat())
|
||||
}
|
||||
drawable.bounds = Rect(0, 0, canvas.width, canvas.height)
|
||||
drawable.draw(canvas)
|
||||
canvas.restoreToCount(saveCount)
|
||||
}
|
||||
|
||||
override fun onLevelChange(level: Int): Boolean {
|
||||
drawable.level = level
|
||||
invalidateSelf()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getIntrinsicWidth(): Int {
|
||||
return drawable.intrinsicWidth
|
||||
}
|
||||
|
||||
override fun getIntrinsicHeight(): Int {
|
||||
return drawable.intrinsicHeight
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
drawable.alpha = alpha
|
||||
}
|
||||
|
||||
override fun getOpacity(): Int {
|
||||
return drawable.opacity
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
drawable.colorFilter = colorFilter
|
||||
}
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
class FlipDrawableBuilder : DrawableWrapperBuilder<FlipDrawableBuilder>() {
|
||||
|
||||
private var orientation: Int = FlipDrawable.ORIENTATION_HORIZONTAL
|
||||
|
||||
fun orientation(orientation: Int) = apply { this.orientation = orientation }
|
||||
|
||||
override fun build(): Drawable {
|
||||
return FlipDrawable(drawable!!, orientation)
|
||||
}
|
||||
}
|
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.os.Build
|
||||
import android.view.Gravity
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.util.*
|
||||
|
||||
class LayerDrawableBuilder {
|
||||
|
||||
companion object {
|
||||
const val DIMEN_UNDEFINED = Int.MIN_VALUE
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private var paddingMode = LayerDrawable.PADDING_MODE_NEST
|
||||
private var paddingLeft = 0
|
||||
private var paddingTop = 0
|
||||
private var paddingRight = 0
|
||||
private var paddingBottom = 0
|
||||
private var paddingStart = 0
|
||||
private var paddingEnd = 0
|
||||
private val layers = ArrayList<Layer>()
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
fun paddingMode(mode: Int) = apply { paddingMode = mode }
|
||||
fun paddingLeft(padding: Int) = apply { paddingLeft = padding }
|
||||
fun paddingTop(padding: Int) = apply { paddingTop = padding }
|
||||
fun paddingRight(padding: Int) = apply { paddingRight = padding }
|
||||
fun paddingBottom(padding: Int) = apply { paddingBottom = padding }
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun paddingStart(padding: Int) = apply { paddingStart = padding }
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun paddingEnd(padding: Int) = apply { paddingEnd = padding }
|
||||
fun padding(padding: Int) = apply {
|
||||
paddingLeft(padding).paddingTop(padding).paddingRight(padding).paddingBottom(padding)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun paddingRelative(padding: Int) = apply {
|
||||
paddingStart(padding).paddingTop(padding).paddingEnd(padding).paddingBottom(padding)
|
||||
}
|
||||
|
||||
fun add(drawable: Drawable) = apply { layers.add(Layer(drawable)) }
|
||||
|
||||
fun width(width: Int) = apply { layers.last().width = width }
|
||||
fun height(height: Int) = apply { layers.last().height = height }
|
||||
|
||||
fun insetLeft(inset: Int) = apply { layers.last().insetLeft = inset }
|
||||
fun insetTop(inset: Int) = apply { layers.last().insetTop = inset }
|
||||
fun insetRight(inset: Int) = apply { layers.last().insetRight = inset }
|
||||
fun insetBottom(inset: Int) = apply { layers.last().insetBottom = inset }
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun insetStart(inset: Int) = apply { layers.last().insetStart = inset }
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun insetEnd(inset: Int) = apply { layers.last().insetEnd = inset }
|
||||
fun inset(inset: Int) =
|
||||
apply { insetLeft(inset).insetTop(inset).insetRight(inset).insetBottom(inset) }
|
||||
|
||||
fun inset(insetLeft: Int, insetTop: Int, insetRight: Int, insetBottom: Int) = apply {
|
||||
insetLeft(insetLeft).insetTop(insetTop).insetRight(insetRight).insetBottom(insetBottom)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun insetRelative(inset: Int) =
|
||||
apply { insetStart(inset).insetTop(inset).insetEnd(inset).insetBottom(inset) }
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun insetRelative(insetStart: Int, insetTop: Int, insetEnd: Int, insetBottom: Int) = apply {
|
||||
insetStart(insetStart).insetTop(insetTop).insetEnd(insetEnd).insetBottom(insetBottom)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun gravity(gravity: Int) = apply { layers.last().gravity = gravity }
|
||||
|
||||
fun modify(
|
||||
index: Int, drawable: Drawable,
|
||||
width: Int = DIMEN_UNDEFINED,
|
||||
height: Int = DIMEN_UNDEFINED,
|
||||
insetLeft: Int = DIMEN_UNDEFINED,
|
||||
insetTop: Int = DIMEN_UNDEFINED,
|
||||
insetRight: Int = DIMEN_UNDEFINED,
|
||||
insetBottom: Int = DIMEN_UNDEFINED,
|
||||
insetStart: Int = DIMEN_UNDEFINED,
|
||||
insetEnd: Int = DIMEN_UNDEFINED
|
||||
) =
|
||||
apply {
|
||||
val layer = layers[index]
|
||||
layer.drawable = drawable
|
||||
if (width != DIMEN_UNDEFINED) layer.width = width
|
||||
if (height != DIMEN_UNDEFINED) layer.height = height
|
||||
if (insetLeft != DIMEN_UNDEFINED) layer.insetLeft = insetLeft
|
||||
if (insetTop != DIMEN_UNDEFINED) layer.insetTop = insetTop
|
||||
if (insetRight != DIMEN_UNDEFINED) layer.insetRight = insetRight
|
||||
if (insetBottom != DIMEN_UNDEFINED) layer.insetBottom = insetBottom
|
||||
if (insetStart != DIMEN_UNDEFINED) layer.insetStart = insetStart
|
||||
if (insetEnd != DIMEN_UNDEFINED) layer.insetEnd = insetEnd
|
||||
}
|
||||
|
||||
fun build(): LayerDrawable {
|
||||
val layerDrawable = LayerDrawable(layers.map { it -> it.drawable }.toTypedArray())
|
||||
for (i in 0 until layers.size) {
|
||||
val layer = layers[i]
|
||||
layerDrawable.setLayerInset(
|
||||
i,
|
||||
layer.insetLeft,
|
||||
layer.insetTop,
|
||||
layer.insetRight,
|
||||
layer.insetBottom
|
||||
)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (layer.insetStart != DIMEN_UNDEFINED || layer.insetEnd != DIMEN_UNDEFINED) {
|
||||
layerDrawable.setLayerInsetRelative(
|
||||
i,
|
||||
layer.insetStart,
|
||||
layer.insetTop,
|
||||
layer.insetEnd,
|
||||
layer.insetBottom
|
||||
)
|
||||
}
|
||||
}
|
||||
layerDrawable.setId(i, i)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
layerDrawable.setLayerGravity(i, layer.gravity)
|
||||
layerDrawable.setLayerInsetStart(i, layer.insetStart)
|
||||
layerDrawable.setLayerInsetEnd(i, layer.insetEnd)
|
||||
}
|
||||
}
|
||||
layerDrawable.paddingMode = paddingMode
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
layerDrawable.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)
|
||||
if (paddingStart != DIMEN_UNDEFINED || paddingEnd != DIMEN_UNDEFINED) {
|
||||
layerDrawable.setPaddingRelative(
|
||||
paddingStart,
|
||||
paddingTop,
|
||||
paddingEnd,
|
||||
paddingBottom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return layerDrawable
|
||||
}
|
||||
|
||||
internal class Layer(var drawable: Drawable) {
|
||||
var gravity: Int = Gravity.NO_GRAVITY
|
||||
var width: Int = -1
|
||||
var height: Int = -1
|
||||
var insetLeft: Int = 0
|
||||
var insetTop: Int = 0
|
||||
var insetRight: Int = 0
|
||||
var insetBottom: Int = 0
|
||||
var insetStart: Int = DIMEN_UNDEFINED
|
||||
var insetEnd: Int = DIMEN_UNDEFINED
|
||||
}
|
||||
}
|
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.graphics.Path
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.graphics.drawable.shapes.PathShape
|
||||
|
||||
class PathShapeDrawableBuilder {
|
||||
|
||||
private var path: Path? = null
|
||||
private var pathStandardWidth: Float = 100f
|
||||
private var pathStandardHeight: Float = 100f
|
||||
private var width: Int = -1
|
||||
private var height: Int = -1
|
||||
|
||||
fun path(path: Path, pathStandardWidth: Float, pathStandardHeight: Float) = apply {
|
||||
this.path = path
|
||||
this.pathStandardWidth = pathStandardWidth
|
||||
this.pathStandardHeight = pathStandardHeight
|
||||
}
|
||||
|
||||
fun width(width: Int) = apply { this.width = width }
|
||||
fun height(height: Int) = apply { this.height = height }
|
||||
fun size(size: Int) = apply { width(size).height(size) }
|
||||
|
||||
fun build(custom: ((shapeDrawable: ShapeDrawable) -> Unit)? = null): ShapeDrawable {
|
||||
val shapeDrawable = ShapeDrawable()
|
||||
if (path == null || width <= 0 || height <= 0) {
|
||||
return shapeDrawable
|
||||
}
|
||||
val pathShape = PathShape(path!!, pathStandardWidth, pathStandardHeight)
|
||||
|
||||
shapeDrawable.shape = pathShape
|
||||
shapeDrawable.intrinsicWidth = width
|
||||
shapeDrawable.intrinsicHeight = height
|
||||
if (custom != null) {
|
||||
custom(shapeDrawable)
|
||||
}
|
||||
return shapeDrawable
|
||||
}
|
||||
}
|
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.*
|
||||
import android.util.StateSet
|
||||
|
||||
class RippleDrawableBuilder : DrawableWrapperBuilder<RippleDrawableBuilder>() {
|
||||
|
||||
private var color: Int = Constants.DEFAULT_COLOR
|
||||
private var colorStateList: ColorStateList? = null
|
||||
private var radius: Int = -1
|
||||
|
||||
fun color(color: Int) = apply { this.color = color }
|
||||
fun colorStateList(colorStateList: ColorStateList?) =
|
||||
apply { this.colorStateList = colorStateList }
|
||||
|
||||
fun radius(radius: Int) = apply { this.radius = radius }
|
||||
|
||||
override fun build(): Drawable {
|
||||
var drawable = this.drawable!!
|
||||
val colorStateList = this.colorStateList ?: ColorStateList(
|
||||
arrayOf(StateSet.WILD_CARD),
|
||||
intArrayOf(color)
|
||||
)
|
||||
|
||||
var mask = if (drawable is DrawableContainer) drawable.getCurrent() else drawable
|
||||
if (mask is ShapeDrawable) {
|
||||
val state = mask.getConstantState()
|
||||
if (state != null) {
|
||||
val temp = state.newDrawable().mutate() as ShapeDrawable
|
||||
temp.paint.color = Color.BLACK
|
||||
mask = temp
|
||||
}
|
||||
} else if (mask is GradientDrawable) {
|
||||
val state = mask.getConstantState()
|
||||
if (state != null) {
|
||||
val temp = state.newDrawable().mutate() as GradientDrawable
|
||||
temp.setColor(Color.BLACK)
|
||||
mask = temp
|
||||
}
|
||||
} else {
|
||||
mask = ColorDrawable(Color.BLACK)
|
||||
}
|
||||
|
||||
val rippleDrawable = RippleDrawable(colorStateList, drawable, mask)
|
||||
setRadius(rippleDrawable, radius)
|
||||
rippleDrawable.invalidateSelf()
|
||||
drawable = rippleDrawable
|
||||
return drawable
|
||||
}
|
||||
}
|
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.RotateDrawable
|
||||
|
||||
class RotateDrawableBuilder : DrawableWrapperBuilder<RotateDrawableBuilder>() {
|
||||
|
||||
private var pivotX: Float = 0.5f
|
||||
private var pivotY: Float = 0.5f
|
||||
private var fromDegrees: Float = 0f
|
||||
private var toDegrees: Float = 360f
|
||||
|
||||
fun pivotX(x: Float) = apply { pivotX = x }
|
||||
fun pivotY(y: Float) = apply { pivotY = y }
|
||||
fun fromDegrees(degree: Float) = apply { fromDegrees = degree }
|
||||
fun toDegrees(degree: Float) = apply { toDegrees = degree }
|
||||
|
||||
override fun build(): Drawable {
|
||||
val rotateDrawable = RotateDrawable()
|
||||
drawable?.let {
|
||||
setDrawable(rotateDrawable, it)
|
||||
apply {
|
||||
setPivotX(rotateDrawable, pivotX)
|
||||
setPivotY(rotateDrawable, pivotY)
|
||||
setFromDegrees(rotateDrawable, fromDegrees)
|
||||
setToDegrees(rotateDrawable, toDegrees)
|
||||
}
|
||||
}
|
||||
return rotateDrawable
|
||||
}
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.ScaleDrawable
|
||||
import android.view.Gravity
|
||||
|
||||
class ScaleDrawableBuilder : DrawableWrapperBuilder<ScaleDrawableBuilder>() {
|
||||
|
||||
private var level: Int = 10000
|
||||
private var scaleGravity = Gravity.CENTER
|
||||
private var scaleWidth: Float = 0f
|
||||
private var scaleHeight: Float = 0f
|
||||
|
||||
fun level(level: Int) = apply { this.level = level }
|
||||
fun scaleGravity(gravity: Int) = apply { this.scaleGravity = gravity }
|
||||
fun scaleWidth(scale: Float) = apply { this.scaleWidth = scale }
|
||||
fun scaleHeight(scale: Float) = apply { this.scaleHeight = scale }
|
||||
|
||||
override fun build(): Drawable {
|
||||
val scaleDrawable = ScaleDrawable(drawable, scaleGravity, scaleWidth, scaleHeight)
|
||||
scaleDrawable.level = level
|
||||
return scaleDrawable
|
||||
}
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.StateListDrawable
|
||||
import android.util.StateSet
|
||||
|
||||
class StateListDrawableBuilder {
|
||||
|
||||
private var pressed: Drawable? = null
|
||||
private var disabled: Drawable? = null
|
||||
private var selected: Drawable? = null
|
||||
private var normal: Drawable = ColorDrawable(Color.TRANSPARENT)
|
||||
|
||||
fun pressed(pressed: Drawable?) = apply { this.pressed = pressed }
|
||||
fun disabled(disabled: Drawable?) = apply { this.disabled = disabled }
|
||||
fun selected(selected: Drawable?) = apply { this.selected = selected }
|
||||
fun normal(normal: Drawable) = apply { this.normal = normal }
|
||||
|
||||
fun build(): StateListDrawable {
|
||||
val stateListDrawable = StateListDrawable()
|
||||
setupStateListDrawable(stateListDrawable)
|
||||
return stateListDrawable
|
||||
}
|
||||
|
||||
private fun setupStateListDrawable(stateListDrawable: StateListDrawable) {
|
||||
pressed?.let {
|
||||
stateListDrawable.addState(intArrayOf(android.R.attr.state_pressed), it)
|
||||
}
|
||||
disabled?.let {
|
||||
stateListDrawable.addState(intArrayOf(-android.R.attr.state_enabled), it)
|
||||
}
|
||||
selected?.let {
|
||||
stateListDrawable.addState(intArrayOf(android.R.attr.state_selected), it)
|
||||
}
|
||||
stateListDrawable.addState(StateSet.WILD_CARD, normal)
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2022/10/6.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.factory
|
||||
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
/** 已添加的返回监听事件 */
|
||||
private val onBackPressedCallbacks = HashMap<AppCompatActivity, OnBackPressedCallback>()
|
||||
|
||||
/**
|
||||
* 手动调用返回事件
|
||||
* @param ignored 是否忽略现有返回监听事件立即返回 - 否则将执行返回事件 - 默认否
|
||||
*/
|
||||
fun AppCompatActivity.callOnBackPressed(ignored: Boolean = false) {
|
||||
if (isDestroyed) return
|
||||
onBackPressedCallbacks[this]?.isEnabled = ignored.not()
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加返回监听事件
|
||||
* @param callback 回调事件
|
||||
*/
|
||||
fun AppCompatActivity.addOnBackPressedEvent(callback: OnBackPressedEvent.() -> Unit) {
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
OnBackPressedEvent(this@addOnBackPressedEvent).apply(callback)
|
||||
}
|
||||
}.also { result ->
|
||||
onBackPressedCallbacks.computeIfAbsent(this) {
|
||||
onBackPressedDispatcher.addCallback(result)
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回监听事件实现类
|
||||
* @param instance 当前实例
|
||||
*/
|
||||
class OnBackPressedEvent(private val instance: AppCompatActivity) {
|
||||
|
||||
/** 立即释放返回事件并调用返回功能 */
|
||||
fun releaseEventAndBack() = instance.callOnBackPressed(ignored = true)
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2022/6/3.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.factory
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.ListView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
|
||||
/**
|
||||
* 绑定 [BaseAdapter] 到 [ListView]
|
||||
* @param initiate 方法体
|
||||
* @return [BaseAdapter]
|
||||
*/
|
||||
inline fun ListView.bindAdapter(initiate: BaseAdapterCreater.() -> Unit) =
|
||||
BaseAdapterCreater(context).apply(initiate).baseAdapter?.apply { adapter = this } ?: error("BaseAdapter not binded")
|
||||
|
||||
/**
|
||||
* [BaseAdapter] 创建类
|
||||
* @param context 实例
|
||||
*/
|
||||
class BaseAdapterCreater(val context: Context) {
|
||||
|
||||
/** 当前 [List] 回调 */
|
||||
var listDataCallback: (() -> List<*>)? = null
|
||||
|
||||
/** 当前 [BaseAdapter] */
|
||||
var baseAdapter: BaseAdapter? = null
|
||||
|
||||
/**
|
||||
* 绑定 [List] 到 [ListView]
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onBindDatas(result: (() -> List<*>)) {
|
||||
listDataCallback = result
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定 [BaseAdapter] 到 [ListView]
|
||||
* @param bindViews 回调 - ([VB] 每项,[Int] 下标)
|
||||
*/
|
||||
inline fun <reified VB : ViewBinding> onBindViews(crossinline bindViews: (binding: VB, position: Int) -> Unit) {
|
||||
baseAdapter = object : BaseAdapter() {
|
||||
override fun getCount() = listDataCallback?.let { it() }?.size ?: 0
|
||||
override fun getItem(position: Int) = listDataCallback?.let { it() }?.get(position)
|
||||
override fun getItemId(position: Int) = position.toLong()
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
var holderView = convertView
|
||||
val holder: VB
|
||||
if (convertView == null) {
|
||||
holder = VB::class.java.method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}.get().invoke<VB>(LayoutInflater.from(context)) ?: error("ViewHolder binding failed")
|
||||
holderView = holder.root.apply { tag = holder }
|
||||
} else holder = convertView.tag as VB
|
||||
bindViews(holder, position)
|
||||
return holderView ?: error("ViewHolder binding failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2022/1/7.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.fankes.miui.notify.utils.factory
|
||||
|
||||
import android.app.Dialog
|
||||
import android.app.TimePickerDialog
|
||||
import android.content.Context
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
|
||||
/**
|
||||
* 显示时间选择对话框
|
||||
* @param timeSet 当前时间 - 不写将使用当前时间格式:HH:mm
|
||||
* @param result 回调 - 小时与分钟 HH:mm
|
||||
*/
|
||||
fun Context.showTimePicker(timeSet: String = "", result: (String) -> Unit) =
|
||||
TimePickerDialog(this, { _, h, m -> result("${h.autoZero}:${m.autoZero}") }, timeSet.hour, timeSet.minute, true).show()
|
||||
|
||||
/**
|
||||
* 构造 [VB] 自定义 View 对话框
|
||||
* @param initiate 对话框方法体
|
||||
*/
|
||||
@JvmName(name = "showDialog_Generics")
|
||||
inline fun <reified VB : ViewBinding> Context.showDialog(initiate: DialogBuilder<VB>.() -> Unit) =
|
||||
DialogBuilder<VB>(context = this, VB::class.java).apply(initiate).show()
|
||||
|
||||
/**
|
||||
* 构造对话框
|
||||
* @param initiate 对话框方法体
|
||||
*/
|
||||
inline fun Context.showDialog(initiate: DialogBuilder<*>.() -> Unit) = DialogBuilder<ViewBinding>(context = this).apply(initiate).show()
|
||||
|
||||
/**
|
||||
* 对话框构造器
|
||||
* @param context 实例
|
||||
* @param bindingClass [ViewBinding] 的 [Class] 实例 or null
|
||||
*/
|
||||
class DialogBuilder<VB : ViewBinding>(val context: Context, private val bindingClass: Class<*>? = null) {
|
||||
|
||||
/** 实例对象 */
|
||||
private var instance: AlertDialog.Builder? = null
|
||||
|
||||
/** 对话框取消监听 */
|
||||
private var onCancel: (() -> Unit)? = null
|
||||
|
||||
/** 对话框实例 */
|
||||
private var dialogInstance: Dialog? = null
|
||||
|
||||
/** 自定义布局 */
|
||||
private var customLayoutView: View? = null
|
||||
|
||||
/**
|
||||
* 获取 [DialogBuilder] 绑定布局对象
|
||||
* @return [VB]
|
||||
*/
|
||||
val binding by lazy {
|
||||
bindingClass?.method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}?.get()?.invoke<VB>(LayoutInflater.from(context))?.apply {
|
||||
customLayoutView = root
|
||||
} ?: error("This dialog maybe not a custom view dialog")
|
||||
}
|
||||
|
||||
init {
|
||||
if (YukiHookAPI.Status.isXposedEnvironment) error("This dialog is not allowed to created in Xposed environment")
|
||||
instance = MaterialAlertDialogBuilder(context)
|
||||
}
|
||||
|
||||
/** 设置对话框不可关闭 */
|
||||
fun noCancelable() {
|
||||
instance?.setCancelable(false)
|
||||
}
|
||||
|
||||
/** 设置对话框标题 */
|
||||
var title
|
||||
get() = ""
|
||||
set(value) {
|
||||
instance?.setTitle(value)
|
||||
}
|
||||
|
||||
/** 设置对话框消息内容 */
|
||||
var msg
|
||||
get() = ""
|
||||
set(value) {
|
||||
instance?.setMessage(value)
|
||||
}
|
||||
|
||||
/** 设置进度条对话框消息内容 */
|
||||
var progressContent
|
||||
get() = ""
|
||||
set(value) {
|
||||
if (customLayoutView == null)
|
||||
customLayoutView = LinearLayout(context).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER or Gravity.START
|
||||
addView(CircularProgressIndicator(context).apply {
|
||||
isIndeterminate = true
|
||||
trackCornerRadius = 10.dp(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<TextView>("progressContent")?.text = value
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框确定按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun confirmButton(text: String = "确定", callback: () -> Unit = {}) {
|
||||
instance?.setPositiveButton(text) { _, _ -> callback() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框取消按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun cancelButton(text: String = "取消", callback: () -> Unit = {}) {
|
||||
instance?.setNegativeButton(text) { _, _ -> callback() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框第三个按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun neutralButton(text: String = "更多", callback: () -> Unit = {}) {
|
||||
instance?.setNeutralButton(text) { _, _ -> callback() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 当对话框关闭时
|
||||
* @param callback 回调
|
||||
*/
|
||||
fun onCancel(callback: () -> Unit) {
|
||||
onCancel = callback
|
||||
}
|
||||
|
||||
/** 取消对话框 */
|
||||
fun cancel() = dialogInstance?.cancel()
|
||||
|
||||
/** 显示对话框 */
|
||||
fun show() = runInSafe {
|
||||
/** 若当前自定义 View 的对话框没有调用 [binding] 将会对其手动调用一次以确保显示布局 */
|
||||
if (bindingClass != null) binding
|
||||
instance?.create()?.apply {
|
||||
customLayoutView?.let { setView(it) }
|
||||
dialogInstance = this
|
||||
setOnCancelListener { onCancel?.invoke() }
|
||||
}?.show()
|
||||
}
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2022/3/13.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.fankes.miui.notify.utils.factory
|
||||
|
||||
import com.highcapable.yukihookapi.hook.log.YLog
|
||||
|
||||
/**
|
||||
* 忽略异常返回值
|
||||
* @param result 回调 - 如果异常为空
|
||||
* @return [T] 发生异常时返回设定值否则返回正常值
|
||||
*/
|
||||
inline fun <T> safeOfNull(result: () -> T): T? = safeOf(default = null, result)
|
||||
|
||||
/**
|
||||
* 忽略异常返回值
|
||||
* @param result 回调 - 如果异常为 false
|
||||
* @return [Boolean] 发生异常时返回设定值否则返回正常值
|
||||
*/
|
||||
inline fun safeOfFalse(result: () -> Boolean) = safeOf(default = false, result)
|
||||
|
||||
/**
|
||||
* 忽略异常返回值
|
||||
* @param result 回调 - 如果异常为 true
|
||||
* @return [Boolean] 发生异常时返回设定值否则返回正常值
|
||||
*/
|
||||
inline fun safeOfTrue(result: () -> Boolean) = safeOf(default = true, result)
|
||||
|
||||
/**
|
||||
* 忽略异常返回值
|
||||
* @param result 回调 - 如果异常为 false
|
||||
* @return [String] 发生异常时返回设定值否则返回正常值
|
||||
*/
|
||||
inline fun safeOfNothing(result: () -> String) = safeOf(default = "", result)
|
||||
|
||||
/**
|
||||
* 忽略异常返回值
|
||||
* @param result 回调 - 如果异常为 false
|
||||
* @return [Int] 发生异常时返回设定值否则返回正常值
|
||||
*/
|
||||
inline fun safeOfNan(result: () -> Int) = safeOf(default = 0, result)
|
||||
|
||||
/**
|
||||
* 忽略异常返回值
|
||||
* @param default 异常返回值
|
||||
* @param result 正常回调值
|
||||
* @return [T] 发生异常时返回设定值否则返回正常值
|
||||
*/
|
||||
inline fun <T> safeOf(default: T, result: () -> T) = try {
|
||||
result()
|
||||
} catch (_: Throwable) {
|
||||
default
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略异常运行
|
||||
* @param msg 出错输出的消息 - 默认为空
|
||||
* @param block 正常回调
|
||||
*/
|
||||
inline fun <T> T.runInSafe(msg: String = "", block: () -> Unit) {
|
||||
runCatching(block).onFailure { if (msg.isNotBlank()) YLog.error(msg, it) }
|
||||
}
|
@@ -0,0 +1,722 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2022/1/7.
|
||||
*/
|
||||
@file:Suppress("unused", "ObsoleteSdkInt")
|
||||
|
||||
package com.fankes.miui.notify.utils.factory
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Notification
|
||||
import android.app.Service
|
||||
import android.app.WallpaperManager
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.PackageInfoFlags
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffXfermode
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.provider.Settings
|
||||
import android.util.Base64
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import com.fankes.miui.notify.wrapper.BuildConfigWrapper
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.highcapable.yukihookapi.hook.factory.hasClass
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.factory.toClassOrNull
|
||||
import com.highcapable.yukihookapi.hook.log.YLog
|
||||
import com.highcapable.yukihookapi.hook.type.java.StringClass
|
||||
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication.Companion.appContext
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 系统深色模式是否开启
|
||||
* @return [Boolean] 是否开启
|
||||
*/
|
||||
val isSystemInDarkMode get() = appContext.isSystemInDarkMode
|
||||
|
||||
/**
|
||||
* 系统深色模式是否没开启
|
||||
* @return [Boolean] 是否开启
|
||||
*/
|
||||
inline val isNotSystemInDarkMode get() = !isSystemInDarkMode
|
||||
|
||||
/**
|
||||
* 系统深色模式是否开启
|
||||
* @return [Boolean] 是否开启
|
||||
*/
|
||||
val Context.isSystemInDarkMode get() = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
|
||||
/**
|
||||
* 系统深色模式是否没开启
|
||||
* @return [Boolean] 是否开启
|
||||
*/
|
||||
inline val Context.isNotSystemInDarkMode get() = !isSystemInDarkMode
|
||||
|
||||
/**
|
||||
* 系统版本是否高于或等于 Android 14
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
inline val isUpperOfAndroidU get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
||||
|
||||
/**
|
||||
* 系统版本是否高于或等于 Android 13
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
inline val isUpperOfAndroidT get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
|
||||
/**
|
||||
* 系统版本是否高于或等于 Android 12
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
inline val isUpperOfAndroidS get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||
|
||||
/**
|
||||
* 系统版本是否低于 Android 9
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
inline val isLowerAndroidP get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.P
|
||||
|
||||
/**
|
||||
* 系统版本是否低于 Android 11
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
inline val isLowerAndroidR get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.R
|
||||
|
||||
/**
|
||||
* 当前设备是否是 MIUI、HyperOS 定制 Android 系统
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
val isMiSystem get() = isMIUI || isMIOS
|
||||
|
||||
/**
|
||||
* 当前设备是否不是 MIUI、HyperOS 定制 Android 系统
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
val isNotMiSystem get() = !isMiSystem
|
||||
|
||||
/**
|
||||
* 当前设备是否是 MIUI 定制 Android 系统
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
val isMIUI by lazy { "android.miui.R".hasClass() }
|
||||
|
||||
/**
|
||||
* 当前设备是否是 HyperOS 定制 Android 系统
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
val isMIOS get() = isMIUI && miuiVersion == "816"
|
||||
|
||||
/**
|
||||
* 当前设备是否不是 MIUI 定制 Android 系统
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
inline val isNotMIUI get() = !isMIUI
|
||||
|
||||
/**
|
||||
* 当前设备是否不是 HyperOS 定制 Android 系统
|
||||
* @return [Boolean] 是否符合条件
|
||||
*/
|
||||
inline val isNotMIOS get() = !isMIOS
|
||||
|
||||
/**
|
||||
* 是否为支持的 MIUI、HyperOS 版本
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isSupportMiSystemVersion
|
||||
get() = when {
|
||||
isMIOS -> when (miosVersion) {
|
||||
"1.0" -> true
|
||||
else -> false
|
||||
}
|
||||
isMIUI -> when (miuiVersion) {
|
||||
"11", "12", "12.5", "13", "14" -> true
|
||||
else -> false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否不为支持的 MIUI、HyperOS 版本
|
||||
* @return [Boolean]
|
||||
*/
|
||||
inline val isNotSupportMiSystemVersion get() = !isSupportMiSystemVersion
|
||||
|
||||
/**
|
||||
* 获取 Android 版本代号
|
||||
* @return [String]
|
||||
*/
|
||||
val androidVersionCodeName
|
||||
get() = when (Build.VERSION.SDK_INT) {
|
||||
34 -> "U"
|
||||
33 -> "T"
|
||||
32 -> "S_V2"
|
||||
31 -> "S"
|
||||
30 -> "R"
|
||||
29 -> "Q"
|
||||
28 -> "P"
|
||||
27 -> "O_MR1"
|
||||
26 -> "O"
|
||||
25 -> "N_MR1"
|
||||
24 -> "N"
|
||||
23 -> "M"
|
||||
22 -> "L_MR1"
|
||||
21 -> "L"
|
||||
else -> "?"
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 MIUI、HyperOS 版本
|
||||
* @return [String]
|
||||
*/
|
||||
val miSystemVersion
|
||||
get() = when {
|
||||
isMIOS -> miosVersion
|
||||
isMIUI -> miuiVersion
|
||||
else -> ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 MIUI、HyperOS 版本号
|
||||
* @return [Float]
|
||||
*/
|
||||
val miSystemVersionCode
|
||||
get() = when {
|
||||
isMIOS -> miosVersionCode
|
||||
isMIUI -> miuiVersionCode
|
||||
else -> 0f
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 MIUI 版本
|
||||
* @return [String]
|
||||
*/
|
||||
val miuiVersion
|
||||
get() = if (isMIUI)
|
||||
findPropString("ro.miui.ui.version.name").let {
|
||||
when (it) {
|
||||
"V11", "V110" -> "11"
|
||||
"V12", "V120" -> "12"
|
||||
"V125" -> "12.5"
|
||||
"V13", "V130" -> "13"
|
||||
"V14", "V140" -> "14"
|
||||
else -> it.replace("V", "")
|
||||
}
|
||||
}.trim()
|
||||
else ""
|
||||
|
||||
/**
|
||||
* 获取 HyperOS 版本
|
||||
* @return [String]
|
||||
*/
|
||||
val miosVersion
|
||||
get() = if (isMIOS)
|
||||
findPropString("ro.mi.os.version.name").let {
|
||||
if (it.startsWith("OS")) it.replaceFirst("OS", "") else it
|
||||
}.trim()
|
||||
else ""
|
||||
|
||||
/**
|
||||
* 获取 MIUI 版本号
|
||||
* @return [Float]
|
||||
*/
|
||||
val miuiVersionCode get() = miuiVersion.toFloatOrNull() ?: 0f
|
||||
|
||||
/**
|
||||
* 获取 HyperOS 版本号
|
||||
* @return [Float]
|
||||
*/
|
||||
val miosVersionCode get() = findPropString("ro.mi.os.version.code").toFloatOrNull() ?: 0f
|
||||
|
||||
/**
|
||||
* 获取 MIUI 次版本号
|
||||
* @return [String]
|
||||
*/
|
||||
val miuiIncrementalVersion get() = findPropString("ro.system.build.version.incremental").trim()
|
||||
|
||||
/**
|
||||
* 获取 HyperOS 次版本号
|
||||
* @return [String]
|
||||
*/
|
||||
val miosIncrementalVersion get() = findPropString("ro.mi.os.version.incremental").trim()
|
||||
|
||||
/**
|
||||
* 获取 MIUI、HyperOS 完全版本
|
||||
* @return [String]
|
||||
*/
|
||||
val systemFullVersion
|
||||
get() = when {
|
||||
isMIOS -> "HyperOS " + miosIncrementalVersion.let {
|
||||
if (it.lowercase().endsWith(".dev").not() && it.lowercase().any { e -> e.code in 97..122 })
|
||||
"${it.replaceFirst("OS", "")} 稳定版"
|
||||
else when {
|
||||
it.lowercase().endsWith(".dev") -> "${it.replaceFirst("OS", "")} 开发版"
|
||||
else -> "$miosVersion $it 开发版"
|
||||
}
|
||||
}
|
||||
isMIUI -> miuiIncrementalVersion.let {
|
||||
if (it.lowercase().endsWith(".dev").not() && it.lowercase().any { e -> e.code in 97..122 })
|
||||
"$it 稳定版"
|
||||
else when {
|
||||
it.lowercase().endsWith(".dev") -> "$it 开发版"
|
||||
else -> "V$miuiVersion $it 开发版"
|
||||
}
|
||||
}
|
||||
else -> "不是 MIUI 或 HyperOS 系统"
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 [Drawable]
|
||||
* @param resId 属性资源 ID
|
||||
* @return [Drawable]
|
||||
*/
|
||||
fun Resources.drawableOf(@DrawableRes resId: Int) = ResourcesCompat.getDrawable(this, resId, null) ?: error("Invalid resources")
|
||||
|
||||
/**
|
||||
* 获取颜色
|
||||
* @param resId 属性资源 ID
|
||||
* @return [Int]
|
||||
*/
|
||||
fun Resources.colorOf(@ColorRes resId: Int) = ResourcesCompat.getColor(this, resId, null)
|
||||
|
||||
/**
|
||||
* 得到 APP 安装包信息 (兼容)
|
||||
* @param packageName APP 包名
|
||||
* @param flag [PackageInfoFlags]
|
||||
* @return [PackageInfo] or null
|
||||
*/
|
||||
private fun Context.getPackageInfoCompat(packageName: String, flag: Number = 0) = runCatching {
|
||||
@Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
|
||||
if (Build.VERSION.SDK_INT >= 33)
|
||||
packageManager?.getPackageInfo(packageName, PackageInfoFlags.of(flag.toLong()))
|
||||
else packageManager?.getPackageInfo(packageName, flag.toInt())
|
||||
}.getOrNull()
|
||||
|
||||
/**
|
||||
* 得到 APP 版本号 (兼容 [PackageInfo.getLongVersionCode])
|
||||
* @return [Int]
|
||||
*/
|
||||
private val PackageInfo.versionCodeCompat get() = PackageInfoCompat.getLongVersionCode(this)
|
||||
|
||||
/**
|
||||
* 判断 APP 是否安装
|
||||
* @param packageName APP 包名
|
||||
* @return [Boolean]
|
||||
*/
|
||||
fun Context.isInstall(packageName: String) = getPackageInfoCompat(packageName)?.let { true } ?: false
|
||||
|
||||
/**
|
||||
* 得到 APP 版本信息
|
||||
* @return [String]
|
||||
*/
|
||||
val Context.appVersionName get() = getPackageInfoCompat(packageName)?.versionName ?: ""
|
||||
|
||||
/**
|
||||
* 得到 APP 版本号
|
||||
* @return [Int]
|
||||
*/
|
||||
val Context.appVersionCode get() = getPackageInfoCompat(packageName)?.versionCodeCompat
|
||||
|
||||
/**
|
||||
* 得到 APP 名称
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [String]
|
||||
*/
|
||||
fun Context.appNameOf(packageName: String = getPackageName()) =
|
||||
getPackageInfoCompat(packageName)?.applicationInfo?.loadLabel(packageManager)?.toString() ?: ""
|
||||
|
||||
/**
|
||||
* 得到 APP 图标
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [Drawable] or null
|
||||
*/
|
||||
fun Context.appIconOf(packageName: String = getPackageName()) = getPackageInfoCompat(packageName)?.applicationInfo?.loadIcon(packageManager)
|
||||
|
||||
/**
|
||||
* 获取 APP 是否为 DEBUG 版本
|
||||
* @param packageName APP 包名
|
||||
* @return [Boolean]
|
||||
*/
|
||||
fun Context.isDebugApp(packageName: String) =
|
||||
safeOfFalse { (getPackageInfoCompat(packageName)?.applicationInfo?.flags?.and(ApplicationInfo.FLAG_DEBUGGABLE) ?: 0) != 0 }
|
||||
|
||||
/**
|
||||
* 获取 APP 是否为系统 APP
|
||||
* @param packageName APP 包名
|
||||
* @return [Boolean]
|
||||
*/
|
||||
fun Context.isSystemApp(packageName: String) =
|
||||
safeOfFalse { (getPackageInfoCompat(packageName)?.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM) ?: 0) != 0 }
|
||||
|
||||
/**
|
||||
* 对数值自动补零
|
||||
* @return [String]
|
||||
*/
|
||||
val Int.autoZero: String get() = if (this < 10) "0$this" else toString()
|
||||
|
||||
/**
|
||||
* 从字符串获取小时
|
||||
* @return [Int]
|
||||
*/
|
||||
val String.hour
|
||||
get() = safeOfNan {
|
||||
Calendar.getInstance().also {
|
||||
it.time = SimpleDateFormat("HH:mm", Locale.CHINA).parse(this) as Date
|
||||
}.get(Calendar.HOUR_OF_DAY)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从字符串获取分钟
|
||||
* @return [Int]
|
||||
*/
|
||||
val String.minute
|
||||
get() = safeOfNan {
|
||||
Calendar.getInstance().also {
|
||||
it.time = SimpleDateFormat("HH:mm", Locale.CHINA).parse(this) as Date
|
||||
}.get(Calendar.MINUTE)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否关闭了通知权限
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isNotNoificationEnabled get() = !NotificationManagerCompat.from(appContext).areNotificationsEnabled()
|
||||
|
||||
/**
|
||||
* 网络连接是否正常
|
||||
* @return [Boolean] 网络是否连接
|
||||
*/
|
||||
val Context.isNetWorkSuccess
|
||||
get() = safeOfFalse {
|
||||
@Suppress("DEPRECATION")
|
||||
getSystemService<ConnectivityManager>()?.activeNetworkInfo != null
|
||||
}
|
||||
|
||||
/**
|
||||
* dp 转换为 pxInt
|
||||
* @param context 使用的实例
|
||||
* @return [Int]
|
||||
*/
|
||||
fun Number.dp(context: Context) = dpFloat(context).toInt()
|
||||
|
||||
/**
|
||||
* dp 转换为 pxFloat
|
||||
* @param context 使用的实例
|
||||
* @return [Float]
|
||||
*/
|
||||
fun Number.dpFloat(context: Context) = toFloat() * context.resources.displayMetrics.density
|
||||
|
||||
/**
|
||||
* 获取系统主题色
|
||||
* @return [Int] Android < 12 返回 [wallpaperColor]
|
||||
*/
|
||||
val Context.systemAccentColor
|
||||
@SuppressLint("InlinedApi")
|
||||
get() = safeOf(wallpaperColor) {
|
||||
if (isUpperOfAndroidS) resources.colorOf(android.R.color.system_accent1_600) else wallpaperColor
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统壁纸颜色
|
||||
* @return [Int] 无法获取时返回默认颜色
|
||||
*/
|
||||
val Context.wallpaperColor
|
||||
get() = runCatching {
|
||||
WallpaperManager.getInstance(this).getWallpaperColors(WallpaperManager.FLAG_SYSTEM)?.primaryColor?.toArgb()
|
||||
}.getOrNull() ?: (if (isSystemInDarkMode) 0xFFD8D8D8.toInt() else 0xFF707173.toInt())
|
||||
|
||||
/**
|
||||
* 获取较浅的颜色
|
||||
* @return [Int]
|
||||
*/
|
||||
val Int.brighterColor: Int
|
||||
get() {
|
||||
val hsv = FloatArray(3)
|
||||
Color.colorToHSV(this, hsv)
|
||||
hsv[1] = hsv[1] - 0.3f
|
||||
hsv[2] = hsv[2] + 0.3f
|
||||
return Color.HSVToColor(hsv)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为白色
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val Int.isWhiteColor
|
||||
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]
|
||||
*/
|
||||
val Bitmap.base64
|
||||
get() = safeOfNothing {
|
||||
val baos = ByteArrayOutputStream()
|
||||
compress(Bitmap.CompressFormat.PNG, 100, baos)
|
||||
Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 加密
|
||||
* @return [String]
|
||||
*/
|
||||
val String.base64: String get() = Base64.encodeToString(toByteArray(), Base64.DEFAULT)
|
||||
|
||||
/**
|
||||
* Base64 解密为字节流
|
||||
* @return [ByteArray]
|
||||
*/
|
||||
val String.unbase64 get() = Base64.decode(this, Base64.DEFAULT) ?: ByteArray(0)
|
||||
|
||||
/**
|
||||
* 字节流解析为位图
|
||||
* @return [Bitmap]
|
||||
*/
|
||||
val ByteArray.bitmap: Bitmap get() = BitmapFactory.decodeByteArray(this, 0, size)
|
||||
|
||||
/**
|
||||
* 字符串解析为位图
|
||||
* @return [Bitmap]
|
||||
*/
|
||||
val String.bitmap: Bitmap get() = unbase64.bitmap
|
||||
|
||||
/**
|
||||
* 圆角图片
|
||||
* @param radius 圆角度
|
||||
* @return [Bitmap] 圆角后的位图 - 失败会返回处理之前的位图
|
||||
*/
|
||||
fun Bitmap.round(radius: Float): Bitmap = safeOf(default = this) {
|
||||
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { out ->
|
||||
Canvas(out).also { canvas ->
|
||||
Paint().also { paint ->
|
||||
paint.isAntiAlias = true
|
||||
canvas.drawARGB(0, 0, 0, 0)
|
||||
paint.color = Color.WHITE
|
||||
canvas.drawRoundRect(RectF(Rect(0, 0, width, height)), radius, radius, paint)
|
||||
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
|
||||
canvas.drawBitmap(this, Rect(0, 0, width, height), Rect(0, 0, width, height), paint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统 Prop 值
|
||||
* @param key Key
|
||||
* @param default 默认值
|
||||
* @return [String]
|
||||
*/
|
||||
fun findPropString(key: String, default: String = "") = safeOf(default) {
|
||||
"android.os.SystemProperties".toClassOrNull()?.method {
|
||||
name = "get"
|
||||
param(StringClass, StringClass)
|
||||
}?.get()?.invoke(key, default) ?: default
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有 Root 权限
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isRootAccess get() = safeOfFalse {
|
||||
@Suppress("DEPRECATION")
|
||||
Shell.rootAccess()
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
* @param cmd 命令
|
||||
* @param isSu 是否使用 Root 权限执行 - 默认:是
|
||||
* @return [String] 执行结果
|
||||
*/
|
||||
fun execShell(cmd: String, isSu: Boolean = true) = safeOfNothing {
|
||||
@Suppress("DEPRECATION")
|
||||
(if (isSu) Shell.su(cmd) else Shell.sh(cmd)).exec().out.let {
|
||||
if (it.isNotEmpty()) it[0].trim() else ""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出 [Toast]
|
||||
* @param msg 提示内容
|
||||
*/
|
||||
fun toast(msg: String) {
|
||||
runCatching {
|
||||
Toast.makeText(appContext, msg, Toast.LENGTH_SHORT).show()
|
||||
}.onFailure { YLog.warn(msg) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到指定页面
|
||||
*
|
||||
* [T] 为指定的 [Activity]
|
||||
*/
|
||||
inline fun <reified T : Activity> Context.navigate() = runInSafe {
|
||||
startActivity(Intent(if (this is Service) applicationContext else this, T::class.java).apply {
|
||||
if (this@navigate !is Activity) flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出 [Snackbar]
|
||||
* @param msg 提示内容
|
||||
* @param actionText 按钮文本 - 不写默认取消按钮
|
||||
* @param callback 按钮事件回调
|
||||
*/
|
||||
fun Context.snake(msg: String, actionText: String = "", callback: () -> Unit = {}) =
|
||||
Snackbar.make((this as Activity).findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG)
|
||||
.apply {
|
||||
if (actionText.isBlank()) return@apply
|
||||
setActionTextColor(if (isSystemInDarkMode) Color.BLACK else Color.WHITE)
|
||||
setAction(actionText) { callback() }
|
||||
}.show()
|
||||
|
||||
/**
|
||||
* 启动系统浏览器
|
||||
* @param url 网址
|
||||
* @param packageName 指定包名 - 可不填
|
||||
*/
|
||||
fun Context.openBrowser(url: String, packageName: String = "") = runCatching {
|
||||
startActivity(Intent().apply {
|
||||
if (packageName.isNotBlank()) setPackage(packageName)
|
||||
action = Intent.ACTION_VIEW
|
||||
data = Uri.parse(url)
|
||||
/** 防止顶栈一样重叠在自己的 APP 中 */
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
}.onFailure {
|
||||
if (packageName.isNotBlank())
|
||||
snake(msg = "启动 $packageName 失败")
|
||||
else snake(msg = "启动系统浏览器失败")
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转 APP 自身设置界面
|
||||
* @param packageName 包名
|
||||
*/
|
||||
fun Context.openSelfSetting(packageName: String = appContext.packageName) = runCatching {
|
||||
if (isInstall(packageName))
|
||||
startActivity(Intent().apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
data = Uri.fromParts("package", packageName, null)
|
||||
})
|
||||
else toast(msg = "你没有安装此应用")
|
||||
}.onFailure { toast(msg = "启动 $packageName 应用信息失败") }
|
||||
|
||||
/** 跳转通知设置界面 */
|
||||
fun Context.openNotifySetting() = 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 = "跳转通知设置失败") }
|
||||
|
||||
/**
|
||||
* 复制到剪贴板
|
||||
* @param content 要复制的文本
|
||||
*/
|
||||
fun Context.copyToClipboard(content: String) = runInSafe {
|
||||
(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 format 格式化方法 - 默认:yyyy-MM-dd HH:mm:ss
|
||||
* @return [String] 目标字符串时间
|
||||
*/
|
||||
fun Long.stampToDate(format: String = "yyyy-MM-dd HH:mm:ss") =
|
||||
safeOfNothing { SimpleDateFormat(format, Locale.CHINA).format(Date(this)) ?: "" }
|
||||
|
||||
/**
|
||||
* 延迟执行
|
||||
* @param ms 毫秒 - 默认:150
|
||||
* @param it 方法体
|
||||
*/
|
||||
fun Any?.delayedRun(ms: Long = 150, it: () -> Unit) = runInSafe {
|
||||
@Suppress("DEPRECATION")
|
||||
Handler().postDelayed({ it() }, ms)
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏或显示启动器图标
|
||||
*
|
||||
* - 你可能需要 LSPosed 的最新版本以开启高版本系统中隐藏 APP 桌面图标功能
|
||||
* @param isShow 是否显示
|
||||
*/
|
||||
fun Context.hideOrShowLauncherIcon(isShow: Boolean) {
|
||||
packageManager?.setComponentEnabledSetting(
|
||||
ComponentName(packageName, "${BuildConfigWrapper.APPLICATION_ID}.Home"),
|
||||
if (isShow) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启动器图标状态
|
||||
* @return [Boolean] 是否显示
|
||||
*/
|
||||
val Context.isLauncherIconShowing
|
||||
get() = packageManager?.getComponentEnabledSetting(
|
||||
ComponentName(packageName, "${BuildConfigWrapper.APPLICATION_ID}.Home")
|
||||
) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2023/4/17.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.tool
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.fankes.miui.notify.R
|
||||
import com.fankes.miui.notify.utils.factory.appIconOf
|
||||
import com.fankes.miui.notify.wrapper.BuildConfigWrapper
|
||||
|
||||
/**
|
||||
* 模块更新激活提醒通知工具类
|
||||
*/
|
||||
object ActivationPromptTool {
|
||||
|
||||
/** 当前模块的包名 */
|
||||
private const val MODULE_PACKAGE_NAME = BuildConfigWrapper.APPLICATION_ID
|
||||
|
||||
/** 推送通知的渠道名称 */
|
||||
private const val NOTIFY_CHANNEL = "activationPromptId"
|
||||
|
||||
/**
|
||||
* 推送提醒通知
|
||||
* @param context 当前实例
|
||||
* @param packageName 当前 APP 包名
|
||||
*/
|
||||
fun prompt(context: Context, packageName: String) {
|
||||
if (packageName != BuildConfigWrapper.APPLICATION_ID) return
|
||||
context.getSystemService(NotificationManager::class.java)?.apply {
|
||||
createNotificationChannel(
|
||||
NotificationChannel(
|
||||
NOTIFY_CHANNEL, "MIUI 原生通知图标 - 版本更新",
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
).apply { enableLights(false) }
|
||||
)
|
||||
notify(packageName.hashCode(), Notification.Builder(context, NOTIFY_CHANNEL).apply {
|
||||
setShowWhen(true)
|
||||
setContentTitle("模块已更新")
|
||||
setContentText("点按通知打开模块以完成新版本激活。")
|
||||
setColor(0xFFE06818.toInt())
|
||||
setAutoCancel(true)
|
||||
setSmallIcon(Icon.createWithResource(MODULE_PACKAGE_NAME, R.drawable.ic_notify_update))
|
||||
setLargeIcon(context.appIconOf(packageName)?.toBitmap())
|
||||
setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context, packageName.hashCode(),
|
||||
Intent().apply {
|
||||
component = ComponentName(MODULE_PACKAGE_NAME, "$MODULE_PACKAGE_NAME.ui.activity.MainActivity")
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}, if (Build.VERSION.SDK_INT < 31) PendingIntent.FLAG_UPDATE_CURRENT else PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
)
|
||||
}.build())
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
@@ -18,17 +18,22 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/2/19.
|
||||
* This file is created by fankes on 2022/2/19.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils
|
||||
package com.fankes.miui.notify.utils.tool
|
||||
|
||||
import android.graphics.*
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.drawable.AnimationDrawable
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.VectorDrawable
|
||||
import android.util.ArrayMap
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.fankes.miui.notify.utils.factory.safeOfFalse
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
@@ -87,7 +92,7 @@ object BitmapCompatTool {
|
||||
ensureBufferSize(size)
|
||||
tempCompactBitmap?.getPixels(tempBuffer, 0, width, 0, 0, width, height)
|
||||
for (i in 0 until size)
|
||||
if (!isGrayscaleColor(tempBuffer[i])) {
|
||||
if (isGrayscaleColor(tempBuffer[i]).not()) {
|
||||
cachedBitmapGrayscales[bitmap.generationId] = false
|
||||
return@let false
|
||||
}
|
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2022/3/20.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.tool
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.icu.util.Calendar
|
||||
import android.icu.util.TimeZone
|
||||
import com.fankes.miui.notify.utils.factory.isNetWorkSuccess
|
||||
import com.fankes.miui.notify.utils.factory.openBrowser
|
||||
import com.fankes.miui.notify.utils.factory.openSelfSetting
|
||||
import com.fankes.miui.notify.utils.factory.runInSafe
|
||||
import com.fankes.miui.notify.utils.factory.showDialog
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
import java.io.Serializable
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 获取 GitHub Release 最新版本工具类
|
||||
*/
|
||||
object GithubReleaseTool {
|
||||
|
||||
/** 仓库作者 */
|
||||
private const val REPO_AUTHOR = "fankes"
|
||||
|
||||
/** 仓库名称 */
|
||||
private const val REPO_NAME = "MIUINativeNotifyIcon"
|
||||
|
||||
/**
|
||||
* 获取最新版本信息
|
||||
* @param context 实例
|
||||
* @param version 当前版本
|
||||
* @param result 成功后回调 - ([String] 最新版本,[Function] 更新对话框方法体)
|
||||
*/
|
||||
fun checkingForUpdate(context: Context, version: String, result: (String, () -> Unit) -> Unit) = checkingInternetConnect(context) {
|
||||
OkHttpClient().newBuilder().build().newCall(
|
||||
Request.Builder()
|
||||
.url("https://api.github.com/repos/$REPO_AUTHOR/$REPO_NAME/releases/latest")
|
||||
.get()
|
||||
.build()
|
||||
).enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) = runInSafe {
|
||||
JSONObject(response.body.string()).apply {
|
||||
GithubReleaseBean(
|
||||
name = getString("name"),
|
||||
htmlUrl = getString("html_url"),
|
||||
content = getString("body"),
|
||||
date = getString("published_at").localTime()
|
||||
).apply {
|
||||
fun showUpdate() = context.showDialog {
|
||||
title = "最新版本 $name"
|
||||
msg = "发布于 $date\n\n" +
|
||||
"更新日志\n\n" + content
|
||||
confirmButton(text = "更新") { context.openBrowser(htmlUrl) }
|
||||
cancelButton()
|
||||
}
|
||||
if (name != version) (context as? Activity?)?.runOnUiThread {
|
||||
showUpdate()
|
||||
result(name) { showUpdate() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查网络连接情况
|
||||
* @param context 实例
|
||||
* @param callback 已连接回调
|
||||
*/
|
||||
private fun checkingInternetConnect(context: Context, callback: () -> Unit) = runInSafe {
|
||||
if (context.isNetWorkSuccess)
|
||||
OkHttpClient().newBuilder().build().newCall(
|
||||
Request.Builder()
|
||||
.url("https://www.baidu.com")
|
||||
.get()
|
||||
.build()
|
||||
).enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
(context as? Activity?)?.runOnUiThread {
|
||||
context.showDialog {
|
||||
title = "网络不可用"
|
||||
msg = "应用的联网权限可能已被禁用,请开启联网权限以定期检查更新。"
|
||||
confirmButton(text = "去开启") { context.openSelfSetting() }
|
||||
cancelButton()
|
||||
noCancelable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) = runInSafe {
|
||||
(context as? Activity?)?.runOnUiThread { runInSafe { callback() } }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间为本地时区
|
||||
* @return [String] 本地时区时间
|
||||
*/
|
||||
private fun String.localTime() = replace("T", " ").replace("Z", "").let {
|
||||
runCatching {
|
||||
val local = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ROOT).apply { timeZone = Calendar.getInstance().timeZone }
|
||||
val current = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ROOT).apply { timeZone = TimeZone.getTimeZone("GMT") }
|
||||
local.format(current.parse(it))
|
||||
}.getOrNull() ?: it
|
||||
}
|
||||
|
||||
/**
|
||||
* GitHub Release bean
|
||||
* @param name 版本名称
|
||||
* @param htmlUrl 网页地址
|
||||
* @param content 更新日志
|
||||
* @param date 发布时间
|
||||
*/
|
||||
private data class GithubReleaseBean(
|
||||
var name: String,
|
||||
var htmlUrl: String,
|
||||
var content: String,
|
||||
var date: String
|
||||
) : Serializable
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2023/2/3.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.tool
|
||||
|
||||
import android.content.Context
|
||||
import com.fankes.miui.notify.utils.factory.showDialog
|
||||
import com.highcapable.yukihookapi.hook.factory.prefs
|
||||
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* I18n 适配警告提示工具类
|
||||
*/
|
||||
object I18nWarnTool {
|
||||
|
||||
/** 推广已读存储键值 */
|
||||
private val LOCALE_WARN_READED = PrefsData("locale_warn_readed", false)
|
||||
|
||||
/**
|
||||
* 检查并显示 I18n 适配警告对话框
|
||||
* @param context 实例
|
||||
*/
|
||||
fun checkingOrShowing(context: Context) {
|
||||
fun saveReaded() = context.prefs().edit { put(LOCALE_WARN_READED, value = true) }
|
||||
if (Locale.getDefault().language.startsWith("zh").not() && context.prefs().get(LOCALE_WARN_READED).not())
|
||||
context.showDialog {
|
||||
title = "Notice of I18n Support"
|
||||
msg = "This Xposed Module is only for Chinese and the Chinese region.\n\n" +
|
||||
"Currently, there will be no internationalization adaptation.\n\n" +
|
||||
"There may be plans for internationalization adaptation in the future, so stay tuned."
|
||||
confirmButton(text = "Got It") { saveReaded() }
|
||||
noCancelable()
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2022/3/21.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.tool
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.fankes.miui.notify.R
|
||||
import com.fankes.miui.notify.hook.HookEntry
|
||||
import com.fankes.miui.notify.utils.factory.appIconOf
|
||||
import com.fankes.miui.notify.utils.factory.appNameOf
|
||||
import com.fankes.miui.notify.utils.factory.isDebugApp
|
||||
import com.fankes.miui.notify.utils.factory.isSystemApp
|
||||
import com.fankes.miui.notify.utils.factory.runInSafe
|
||||
import com.fankes.miui.notify.utils.factory.stampToDate
|
||||
import com.fankes.miui.notify.wrapper.BuildConfigWrapper
|
||||
|
||||
/**
|
||||
* 通知图标适配推送通知类
|
||||
*
|
||||
* 这个类需要在 [HookEntry] 中调用
|
||||
*/
|
||||
object IconAdaptationTool {
|
||||
|
||||
/** 当前模块的包名 */
|
||||
private const val MODULE_PACKAGE_NAME = BuildConfigWrapper.APPLICATION_ID
|
||||
|
||||
/** 推送通知的渠道名称 */
|
||||
private const val NOTIFY_CHANNEL = "notifyRuleSupportId"
|
||||
|
||||
/** 已过期的时间 */
|
||||
private val outTimeLimits = HashSet<String>()
|
||||
|
||||
/**
|
||||
* 推送新 APP 安装适配通知
|
||||
* @param context 实例
|
||||
* @param packageName 安装的 APP 包名
|
||||
*/
|
||||
fun pushNewAppSupportNotify(context: Context, packageName: String) {
|
||||
if (packageName.startsWith("com.google.android.trichromelibrary")) return
|
||||
if (context.isSystemApp(packageName) || context.isDebugApp(packageName)) return
|
||||
context.getSystemService(NotificationManager::class.java)?.apply {
|
||||
createNotificationChannel(
|
||||
NotificationChannel(
|
||||
NOTIFY_CHANNEL, "通知图标优化适配",
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
).apply { enableLights(false) }
|
||||
)
|
||||
notify(packageName.hashCode(), Notification.Builder(context, NOTIFY_CHANNEL).apply {
|
||||
setShowWhen(true)
|
||||
setContentTitle("您已安装 ${context.appNameOf(packageName)}")
|
||||
setContentText("尚未适配此应用,点按打开在线规则。")
|
||||
setColor(0xFF2993F0.toInt())
|
||||
setAutoCancel(true)
|
||||
setSmallIcon(Icon.createWithResource(MODULE_PACKAGE_NAME, R.drawable.ic_unsupported))
|
||||
setLargeIcon(context.appIconOf(packageName)?.toBitmap())
|
||||
setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context, packageName.hashCode(),
|
||||
Intent().apply {
|
||||
component = ComponentName(
|
||||
MODULE_PACKAGE_NAME,
|
||||
"$MODULE_PACKAGE_NAME.ui.activity.ConfigureActivity"
|
||||
)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}.apply {
|
||||
putExtra("isNewAppSupport", true)
|
||||
putExtra("appName", context.appNameOf(packageName))
|
||||
putExtra("pkgName", packageName)
|
||||
}, if (Build.VERSION.SDK_INT < 31) PendingIntent.FLAG_UPDATE_CURRENT else PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
)
|
||||
}.build())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测 APP 卸载后移除相关通知
|
||||
* @param context 实例
|
||||
* @param packageName 卸载的 APP 包名
|
||||
*/
|
||||
fun removeNewAppSupportNotify(context: Context, packageName: String) = runInSafe {
|
||||
context.getSystemService(NotificationManager::class.java)?.cancel(packageName.hashCode())
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动更新通知图标优化在线规则
|
||||
* @param context 实例
|
||||
* @param timeSet 设定的时间
|
||||
*/
|
||||
fun prepareAutoUpdateIconRule(context: Context, timeSet: String) = runInSafe {
|
||||
System.currentTimeMillis().also {
|
||||
val nowTime = it.stampToDate(format = "HH:mm")
|
||||
if (timeSet != nowTime || outTimeLimits.any { e -> e == nowTime }) return
|
||||
outTimeLimits.add(nowTime)
|
||||
context.startActivity(
|
||||
Intent().apply {
|
||||
component = ComponentName(MODULE_PACKAGE_NAME, "$MODULE_PACKAGE_NAME.ui.activity.auto.NotifyIconRuleUpdateActivity")
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,490 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2022/2/25.
|
||||
*/
|
||||
@file:Suppress("TrustAllX509TrustManager", "CustomX509TrustManager", "IMPLICIT_CAST_TO_ANY")
|
||||
|
||||
package com.fankes.miui.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.miui.notify.R
|
||||
import com.fankes.miui.notify.const.IconRuleSourceSyncType
|
||||
import com.fankes.miui.notify.data.ConfigData
|
||||
import com.fankes.miui.notify.databinding.DiaSourceFromBinding
|
||||
import com.fankes.miui.notify.databinding.DiaSourceFromStringBinding
|
||||
import com.fankes.miui.notify.params.IconPackParams
|
||||
import com.fankes.miui.notify.ui.activity.ConfigureActivity
|
||||
import com.fankes.miui.notify.utils.factory.delayedRun
|
||||
import com.fankes.miui.notify.utils.factory.openBrowser
|
||||
import com.fankes.miui.notify.utils.factory.safeOfNull
|
||||
import com.fankes.miui.notify.utils.factory.showDialog
|
||||
import com.fankes.miui.notify.utils.factory.snake
|
||||
import com.highcapable.yukihookapi.hook.log.YLog
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import java.io.IOException
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.HostnameVerifier
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
/**
|
||||
* 通知图标在线规则管理类
|
||||
*/
|
||||
object IconRuleManagerTool {
|
||||
|
||||
/** 推送通知的渠道名称 */
|
||||
private const val NOTIFY_CHANNEL = "notifyRuleUpdateId"
|
||||
|
||||
/** 当前规则的系统名称 */
|
||||
private const val OS_TAG = "MIUI"
|
||||
|
||||
/** 当前规则的通知图标颜色 */
|
||||
private const val OS_COLOR = 0xFFE06818.toInt()
|
||||
|
||||
/** 同步地址 - GitHub Raw (直连) */
|
||||
private const val SYNC_DIRECT_URL = "https://raw.githubusercontent.com/fankes/AndroidNotifyIconAdapt/main"
|
||||
|
||||
/** 同步地址 - GitHub Raw (代理 - GitHub Proxy) */
|
||||
private const val SYNC_PROXY_1_URL = "https://ghproxy.com/$SYNC_DIRECT_URL"
|
||||
|
||||
/** 同步地址 - GitHub Raw (代理 - 7ED Services) */
|
||||
private const val SYNC_PROXY_2_URL = "https://raw.githubusercontentS.com/fankes/AndroidNotifyIconAdapt/main"
|
||||
|
||||
/** 云端规则展示地址 (OS) */
|
||||
private const val RULES_TRAVELER_OS_URL = "https://fankes.github.io/AndroidNotifyIconAdapt/?notify-rules-miui"
|
||||
|
||||
/** 云端规则展示地址 (APP) */
|
||||
private const val RULES_TRAVELER_APP_URL = "https://fankes.github.io/AndroidNotifyIconAdapt/?notify-rules-app"
|
||||
|
||||
/** 请求适配通知图标优化名单地址 */
|
||||
internal const val RULES_FEEDBACK_URL = "https://fankes.github.io/AndroidNotifyIconAdapt/?feedback"
|
||||
|
||||
/** 贡献通知图标优化名单地址 */
|
||||
internal const val RULES_CONTRIBUTING_URL = "https://fankes.github.io/AndroidNotifyIconAdapt/?contribute"
|
||||
|
||||
/**
|
||||
* 从在线地址手动同步规则
|
||||
* @param context 实例
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun syncByHand(context: Context, callback: () -> Unit) =
|
||||
context.showDialog<DiaSourceFromBinding> {
|
||||
title = "同步列表"
|
||||
var sourceType = ConfigData.iconRuleSourceSyncType
|
||||
var customUrl = ConfigData.iconRuleSourceSyncCustomUrl
|
||||
binding.sourceUrlEdit.apply {
|
||||
if (customUrl.isNotBlank()) {
|
||||
setText(customUrl)
|
||||
setSelection(customUrl.length)
|
||||
}
|
||||
doOnTextChanged { text, _, _, _ -> customUrl = text.toString() }
|
||||
}
|
||||
binding.sourceFromTextLin.isVisible = sourceType == IconRuleSourceSyncType.CUSTOM_URL
|
||||
binding.sourceTravelerLin.isVisible = sourceType != IconRuleSourceSyncType.CUSTOM_URL
|
||||
binding.sourceRadio0.isChecked = sourceType == IconRuleSourceSyncType.GITHUB_RAW_PROXY_1
|
||||
binding.sourceRadio1.isChecked = sourceType == IconRuleSourceSyncType.GITHUB_RAW_PROXY_2
|
||||
binding.sourceRadio2.isChecked = sourceType == IconRuleSourceSyncType.GITHUB_RAW_DIRECT
|
||||
binding.sourceRadio3.isChecked = sourceType == IconRuleSourceSyncType.CUSTOM_URL
|
||||
binding.sourceRadio0.setOnClickListener {
|
||||
binding.sourceFromTextLin.isVisible = false
|
||||
binding.sourceTravelerLin.isVisible = true
|
||||
sourceType = IconRuleSourceSyncType.GITHUB_RAW_PROXY_1
|
||||
}
|
||||
binding.sourceRadio1.setOnClickListener {
|
||||
binding.sourceFromTextLin.isVisible = false
|
||||
binding.sourceTravelerLin.isVisible = true
|
||||
sourceType = IconRuleSourceSyncType.GITHUB_RAW_PROXY_2
|
||||
}
|
||||
binding.sourceRadio2.setOnClickListener {
|
||||
binding.sourceFromTextLin.isVisible = false
|
||||
binding.sourceTravelerLin.isVisible = true
|
||||
sourceType = IconRuleSourceSyncType.GITHUB_RAW_DIRECT
|
||||
}
|
||||
binding.sourceRadio3.setOnClickListener {
|
||||
binding.sourceFromTextLin.isVisible = true
|
||||
binding.sourceTravelerLin.isVisible = false
|
||||
sourceType = IconRuleSourceSyncType.CUSTOM_URL
|
||||
}
|
||||
binding.sourceTravelerOsButton.setOnClickListener { context.openBrowser(RULES_TRAVELER_OS_URL) }
|
||||
binding.sourceTravelerAppButton.setOnClickListener { context.openBrowser(RULES_TRAVELER_APP_URL) }
|
||||
confirmButton {
|
||||
ConfigData.iconRuleSourceSyncType = sourceType
|
||||
ConfigData.iconRuleSourceSyncCustomUrl = customUrl
|
||||
sync(context, sourceType, customUrl, callback)
|
||||
}
|
||||
cancelButton()
|
||||
neutralButton(text = "自定义规则") {
|
||||
context.showDialog<DiaSourceFromStringBinding> {
|
||||
title = "自定义规则 (调试)"
|
||||
binding.jsonRuleEdit.apply {
|
||||
requestFocus()
|
||||
invalidate()
|
||||
}
|
||||
IconPackParams(context).also { params ->
|
||||
confirmButton(text = "合并") {
|
||||
binding.jsonRuleEdit.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]"
|
||||
)
|
||||
)
|
||||
notifyRefresh(context)
|
||||
callback()
|
||||
}
|
||||
else -> context.snake(msg = "请输入有效内容")
|
||||
}
|
||||
}
|
||||
}
|
||||
cancelButton(text = "覆盖") {
|
||||
binding.jsonRuleEdit.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]")
|
||||
notifyRefresh(context)
|
||||
callback()
|
||||
}
|
||||
else -> context.snake(msg = "请输入有效内容")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
neutralButton(text = "取消")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从在线地址同步规则
|
||||
* @param context 实例
|
||||
* @param sourceType 同步地址类型 - 默认自动获取已存储的键值
|
||||
* @param customUrl 自定义同步地址 - 默认自动获取已存储的键值
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun sync(
|
||||
context: Context,
|
||||
sourceType: Int = ConfigData.iconRuleSourceSyncType,
|
||||
customUrl: String = ConfigData.iconRuleSourceSyncCustomUrl,
|
||||
callback: () -> Unit
|
||||
) {
|
||||
when (sourceType) {
|
||||
IconRuleSourceSyncType.GITHUB_RAW_PROXY_1 -> onRefreshing(context, SYNC_PROXY_1_URL, callback)
|
||||
IconRuleSourceSyncType.GITHUB_RAW_PROXY_2 -> onRefreshing(context, SYNC_PROXY_2_URL, callback)
|
||||
IconRuleSourceSyncType.GITHUB_RAW_DIRECT -> onRefreshing(context, SYNC_DIRECT_URL, callback)
|
||||
IconRuleSourceSyncType.CUSTOM_URL ->
|
||||
if (customUrl.isNotBlank())
|
||||
if (customUrl.startsWith("http://") || customUrl.startsWith("https://"))
|
||||
onRefreshingCustom(context, customUrl, callback)
|
||||
else context.snakeOrNotify(title = "同步失败", msg = "同步地址不是一个合法的 URL")
|
||||
else context.snakeOrNotify(title = "同步失败", msg = "同步地址不能为空")
|
||||
else -> context.snakeOrNotify(title = "同步异常", msg = "同步类型错误")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始更新数据
|
||||
* @param context 实例
|
||||
* @param url
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
private fun onRefreshing(context: Context, url: String, callback: () -> Unit) = checkingInternetConnect(context) {
|
||||
fun doParse(result: (Boolean) -> Unit = {}) {
|
||||
wait(context, url = "$url/OS/$OS_TAG/NotifyIconsSupportConfig.json") { isDone1, ctOS ->
|
||||
result(true)
|
||||
wait(context, url = "$url/APP/NotifyIconsSupportConfig.json") { isDone2, ctAPP ->
|
||||
result(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)
|
||||
notifyRefresh(context)
|
||||
callback()
|
||||
}
|
||||
else -> (if (context is AppCompatActivity) context.snake(msg = "列表数据已是最新"))
|
||||
}
|
||||
}
|
||||
context is AppCompatActivity ->
|
||||
context.showDialog {
|
||||
title = "连接失败"
|
||||
msg = "连接失败,错误如下:\n${if (isDone1.not()) ctOS else ctAPP}"
|
||||
confirmButton(text = "再试一次") { syncByHand(context, callback) }
|
||||
cancelButton()
|
||||
}
|
||||
else -> pushNotify(context, title = "同步地址不可用", msg = if (isDone1.not()) 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 callback 成功后回调
|
||||
*/
|
||||
private fun onRefreshingCustom(context: Context, url: String, callback: () -> Unit) = checkingInternetConnect(context) {
|
||||
fun doParse(result: () -> Unit = {}) {
|
||||
wait(context, url) { isDone, content ->
|
||||
result()
|
||||
/** 延迟重新回调防止对话框无法取消 */
|
||||
delayedRun { result() }
|
||||
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)
|
||||
notifyRefresh(context)
|
||||
callback()
|
||||
}
|
||||
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 callback 已连接回调
|
||||
*/
|
||||
private fun checkingInternetConnect(context: Context, callback: () -> Unit) =
|
||||
if (context is AppCompatActivity) context.showDialog {
|
||||
title = "准备中"
|
||||
progressContent = "正在检查网络连接情况"
|
||||
noCancelable()
|
||||
baseCheckingInternetConnect(context) { isDone ->
|
||||
cancel()
|
||||
if (isDone) callback() 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) callback() else pushNotify(context, title = "网络不可用", msg = "网络连接失败,无法更新通知图标规则,点击重试", isRetry = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查网络连接情况
|
||||
* @param context 实例
|
||||
* @param result 已连接回调
|
||||
*/
|
||||
private fun baseCheckingInternetConnect(context: Context, result: (Boolean) -> Unit) =
|
||||
wait(context, url = "https://www.baidu.com") { isDone, _ -> result(isDone) }
|
||||
|
||||
/**
|
||||
* 发送 GET 请求内容并等待
|
||||
* @param context 实例
|
||||
* @param url 请求地址
|
||||
* @param result 回调 - ([Boolean] 是否成功,[String] 成功的内容或失败消息)
|
||||
*/
|
||||
private fun wait(context: Context, url: String, result: (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 { result(false, e.toString()) } ?: result(false, e.toString())
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
val bodyString = response.body.string()
|
||||
(context as? Activity?)?.runOnUiThread { result(true, bodyString) } ?: result(true, bodyString)
|
||||
}
|
||||
})
|
||||
}.onFailure { result(false, "URL 无效") }
|
||||
|
||||
/**
|
||||
* 推送通知图标更新通知
|
||||
* @param context 实例
|
||||
*/
|
||||
private fun notifyRefresh(context: Context) {
|
||||
if (context !is AppCompatActivity) pushNotify(context, title = "同步完成", msg = "已更新通知图标优化名单,点击查看")
|
||||
SystemUITool.refreshSystemUI(context) { if (context is AppCompatActivity) context.snake(msg = "已更新通知图标优化名单") }
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据实例类型决定推送通知还是弹出提示
|
||||
* @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 通知消息
|
||||
* @param isRetry 是否发送重试动作 - 默认否
|
||||
*/
|
||||
private fun pushNotify(context: Context, title: String, msg: String, isRetry: Boolean = false) {
|
||||
if (context !is AppCompatActivity)
|
||||
context.getSystemService<NotificationManager>()?.apply {
|
||||
createNotificationChannel(
|
||||
NotificationChannel(
|
||||
NOTIFY_CHANNEL, "通知图标优化规则",
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
)
|
||||
)
|
||||
notify(0, NotificationCompat.Builder(context, NOTIFY_CHANNEL).apply {
|
||||
setContentTitle(title)
|
||||
setContentText(msg)
|
||||
color = OS_COLOR
|
||||
setAutoCancel(true)
|
||||
setSmallIcon(R.drawable.ic_nf_icon_update)
|
||||
setSound(null)
|
||||
setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context, msg.hashCode(),
|
||||
Intent(context, ConfigureActivity::class.java).apply {
|
||||
if (isRetry) putExtra("isDirectUpdate", true) else 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>(trustManager), SecureRandom())
|
||||
it.socketFactory
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用的实例
|
||||
* @return [HostnameVerifier]
|
||||
*/
|
||||
val hostnameVerifier get() = HostnameVerifier { _, _ -> true }
|
||||
|
||||
/**
|
||||
* 信任管理者
|
||||
* @return [X509TrustManager]
|
||||
*/
|
||||
val trustManager
|
||||
get() = object : X509TrustManager {
|
||||
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) {
|
||||
YLog.debug("TrustX509 --> $authType")
|
||||
}
|
||||
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate?>?, authType: String?) {
|
||||
YLog.debug("TrustX509 --> $authType")
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers() = arrayOf<X509Certificate>()
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
* <p>
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2022/2/8.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.tool
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.fankes.miui.notify.const.PackageName
|
||||
import com.fankes.miui.notify.data.ConfigData
|
||||
import com.fankes.miui.notify.ui.activity.MainActivity
|
||||
import com.fankes.miui.notify.utils.factory.delayedRun
|
||||
import com.fankes.miui.notify.utils.factory.execShell
|
||||
import com.fankes.miui.notify.utils.factory.isMIOS
|
||||
import com.fankes.miui.notify.utils.factory.showDialog
|
||||
import com.fankes.miui.notify.utils.factory.snake
|
||||
import com.fankes.miui.notify.utils.factory.systemFullVersion
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
import com.highcapable.yukihookapi.hook.factory.dataChannel
|
||||
import com.highcapable.yukihookapi.hook.log.YLog
|
||||
import com.highcapable.yukihookapi.hook.log.data.YLogData
|
||||
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||
import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 系统界面工具
|
||||
*/
|
||||
object SystemUITool {
|
||||
|
||||
private val CALL_HOST_REFRESH_CACHING = ChannelData("call_host_refresh_caching", false)
|
||||
private val CALL_MODULE_REFRESH_RESULT = ChannelData("call_module_refresh_result", false)
|
||||
|
||||
/** 当前全部调试日志 */
|
||||
private var debugLogs = listOf<YLogData>()
|
||||
|
||||
/** 当前启动器实例 */
|
||||
private var launcher: ActivityResultLauncher<String>? = null
|
||||
|
||||
/**
|
||||
* 宿主注册监听
|
||||
*/
|
||||
object Host {
|
||||
|
||||
/**
|
||||
* 监听系统界面刷新改变
|
||||
* @param param 实例
|
||||
* @param result 回调 - ([Boolean] 是否成功)
|
||||
*/
|
||||
fun onRefreshSystemUI(param: PackageParam, result: (Boolean) -> Boolean) {
|
||||
param.dataChannel.with { wait(CALL_HOST_REFRESH_CACHING) { put(CALL_MODULE_REFRESH_RESULT, result(it)) } }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否激活
|
||||
* @param context 实例
|
||||
* @param result 成功后回调
|
||||
*/
|
||||
fun checkingActivated(context: Context, result: (Boolean) -> Unit) =
|
||||
context.dataChannel(PackageName.SYSTEMUI).checkingVersionEquals(result = result)
|
||||
|
||||
/**
|
||||
* 注册导出调试日志启动器到 [AppCompatActivity]
|
||||
* @param activity 实例
|
||||
*/
|
||||
fun registerExportDebugLogsLauncher(activity: AppCompatActivity) {
|
||||
launcher = activity.registerForActivityResult(ActivityResultContracts.CreateDocument("*/application")) { result ->
|
||||
runCatching {
|
||||
result?.let { e ->
|
||||
val content = "" +
|
||||
"================================================================\n" +
|
||||
" Generated by MIUINativeNotifyIcon\n" +
|
||||
" Project Url: https://github.com/fankes/MIUINativeNotifyIcon\n" +
|
||||
"================================================================\n\n" +
|
||||
"[Device Brand]: ${Build.BRAND}\n" +
|
||||
"[Device Model]: ${Build.MODEL}\n" +
|
||||
"[Display]: ${Build.DISPLAY}\n" +
|
||||
"[Android Version]: ${Build.VERSION.RELEASE}\n" +
|
||||
"[Android API Level]: ${Build.VERSION.SDK_INT}\n" +
|
||||
"[${if (isMIOS) "HyperOS" else "MIUI"} Version]: $systemFullVersion\n" +
|
||||
"[System Locale]: ${Locale.getDefault()}\n\n" + YLog.contents(debugLogs).trim()
|
||||
activity.contentResolver?.openOutputStream(e)?.apply { write(content.toByteArray()) }?.close()
|
||||
activity.snake(msg = "导出完成")
|
||||
} ?: activity.snake(msg = "已取消操作")
|
||||
}.onFailure { activity.snake(msg = "导出过程发生错误") }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取并导出全部调试日志
|
||||
* @param context 实例
|
||||
*/
|
||||
fun obtainAndExportDebugLogs(context: Context) {
|
||||
/** 执行导出操作 */
|
||||
fun doExport() {
|
||||
context.dataChannel(PackageName.SYSTEMUI).obtainLoggerInMemoryData {
|
||||
if (it.isNotEmpty()) {
|
||||
debugLogs = it
|
||||
runCatching { launcher?.launch("miui_notification_icons_processing_logs.log") }
|
||||
.onFailure { context.snake(msg = "启动系统文件选择器失败") }
|
||||
} else context.snake(msg = "暂无调试日志")
|
||||
}
|
||||
}
|
||||
if (YukiHookAPI.Status.isXposedModuleActive)
|
||||
context.showDialog {
|
||||
title = "导出全部调试日志"
|
||||
msg = "调试日志中会包含当前系统推送的全部通知内容,其中可能包含你的个人隐私," +
|
||||
"你可以在导出后的日志文件中选择将这些敏感信息模糊化处理再进行共享," +
|
||||
"开发者使用并查看你导出的调试日志仅为排查与修复问题,并且在之后会及时销毁这些日志。\n\n" +
|
||||
"继续导出即代表你已阅读并知悉上述内容。"
|
||||
confirmButton(text = "继续") { doExport() }
|
||||
cancelButton()
|
||||
}
|
||||
else context.snake(msg = "模块没有激活,请先激活模块")
|
||||
}
|
||||
|
||||
/** 当 Root 权限获取失败时显示对话框 */
|
||||
private fun Context.showWhenAccessRootFail() =
|
||||
showDialog {
|
||||
title = "获取 Root 权限失败"
|
||||
msg = "当前无法获取 Root 权限,请确认你的设备已经被 Root 且同意授予 Root 权限。\n" +
|
||||
"如果你正在使用 Magisk 并安装了 Shamiko 模块," +
|
||||
"请确认当前是否正处于白名单模式。 (白名单模式将导致无法申请 Root 权限)"
|
||||
confirmButton(text = "我知道了")
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开 MIUI 通知显示设置界面
|
||||
* @param context 实例
|
||||
*/
|
||||
fun openMiuiNotificationDisplaySettings(context: Context) {
|
||||
runCatching {
|
||||
context.startActivity(Intent().apply {
|
||||
component = ComponentName(
|
||||
"com.miui.notification",
|
||||
"miui.notification.management.activity.NotificationDisplaySettingsActivity"
|
||||
)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
}.onFailure {
|
||||
execShell(
|
||||
cmd = "am start -a" +
|
||||
"com.miui.notification " +
|
||||
"com.miui.notification/miui.notification.management.activity.NotificationDisplaySettingsActivity"
|
||||
).also {
|
||||
when {
|
||||
it.isBlank() -> context.showWhenAccessRootFail()
|
||||
else -> context.snake(msg = "已发送请求,如果未打开则是当前系统不支持此功能")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启系统界面
|
||||
* @param context 实例
|
||||
*/
|
||||
fun restartSystemUI(context: Context) {
|
||||
/** 动态刷新功能是否可用 */
|
||||
val isDynamicAvailable = ConfigData.isEnableModule && MainActivity.isModuleRegular && MainActivity.isModuleValied
|
||||
context.showDialog {
|
||||
title = "重启系统界面"
|
||||
msg = "你确定要立即重启系统界面吗?\n\n" +
|
||||
"部分 MIUI 内测和开发版中使用了状态栏主题可能会发生主题失效的情况,这种情况请再重启一次即可。\n\n" +
|
||||
"重启过程会黑屏并等待进入锁屏重新解锁。" + (if (isDynamicAvailable)
|
||||
"\n\n你也可以选择“立即生效”来动态刷新系统界面并生效当前模块设置。" else "")
|
||||
confirmButton {
|
||||
execShell(cmd = "pgrep systemui").also { pid ->
|
||||
if (pid.isNotBlank())
|
||||
execShell(cmd = "kill -9 $pid")
|
||||
else context.showWhenAccessRootFail()
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
if (isDynamicAvailable) neutralButton(text = "立即生效") { refreshSystemUI(context) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新系统界面状态栏与通知图标
|
||||
* @param context 实例
|
||||
* @param isRefreshCacheOnly 仅刷新缓存不刷新图标和通知改变 - 默认:否
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun refreshSystemUI(context: Context? = null, isRefreshCacheOnly: Boolean = false, callback: () -> Unit = {}) {
|
||||
/**
|
||||
* 刷新系统界面
|
||||
* @param result 回调结果
|
||||
*/
|
||||
fun doRefresh(result: (Boolean) -> Unit) {
|
||||
context?.dataChannel(PackageName.SYSTEMUI)?.with {
|
||||
wait(CALL_MODULE_REFRESH_RESULT) { result(it) }
|
||||
put(CALL_HOST_REFRESH_CACHING, isRefreshCacheOnly)
|
||||
}
|
||||
}
|
||||
when {
|
||||
YukiHookAPI.Status.isXposedModuleActive && context is AppCompatActivity ->
|
||||
context.showDialog {
|
||||
title = "请稍后"
|
||||
progressContent = "正在等待系统界面刷新"
|
||||
/** 是否等待成功 */
|
||||
var isWaited = false
|
||||
/** 设置等待延迟 */
|
||||
delayedRun(ms = 5000) {
|
||||
if (isWaited) return@delayedRun
|
||||
cancel()
|
||||
context.snake(msg = "预计响应超时,建议重启系统界面", actionText = "立即重启") { restartSystemUI(context) }
|
||||
}
|
||||
checkingActivated(context) { isValied ->
|
||||
when {
|
||||
isValied.not() -> {
|
||||
cancel()
|
||||
isWaited = true
|
||||
context.snake(msg = "请重启系统界面以生效模块更新", actionText = "立即重启") { restartSystemUI(context) }
|
||||
}
|
||||
else -> doRefresh {
|
||||
cancel()
|
||||
isWaited = true
|
||||
callback()
|
||||
if (it.not()) context.snake(msg = "刷新失败,建议重启系统界面", actionText = "立即重启") { restartSystemUI(context) }
|
||||
}
|
||||
}
|
||||
}
|
||||
noCancelable()
|
||||
}
|
||||
YukiHookAPI.Status.isXposedModuleActive.not() && context is AppCompatActivity -> context.snake(msg = "模块没有激活,更改不会生效")
|
||||
else -> doRefresh { callback() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示需要重启系统界面的 [Snackbar]
|
||||
* @param context 实例
|
||||
*/
|
||||
fun showNeedRestartSnake(context: Context) =
|
||||
if (YukiHookAPI.Status.isXposedModuleActive)
|
||||
context.snake(msg = "设置需要重启系统界面才能生效", actionText = "立即重启") { restartSystemUI(context) }
|
||||
else context.snake(msg = "模块没有激活,更改不会生效")
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/fankes/MIUINativeNotifyIcon
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
@@ -18,18 +18,20 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/1/8.
|
||||
* This file is created by fankes on 2023/9/17.
|
||||
*/
|
||||
package com.fankes.miui.notify.utils.drawable.drawabletoolbox
|
||||
@file:Suppress("unused")
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
package com.fankes.miui.notify.wrapper
|
||||
|
||||
abstract class DrawableWrapperBuilder<T: DrawableWrapperBuilder<T>> {
|
||||
import com.fankes.miui.notify.BuildConfig
|
||||
|
||||
protected var drawable: Drawable? = null
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun drawable(drawable: Drawable): T = apply { this.drawable = drawable } as T
|
||||
|
||||
abstract fun build(): Drawable
|
||||
/**
|
||||
* 对 [BuildConfig] 的包装
|
||||
*/
|
||||
object BuildConfigWrapper {
|
||||
const val APPLICATION_ID = BuildConfig.APPLICATION_ID
|
||||
const val VERSION_NAME = BuildConfig.VERSION_NAME
|
||||
const val VERSION_CODE = BuildConfig.VERSION_CODE
|
||||
val isDebug = BuildConfig.DEBUG
|
||||
}
|
6
app/src/main/res/drawable/bg_orange_round.xml
Executable file
6
app/src/main/res/drawable/bg_orange_round.xml
Executable file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FF7043" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
6
app/src/main/res/drawable/bg_yellow_round.xml
Executable file
6
app/src/main/res/drawable/bg_yellow_round.xml
Executable file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FF9800" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
9
app/src/main/res/drawable/ic_about.xml
Normal file
9
app/src/main/res/drawable/ic_about.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150.1dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1025"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M983.8,314.4c-25.7,-60.8 -62.5,-115.4 -109.4,-162.3 -46.9,-46.9 -101.5,-83.7 -162.3,-109.4 -62.9,-26.7 -129.9,-40.2 -198.8,-40.2S377.5,16 314.5,42.7C253.8,68.4 199.1,105.2 152.3,152.2c-46.9,46.9 -83.7,101.5 -109.4,162.3 -26.7,62.9 -40.2,129.9 -40.2,198.8s13.5,135.8 40.2,198.8c25.7,60.8 62.5,115.4 109.4,162.3 46.9,46.9 101.5,83.7 162.3,109.4 62.9,26.7 129.9,40.2 198.8,40.2s135.8,-13.5 198.8,-40.2c60.8,-25.7 115.4,-62.5 162.3,-109.4 46.9,-46.9 83.7,-101.5 109.4,-162.3 26.7,-62.9 40.2,-129.9 40.2,-198.8s-13.6,-135.9 -40.3,-198.9zM550.5,768.2c0,21 -17,38 -38,38s-38,-17 -38,-38L474.5,395.6c0,-21 17,-38 38,-38s38,17 38,38v372.6zM510.8,305.5c-29.2,0 -52.7,-23.7 -52.7,-52.7 0,-29.2 23.7,-52.7 52.7,-52.7 29.2,0 52.7,23.7 52.7,52.7 0.1,29.2 -23.6,52.7 -52.7,52.7z" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_back.xml
Normal file
9
app/src/main/res/drawable/ic_back.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="192dp"
|
||||
android:height="150.39165dp"
|
||||
android:viewportWidth="1307"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M268.7,566.5h929.6c36.3,0 72.6,-29 72.6,-72.6 0,-36.3 -29,-72.6 -72.6,-72.6H305l297.8,-297.8c29,-29 29,-72.6 0,-101.7 -29,-29 -72.6,-29 -101.7,0L72.6,450.3c-14.5,14.5 -21.8,36.3 -21.8,58.1 0,21.8 0,43.6 21.8,58.1l428.5,428.5c29,29 72.6,29 101.7,0 29,-29 29,-72.6 0,-101.7l-334.1,-326.8z" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_filter.xml
Normal file
9
app/src/main/res/drawable/ic_filter.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="184.6dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1260"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M419.2,548.3L17,108.9C-23.6,64.4 13,0 79.7,0h846.5c66.6,0 103.3,64.4 62.8,108.9l-402.2,439.3v472.7c0,1.6 -1.3,2.9 -2.8,2.9a8.3,8.3 0,0 1,-5.2 -1.8l-154.3,-120a13.7,13.7 0,0 1,-5.3 -10.8L419.2,548.3zM838.2,511.9L1257.3,511.9L1257.3,597.2h-419.1L838.2,511.9zM838.2,682.5L1257.3,682.5v85.3h-419.1L838.2,682.5zM838.2,853.2L1257.3,853.2v85.3h-419.1v-85.3z" />
|
||||
</vector>
|
52
app/src/main/res/drawable/ic_function.xml
Normal file
52
app/src/main/res/drawable/ic_function.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M11,16V42"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M24,29V42"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M24,19V6"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M37,6V32"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M11,16C13.761,16 16,13.761 16,11C16,8.239 13.761,6 11,6C8.239,6 6,8.239 6,11C6,13.761 8.239,16 11,16Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M24,29C26.761,29 29,26.761 29,24C29,21.239 26.761,19 24,19C21.239,19 19,21.239 19,24C19,26.761 21.239,29 24,29Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M37,42C39.761,42 42,39.761 42,37C42,34.239 39.761,32 37,32C34.239,32 32,34.239 32,37C32,39.761 34.239,42 37,42Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_github.xml
Normal file
9
app/src/main/res/drawable/ic_github.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M512,12.6c-282.8,0 -512,229.2 -512,512 0,226.2 146.7,418.1 350.1,485.8 25.6,4.7 35,-11.1 35,-24.6 0,-12.2 -0.5,-52.5 -0.7,-95.3 -142.5,31 -172.5,-60.4 -172.5,-60.4 -23.3,-59.2 -56.8,-74.9 -56.8,-74.9 -46.5,-31.8 3.5,-31.1 3.5,-31.1 51.4,3.6 78.5,52.8 78.5,52.8 45.7,78.3 119.8,55.6 149,42.6 4.6,-33.1 17.9,-55.7 32.5,-68.5 -113.7,-12.9 -233.3,-56.9 -233.3,-253 0,-55.9 20,-101.6 52.8,-137.4 -5.3,-12.9 -22.8,-65 5,-135.5 0,0 43,-13.8 140.8,52.5 40.8,-11.4 84.6,-17 128.2,-17.2 43.5,0.2 87.3,5.9 128.3,17.2 97.7,-66.2 140.6,-52.5 140.6,-52.5 27.9,70.5 10.3,122.6 5,135.5 32.8,35.8 52.7,81.5 52.7,137.4 0,196.6 -119.8,239.9 -233.8,252.6 18.4,15.9 34.7,47 34.7,94.8 0,68.5 -0.6,123.6 -0.6,140.5 0,13.6 9.2,29.6 35.2,24.6 203.3,-67.8 349.9,-259.6 349.9,-485.8 0,-282.8 -229.2,-512 -512,-512z" />
|
||||
</vector>
|
25
app/src/main/res/drawable/ic_home.xml
Normal file
25
app/src/main/res/drawable/ic_home.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,18V42H39V18L24,6L9,18Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29V42H29V29H19Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,42H39"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round" />
|
||||
</vector>
|
30
app/src/main/res/drawable/ic_info.xml
Normal file
30
app/src/main/res/drawable/ic_info.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M24,44C29.523,44 34.523,41.761 38.142,38.142C41.761,34.523 44,29.523 44,24C44,18.477 41.761,13.477 38.142,9.858C34.523,6.239 29.523,4 24,4C18.477,4 13.477,6.239 9.858,9.858C6.239,13.477 4,18.477 4,24C4,29.523 6.239,34.523 9.858,38.142C13.477,41.761 18.477,44 24,44Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M24,11C25.381,11 26.5,12.119 26.5,13.5C26.5,14.881 25.381,16 24,16C22.619,16 21.5,14.881 21.5,13.5C21.5,12.119 22.619,11 24,11Z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M24.5,34V20H23.5H22.5"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M21,34H28"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_message.xml
Normal file
10
app/src/main/res/drawable/ic_message.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="m522.8,93.9c-244.2,0 -442.1,173.2 -442.1,386.9 0,122.1 64.9,230.9 165.8,301.7v195.7l193.7,-117.5c26.8,4.4 54.3,7 82.6,7 244.2,0 442.1,-173.2 442.1,-386.9C965,267.1 767.1,93.9 522.8,93.9Z"
|
||||
android:strokeWidth="1.10883" />
|
||||
</vector>
|
36
app/src/main/res/drawable/ic_modify.xml
Normal file
36
app/src/main/res/drawable/ic_modify.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M20.071,9.586L15.828,5.343C15.047,4.562 13.781,4.562 13,5.343L7.343,11C6.562,11.781 6.562,13.047 7.343,13.828L11.586,18.071"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M28.929,37.414L33.171,41.657C33.952,42.438 35.219,42.438 36,41.657L41.657,36C42.438,35.219 42.438,33.953 41.657,33.172L37.414,28.929"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M36.021,6.322L41.677,11.979A2,2 0,0 1,41.677 14.808L14.807,41.678A2,2 0,0 1,11.979 41.678L6.322,36.021A2,2 132.403,0 1,6.322 33.192L33.192,6.322A2,2 102.008,0 1,36.021 6.322z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M24,24m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M20,28m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M28,20m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_nf_icon_update.xml
Normal file
10
app/src/main/res/drawable/ic_nf_icon_update.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/white"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#ffffffff"
|
||||
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z" />
|
||||
</vector>
|
20
app/src/main/res/drawable/ic_notify.xml
Normal file
20
app/src/main/res/drawable/ic_notify.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M10,38V18C10,10.268 16.268,4 24,4C31.732,4 38,10.268 38,18V38M4,38H44"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M24,44C26.761,44 29,41.761 29,39V38H19V39C19,41.761 21.239,44 24,44Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_notify_icon.xml
Normal file
9
app/src/main/res/drawable/ic_notify_icon.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffffff"
|
||||
android:pathData="M192,736 L192,416C192,261.2 302,132.1 448,102.4L448,64C448,28.6 476.6,0 512,0 547.4,0 576,28.6 576,64L576,102.4C722,132.1 832,261.2 832,416L832,736 864.1,736C899.4,736 928,764.4 928,800 928,835.4 899.4,864 864.1,864L159.9,864C124.6,864 96,835.6 96,800 96,764.6 124.6,736 159.9,736L192,736ZM608,928C608,981 565,1024 512,1024 459,1024 416,981 416,928L608,928Z" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_notify_update.xml
Normal file
9
app/src/main/res/drawable/ic_notify_update.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="50dp"
|
||||
android:height="50dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M906.4,791.6a247.9,247.9 0,0 0,-98.1 -186.5L808.3,399.4c0,-118.8 -85.1,-218.9 -197.4,-242.5v-4.7c0,-54.2 -44.5,-98.6 -98.8,-98.6 -54.3,0 -98.8,44.4 -98.8,98.6v4.7C300.8,180.5 215.7,280.4 215.7,399.4v205.8a247.7,247.7 0,0 0,-98.1 186.5h98.1v0.6h592.5v-0.6h98.1v-0.1zM491.1,970.4h24.5c64.3,0 117.8,-48.5 125.6,-110.7L383,859.8a126.8,126.8 0,0 0,125.6 110.7h-17.5z" />
|
||||
</vector>
|
12
app/src/main/res/drawable/ic_page_bottom.xml
Normal file
12
app/src/main/res/drawable/ic_page_bottom.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="72dp"
|
||||
android:height="72dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="m563.5,143.3m-41,0v0q-41,0 -41,41v573.4q0,41 41,41v0q41,0 41,-41v-573.4q0,-41 -41,-41z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M521.5,796.2 L318.2,593a41,41 0,1 0,-57.9 57.9l231.7,231.7a41,41 0,0 0,58.4 0.5L782.2,651.4a41,41 0,1 0,-57.9 -57.9z" />
|
||||
</vector>
|
12
app/src/main/res/drawable/ic_page_top.xml
Normal file
12
app/src/main/res/drawable/ic_page_top.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="72dp"
|
||||
android:height="72dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="m563.5,895.1m-41,0v0q-41,0 -41,-41v-573.4q0,-41 41,-41v0q41,0 41,41v573.4q0,41 -41,41z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M521.5,242.2 L318.2,445.4a41,41 0,1 1,-57.9 -57.9l231.7,-231.7a41,41 0,0 1,58.4 -0.5l231.7,231.7a41,41 0,1 1,-57.9 57.9z" />
|
||||
</vector>
|
13
app/src/main/res/drawable/ic_preference.xml
Normal file
13
app/src/main/res/drawable/ic_preference.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M44,16C44,22.627 38.627,28 32,28C29.973,28 28.064,27.497 26.39,26.61L9,44L4,39L21.39,21.61C20.503,19.936 20,18.027 20,16C20,9.373 25.373,4 32,4C34.027,4 35.936,4.502 37.61,5.39L30,13L35,18L42.61,10.39C43.498,12.064 44,13.973 44,16Z"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
30
app/src/main/res/drawable/ic_restart.xml
Normal file
30
app/src/main/res/drawable/ic_restart.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="4800"
|
||||
android:viewportHeight="4800">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M2270.9,56.1L2527.1,56.1A200,307.5 0,0 1,2727.1 363.6L2727.1,1098.6A200,307.5 0,0 1,2527.1 1406.1L2270.9,1406.1A200,307.5 0,0 1,2070.9 1098.6L2070.9,363.6A200,307.5 0,0 1,2270.9 56.1z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M2270.9,3406.1L2527.1,3406.1A200,307.5 0,0 1,2727.1 3713.6L2727.1,4448.6A200,307.5 0,0 1,2527.1 4756.1L2270.9,4756.1A200,307.5 0,0 1,2070.9 4448.6L2070.9,3713.6A200,307.5 0,0 1,2270.9 3406.1z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M646.7,835L827.8,653.9A200,307.5 135,0 1,1186.7 729.9L1706.4,1249.6A200,307.5 135,0 1,1782.4 1608.5L1601.3,1789.6A307.5,200 45,0 1,1242.4 1713.6L722.7,1193.9A307.5,200 45,0 1,646.7 835z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M3015.5,3203.8L3196.7,3022.7A200,307.5 135,0 1,3555.5 3098.7L4075.2,3618.4A307.5,200 45,0 1,4151.3 3977.3L3970.1,4158.4A307.5,200 45,0 1,3611.3 4082.4L3091.5,3562.7A307.5,200 45,0 1,3015.5 3203.8z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M49,2534.2L49,2278A200,307.5 90,0 1,356.5 2078L1091.5,2078A200,307.5 90,0 1,1399 2278L1399,2534.2A200,307.5 90,0 1,1091.5 2734.2L356.5,2734.2A200,307.5 90,0 1,49 2534.2z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M3399,2534.2L3399,2278.1A200,307.5 90,0 1,3706.5 2078.1L4441.5,2078.1A200,307.5 90,0 1,4749 2278.1L4749,2534.2A200,307.5 90,0 1,4441.5 2734.2L3706.5,2734.2A200,307.5 90,0 1,3399 2534.2z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M827.8,4158.4L646.7,3977.3A307.5,200 135,0 1,722.7 3618.4L1242.4,3098.7A200,307.5 45,0 1,1601.3 3022.7L1782.4,3203.8A200,307.5 45,0 1,1706.4 3562.7L1186.7,4082.4A200,307.5 45,0 1,827.8 4158.4z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M3196.7,1789.6L3015.5,1608.4A307.5,200 135,0 1,3091.5 1249.6L3611.3,729.9A307.5,200 135,0 1,3970.1 653.9L4151.3,835A200,307.5 45,0 1,4075.2 1193.9L3555.5,1713.6A200,307.5 45,0 1,3196.7 1789.6z" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_success.xml
Normal file
9
app/src/main/res/drawable/ic_success.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1008.7"
|
||||
android:viewportHeight="1008.7">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M504.4,0C226.3,0 0,226.3 0,504.4 0,782.5 226.3,1008.7 504.4,1008.7c278.1,0 504.4,-226.3 504.4,-504.4C1008.7,226.3 782.5,0 504.4,0ZM786.6,407.7 L458.6,743.9c-7.8,8 -18.6,12.6 -29.8,12.6h-0.2c-11.1,0 -21.8,-4.4 -29.7,-12.3L222.5,567.9c-16.4,-16.4 -16.4,-43 0,-59.4 16.4,-16.4 43,-16.4 59.4,0L428.2,654.8 726.5,348.9c16.3,-16.6 42.9,-16.9 59.4,-0.7 16.6,16.2 16.9,42.8 0.7,59.4z" />
|
||||
</vector>
|
12
app/src/main/res/drawable/ic_sync.xml
Normal file
12
app/src/main/res/drawable/ic_sync.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="72dp"
|
||||
android:height="72dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M628.5,544a224,224 0,0 0,-96 116.5l60.2,21.8a160,160 0,0 1,68.8 -83.2,152.6 152.6,0 0,1 117.4,-16 154.9,154.9 0,0 1,92.5 69.8l-39.4,28.8 116.2,32 11.8,-121.6 -35.8,25.3A217.9,217.9 0,0 0,628.5 544zM810.9,875.5a150.4,150.4 0,0 1,-118.1 9.6,155.8 155.8,0 0,1 -88.6,-74.6l40,-25.3 -114.2,-39 -17.9,121.6 37.1,-23.4A217.9,217.9 0,0 0,672 946.2a224,224 0,0 0,67.8 10.9,215 215,0 0,0 99.5,-24.6 221.1,221.1 0,0 0,111.4 -136l-61.4,-17.9a155.2,155.2 0,0 1,-78.4 97z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M736,448a285.1,285.1 0,0 1,138.6 35.5L874.6,119a48,48 0,0 0,-48 -48h-640a48,48 0,0 0,-48 48v768a48,48 0,0 0,48 48h341.8A288,288 0,0 1,736 448zM314.6,311.4h320v64h-320zM314.6,439.4h192v64h-192z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_system_clock.xml
Normal file
10
app/src/main/res/drawable/ic_system_clock.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/white"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#ffffffff"
|
||||
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM15.29,16.71L11,12.41V7h2v4.59l3.71,3.71L15.29,16.71z" />
|
||||
</vector>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user