mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-08-14 21:47:25 +00:00
Compare commits
1512 Commits
v24.0
...
canary-281
Author | SHA1 | Date | |
---|---|---|---|
![]() |
47cc532d96 | ||
![]() |
218327f92b | ||
![]() |
4eae66a1a7 | ||
![]() |
b09ceeb43c | ||
![]() |
4fb539c110 | ||
![]() |
849b284da5 | ||
![]() |
895b5f6cbf | ||
![]() |
cb3d4ea514 | ||
![]() |
0d89a2a97d | ||
![]() |
3ca5913055 | ||
![]() |
df6b808f49 | ||
![]() |
09c7ac754b | ||
![]() |
805da67c23 | ||
![]() |
3c6889505b | ||
![]() |
c8e9ce7627 | ||
![]() |
837c679a31 | ||
![]() |
06616659b8 | ||
![]() |
a34c04f999 | ||
![]() |
da43ac89a0 | ||
![]() |
830fc758b9 | ||
![]() |
0f3cfef278 | ||
![]() |
b32d7bfafd | ||
![]() |
c0899f2939 | ||
![]() |
082330808f | ||
![]() |
024da05888 | ||
![]() |
377b6d0cc2 | ||
![]() |
c661009b31 | ||
![]() |
613f2d31c5 | ||
![]() |
7dbb973db5 | ||
![]() |
f4502f8be8 | ||
![]() |
455b13b83c | ||
![]() |
8b98709743 | ||
![]() |
1b12f45f39 | ||
![]() |
a5cad532ff | ||
![]() |
070719db50 | ||
![]() |
28cccdf7aa | ||
![]() |
b7e0986a5c | ||
![]() |
da709745dd | ||
![]() |
8b6771d487 | ||
![]() |
e1b847fbc5 | ||
![]() |
7188de1205 | ||
![]() |
44fb7dbcbe | ||
![]() |
8086b5933c | ||
![]() |
7f675f4bf7 | ||
![]() |
5e6b53e0da | ||
![]() |
5b29fefc65 | ||
![]() |
16a168535d | ||
![]() |
33f70f8f6d | ||
![]() |
4f18a66d73 | ||
![]() |
250dc16007 | ||
![]() |
7af273e047 | ||
![]() |
e381aebaa0 | ||
![]() |
45d91c9658 | ||
![]() |
4a9158f667 | ||
![]() |
0d9ee89e7f | ||
![]() |
abaff72304 | ||
![]() |
b828e2d0b2 | ||
![]() |
53d7cbc11b | ||
![]() |
310be7ab47 | ||
![]() |
60894e458f | ||
![]() |
fbebb6ac10 | ||
![]() |
a9f8c20703 | ||
![]() |
ae0b15d197 | ||
![]() |
869aa62328 | ||
![]() |
dcd3bc58a3 | ||
![]() |
a82f17c594 | ||
![]() |
b38fd1ca5f | ||
![]() |
8e82113bce | ||
![]() |
f723ef153b | ||
![]() |
1dc723fb6d | ||
![]() |
8f271c2575 | ||
![]() |
7cf56b4406 | ||
![]() |
c2eb603957 | ||
![]() |
e6bd2ff60f | ||
![]() |
5604074eba | ||
![]() |
3f061c1a1e | ||
![]() |
55e78a7b1a | ||
![]() |
000f1d6041 | ||
![]() |
2cbec20238 | ||
![]() |
4b724c7257 | ||
![]() |
ab04c6ab39 | ||
![]() |
821a6c6954 | ||
![]() |
5f27a62221 | ||
![]() |
c76cc4c6bd | ||
![]() |
52b75c53b6 | ||
![]() |
9db2e99086 | ||
![]() |
e9e2ecf2dd | ||
![]() |
9a9e617c35 | ||
![]() |
3a0becc783 | ||
![]() |
1f974cb220 | ||
![]() |
1db80228e8 | ||
![]() |
838e1e254d | ||
![]() |
554eda8fe1 | ||
![]() |
2bdc047c4d | ||
![]() |
e64f59ce5b | ||
![]() |
b8140ad4e6 | ||
![]() |
5a55483698 | ||
![]() |
2d341863f5 | ||
![]() |
278046becb | ||
![]() |
5c0497354f | ||
![]() |
98c258df93 | ||
![]() |
c578cccfd5 | ||
![]() |
07835a3e0e | ||
![]() |
09131aca89 | ||
![]() |
9ce998a6df | ||
![]() |
ca36b42d79 | ||
![]() |
37df39ec37 | ||
![]() |
1701361a73 | ||
![]() |
4c14ae33f5 | ||
![]() |
d4a9ef7b7f | ||
![]() |
1539cfe888 | ||
![]() |
9093be1329 | ||
![]() |
606d076251 | ||
![]() |
46a34e19bc | ||
![]() |
5ac7dc0b37 | ||
![]() |
3b27de3715 | ||
![]() |
939bfac920 | ||
![]() |
f601bf12d5 | ||
![]() |
0495468d02 | ||
![]() |
300a2a242c | ||
![]() |
33aebb5976 | ||
![]() |
b3d6809c0b | ||
![]() |
461f7e9f89 | ||
![]() |
9cc50b20d8 | ||
![]() |
f488e9df8f | ||
![]() |
0dc596e206 | ||
![]() |
c3bf03190b | ||
![]() |
021ae891a9 | ||
![]() |
9c03514eb1 | ||
![]() |
eb74b266e1 | ||
![]() |
80eb6ff25a | ||
![]() |
7b81e2d2d1 | ||
![]() |
a8789073f1 | ||
![]() |
c8fe0f5524 | ||
![]() |
d33b077a13 | ||
![]() |
2282365cf8 | ||
![]() |
9a00b7b942 | ||
![]() |
d54baadbed | ||
![]() |
0869a90fe3 | ||
![]() |
2754b1dcf8 | ||
![]() |
0db6314661 | ||
![]() |
b5d2ef18e8 | ||
![]() |
6e22476acc | ||
![]() |
b26db8cee6 | ||
![]() |
33cb39c8af | ||
![]() |
f247759a6e | ||
![]() |
de7e5bdfe7 | ||
![]() |
53a8ba8cfe | ||
![]() |
f2d057baba | ||
![]() |
93bcf2cd25 | ||
![]() |
6d82515cfc | ||
![]() |
a177d3b022 | ||
![]() |
92b2e06e57 | ||
![]() |
f919bb0e99 | ||
![]() |
054971e899 | ||
![]() |
93c3d36452 | ||
![]() |
4c38af994d | ||
![]() |
bbb8efe92c | ||
![]() |
659dd09723 | ||
![]() |
4931825912 | ||
![]() |
ef81cdab4f | ||
![]() |
7c0b25cad9 | ||
![]() |
b38ab2a7d6 | ||
![]() |
a97191052b | ||
![]() |
2963d4ca9e | ||
![]() |
6aab856de7 | ||
![]() |
94d1c66f8a | ||
![]() |
7ff4d7608e | ||
![]() |
46ef915c83 | ||
![]() |
63b0a0d96b | ||
![]() |
ea4cabdfc5 | ||
![]() |
0185ddf577 | ||
![]() |
ddae568741 | ||
![]() |
fcb7ebb090 | ||
![]() |
8d446fcc16 | ||
![]() |
881d3b5221 | ||
![]() |
fe9ec3bc6d | ||
![]() |
480198dcd0 | ||
![]() |
4ab7bc0d97 | ||
![]() |
7173693d1b | ||
![]() |
6b81716440 | ||
![]() |
88e8e15607 | ||
![]() |
69181a6b72 | ||
![]() |
b11b81122a | ||
![]() |
648e3ee36b | ||
![]() |
724b94f320 | ||
![]() |
a6e65f9a7e | ||
![]() |
af5c4d09c4 | ||
![]() |
872394cb58 | ||
![]() |
fcbbe9a22e | ||
![]() |
b168163ef0 | ||
![]() |
3e38b8fed1 | ||
![]() |
f90c548f27 | ||
![]() |
c981c40218 | ||
![]() |
dcbf37c5e8 | ||
![]() |
300b233a27 | ||
![]() |
e32cd03d0b | ||
![]() |
a07b9315a5 | ||
![]() |
e9694c6195 | ||
![]() |
4a2a37c87a | ||
![]() |
7dca5b831a | ||
![]() |
be5ff68140 | ||
![]() |
59f40d5fe5 | ||
![]() |
1fbd053a42 | ||
![]() |
966c6314f8 | ||
![]() |
c92204c724 | ||
![]() |
bb9947d4d2 | ||
![]() |
7c8cdb4ad6 | ||
![]() |
bd7f9c9e46 | ||
![]() |
9a33a4dfe2 | ||
![]() |
47e918bc92 | ||
![]() |
c194168d9b | ||
![]() |
cacc60b1ac | ||
![]() |
52063b3652 | ||
![]() |
85a4eaff59 | ||
![]() |
45fa1fce70 | ||
![]() |
2112c916f5 | ||
![]() |
d6e159bff9 | ||
![]() |
2f710a564f | ||
![]() |
27cfc4945c | ||
![]() |
7cdada92c8 | ||
![]() |
8f1e57d4f9 | ||
![]() |
8178666b49 | ||
![]() |
313532dcaa | ||
![]() |
2f8f3dc266 | ||
![]() |
df6ada5ce3 | ||
![]() |
a89b9e6af1 | ||
![]() |
23ed275614 | ||
![]() |
cfd1e0cf22 | ||
![]() |
eb400f19b1 | ||
![]() |
19f15f16f6 | ||
![]() |
e158cfddfa | ||
![]() |
d0cf93a08d | ||
![]() |
08ad0e74dd | ||
![]() |
722374a024 | ||
![]() |
c6f0762510 | ||
![]() |
941a363c5a | ||
![]() |
2afcdc64a0 | ||
![]() |
3c66c4bbc5 | ||
![]() |
9f5cd5e1cc | ||
![]() |
a35f2bb73b | ||
![]() |
6cf00130f4 | ||
![]() |
6c27ba6b88 | ||
![]() |
dd3b9980e7 | ||
![]() |
02e189a029 | ||
![]() |
72b8d12ee4 | ||
![]() |
eed03080c1 | ||
![]() |
090cb4b0f9 | ||
![]() |
6f2c76b898 | ||
![]() |
f61827cbec | ||
![]() |
3f2264f2c7 | ||
![]() |
c1cadf4bdc | ||
![]() |
0e56991369 | ||
![]() |
4dc1c59040 | ||
![]() |
33b7b8b297 | ||
![]() |
e6af5ed460 | ||
![]() |
b678afa4b6 | ||
![]() |
4bac2df4e7 | ||
![]() |
50416eee09 | ||
![]() |
73cf501d33 | ||
![]() |
d2b7907bed | ||
![]() |
99d5dd5ea8 | ||
![]() |
5fdb841fa8 | ||
![]() |
7c88484d64 | ||
![]() |
b22b6a4204 | ||
![]() |
2a3d34c812 | ||
![]() |
c50ee722a1 | ||
![]() |
ffc1e38e48 | ||
![]() |
6219d5fcbf | ||
![]() |
2e4440b702 | ||
![]() |
0d9ec0931b | ||
![]() |
60e8415369 | ||
![]() |
652a26d5d9 | ||
![]() |
f57839379a | ||
![]() |
36bd00a046 | ||
![]() |
fb5ee86615 | ||
![]() |
30bf5c8448 | ||
![]() |
2051836a73 | ||
![]() |
2cb0af1ff3 | ||
![]() |
a1b6568226 | ||
![]() |
1eddbfd72c | ||
![]() |
21ed095601 | ||
![]() |
000a2e4d59 | ||
![]() |
7abe635de9 | ||
![]() |
9a008c17ba | ||
![]() |
08dbf728a4 | ||
![]() |
4670f762d3 | ||
![]() |
efa49567fa | ||
![]() |
0ffc4527a7 | ||
![]() |
dd9d43be96 | ||
![]() |
865fca71a5 | ||
![]() |
6b4baa3bcd | ||
![]() |
a9ee2d7d18 | ||
![]() |
d654b9cb97 | ||
![]() |
4d2921e742 | ||
![]() |
ecc74d45d1 | ||
![]() |
5de597f079 | ||
![]() |
156b0e67ca | ||
![]() |
10069215f4 | ||
![]() |
92b305a389 | ||
![]() |
d20b30c771 | ||
![]() |
83209b21ff | ||
![]() |
81658d45f7 | ||
![]() |
c951b208a1 | ||
![]() |
050a073771 | ||
![]() |
21d374214f | ||
![]() |
19ea25a9d0 | ||
![]() |
dbf6e40dfe | ||
![]() |
d56f4fbc90 | ||
![]() |
73c3d741a7 | ||
![]() |
2b5fc75127 | ||
![]() |
991802ab82 | ||
![]() |
7f6b5305ba | ||
![]() |
825c6c4316 | ||
![]() |
f00408c793 | ||
![]() |
a6ff3672af | ||
![]() |
2290ddeb89 | ||
![]() |
74af79ad03 | ||
![]() |
b6c24a3a8a | ||
![]() |
a8c2ae223a | ||
![]() |
953d44302c | ||
![]() |
24e46a5971 | ||
![]() |
b1297c4192 | ||
![]() |
9ae328fd84 | ||
![]() |
625a1d6f44 | ||
![]() |
987e5f5413 | ||
![]() |
715284b70d | ||
![]() |
62fc7868ac | ||
![]() |
1a70796339 | ||
![]() |
af6965eefa | ||
![]() |
8f7d2e38f7 | ||
![]() |
be433fa667 | ||
![]() |
0ccd6e7381 | ||
![]() |
907bbbda41 | ||
![]() |
4393bc077d | ||
![]() |
365b373480 | ||
![]() |
47e6dd286d | ||
![]() |
0dbaf52566 | ||
![]() |
66f49dfab5 | ||
![]() |
f8967e9274 | ||
![]() |
a4f008fde5 | ||
![]() |
e9980c778b | ||
![]() |
06b6fb0c33 | ||
![]() |
38cb3d4105 | ||
![]() |
db99caf258 | ||
![]() |
39dbffadfe | ||
![]() |
b7505c3c9c | ||
![]() |
3185e5a7ca | ||
![]() |
e0cbe28711 | ||
![]() |
66cee19cea | ||
![]() |
2ec29ade79 | ||
![]() |
c865d4e187 | ||
![]() |
a42a0a53ce | ||
![]() |
6d79de7d71 | ||
![]() |
7e9abe6e90 | ||
![]() |
4d5510be4f | ||
![]() |
b04e1394c0 | ||
![]() |
2aa923191e | ||
![]() |
4bf1c74164 | ||
![]() |
472c7878b2 | ||
![]() |
38ad871e33 | ||
![]() |
c5d34670c4 | ||
![]() |
154121f3dd | ||
![]() |
3d91a561fe | ||
![]() |
2c6adbc69b | ||
![]() |
5280982363 | ||
![]() |
18c45ae289 | ||
![]() |
41fbd2a7be | ||
![]() |
5e45884af4 | ||
![]() |
d78ee171bc | ||
![]() |
356ee1febd | ||
![]() |
cc044ccc4c | ||
![]() |
9c638cc463 | ||
![]() |
df786eb2b6 | ||
![]() |
8e7186eebb | ||
![]() |
74b7b84561 | ||
![]() |
308c9999fa | ||
![]() |
930bb8687f | ||
![]() |
f2c4288d2d | ||
![]() |
b44141ae39 | ||
![]() |
86e0020964 | ||
![]() |
94d3daeadf | ||
![]() |
79334b7702 | ||
![]() |
df66458db6 | ||
![]() |
97705704e2 | ||
![]() |
1206179580 | ||
![]() |
a0b8aa4da6 | ||
![]() |
65207f96c8 | ||
![]() |
062e498bdd | ||
![]() |
1057cb3e3c | ||
![]() |
2dd23b2518 | ||
![]() |
8cab12998c | ||
![]() |
48b1c26dc8 | ||
![]() |
f1e0bc3e4a | ||
![]() |
38527cd58f | ||
![]() |
e94d65b4b2 | ||
![]() |
27ece3c7df | ||
![]() |
06687abffc | ||
![]() |
deedb462a0 | ||
![]() |
c48962bdf7 | ||
![]() |
1ef3f6e13b | ||
![]() |
83a34a9004 | ||
![]() |
e30bda6c8d | ||
![]() |
00e9d76a5a | ||
![]() |
6cda6c2fae | ||
![]() |
6dfda6dc39 | ||
![]() |
f41994cb52 | ||
![]() |
a003336497 | ||
![]() |
401090d6fe | ||
![]() |
90dcc1cd30 | ||
![]() |
2ac464b186 | ||
![]() |
8b7fae278b | ||
![]() |
d73c2daf6d | ||
![]() |
ca25935de3 | ||
![]() |
d7750b7220 | ||
![]() |
98861f0b5a | ||
![]() |
e35925d520 | ||
![]() |
685a2d2101 | ||
![]() |
f7e471616d | ||
![]() |
c013a349af | ||
![]() |
61ea59a27b | ||
![]() |
e55f338367 | ||
![]() |
1425cf4105 | ||
![]() |
b493a985b0 | ||
![]() |
1fe9ede940 | ||
![]() |
1fd49e4987 | ||
![]() |
d49b02b274 | ||
![]() |
d47e70cfaa | ||
![]() |
40cb031af5 | ||
![]() |
1dcf325547 | ||
![]() |
4e99997013 | ||
![]() |
334554697d | ||
![]() |
e77cbd0c15 | ||
![]() |
46ba008b9d | ||
![]() |
58aded31c2 | ||
![]() |
6f6b0ade06 | ||
![]() |
d9b67a207b | ||
![]() |
c7083659aa | ||
![]() |
a6d1803105 | ||
![]() |
66eef75673 | ||
![]() |
367f5f7b44 | ||
![]() |
0edcb03c45 | ||
![]() |
63eef153de | ||
![]() |
68442f38ac | ||
![]() |
8d5b9e5329 | ||
![]() |
6c0966b795 | ||
![]() |
7c2e93d266 | ||
![]() |
1ff7b9055f | ||
![]() |
49f241b77c | ||
![]() |
cfb20b0f86 | ||
![]() |
6d6f14fcb3 | ||
![]() |
977c981265 | ||
![]() |
ef48abf19d | ||
![]() |
65c18f9c09 | ||
![]() |
ecb31eed40 | ||
![]() |
a80cadf587 | ||
![]() |
fce1bf2365 | ||
![]() |
cbc6d40b2c | ||
![]() |
9fbd079560 | ||
![]() |
42eb928054 | ||
![]() |
577483fde1 | ||
![]() |
aa6c7c15cc | ||
![]() |
6c807d35b2 | ||
![]() |
8ca8cdae97 | ||
![]() |
75e37be6f3 | ||
![]() |
4985314ca6 | ||
![]() |
ac5ceb18c8 | ||
![]() |
72b39594d3 | ||
![]() |
16ae4aedf1 | ||
![]() |
3ba00858e6 | ||
![]() |
489100c755 | ||
![]() |
da766f2a4e | ||
![]() |
c81d7ff76c | ||
![]() |
a6e50d3648 | ||
![]() |
a177846044 | ||
![]() |
19a4e11645 | ||
![]() |
67cc36268e | ||
![]() |
28770b9a32 | ||
![]() |
9f92e1bf15 | ||
![]() |
23fe5d5a19 | ||
![]() |
9088b584f6 | ||
![]() |
beaf636415 | ||
![]() |
09bb2fe8dc | ||
![]() |
1d6747d90e | ||
![]() |
efadd94de3 | ||
![]() |
8c0b4e444a | ||
![]() |
32c7106e40 | ||
![]() |
d2f2a9e4c8 | ||
![]() |
985454afd4 | ||
![]() |
9e1322de25 | ||
![]() |
4e4ec73d94 | ||
![]() |
bb39a524d0 | ||
![]() |
196d9af099 | ||
![]() |
1eeb2a34a1 | ||
![]() |
cf43c56218 | ||
![]() |
e6c1aec443 | ||
![]() |
43fd1c4c1b | ||
![]() |
022caca979 | ||
![]() |
0352ea2cca | ||
![]() |
e483d6befe | ||
![]() |
678c07fff5 | ||
![]() |
91c92051f1 | ||
![]() |
4b8a0388e7 | ||
![]() |
66788dc58c | ||
![]() |
dd8c28b1cb | ||
![]() |
32c5153e8e | ||
![]() |
36de62873a | ||
![]() |
51e37880c6 | ||
![]() |
4b83c1e76c | ||
![]() |
b0b04690d5 | ||
![]() |
6d1e8d86cb | ||
![]() |
eda8c70a80 | ||
![]() |
587b6cfd41 | ||
![]() |
e774408782 | ||
![]() |
187f583c95 | ||
![]() |
f5d3a71478 | ||
![]() |
d868ff3080 | ||
![]() |
f80198a669 | ||
![]() |
6076b52c48 | ||
![]() |
79a1c39b30 | ||
![]() |
5c92d39498 | ||
![]() |
6e7a995716 | ||
![]() |
a55d570213 | ||
![]() |
5d07d0b964 | ||
![]() |
ec115cd7e3 | ||
![]() |
9b3896fd3d | ||
![]() |
a3f5918d25 | ||
![]() |
b28326198c | ||
![]() |
46275b90c2 | ||
![]() |
15e13a8d8b | ||
![]() |
b750c89c87 | ||
![]() |
8d7c7c3dfb | ||
![]() |
8e1a91509c | ||
![]() |
927cd571f8 | ||
![]() |
5fbd3e5c65 | ||
![]() |
877aeb66cb | ||
![]() |
8a88d8465a | ||
![]() |
dda8cc85c9 | ||
![]() |
6a59939d9a | ||
![]() |
4745e86c1b | ||
![]() |
9aa466c773 | ||
![]() |
0243610c09 | ||
![]() |
0a2a590ab7 | ||
![]() |
89aee6ffa7 | ||
![]() |
4eaf701cb7 | ||
![]() |
4fff2aa7d8 | ||
![]() |
35b3c8ba5c | ||
![]() |
1d7cff7703 | ||
![]() |
8d81bd0e33 | ||
![]() |
7826d7527f | ||
![]() |
d4e552d08b | ||
![]() |
5a16418543 | ||
![]() |
7297aba15a | ||
![]() |
bc5d5f9502 | ||
![]() |
1761986c1b | ||
![]() |
1e034e3e0e | ||
![]() |
bbf9756bfa | ||
![]() |
96e559fb0e | ||
![]() |
4c45775131 | ||
![]() |
c072b4254d | ||
![]() |
2dbb812126 | ||
![]() |
be50f17f55 | ||
![]() |
6f77f190f2 | ||
![]() |
6bdc57cbe4 | ||
![]() |
de00f1d5a9 | ||
![]() |
e9b9bf987b | ||
![]() |
f4b6385f9f | ||
![]() |
75d905a56d | ||
![]() |
b1363ee479 | ||
![]() |
51afe43a30 | ||
![]() |
189c03c047 | ||
![]() |
ae9d270a32 | ||
![]() |
e47e869f6b | ||
![]() |
c39038a439 | ||
![]() |
69174e2c13 | ||
![]() |
474268a0af | ||
![]() |
eadb0307fa | ||
![]() |
5a5d0d5d72 | ||
![]() |
a1273bc467 | ||
![]() |
0c28a916be | ||
![]() |
0ba573b789 | ||
![]() |
ec42ee152c | ||
![]() |
abcb487361 | ||
![]() |
d12d9e82f1 | ||
![]() |
275208e81b | ||
![]() |
41226c12b8 | ||
![]() |
f86c66c99d | ||
![]() |
93876b5fd3 | ||
![]() |
b5b14ce343 | ||
![]() |
350d0d600c | ||
![]() |
f924ffcbf3 | ||
![]() |
0f5963f231 | ||
![]() |
1961ff2c40 | ||
![]() |
40003691d6 | ||
![]() |
8290358241 | ||
![]() |
ee34f775c3 | ||
![]() |
feb47cd88c | ||
![]() |
c6efb51f61 | ||
![]() |
a5acf33ccd | ||
![]() |
ab9ee449e4 | ||
![]() |
9571b6f9be | ||
![]() |
207d7fd3f6 | ||
![]() |
bcdcfa1104 | ||
![]() |
e0a4230dac | ||
![]() |
17ba5cba3e | ||
![]() |
f2e109ad7d | ||
![]() |
c83e141a1c | ||
![]() |
6089cc36de | ||
![]() |
9638dc0a66 | ||
![]() |
b191a14a23 | ||
![]() |
cf1bc82537 | ||
![]() |
6141bb5bb3 | ||
![]() |
4d2b62da0d | ||
![]() |
39383229d1 | ||
![]() |
08bfbb154a | ||
![]() |
d390ca2fdf | ||
![]() |
7ad77a14ae | ||
![]() |
f33343b4e6 | ||
![]() |
af65d07456 | ||
![]() |
16d728f379 | ||
![]() |
c97ab690b6 | ||
![]() |
4caed73fe0 | ||
![]() |
4856da1584 | ||
![]() |
0a07018fec | ||
![]() |
64c82e1f2c | ||
![]() |
e8e8afa6c2 | ||
![]() |
af2207433d | ||
![]() |
75ba62d588 | ||
![]() |
606d97ae4d | ||
![]() |
d778b0b0a7 | ||
![]() |
5ee6daf126 | ||
![]() |
43b9a09c9b | ||
![]() |
8475a2bb94 | ||
![]() |
d8692de2f4 | ||
![]() |
33a9abc946 | ||
![]() |
ee943afbc9 | ||
![]() |
1f7c3e9f14 | ||
![]() |
46770db18b | ||
![]() |
92f980601c | ||
![]() |
d0b8c16651 | ||
![]() |
a470ee6f93 | ||
![]() |
ff1c56683d | ||
![]() |
4ee4cbada6 | ||
![]() |
dbc2236dd2 | ||
![]() |
a8c4a33e91 | ||
![]() |
279f955a84 | ||
![]() |
fbd1dbb20c | ||
![]() |
6c09fc2e64 | ||
![]() |
f3304b482c | ||
![]() |
0a85ef61c3 | ||
![]() |
dc26ad7125 | ||
![]() |
24b1c607f3 | ||
![]() |
732a161b67 | ||
![]() |
9c7cf340a1 | ||
![]() |
399b9e5eba | ||
![]() |
5805573625 | ||
![]() |
a6b1149b9f | ||
![]() |
51e985ae7f | ||
![]() |
9929b25339 | ||
![]() |
2359cfc480 | ||
![]() |
81493475f9 | ||
![]() |
0493829231 | ||
![]() |
e2d1952ad9 | ||
![]() |
7450965458 | ||
![]() |
f45384685b | ||
![]() |
8abcccc262 | ||
![]() |
a9c89cbbbb | ||
![]() |
d2eaa6e6c1 | ||
![]() |
53257b6ea1 | ||
![]() |
c874391be4 | ||
![]() |
7e8e013832 | ||
![]() |
037f46f7f0 | ||
![]() |
d3e1c496ca | ||
![]() |
d7d0a44693 | ||
![]() |
9d6f6764cb | ||
![]() |
cb3ab63815 | ||
![]() |
caae932117 | ||
![]() |
e9cf27eb5a | ||
![]() |
6ee6685f4c | ||
![]() |
d15017b777 | ||
![]() |
a9387e63e1 | ||
![]() |
23c1f0111b | ||
![]() |
866386e21f | ||
![]() |
bf10496fa9 | ||
![]() |
607e6547a7 | ||
![]() |
6b21091fe2 | ||
![]() |
e58f98e844 | ||
![]() |
b8cb9cd84d | ||
![]() |
c1038ac6f9 | ||
![]() |
c556dd0aac | ||
![]() |
d2fbcd07b7 | ||
![]() |
bf6359abaa | ||
![]() |
d1621845b8 | ||
![]() |
f33f1d25d0 | ||
![]() |
40f25f4d56 | ||
![]() |
e13775ec2c | ||
![]() |
ee4dad7a13 | ||
![]() |
5e2ef1b7f4 | ||
![]() |
f8c38eab74 | ||
![]() |
305e8b3d14 | ||
![]() |
2a654e5d7f | ||
![]() |
57afae3425 | ||
![]() |
feb44f875e | ||
![]() |
7eebe62bb6 | ||
![]() |
9ea9f01933 | ||
![]() |
665c6bdc4b | ||
![]() |
c79bc83275 | ||
![]() |
c30fbdf145 | ||
![]() |
f12951bd1d | ||
![]() |
52f2e8c4a0 | ||
![]() |
1b2af1ed6d | ||
![]() |
0f9b2a7df8 | ||
![]() |
f2846694e1 | ||
![]() |
e668dbf6f7 | ||
![]() |
d77a368176 | ||
![]() |
ad0da08610 | ||
![]() |
0c52385ad4 | ||
![]() |
5b8b48ccc1 | ||
![]() |
659b9c6fee | ||
![]() |
ec31cab5a7 | ||
![]() |
dd93556ad8 | ||
![]() |
533aeadd38 | ||
![]() |
18d0cedbe2 | ||
![]() |
5a94ef9106 | ||
![]() |
8e8f01f8b5 | ||
![]() |
7087badf87 | ||
![]() |
47d2d4e3a5 | ||
![]() |
6c47d8f556 | ||
![]() |
8c9d0314fb | ||
![]() |
69144942e3 | ||
![]() |
5627053b74 | ||
![]() |
0f666de5e6 | ||
![]() |
eddc862fa3 | ||
![]() |
4327682120 | ||
![]() |
af5bdee78f | ||
![]() |
0e36e86dbf | ||
![]() |
f95478f1f1 | ||
![]() |
9fe8741a02 | ||
![]() |
a5768e02ea | ||
![]() |
f5aaff2b1e | ||
![]() |
655f778171 | ||
![]() |
2e77a426b2 | ||
![]() |
2bcf2e76f1 | ||
![]() |
57bd450798 | ||
![]() |
582cad1b8d | ||
![]() |
6ca2a3d841 | ||
![]() |
91773c3311 | ||
![]() |
dc61033b2c | ||
![]() |
f8d62a4b6c | ||
![]() |
1d2145b1b7 | ||
![]() |
1f7f84b74a | ||
![]() |
cd7a335d0f | ||
![]() |
17569005a4 | ||
![]() |
f36b21bae5 | ||
![]() |
fe1ca52f6d | ||
![]() |
1be647a279 | ||
![]() |
2ea1a47bec | ||
![]() |
2d799dae0d | ||
![]() |
c6408babac | ||
![]() |
a8c1ed8795 | ||
![]() |
e3cb5f8ddd | ||
![]() |
e160e211dd | ||
![]() |
22d05ca399 | ||
![]() |
bd2651057d | ||
![]() |
1610092ec4 | ||
![]() |
b9e6937996 | ||
![]() |
a207f03952 | ||
![]() |
851153dd7c | ||
![]() |
583ffc8177 | ||
![]() |
7518092ad2 | ||
![]() |
8c2ad3883a | ||
![]() |
d364554425 | ||
![]() |
726ffdcd98 | ||
![]() |
f9d22cf8ee | ||
![]() |
ee50da566f | ||
![]() |
9f7d410959 | ||
![]() |
bc94ea4334 | ||
![]() |
c0c9204848 | ||
![]() |
c0d1bf63bc | ||
![]() |
bbda0cdffe | ||
![]() |
7b5ff99cd1 | ||
![]() |
21ddb26db8 | ||
![]() |
7bf2e3875f | ||
![]() |
b136aba1e2 | ||
![]() |
0d84f80b3c | ||
![]() |
af45aeb771 | ||
![]() |
1c5a435e1f | ||
![]() |
0ea1257dcd | ||
![]() |
4c92677b5a | ||
![]() |
979260bd62 | ||
![]() |
f7de649a36 | ||
![]() |
0cf0d2b821 | ||
![]() |
3733c9a091 | ||
![]() |
e9f32e4f68 | ||
![]() |
68c2817d40 | ||
![]() |
83d837d868 | ||
![]() |
093eb15ee1 | ||
![]() |
c6412c1b1b | ||
![]() |
1151393d74 | ||
![]() |
468f3efb13 | ||
![]() |
d6b19b9d4c | ||
![]() |
709f25f600 | ||
![]() |
4b16e4b026 | ||
![]() |
cdfbc02922 | ||
![]() |
d0c9384233 | ||
![]() |
2488668b06 | ||
![]() |
52a98cbd51 | ||
![]() |
1840c4c486 | ||
![]() |
34080f3958 | ||
![]() |
e9b76b6aa5 | ||
![]() |
b7799b53d9 | ||
![]() |
1e206515c7 | ||
![]() |
6bb313184d | ||
![]() |
2763992434 | ||
![]() |
18fe0e6442 | ||
![]() |
a70c73bffd | ||
![]() |
b4ae3493a6 | ||
![]() |
1a16004b20 | ||
![]() |
56707b8119 | ||
![]() |
c3f9533ddc | ||
![]() |
3b3abd63cc | ||
![]() |
411d3ed4e9 | ||
![]() |
f29cc26103 | ||
![]() |
1cd595a598 | ||
![]() |
22e023b58d | ||
![]() |
7be958e35d | ||
![]() |
69b66ef637 | ||
![]() |
daf8653c38 | ||
![]() |
e2545e57cf | ||
![]() |
7cb0909c70 | ||
![]() |
cc5ff36165 | ||
![]() |
18b1ef6c29 | ||
![]() |
7fe012347a | ||
![]() |
5c165c9bb0 | ||
![]() |
6c3519923d | ||
![]() |
9ea859810d | ||
![]() |
8dae7b5451 | ||
![]() |
f827755aaf | ||
![]() |
637a8af234 | ||
![]() |
b0fc580860 | ||
![]() |
9279f30e89 | ||
![]() |
b505819ca2 | ||
![]() |
39d1d23909 | ||
![]() |
69529ac59c | ||
![]() |
a18a440236 | ||
![]() |
aa7846c1c0 | ||
![]() |
24ba4ab95b | ||
![]() |
762b70ba9d | ||
![]() |
41b77e4f25 | ||
![]() |
2087e47300 | ||
![]() |
46ce765860 | ||
![]() |
5117dc1a31 | ||
![]() |
620fd7d124 | ||
![]() |
3e991dc003 | ||
![]() |
15cab86152 | ||
![]() |
aa785b5845 | ||
![]() |
97731a519a | ||
![]() |
b696dae808 | ||
![]() |
732a8260c2 | ||
![]() |
4ff60ef9a9 | ||
![]() |
23b1b69110 | ||
![]() |
3a4fe53f27 | ||
![]() |
e48afff5e8 | ||
![]() |
3f4f4598e8 | ||
![]() |
3921e9cb1b | ||
![]() |
0b987dd0b0 | ||
![]() |
1620e15f99 | ||
![]() |
b089511e91 | ||
![]() |
958788c1aa | ||
![]() |
b5a8a27296 | ||
![]() |
98123775ad | ||
![]() |
c7133974be | ||
![]() |
04324a7ebe | ||
![]() |
f54daa3469 | ||
![]() |
07c22ccd39 | ||
![]() |
e893c13cf1 | ||
![]() |
dba5020e4f | ||
![]() |
87e036a190 | ||
![]() |
3dd94672b0 | ||
![]() |
004b193f69 | ||
![]() |
4417997749 | ||
![]() |
2eef542054 | ||
![]() |
a07d4080b6 | ||
![]() |
b9d0a3b3d4 | ||
![]() |
76405bd984 | ||
![]() |
4e2b88b3d0 | ||
![]() |
7048aa1014 | ||
![]() |
1c2fcd14b5 | ||
![]() |
84e1bd7bc3 | ||
![]() |
362eea741f | ||
![]() |
4de93cfd4b | ||
![]() |
03cee0b8d4 | ||
![]() |
54ecc001f4 | ||
![]() |
5c325d9466 | ||
![]() |
0e851cdcf8 | ||
![]() |
af054e4e31 | ||
![]() |
33fb4653f0 | ||
![]() |
d9f0aed571 | ||
![]() |
98813c24fb | ||
![]() |
3cc81bb3fd | ||
![]() |
366dd52419 | ||
![]() |
fe6b658c02 | ||
![]() |
3cf66d1c57 | ||
![]() |
382568bd3c | ||
![]() |
d130aa02a1 | ||
![]() |
1a1646795f | ||
![]() |
d52ea1b068 | ||
![]() |
e14f7b6908 | ||
![]() |
4709a32641 | ||
![]() |
71b7f52663 | ||
![]() |
981ccabbef | ||
![]() |
9e07eb592c | ||
![]() |
9555380818 | ||
![]() |
f80d5d858e | ||
![]() |
a1ce6f5f12 | ||
![]() |
1aade8f8a8 | ||
![]() |
b9213b7043 | ||
![]() |
4af72324f4 | ||
![]() |
b6ea5b8984 | ||
![]() |
c279e08c88 | ||
![]() |
2717feac21 | ||
![]() |
8adf27859d | ||
![]() |
307cf87215 | ||
![]() |
ca31412c05 | ||
![]() |
f59fbd5dca | ||
![]() |
2285f5e888 | ||
![]() |
da36e5bcd5 | ||
![]() |
4ed9f57fdc | ||
![]() |
ea7be6162f | ||
![]() |
3726eb6032 | ||
![]() |
6e918ffd68 | ||
![]() |
4772868d6a | ||
![]() |
78df677a42 | ||
![]() |
85a4b249b3 | ||
![]() |
d06e9a0b51 | ||
![]() |
5eb774a2ad | ||
![]() |
cbbbbab483 | ||
![]() |
e5641d5bdb | ||
![]() |
a721206c6f | ||
![]() |
c7a27481f9 | ||
![]() |
594c304634 | ||
![]() |
d0ec387c28 | ||
![]() |
7dbfba76bf | ||
![]() |
2a4aa95a6f | ||
![]() |
5520f0fbf7 | ||
![]() |
a1a87c9956 | ||
![]() |
2c53356bfd | ||
![]() |
85d9756f62 | ||
![]() |
79586ece4c | ||
![]() |
6851d11a8e | ||
![]() |
996a857096 | ||
![]() |
d7158131e4 | ||
![]() |
3d3082bc82 | ||
![]() |
744ebca206 | ||
![]() |
92077ebe53 | ||
![]() |
78ca682bc5 | ||
![]() |
af01a36296 | ||
![]() |
97ed1b16d0 | ||
![]() |
508a001753 | ||
![]() |
c1909d520b | ||
![]() |
9b1e173373 | ||
![]() |
4ba365565f | ||
![]() |
ae34659b26 | ||
![]() |
79a85f5937 | ||
![]() |
b249832571 | ||
![]() |
577b5912af | ||
![]() |
9e8c68af12 | ||
![]() |
03418ddcbf | ||
![]() |
220a1c84ce | ||
![]() |
9a4458ffac | ||
![]() |
7a9e6d2ad2 | ||
![]() |
9656cf2f86 | ||
![]() |
584bad5314 | ||
![]() |
459088024f | ||
![]() |
d740bbe058 | ||
![]() |
6ecc04a4df | ||
![]() |
15a7e9af57 | ||
![]() |
0329f00129 | ||
![]() |
cd8a2edefb | ||
![]() |
4318ab5cd2 | ||
![]() |
3517e6d752 | ||
![]() |
67845f9c21 | ||
![]() |
f562710438 | ||
![]() |
e836909c50 | ||
![]() |
7769ba5f54 | ||
![]() |
7fe9db90a1 | ||
![]() |
8f7d6dfb77 | ||
![]() |
2839978cc1 | ||
![]() |
e73f87b758 | ||
![]() |
bd0409fd15 | ||
![]() |
babdfe80cb | ||
![]() |
636223b289 | ||
![]() |
aa0a2f77cf | ||
![]() |
e38f35eab2 | ||
![]() |
cb39514705 | ||
![]() |
78a444d601 | ||
![]() |
37b81ad1f6 | ||
![]() |
7871c2f595 | ||
![]() |
57d83635c6 | ||
![]() |
76fbf4634a | ||
![]() |
7ce4bd3330 | ||
![]() |
ad0e6511e1 | ||
![]() |
a4a734458b | ||
![]() |
f989756b93 | ||
![]() |
5763a3d908 | ||
![]() |
1b745ae1a0 | ||
![]() |
b6d50bea2c | ||
![]() |
831a398bf1 | ||
![]() |
a848783b97 | ||
![]() |
4d876f0145 | ||
![]() |
bdfedea4e0 | ||
![]() |
ea0e3a09ef | ||
![]() |
dadae20960 | ||
![]() |
4ed34cd648 | ||
![]() |
0d38c94c9c | ||
![]() |
2a2a452bd4 | ||
![]() |
13c2695e98 | ||
![]() |
3ff60ed49f | ||
![]() |
bbb1786ec3 | ||
![]() |
4bfd2dac54 | ||
![]() |
857c12372a | ||
![]() |
33f5154269 | ||
![]() |
ed37ddd570 | ||
![]() |
cd5384f13e | ||
![]() |
11b2ddbad8 | ||
![]() |
cf9957ce4d | ||
![]() |
44643ad7b3 | ||
![]() |
1e53a5555e | ||
![]() |
616adc22e1 | ||
![]() |
916e373edb | ||
![]() |
021ae15395 | ||
![]() |
52cf72002a | ||
![]() |
68874bf571 | ||
![]() |
a468fd946d | ||
![]() |
e327565434 | ||
![]() |
c3b4678f6e | ||
![]() |
978216eade | ||
![]() |
44cfe94e4d | ||
![]() |
f9e82c9e8a | ||
![]() |
25b4b107d3 | ||
![]() |
db651fa9ec | ||
![]() |
23ad611566 | ||
![]() |
095d821240 | ||
![]() |
e23f23a8b7 | ||
![]() |
48f829b76e | ||
![]() |
0b82fe197c | ||
![]() |
af99c1b843 | ||
![]() |
c6646efe68 | ||
![]() |
66a7ef5615 | ||
![]() |
9474750bdf | ||
![]() |
e86db0bd61 | ||
![]() |
a29fc11798 | ||
![]() |
a66a3b7438 | ||
![]() |
44029875a6 | ||
![]() |
ccf21b0992 | ||
![]() |
4e14dab60a | ||
![]() |
6e299018a4 | ||
![]() |
555a54ec53 | ||
![]() |
1565bf5442 | ||
![]() |
14b830027b | ||
![]() |
38325e708e | ||
![]() |
646260ad6d | ||
![]() |
d1d26f4481 | ||
![]() |
357d913f18 | ||
![]() |
71b0c8b42b | ||
![]() |
cdc66c1ac8 | ||
![]() |
e9af773901 | ||
![]() |
eadf6e8b96 | ||
![]() |
87bec70d9f | ||
![]() |
3668b28f62 | ||
![]() |
933e4bd163 | ||
![]() |
e3ab9e9a1e | ||
![]() |
58ad2c1416 | ||
![]() |
c5291ad33b | ||
![]() |
77d8445bfd | ||
![]() |
f8395a7dc6 | ||
![]() |
727c70005e | ||
![]() |
38ab6858f0 | ||
![]() |
a54114f149 | ||
![]() |
7a4a5c8992 | ||
![]() |
928a16d8cc | ||
![]() |
3f7f6e619a | ||
![]() |
c2f96975ce | ||
![]() |
8bd4760b00 | ||
![]() |
4f4aeb893d | ||
![]() |
fed4f1b50f | ||
![]() |
e11087cd1a | ||
![]() |
e6eb51551c | ||
![]() |
c5c608f0d3 | ||
![]() |
4737c5117a | ||
![]() |
9806b38d8e | ||
![]() |
6bfe34e5a8 | ||
![]() |
34dd9eb7d6 | ||
![]() |
2d8beabbd4 | ||
![]() |
4d9b7e7114 | ||
![]() |
40aab13601 | ||
![]() |
4c0f72f68f | ||
![]() |
dd565a11ea | ||
![]() |
1735a713cb | ||
![]() |
52ba6d11bc | ||
![]() |
7357a35f8d | ||
![]() |
aeb7fd7cb3 | ||
![]() |
1b4a6850b8 | ||
![]() |
07b45f39df | ||
![]() |
1d0b873950 | ||
![]() |
d449f49d73 | ||
![]() |
e8787b5cfd | ||
![]() |
d17ed2b979 | ||
![]() |
b496923cbb | ||
![]() |
759d196aad | ||
![]() |
a7ab8216ce | ||
![]() |
b9e89a1a2d | ||
![]() |
c7c9fb9576 | ||
![]() |
8b095de04d | ||
![]() |
468325b51a | ||
![]() |
e5058bfb8b | ||
![]() |
d4b9ef736d | ||
![]() |
00d3cb0908 | ||
![]() |
d35072d4e6 | ||
![]() |
1a964e78dd | ||
![]() |
4264ae49c0 | ||
![]() |
f08712cd0a | ||
![]() |
3906fe75dc | ||
![]() |
2497e548c9 | ||
![]() |
e4635684e9 | ||
![]() |
9b61bdfc9a | ||
![]() |
6066b5cf86 | ||
![]() |
5cdf95a4d0 | ||
![]() |
910a36fdc1 | ||
![]() |
8331206acb | ||
![]() |
8423dc8d63 | ||
![]() |
6077c989a7 | ||
![]() |
c97d1044fa | ||
![]() |
f42c089b26 | ||
![]() |
1f8c063dc6 | ||
![]() |
4874520d65 | ||
![]() |
5e53639969 | ||
![]() |
83ab0ca6cd | ||
![]() |
70fd03d5fc | ||
![]() |
2e52875b50 | ||
![]() |
fd9b990ad7 | ||
![]() |
69978a9442 | ||
![]() |
d155da52ce | ||
![]() |
9c5b131913 | ||
![]() |
9d740cec1a | ||
![]() |
c2978eb9c3 | ||
![]() |
38abad1e44 | ||
![]() |
b4863eb51b | ||
![]() |
3817167ba1 | ||
![]() |
d1a35dd2ba | ||
![]() |
26116ac414 | ||
![]() |
0b26882fce | ||
![]() |
a2495fb5fb | ||
![]() |
0beb3bf16a | ||
![]() |
b68658e974 | ||
![]() |
3ae7344747 | ||
![]() |
4eb71830b3 | ||
![]() |
9183a0a6ea | ||
![]() |
bb64ba0ef6 | ||
![]() |
d89a568897 | ||
![]() |
9fd1f41e8b | ||
![]() |
c1ab348673 | ||
![]() |
00247c7901 | ||
![]() |
3c75f474c6 | ||
![]() |
db1f5b0397 | ||
![]() |
db277c3e55 | ||
![]() |
b9c93c66f6 | ||
![]() |
a250e2b56c | ||
![]() |
cd96454886 | ||
![]() |
741b679306 | ||
![]() |
90013e486d | ||
![]() |
4e2ecdb920 | ||
![]() |
6e5df1f06b | ||
![]() |
9469e79e3c | ||
![]() |
db78c20161 | ||
![]() |
1699da1754 | ||
![]() |
754e690274 | ||
![]() |
6f74ed6ceb | ||
![]() |
71205bc530 | ||
![]() |
10e236abdf | ||
![]() |
2248af00f3 | ||
![]() |
7e61716277 | ||
![]() |
50edb8d072 | ||
![]() |
515f81944c | ||
![]() |
46d4708386 | ||
![]() |
aabc36f86b | ||
![]() |
e0d5d90267 | ||
![]() |
482a5b991b | ||
![]() |
20124fe410 | ||
![]() |
f8dcec116a | ||
![]() |
343a339aae | ||
![]() |
42606efe56 | ||
![]() |
cae58c8790 | ||
![]() |
3a39dd4049 | ||
![]() |
89ff3c6572 | ||
![]() |
7bf9c74216 | ||
![]() |
e2f3753551 | ||
![]() |
cacf873645 | ||
![]() |
11e1e7ee36 | ||
![]() |
87801b6f23 | ||
![]() |
7ce4789e17 | ||
![]() |
9dc6d9afce | ||
![]() |
d6a5354bff | ||
![]() |
07af37475b | ||
![]() |
1b9c273b10 | ||
![]() |
262c52db56 | ||
![]() |
eb777296d4 | ||
![]() |
fc70a384d3 | ||
![]() |
34b2f525a3 | ||
![]() |
569e9ad937 | ||
![]() |
c495b3d183 | ||
![]() |
8b16bfbb54 | ||
![]() |
b2f1fd9966 | ||
![]() |
317153c53a | ||
![]() |
fa60daf9b5 | ||
![]() |
aadb2d825c | ||
![]() |
0e7fe537e3 | ||
![]() |
409de3ac44 | ||
![]() |
759055eaa5 | ||
![]() |
9016e6727d | ||
![]() |
a3381da7ed | ||
![]() |
351e094440 | ||
![]() |
2106751ea4 | ||
![]() |
7ae3cd1c43 | ||
![]() |
edfd4dcddf | ||
![]() |
fb89cf1367 | ||
![]() |
b7b345cf8a | ||
![]() |
0be487e47e | ||
![]() |
5471147422 | ||
![]() |
6305159c5e | ||
![]() |
2ed092c9db | ||
![]() |
5c6a7ffa6f | ||
![]() |
9ab7550970 | ||
![]() |
47e7a0a434 | ||
![]() |
4cc5e9f986 | ||
![]() |
6a2ae89846 | ||
![]() |
3c93539e02 | ||
![]() |
05e5ac2ad2 | ||
![]() |
10b1782732 | ||
![]() |
e029994ef8 | ||
![]() |
9679874874 | ||
![]() |
8186f253e8 | ||
![]() |
d4fe8632ec | ||
![]() |
d7776f6597 | ||
![]() |
3219d945f5 | ||
![]() |
8a73a16029 | ||
![]() |
ce90f9b60d | ||
![]() |
bdf54d562f | ||
![]() |
e744cc8ea6 | ||
![]() |
babcf36495 | ||
![]() |
e4094c0caa | ||
![]() |
2e51fe20a1 | ||
![]() |
c29636c452 | ||
![]() |
22017a5543 | ||
![]() |
50e2f33d1c | ||
![]() |
5e6eb8dd01 | ||
![]() |
18acb97dfe | ||
![]() |
bf2f823b8c | ||
![]() |
d0c4226997 | ||
![]() |
4ea8bd0229 | ||
![]() |
ee0d58a9b8 | ||
![]() |
bf04fa134b | ||
![]() |
297662cafb | ||
![]() |
f464a9b269 | ||
![]() |
d19fcd5e21 | ||
![]() |
c0981174a8 | ||
![]() |
0b5f973b31 | ||
![]() |
4159b3871c | ||
![]() |
580c993c0b | ||
![]() |
0cc29350a0 | ||
![]() |
490a784993 | ||
![]() |
9c774f96db | ||
![]() |
99afe7ac07 | ||
![]() |
b3f05fd925 | ||
![]() |
683cfee88b | ||
![]() |
3bcaf0ed5b | ||
![]() |
edb76503d3 | ||
![]() |
484038638f | ||
![]() |
8dfb30fefe | ||
![]() |
2a252d13b8 | ||
![]() |
afa364cfc3 | ||
![]() |
dfa36fb25d | ||
![]() |
c8492b0c58 | ||
![]() |
083ef803fe | ||
![]() |
351f0269ae | ||
![]() |
a29ae15ff7 | ||
![]() |
34dded3b25 | ||
![]() |
975b1a5e36 | ||
![]() |
e11508f84d | ||
![]() |
0772f6dcaf | ||
![]() |
d3fe3a711a | ||
![]() |
756d8356ca | ||
![]() |
42003b4006 | ||
![]() |
dc65a2b884 | ||
![]() |
071ae79fa8 | ||
![]() |
c11ccbae2d | ||
![]() |
6ef86d8d20 | ||
![]() |
985249c3d0 | ||
![]() |
622e09862a | ||
![]() |
7505599ea0 | ||
![]() |
575c417403 | ||
![]() |
9f7a3db8be | ||
![]() |
029422679c | ||
![]() |
05d6d2b51b | ||
![]() |
4cff0384f7 | ||
![]() |
68db366696 | ||
![]() |
358538717c | ||
![]() |
24603b3cef | ||
![]() |
4eb9240806 | ||
![]() |
0469f0b5ae | ||
![]() |
0b8577d02b | ||
![]() |
97135879a1 | ||
![]() |
fef41f68c0 | ||
![]() |
0ac19e3a4e | ||
![]() |
2793d209a4 | ||
![]() |
71e9c044e6 | ||
![]() |
42e5f5150a | ||
![]() |
90545057e9 | ||
![]() |
cffd024e9e | ||
![]() |
8c858592c4 | ||
![]() |
4f1a1879e5 | ||
![]() |
e88eed9a8d | ||
![]() |
9581ae8245 | ||
![]() |
4202b7a9dc | ||
![]() |
b4c398542a | ||
![]() |
081148b2d7 | ||
![]() |
a32c4561ed | ||
![]() |
cc79a96fa3 | ||
![]() |
ff340ce3d8 | ||
![]() |
134508193d | ||
![]() |
c2b74aa83e | ||
![]() |
3358eab991 | ||
![]() |
a609e0aad4 | ||
![]() |
f97866a961 | ||
![]() |
e1987c42c4 | ||
![]() |
18566715e1 | ||
![]() |
79f0f3230c | ||
![]() |
63a89d9f04 | ||
![]() |
f639f39e79 | ||
![]() |
b4099fc5f9 | ||
![]() |
ff2513e276 | ||
![]() |
f24d52436b | ||
![]() |
9de6e8846b | ||
![]() |
01a1213463 | ||
![]() |
f0fbd9214a | ||
![]() |
c4f37c550f | ||
![]() |
448384af06 | ||
![]() |
3f840f53a0 | ||
![]() |
d8718d8ac8 | ||
![]() |
2fb46a11dc | ||
![]() |
9a11412719 | ||
![]() |
98874be171 | ||
![]() |
704f91545e | ||
![]() |
efb3239cbd | ||
![]() |
7e7ddeb9e2 | ||
![]() |
9e8218089b | ||
![]() |
3f660a3963 | ||
![]() |
daeb6711b0 | ||
![]() |
4e1aec28a0 | ||
![]() |
5512917ec1 | ||
![]() |
cd1edc5d56 | ||
![]() |
4f52587586 | ||
![]() |
d7ee4ef5f5 | ||
![]() |
31f88e0f05 | ||
![]() |
9f1740cc4f | ||
![]() |
f2c15c7701 | ||
![]() |
e67d0678f9 | ||
![]() |
b1faa5eed4 | ||
![]() |
7f1f0b9048 | ||
![]() |
183e5f2ecc | ||
![]() |
14efe4939a | ||
![]() |
3dc7d77ea9 | ||
![]() |
0f07bbb3e5 | ||
![]() |
dd5a3416bf | ||
![]() |
2fb49ad780 | ||
![]() |
92f0e53fee | ||
![]() |
876132694d | ||
![]() |
1257ba41c6 | ||
![]() |
2cc71ac7ed | ||
![]() |
753808a4ce | ||
![]() |
32cd694ad5 | ||
![]() |
f008420891 | ||
![]() |
fa8900be65 | ||
![]() |
69c2f407d6 | ||
![]() |
ffcd093db1 | ||
![]() |
8dbf93750f | ||
![]() |
e266a81167 | ||
![]() |
e841aab9e7 | ||
![]() |
49f259065d | ||
![]() |
b10379e700 | ||
![]() |
810d27a618 | ||
![]() |
9b60c005c7 | ||
![]() |
cc6ca0bda2 | ||
![]() |
4512232637 | ||
![]() |
2c092ffdef | ||
![]() |
66406227d6 | ||
![]() |
a11d25bb44 | ||
![]() |
2e58d902b7 | ||
![]() |
237794b05c | ||
![]() |
563a587882 | ||
![]() |
24505cd111 | ||
![]() |
0c681cdab4 | ||
![]() |
13ef3058c6 | ||
![]() |
50b159b43d | ||
![]() |
8c6c328730 | ||
![]() |
c9812ddf08 | ||
![]() |
2ef0449c2c | ||
![]() |
5edc750c47 | ||
![]() |
2f0e396d7f | ||
![]() |
000a163beb | ||
![]() |
80dd37ee31 | ||
![]() |
e0b5645064 | ||
![]() |
e51aacb0b7 | ||
![]() |
2d6af94aa0 | ||
![]() |
7cfce9ff7a | ||
![]() |
7f088d6241 | ||
![]() |
d11038f3de | ||
![]() |
6df42a4be7 | ||
![]() |
7fd111b91f | ||
![]() |
dd7dc2ec5a | ||
![]() |
86c586d882 | ||
![]() |
66ac6f72fc | ||
![]() |
f21f448099 | ||
![]() |
548d70f30c | ||
![]() |
39e714c6d8 | ||
![]() |
9968af0785 | ||
![]() |
be7586137c | ||
![]() |
7999b66c3c | ||
![]() |
c82a46c1ee | ||
![]() |
666ab1941f | ||
![]() |
71e37345b4 | ||
![]() |
e7c82f20e3 | ||
![]() |
afa771a980 | ||
![]() |
0d1de98cca | ||
![]() |
02bf7dca01 | ||
![]() |
8cc76b1d86 | ||
![]() |
77a275cbcd | ||
![]() |
3956cbe2d2 | ||
![]() |
945de8d9a0 | ||
![]() |
6dabd3bb2d | ||
![]() |
4c80808997 | ||
![]() |
5a39f7cdde | ||
![]() |
5d400fbe90 | ||
![]() |
e36596470c | ||
![]() |
668e549208 | ||
![]() |
256ff31d11 | ||
![]() |
2414d5d7f5 | ||
![]() |
b7fc15d399 | ||
![]() |
c09b4dabc4 | ||
![]() |
a4aa4a91a3 | ||
![]() |
8f0ea5925a | ||
![]() |
936ad1aa20 | ||
![]() |
d021bca6ef | ||
![]() |
55ed6109c1 | ||
![]() |
f6d765bf81 | ||
![]() |
88e8f2bf83 | ||
![]() |
c849759682 | ||
![]() |
605eae21bc | ||
![]() |
93eb277a88 | ||
![]() |
8edf556c9e | ||
![]() |
7fcb63230f | ||
![]() |
12093a3dad | ||
![]() |
ebb0ec6c42 | ||
![]() |
188546515c | ||
![]() |
c8990b0f68 | ||
![]() |
7dced4b9d9 | ||
![]() |
3145e67feb | ||
![]() |
e9348d9b6a | ||
![]() |
1a1b346c05 | ||
![]() |
920d059837 | ||
![]() |
bef5c3bd1b | ||
![]() |
97037f7d03 | ||
![]() |
a7392ed3d7 | ||
![]() |
3eb1a7e384 | ||
![]() |
1ecdc78c2f | ||
![]() |
d279dba37e | ||
![]() |
a4f97fa151 | ||
![]() |
ff7ac582f0 | ||
![]() |
d2c2456fbe | ||
![]() |
e9f562a8b7 | ||
![]() |
084e0a73dc | ||
![]() |
10f991b8d0 | ||
![]() |
79620c97d1 | ||
![]() |
ffec9a4ddd | ||
![]() |
9b18960bbd | ||
![]() |
a009fdbdc3 | ||
![]() |
c1fc3f373c | ||
![]() |
f4cf5dc0cd | ||
![]() |
355341f0ab | ||
![]() |
7f65f7d3ca | ||
![]() |
9fa096c6f4 | ||
![]() |
70415a396a | ||
![]() |
c921964938 | ||
![]() |
3bf47a6838 | ||
![]() |
d3d28f0623 | ||
![]() |
f880b57544 | ||
![]() |
32b7a26fa6 | ||
![]() |
32fc34f922 | ||
![]() |
b82a393692 | ||
![]() |
3c7e792167 | ||
![]() |
0ad66875ab | ||
![]() |
1191ac2671 | ||
![]() |
928b3425e3 | ||
![]() |
0726a00e3b | ||
![]() |
5a88984d34 | ||
![]() |
18de60f68c | ||
![]() |
1893359142 | ||
![]() |
f5e5ab2436 | ||
![]() |
ff5ea1a70d | ||
![]() |
54ee63a409 | ||
![]() |
f095606b50 | ||
![]() |
e8f31c78d7 |
17
.github/ISSUE_TEMPLATE/bug_report.md
vendored
17
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,19 +1,18 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: ''
|
title: ""
|
||||||
labels: ''
|
labels: ""
|
||||||
assignees: ''
|
assignees: ""
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
## READ BEFORE OPENING ISSUES
|
## READ BEFORE OPENING ISSUES
|
||||||
|
|
||||||
All bug reports require you to **USE CANARY BUILDS**. Please include the version name and version code in the bug report.
|
All bug reports require you to **USE DEBUG BUILDS**. Please include the version name and version code in the bug report.
|
||||||
|
|
||||||
If you experience a bootloop, attach a `dmesg` (kernel logs) when the device refuse to boot. This may very likely require a custom kernel on some devices as `last_kmsg` or `pstore ramoops` are usually not enabled by default. In addition, please also upload the result of `cat /proc/mounts` when your device is working correctly **WITHOUT ROOT**.
|
If you experience a bootloop, attach a `dmesg` (kernel logs) when the device refuse to boot. This may very likely require a custom kernel on some devices as `last_kmsg` or `pstore ramoops` are usually not enabled by default. In addition, please also upload the result of `cat /proc/mounts` when your device is working correctly **WITHOUT MAGISK**.
|
||||||
|
|
||||||
If you experience issues during installation, in recovery, upload the recovery logs, or in Magisk, upload the install logs. Please also upload the `boot.img` or `recovery.img` that you are using for patching.
|
If you experience issues during installation, in recovery, upload the recovery logs, or in Magisk, upload the install logs. Please also upload the `boot.img` or `recovery.img` that you are using for patching.
|
||||||
|
|
||||||
@@ -31,7 +30,7 @@ Without following the rules above, your issue will be closed without explanation
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
Device:
|
Device:
|
||||||
Android version:
|
Android version:
|
||||||
Magisk version name:
|
Magisk version name:
|
||||||
Magisk version code:
|
Magisk version code:
|
||||||
|
100
.github/actions/setup/action.yml
vendored
Normal file
100
.github/actions/setup/action.yml
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
name: Magisk Setup
|
||||||
|
inputs:
|
||||||
|
is-asset-build:
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: "temurin"
|
||||||
|
java-version: "21"
|
||||||
|
|
||||||
|
- name: Set up Python 3
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
|
||||||
|
- name: Install GNU make
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
brew install make
|
||||||
|
echo 'GNUMAKE=gmake' >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Cache sccache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .sccache
|
||||||
|
key: sccache-${{ runner.os }}-${{ github.sha }}
|
||||||
|
restore-keys: sccache-${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Set up sccache
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
SCCACHE_DIRECT: false
|
||||||
|
SCCACHE_DIR: ${{ github.workspace }}/.sccache
|
||||||
|
SCCACHE_CACHE_SIZE: 2G
|
||||||
|
SCCACHE_IDLE_TIMEOUT: 0
|
||||||
|
run: |
|
||||||
|
bash $GITHUB_ACTION_PATH/sccache.sh
|
||||||
|
sccache --start-server
|
||||||
|
sccache -z
|
||||||
|
|
||||||
|
- name: Show sccache stats
|
||||||
|
uses: gacts/run-and-post-run@v1
|
||||||
|
with:
|
||||||
|
run: sccache -s
|
||||||
|
post: sccache -s
|
||||||
|
|
||||||
|
- name: Set GRADLE_USER_HOME
|
||||||
|
shell: bash
|
||||||
|
run: echo "GRADLE_USER_HOME=$GITHUB_WORKSPACE/.gradle" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Cache Gradle dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
if: inputs.is-asset-build == 'true'
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.gradle/caches
|
||||||
|
.gradle/wrapper
|
||||||
|
!.gradle/caches/build-cache-*
|
||||||
|
key: gradle-cache-${{ hashFiles('gradle/**') }}
|
||||||
|
restore-keys: gradle-cache-
|
||||||
|
|
||||||
|
- name: Restore Gradle dependencies
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
if: inputs.is-asset-build == 'false'
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.gradle/caches
|
||||||
|
.gradle/wrapper
|
||||||
|
!.gradle/caches/build-cache-*
|
||||||
|
key: gradle-cache-${{ hashFiles('gradle/**') }}
|
||||||
|
restore-keys: gradle-cache-
|
||||||
|
enableCrossOsArchive: true
|
||||||
|
|
||||||
|
- name: Cache Gradle build cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
if: inputs.is-asset-build == 'true'
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.gradle/caches/build-cache-*
|
||||||
|
key: gradle-build-cache-${{ github.sha }}
|
||||||
|
restore-keys: gradle-build-cache-
|
||||||
|
|
||||||
|
- name: Restore Gradle build cache
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
if: inputs.is-asset-build == 'false'
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.gradle/caches/build-cache-*
|
||||||
|
key: gradle-build-cache-${{ github.sha }}
|
||||||
|
restore-keys: gradle-build-cache-
|
||||||
|
enableCrossOsArchive: true
|
||||||
|
|
||||||
|
- name: Set up NDK
|
||||||
|
shell: bash
|
||||||
|
run: python build.py -v ndk
|
25
.github/actions/setup/sccache.sh
vendored
Executable file
25
.github/actions/setup/sccache.sh
vendored
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Get latest sccache version
|
||||||
|
get_sccache_ver() {
|
||||||
|
curl -sL 'https://api.github.com/repos/mozilla/sccache/releases/latest' | jq -r .name
|
||||||
|
}
|
||||||
|
|
||||||
|
# $1=variant
|
||||||
|
# $2=install_dir
|
||||||
|
# $3=exe
|
||||||
|
install_from_gh() {
|
||||||
|
local ver=$(curl -sL 'https://api.github.com/repos/mozilla/sccache/releases/latest' | jq -r .name)
|
||||||
|
local url="https://github.com/mozilla/sccache/releases/download/${ver}/sccache-${ver}-$1.tar.gz"
|
||||||
|
local dest="$2/$3"
|
||||||
|
curl -L "$url" | tar xz -O --wildcards "*/$3" > $dest
|
||||||
|
chmod +x $dest
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ $RUNNER_OS = "macOS" ]; then
|
||||||
|
brew install sccache
|
||||||
|
elif [ $RUNNER_OS = "Linux" ]; then
|
||||||
|
install_from_gh x86_64-unknown-linux-musl /usr/local/bin sccache
|
||||||
|
elif [ $RUNNER_OS = "Windows" ]; then
|
||||||
|
install_from_gh x86_64-pc-windows-msvc $USERPROFILE/.cargo/bin sccache.exe
|
||||||
|
fi
|
19
.github/ccache.sh
vendored
19
.github/ccache.sh
vendored
@@ -1,19 +0,0 @@
|
|||||||
OS=$(uname)
|
|
||||||
CCACHE_VER=4.4
|
|
||||||
|
|
||||||
case $OS in
|
|
||||||
Darwin )
|
|
||||||
brew install ccache
|
|
||||||
ln -s $(which ccache) ./ccache
|
|
||||||
;;
|
|
||||||
Linux )
|
|
||||||
sudo apt-get install -y ccache
|
|
||||||
ln -s $(which ccache) ./ccache
|
|
||||||
;;
|
|
||||||
* )
|
|
||||||
curl -OL https://github.com/ccache/ccache/releases/download/v${CCACHE_VER}/ccache-${CCACHE_VER}-windows-64.zip
|
|
||||||
unzip -j ccache-*-windows-64.zip '*/ccache.exe'
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
mkdir ./.ccache
|
|
||||||
./ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'
|
|
1
.github/ci.prop
vendored
Normal file
1
.github/ci.prop
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
abiList=arm64-v8a
|
238
.github/workflows/build.yml
vendored
238
.github/workflows/build.yml
vendored
@@ -2,90 +2,204 @@ name: Magisk Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [master]
|
||||||
paths:
|
paths:
|
||||||
- 'app/**'
|
- "app/**"
|
||||||
- 'native/**'
|
- "native/**"
|
||||||
- 'stub/**'
|
- "buildSrc/**"
|
||||||
- 'buildSrc/**'
|
- "build.py"
|
||||||
- 'build.py'
|
- "gradle.properties"
|
||||||
- 'gradle.properties'
|
- ".github/workflows/build.yml"
|
||||||
- '.github/workflows/build.yml'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [master]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build on ${{ matrix.os }}
|
name: Build Magisk artifacts
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: macos-14
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
|
||||||
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
|
||||||
env:
|
|
||||||
NDK_CCACHE: ${{ github.workspace }}/ccache
|
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out
|
- name: Check out
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: "recursive"
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set up JDK 11
|
- name: Setup environment
|
||||||
uses: actions/setup-java@v1
|
uses: ./.github/actions/setup
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
is-asset-build: true
|
||||||
|
|
||||||
- name: Set up Python 3
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: '3.x'
|
|
||||||
|
|
||||||
- name: Set up ccache
|
|
||||||
run: bash .github/ccache.sh
|
|
||||||
|
|
||||||
- name: Cache Gradle dependencies
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.gradle/caches
|
|
||||||
~/.gradle/wrapper
|
|
||||||
!~/.gradle/caches/build-cache-*
|
|
||||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
|
|
||||||
restore-keys: ${{ runner.os }}-gradle-
|
|
||||||
|
|
||||||
- name: Cache build cache
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
${{ github.workspace }}/.ccache
|
|
||||||
~/.gradle/caches/build-cache-*
|
|
||||||
key: ${{ runner.os }}-build-cache-${{ github.sha }}
|
|
||||||
restore-keys: ${{ runner.os }}-build-cache-
|
|
||||||
|
|
||||||
- name: Set up NDK
|
|
||||||
run: python build.py -v ndk
|
|
||||||
|
|
||||||
- name: Build release
|
- name: Build release
|
||||||
run: |
|
run: ./build.py -vr all
|
||||||
./ccache -zp
|
|
||||||
python build.py -vr all
|
|
||||||
|
|
||||||
- name: Build debug
|
- name: Build debug
|
||||||
run: |
|
run: ./build.py -v all
|
||||||
python build.py -v all
|
|
||||||
./ccache -s
|
|
||||||
|
|
||||||
- name: Stop gradle daemon
|
- name: Stop gradle daemon
|
||||||
run: ./gradlew --stop
|
run: ./gradlew --stop
|
||||||
|
|
||||||
# Only upload artifacts built on Linux
|
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
if: runner.os == 'Linux'
|
uses: actions/upload-artifact@v4
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
with:
|
||||||
name: ${{ github.sha }}
|
name: ${{ github.sha }}
|
||||||
path: out
|
path: out
|
||||||
|
compression-level: 9
|
||||||
|
|
||||||
|
- name: Upload mapping and native debug symbols
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.sha }}-symbols
|
||||||
|
path: app/apk/build/outputs
|
||||||
|
compression-level: 9
|
||||||
|
|
||||||
|
test-build:
|
||||||
|
name: Test building on ${{ matrix.os }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [windows-latest, ubuntu-latest]
|
||||||
|
steps:
|
||||||
|
- name: Check out
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: "recursive"
|
||||||
|
|
||||||
|
- name: Setup environment
|
||||||
|
uses: ./.github/actions/setup
|
||||||
|
|
||||||
|
- name: Test build
|
||||||
|
run: python build.py -v -c .github/ci.prop all
|
||||||
|
|
||||||
|
- name: Stop gradle daemon
|
||||||
|
run: ./gradlew --stop
|
||||||
|
|
||||||
|
avd-test:
|
||||||
|
name: Test API ${{ matrix.version }} (x86_64)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
|
||||||
|
type: [""]
|
||||||
|
include:
|
||||||
|
- version: "Baklava"
|
||||||
|
type: "google_apis"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download build artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.sha }}
|
||||||
|
path: out
|
||||||
|
|
||||||
|
- name: Enable KVM group perms
|
||||||
|
run: |
|
||||||
|
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
sudo udevadm trigger --name-match=kvm
|
||||||
|
|
||||||
|
- name: Run AVD test
|
||||||
|
timeout-minutes: 10
|
||||||
|
env:
|
||||||
|
AVD_TEST_LOG: 1
|
||||||
|
run: scripts/avd_test.sh ${{ matrix.version }} ${{ matrix.type }}
|
||||||
|
|
||||||
|
- name: Upload logs on error
|
||||||
|
if: ${{ failure() }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: "avd-logs-${{ matrix.version }}"
|
||||||
|
path: |
|
||||||
|
kernel.log
|
||||||
|
logcat.log
|
||||||
|
|
||||||
|
avd-test-32:
|
||||||
|
name: Test API ${{ matrix.version }} (x86)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
version: [23, 24, 25, 26, 27, 28, 29, 30]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download build artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.sha }}
|
||||||
|
path: out
|
||||||
|
|
||||||
|
- name: Enable KVM group perms
|
||||||
|
run: |
|
||||||
|
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
sudo udevadm trigger --name-match=kvm
|
||||||
|
|
||||||
|
- name: Run AVD test
|
||||||
|
timeout-minutes: 10
|
||||||
|
env:
|
||||||
|
FORCE_32_BIT: 1
|
||||||
|
AVD_TEST_LOG: 1
|
||||||
|
run: scripts/avd_test.sh ${{ matrix.version }}
|
||||||
|
|
||||||
|
- name: Upload logs on error
|
||||||
|
if: ${{ failure() }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: "avd32-logs-${{ matrix.version }}"
|
||||||
|
path: |
|
||||||
|
kernel.log
|
||||||
|
logcat.log
|
||||||
|
|
||||||
|
cf_test:
|
||||||
|
name: Test ${{ matrix.device }}
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs: build
|
||||||
|
env:
|
||||||
|
CF_HOME: /home/runner/aosp_cf_phone
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- branch: "aosp-main"
|
||||||
|
device: "aosp_cf_x86_64_phone"
|
||||||
|
- branch: "aosp-main-throttled"
|
||||||
|
device: "aosp_cf_x86_64_phone_pgagnostic"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download build artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.sha }}
|
||||||
|
path: out
|
||||||
|
|
||||||
|
- name: Setup Cuttlefish environment
|
||||||
|
run: |
|
||||||
|
scripts/cuttlefish.sh setup
|
||||||
|
scripts/cuttlefish.sh download ${{ matrix.branch }} ${{ matrix.device }}
|
||||||
|
|
||||||
|
- name: Run Cuttlefish test
|
||||||
|
timeout-minutes: 10
|
||||||
|
run: su $USER -c 'scripts/cuttlefish.sh test'
|
||||||
|
|
||||||
|
- name: Upload logs on error
|
||||||
|
if: ${{ failure() }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: "cvd-logs-${{ matrix.device }}"
|
||||||
|
path: |
|
||||||
|
/home/runner/aosp_cf_phone/cuttlefish/instances/cvd-1/logs
|
||||||
|
/home/runner/aosp_cf_phone/cuttlefish/instances/cvd-1/cuttlefish_config.json
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -3,8 +3,9 @@ out
|
|||||||
*.jks
|
*.jks
|
||||||
*.apk
|
*.apk
|
||||||
/config.prop
|
/config.prop
|
||||||
|
/notes.md
|
||||||
/update.sh
|
/update.sh
|
||||||
/dict.txt
|
/app/dict.txt
|
||||||
|
|
||||||
# Built binaries
|
# Built binaries
|
||||||
native/out
|
native/out
|
||||||
@@ -12,7 +13,8 @@ native/out
|
|||||||
# Android Studio / Gradle
|
# Android Studio / Gradle
|
||||||
*.iml
|
*.iml
|
||||||
.gradle
|
.gradle
|
||||||
|
.idea
|
||||||
|
.kotlin
|
||||||
/local.properties
|
/local.properties
|
||||||
/.idea
|
|
||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
|
51
.gitmodules
vendored
51
.gitmodules
vendored
@@ -1,45 +1,36 @@
|
|||||||
[submodule "selinux"]
|
[submodule "selinux"]
|
||||||
path = native/jni/external/selinux
|
path = native/src/external/selinux
|
||||||
url = https://github.com/topjohnwu/selinux.git
|
url = https://github.com/topjohnwu/selinux.git
|
||||||
[submodule "busybox"]
|
|
||||||
path = native/jni/external/busybox
|
|
||||||
url = https://github.com/topjohnwu/ndk-busybox.git
|
|
||||||
[submodule "dtc"]
|
|
||||||
path = native/jni/external/dtc
|
|
||||||
url = https://github.com/dgibson/dtc.git
|
|
||||||
[submodule "lz4"]
|
[submodule "lz4"]
|
||||||
path = native/jni/external/lz4
|
path = native/src/external/lz4
|
||||||
url = https://github.com/lz4/lz4.git
|
url = https://github.com/lz4/lz4.git
|
||||||
[submodule "bzip2"]
|
[submodule "bzip2"]
|
||||||
path = native/jni/external/bzip2
|
path = native/src/external/bzip2
|
||||||
url = https://github.com/nemequ/bzip2.git
|
url = https://github.com/nemequ/bzip2.git
|
||||||
[submodule "xz"]
|
[submodule "xz"]
|
||||||
path = native/jni/external/xz
|
path = native/src/external/xz
|
||||||
url = https://github.com/xz-mirror/xz.git
|
url = https://github.com/xz-mirror/xz.git
|
||||||
[submodule "nanopb"]
|
|
||||||
path = native/jni/external/nanopb
|
|
||||||
url = https://github.com/nanopb/nanopb.git
|
|
||||||
[submodule "mincrypt"]
|
|
||||||
path = native/jni/external/mincrypt
|
|
||||||
url = https://github.com/topjohnwu/mincrypt.git
|
|
||||||
[submodule "pcre"]
|
|
||||||
path = native/jni/external/pcre
|
|
||||||
url = https://android.googlesource.com/platform/external/pcre
|
|
||||||
[submodule "xhook"]
|
|
||||||
path = native/jni/external/xhook
|
|
||||||
url = https://github.com/iqiyi/xHook.git
|
|
||||||
[submodule "libcxx"]
|
[submodule "libcxx"]
|
||||||
path = native/jni/external/libcxx
|
path = native/src/external/libcxx
|
||||||
url = https://github.com/topjohnwu/libcxx.git
|
url = https://github.com/topjohnwu/libcxx.git
|
||||||
[submodule "zlib"]
|
[submodule "zlib"]
|
||||||
path = native/jni/external/zlib
|
path = native/src/external/zlib
|
||||||
url = https://android.googlesource.com/platform/external/zlib
|
url = https://android.googlesource.com/platform/external/zlib
|
||||||
[submodule "parallel-hashmap"]
|
[submodule "zopfli"]
|
||||||
path = native/jni/external/parallel-hashmap
|
path = native/src/external/zopfli
|
||||||
url = https://github.com/greg7mdp/parallel-hashmap.git
|
url = https://github.com/google/zopfli.git
|
||||||
|
[submodule "cxx-rs"]
|
||||||
|
path = native/src/external/cxx-rs
|
||||||
|
url = https://github.com/topjohnwu/cxx.git
|
||||||
|
[submodule "lsplt"]
|
||||||
|
path = native/src/external/lsplt
|
||||||
|
url = https://github.com/LSPosed/LSPlt.git
|
||||||
|
[submodule "system_properties"]
|
||||||
|
path = native/src/external/system_properties
|
||||||
|
url = https://github.com/topjohnwu/system_properties.git
|
||||||
|
[submodule "crt0"]
|
||||||
|
path = native/src/external/crt0
|
||||||
|
url = https://github.com/topjohnwu/crt0.git
|
||||||
[submodule "termux-elf-cleaner"]
|
[submodule "termux-elf-cleaner"]
|
||||||
path = tools/termux-elf-cleaner
|
path = tools/termux-elf-cleaner
|
||||||
url = https://github.com/termux/termux-elf-cleaner.git
|
url = https://github.com/termux/termux-elf-cleaner.git
|
||||||
[submodule "zopfli"]
|
|
||||||
path = native/jni/external/zopfli
|
|
||||||
url = https://github.com/google/zopfli.git
|
|
||||||
|
39
README.MD
39
README.MD
@@ -6,60 +6,45 @@
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Magisk is a suite of open source software for customizing Android, supporting devices higher than Android 5.0.<br>
|
Magisk is a suite of open source software for customizing Android, supporting devices higher than Android 6.0.<br>
|
||||||
Some highlight features:
|
Some highlight features:
|
||||||
|
|
||||||
- **MagiskSU**: Provide root access for applications
|
- **MagiskSU**: Provide root access for applications
|
||||||
- **Magisk Modules**: Modify read-only partitions by installing modules
|
- **Magisk Modules**: Modify read-only partitions by installing modules
|
||||||
- **MagiskBoot**: The most complete tool for unpacking and repacking Android boot images
|
- **MagiskBoot**: The most complete tool for unpacking and repacking Android boot images
|
||||||
|
- **Zygisk**: Run code in every Android applications' processes
|
||||||
|
|
||||||
## Downloads
|
## Downloads
|
||||||
|
|
||||||
[Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads.
|
[Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads.
|
||||||
|
|
||||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v23.0)
|
Click the icon below to download Magisk apk.
|
||||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v23.0)
|
|
||||||
[](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk)
|
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
|
||||||
|
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
|
||||||
|
[](https://github.com/topjohnwu/Magisk/releases/tag/canary-28101)
|
||||||
|
|
||||||
## Useful Links
|
## Useful Links
|
||||||
|
|
||||||
- [Installation Instruction](https://topjohnwu.github.io/Magisk/install.html)
|
- [Installation Instruction](https://topjohnwu.github.io/Magisk/install.html)
|
||||||
- [Frequently Asked Questions](https://topjohnwu.github.io/Magisk/faq.html)
|
- [Building and Development](https://topjohnwu.github.io/Magisk/build.html)
|
||||||
- [Magisk Documentation](https://topjohnwu.github.io/Magisk/)
|
- [Magisk Documentation](https://topjohnwu.github.io/Magisk/)
|
||||||
- [Magisk Troubleshoot Wiki](https://www.didgeridoohan.com/magisk/HomePage) (by [@Didgeridoohan](https://github.com/Didgeridoohan))
|
- [Zygisk module sample](https://github.com/topjohnwu/zygisk-module-sample)
|
||||||
|
|
||||||
## Bug Reports
|
## Bug Reports
|
||||||
|
|
||||||
**Only bug reports from Canary builds will be accepted.**
|
**Only bug reports from Debug builds will be accepted.**
|
||||||
|
|
||||||
For installation issues, upload both boot image and install logs.<br>
|
For installation issues, upload both boot image and install logs.<br>
|
||||||
For Magisk issues, upload boot logcat or dmesg.<br>
|
For Magisk issues, upload boot logcat or dmesg.<br>
|
||||||
For Magisk app crashes, record and upload the logcat when the crash occurs.
|
For Magisk app crashes, record and upload the logcat when the crash occurs.
|
||||||
|
|
||||||
## Building and Development
|
|
||||||
|
|
||||||
- Magisk builds on any OS Android Studio supports. Install Android Studio and do the initial setups.
|
|
||||||
- Clone sources: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
|
|
||||||
- Install Python 3.6+ \
|
|
||||||
(Windows only: select **'Add Python to PATH'** in installer, and run `pip install colorama` after install)
|
|
||||||
- Configure to use the JDK bundled in Android Studio:
|
|
||||||
- macOS: `export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/Contents/Home"`
|
|
||||||
- Linux: `export PATH="/path/to/androidstudio/jre/bin:$PATH"`
|
|
||||||
- Windows: Add `C:\Path\To\Android Studio\jre\bin` to environment variable `PATH`
|
|
||||||
- Set environment variable `ANDROID_SDK_ROOT` to the Android SDK folder (can be found in Android Studio settings)
|
|
||||||
- Run `./build.py ndk` to let the script download and install NDK for you
|
|
||||||
- To start building, run `build.py` to see your options. \
|
|
||||||
For each action, use `-h` to access help (e.g. `./build.py all -h`)
|
|
||||||
- To start development, open the project with Android Studio. The IDE can be used for both app (Kotlin/Java) and native (C++/C) sources.
|
|
||||||
- Optionally, set custom configs with `config.prop`. A sample `config.prop.sample` is provided.
|
|
||||||
- To sign APKs and zips with your own private keys, set signing configs in `config.prop`. For more info, check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key).
|
|
||||||
|
|
||||||
## Translation Contributions
|
## Translation Contributions
|
||||||
|
|
||||||
Default string resources for the Magisk app and its stub APK are located here:
|
Default string resources for the Magisk app and its stub APK are located here:
|
||||||
|
|
||||||
- `app/src/main/res/values/strings.xml`
|
- `app/core/src/main/res/values/strings.xml`
|
||||||
- `stub/src/main/res/values/strings.xml`
|
- `app/stub/src/main/res/values/strings.xml`
|
||||||
|
|
||||||
Translate each and place them in the respective locations (`[module]/src/main/res/values-[lang]/strings.xml`).
|
Translate each and place them in the respective locations (`[module]/src/main/res/values-[lang]/strings.xml`).
|
||||||
|
|
||||||
|
12
app/.gitignore
vendored
12
app/.gitignore
vendored
@@ -1,12 +0,0 @@
|
|||||||
*.iml
|
|
||||||
.gradle
|
|
||||||
/local.properties
|
|
||||||
.idea/
|
|
||||||
/build
|
|
||||||
app/release
|
|
||||||
*.hprof
|
|
||||||
.externalNativeBuild/
|
|
||||||
*.apk
|
|
||||||
src/main/assets
|
|
||||||
src/main/jniLibs
|
|
||||||
src/main/resources
|
|
1
app/apk/.gitignore
vendored
Normal file
1
app/apk/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
59
app/apk/build.gradle.kts
Normal file
59
app/apk/build.gradle.kts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
kotlin("android")
|
||||||
|
kotlin("plugin.parcelize")
|
||||||
|
kotlin("kapt")
|
||||||
|
id("androidx.navigation.safeargs.kotlin")
|
||||||
|
}
|
||||||
|
|
||||||
|
setupMainApk()
|
||||||
|
|
||||||
|
kapt {
|
||||||
|
correctErrorTypes = true
|
||||||
|
useBuildCache = true
|
||||||
|
mapDiagnosticLocations = true
|
||||||
|
javacOptions {
|
||||||
|
option("-Xmaxerrs", "1000")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
buildFeatures {
|
||||||
|
dataBinding = true
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
isCoreLibraryDesugaringEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = true
|
||||||
|
isShrinkResources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":app:core"))
|
||||||
|
coreLibraryDesugaring(libs.jdk.libs)
|
||||||
|
|
||||||
|
implementation(libs.indeterminate.checkbox)
|
||||||
|
implementation(libs.rikka.layoutinflater)
|
||||||
|
implementation(libs.rikka.insets)
|
||||||
|
implementation(libs.rikka.recyclerview)
|
||||||
|
|
||||||
|
implementation(libs.navigation.fragment.ktx)
|
||||||
|
implementation(libs.navigation.ui.ktx)
|
||||||
|
|
||||||
|
implementation(libs.constraintlayout)
|
||||||
|
implementation(libs.swiperefreshlayout)
|
||||||
|
implementation(libs.recyclerview)
|
||||||
|
implementation(libs.transition)
|
||||||
|
implementation(libs.fragment.ktx)
|
||||||
|
implementation(libs.appcompat)
|
||||||
|
implementation(libs.material)
|
||||||
|
|
||||||
|
// Make sure kapt runs with a proper kotlin-stdlib
|
||||||
|
kapt(kotlin("stdlib"))
|
||||||
|
}
|
33
app/apk/src/main/AndroidManifest.xml
Normal file
33
app/apk/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<application android:localeConfig="@xml/locale_config">
|
||||||
|
<activity
|
||||||
|
android:name=".ui.MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/SplashTheme">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.surequest.SuRequestActivity"
|
||||||
|
android:directBootAware="true"
|
||||||
|
android:exported="false"
|
||||||
|
android:taskAffinity=""
|
||||||
|
tools:ignore="AppLinkUrlError">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
@@ -0,0 +1,22 @@
|
|||||||
|
package com.topjohnwu.magisk.arch
|
||||||
|
|
||||||
|
import androidx.annotation.MainThread
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
abstract class AsyncLoadViewModel : BaseViewModel() {
|
||||||
|
|
||||||
|
private var loadingJob: Job? = null
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
fun startLoading() {
|
||||||
|
if (loadingJob?.isActive == true) {
|
||||||
|
// Prevent multiple jobs from running at the same time
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loadingJob = viewModelScope.launch { doLoadWork() }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract suspend fun doLoadWork()
|
||||||
|
}
|
@@ -5,26 +5,28 @@ import android.view.KeyEvent
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.MenuProvider
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.databinding.OnRebindCallback
|
import androidx.databinding.OnRebindCallback
|
||||||
import androidx.databinding.ViewDataBinding
|
import androidx.databinding.ViewDataBinding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.navigation.NavDirections
|
import androidx.navigation.NavDirections
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.ktx.startAnimations
|
|
||||||
|
|
||||||
abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHolder {
|
abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHolder {
|
||||||
|
|
||||||
val activity get() = requireActivity() as NavigationActivity<*>
|
val activity get() = getActivity() as? NavigationActivity<*>
|
||||||
protected lateinit var binding: Binding
|
protected lateinit var binding: Binding
|
||||||
protected abstract val layoutRes: Int
|
protected abstract val layoutRes: Int
|
||||||
|
|
||||||
private val navigation get() = activity.navigation
|
private val navigation get() = activity?.navigation
|
||||||
|
open val snackbarView: View? get() = null
|
||||||
open val snackbarAnchorView: View? get() = null
|
open val snackbarAnchorView: View? get() = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
startObserveEvents()
|
startObserveLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
@@ -36,17 +38,25 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
|
|||||||
it.setVariable(BR.viewModel, viewModel)
|
it.setVariable(BR.viewModel, viewModel)
|
||||||
it.lifecycleOwner = viewLifecycleOwner
|
it.lifecycleOwner = viewLifecycleOwner
|
||||||
}
|
}
|
||||||
|
if (this is MenuProvider) {
|
||||||
|
activity?.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.STARTED)
|
||||||
|
}
|
||||||
|
savedInstanceState?.let { viewModel.onRestoreState(it) }
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
viewModel.onSaveState(outState)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
activity.supportActionBar?.subtitle = null
|
activity?.supportActionBar?.subtitle = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEventDispatched(event: ViewEvent) = when(event) {
|
override fun onEventDispatched(event: ViewEvent) = when(event) {
|
||||||
is ContextExecutor -> event(requireContext())
|
is ContextExecutor -> event(requireContext())
|
||||||
is ActivityExecutor -> event(activity)
|
is ActivityExecutor -> activity?.let { event(it) } ?: Unit
|
||||||
is FragmentExecutor -> event(this)
|
is FragmentExecutor -> event(this)
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
@@ -70,7 +80,10 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
viewModel.requestRefresh()
|
viewModel.let {
|
||||||
|
if (it is AsyncLoadViewModel)
|
||||||
|
it.startLoading()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun onPreBind(binding: Binding) {
|
protected open fun onPreBind(binding: Binding) {
|
||||||
@@ -78,7 +91,6 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun NavDirections.navigate() {
|
fun NavDirections.navigate() {
|
||||||
navigation?.navigate(this)
|
navigation?.currentDestination?.getAction(actionId)?.let { navigation!!.navigate(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -0,0 +1,83 @@
|
|||||||
|
package com.topjohnwu.magisk.arch
|
||||||
|
|
||||||
|
import android.Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
|
||||||
|
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.databinding.PropertyChangeRegistry
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.navigation.NavDirections
|
||||||
|
import com.topjohnwu.magisk.core.R
|
||||||
|
import com.topjohnwu.magisk.databinding.ObservableHost
|
||||||
|
import com.topjohnwu.magisk.events.BackPressEvent
|
||||||
|
import com.topjohnwu.magisk.events.DialogBuilder
|
||||||
|
import com.topjohnwu.magisk.events.DialogEvent
|
||||||
|
import com.topjohnwu.magisk.events.NavigationEvent
|
||||||
|
import com.topjohnwu.magisk.events.PermissionEvent
|
||||||
|
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||||
|
|
||||||
|
abstract class BaseViewModel : ViewModel(), ObservableHost {
|
||||||
|
|
||||||
|
override var callbacks: PropertyChangeRegistry? = null
|
||||||
|
|
||||||
|
private val _viewEvents = MutableLiveData<ViewEvent>()
|
||||||
|
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
|
||||||
|
|
||||||
|
open fun onSaveState(state: Bundle) {}
|
||||||
|
open fun onRestoreState(state: Bundle) {}
|
||||||
|
open fun onNetworkChanged(network: Boolean) {}
|
||||||
|
|
||||||
|
fun withPermission(permission: String, callback: (Boolean) -> Unit) {
|
||||||
|
PermissionEvent(permission, callback).publish()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun withExternalRW(crossinline callback: () -> Unit) {
|
||||||
|
withPermission(WRITE_EXTERNAL_STORAGE) {
|
||||||
|
if (!it) {
|
||||||
|
SnackbarEvent(R.string.external_rw_permission_denied).publish()
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
inline fun withInstallPermission(crossinline callback: () -> Unit) {
|
||||||
|
withPermission(REQUEST_INSTALL_PACKAGES) {
|
||||||
|
if (!it) {
|
||||||
|
SnackbarEvent(R.string.install_unknown_denied).publish()
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
inline fun withPostNotificationPermission(crossinline callback: () -> Unit) {
|
||||||
|
withPermission(POST_NOTIFICATIONS) {
|
||||||
|
if (!it) {
|
||||||
|
SnackbarEvent(R.string.post_notifications_denied).publish()
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun back() = BackPressEvent().publish()
|
||||||
|
|
||||||
|
fun ViewEvent.publish() {
|
||||||
|
_viewEvents.postValue(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DialogBuilder.show() {
|
||||||
|
DialogEvent(this).publish()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavDirections.navigate(pop: Boolean = false) {
|
||||||
|
_viewEvents.postValue(NavigationEvent(this, pop))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -20,12 +20,14 @@ abstract class NavigationActivity<Binding : ViewDataBinding> : UIActivity<Bindin
|
|||||||
val navigation: NavController get() = navHostFragment.navController
|
val navigation: NavController get() = navHostFragment.navController
|
||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
return currentFragment?.onKeyEvent(event) == true || super.dispatchKeyEvent(event)
|
return if (binded && currentFragment?.onKeyEvent(event) == true) true else super.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (currentFragment?.onBackPressed()?.not() == true) {
|
if (binded) {
|
||||||
super.onBackPressed()
|
if (currentFragment?.onBackPressed() == false) {
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@@ -1,41 +1,69 @@
|
|||||||
package com.topjohnwu.magisk.arch
|
package com.topjohnwu.magisk.arch
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.content.res.use
|
import androidx.core.content.res.use
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.databinding.ViewDataBinding
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
|
import androidx.transition.AutoTransition
|
||||||
|
import androidx.transition.TransitionManager
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
import com.topjohnwu.magisk.core.base.ActivityExtension
|
||||||
|
import com.topjohnwu.magisk.core.base.IActivityExtension
|
||||||
|
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||||
|
import com.topjohnwu.magisk.core.ktx.reflectField
|
||||||
|
import com.topjohnwu.magisk.core.wrap
|
||||||
import rikka.insets.WindowInsetsHelper
|
import rikka.insets.WindowInsetsHelper
|
||||||
import rikka.layoutinflater.view.LayoutInflaterFactory
|
import rikka.layoutinflater.view.LayoutInflaterFactory
|
||||||
|
|
||||||
abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModelHolder {
|
abstract class UIActivity<Binding : ViewDataBinding>
|
||||||
|
: AppCompatActivity(), ViewModelHolder, IActivityExtension {
|
||||||
|
|
||||||
protected lateinit var binding: Binding
|
protected lateinit var binding: Binding
|
||||||
protected abstract val layoutRes: Int
|
protected abstract val layoutRes: Int
|
||||||
|
override val extension = ActivityExtension(this)
|
||||||
|
|
||||||
|
protected val binded get() = ::binding.isInitialized
|
||||||
|
|
||||||
open val snackbarView get() = binding.root
|
open val snackbarView get() = binding.root
|
||||||
open val snackbarAnchorView: View? get() = null
|
open val snackbarAnchorView: View? get() = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val theme = Config.darkTheme
|
AppCompatDelegate.setDefaultNightMode(Config.darkTheme)
|
||||||
AppCompatDelegate.setDefaultNightMode(theme)
|
}
|
||||||
|
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
super.attachBaseContext(base.wrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
layoutInflater.factory2 = LayoutInflaterFactory(delegate)
|
layoutInflater.factory2 = LayoutInflaterFactory(delegate)
|
||||||
.addOnViewCreatedListener(WindowInsetsHelper.LISTENER)
|
.addOnViewCreatedListener(WindowInsetsHelper.LISTENER)
|
||||||
|
|
||||||
|
extension.onCreate(savedInstanceState)
|
||||||
|
if (isRunningAsStub) {
|
||||||
|
// Overwrite private members to avoid nasty "false" stack traces being logged
|
||||||
|
val delegate = delegate
|
||||||
|
val clz = delegate.javaClass
|
||||||
|
clz.reflectField("mActivityHandlesConfigFlagsChecked").set(delegate, true)
|
||||||
|
clz.reflectField("mActivityHandlesConfigFlags").set(delegate, 0)
|
||||||
|
}
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
startObserveEvents()
|
startObserveLiveData()
|
||||||
|
|
||||||
// We need to set the window background explicitly since for whatever reason it's not
|
// We need to set the window background explicitly since for whatever reason it's not
|
||||||
// propagated upstream
|
// propagated upstream
|
||||||
@@ -63,6 +91,11 @@ abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
extension.onSaveInstanceState(outState)
|
||||||
|
}
|
||||||
|
|
||||||
fun setContentView() {
|
fun setContentView() {
|
||||||
binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).also {
|
binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).also {
|
||||||
it.setVariable(BR.viewModel, viewModel)
|
it.setVariable(BR.viewModel, viewModel)
|
||||||
@@ -74,9 +107,19 @@ abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModel
|
|||||||
binding.root.rootView.accessibilityDelegate = delegate
|
binding.root.rootView.accessibilityDelegate = delegate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showSnackbar(
|
||||||
|
message: CharSequence,
|
||||||
|
length: Int = Snackbar.LENGTH_SHORT,
|
||||||
|
builder: Snackbar.() -> Unit = {}
|
||||||
|
) = Snackbar.make(snackbarView, message, length)
|
||||||
|
.setAnchorView(snackbarAnchorView).apply(builder).show()
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
viewModel.requestRefresh()
|
viewModel.let {
|
||||||
|
if (it is AsyncLoadViewModel)
|
||||||
|
it.startLoading()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEventDispatched(event: ViewEvent) = when (event) {
|
override fun onEventDispatched(event: ViewEvent) = when (event) {
|
||||||
@@ -85,3 +128,14 @@ abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModel
|
|||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ViewGroup.startAnimations() {
|
||||||
|
val transition = AutoTransition()
|
||||||
|
.setInterpolator(FastOutSlowInInterpolator())
|
||||||
|
.setDuration(400)
|
||||||
|
.excludeTarget(R.id.main_toolbar, true)
|
||||||
|
TransitionManager.beginDelayedTransition(
|
||||||
|
this,
|
||||||
|
transition
|
||||||
|
)
|
||||||
|
}
|
@@ -1,7 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.arch
|
package com.topjohnwu.magisk.arch
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for passing events from ViewModels to Activities/Fragments
|
* Class for passing events from ViewModels to Activities/Fragments
|
||||||
@@ -9,10 +8,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
*/
|
*/
|
||||||
abstract class ViewEvent
|
abstract class ViewEvent
|
||||||
|
|
||||||
abstract class ViewEventWithScope: ViewEvent() {
|
|
||||||
lateinit var scope: CoroutineScope
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ContextExecutor {
|
interface ContextExecutor {
|
||||||
operator fun invoke(context: Context)
|
operator fun invoke(context: Context)
|
||||||
}
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
package com.topjohnwu.magisk.arch
|
||||||
|
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.ViewModelStoreOwner
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||||
|
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.install.InstallViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.log.LogViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.surequest.SuRequestViewModel
|
||||||
|
|
||||||
|
interface ViewModelHolder : LifecycleOwner, ViewModelStoreOwner {
|
||||||
|
|
||||||
|
val viewModel: BaseViewModel
|
||||||
|
|
||||||
|
fun startObserveLiveData() {
|
||||||
|
viewModel.viewEvents.observe(this, this::onEventDispatched)
|
||||||
|
Info.isConnected.observe(this, viewModel::onNetworkChanged)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called for all [ViewEvent]s published by associated viewModel.
|
||||||
|
*/
|
||||||
|
fun onEventDispatched(event: ViewEvent) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
object VMFactory : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return when (modelClass) {
|
||||||
|
HomeViewModel::class.java -> HomeViewModel(ServiceLocator.networkService)
|
||||||
|
LogViewModel::class.java -> LogViewModel(ServiceLocator.logRepo)
|
||||||
|
SuperuserViewModel::class.java -> SuperuserViewModel(ServiceLocator.policyDB)
|
||||||
|
InstallViewModel::class.java ->
|
||||||
|
InstallViewModel(ServiceLocator.networkService, ServiceLocator.markwon)
|
||||||
|
SuRequestViewModel::class.java ->
|
||||||
|
SuRequestViewModel(ServiceLocator.policyDB, ServiceLocator.timeoutPrefs)
|
||||||
|
else -> modelClass.newInstance()
|
||||||
|
} as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified VM : ViewModel> ViewModelHolder.viewModel() =
|
||||||
|
lazy(LazyThreadSafetyMode.NONE) {
|
||||||
|
ViewModelProvider(this, VMFactory)[VM::class.java]
|
||||||
|
}
|
@@ -8,9 +8,11 @@ import android.text.Spanned
|
|||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.Spinner
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
@@ -23,13 +25,18 @@ import androidx.databinding.BindingAdapter
|
|||||||
import androidx.databinding.InverseBindingAdapter
|
import androidx.databinding.InverseBindingAdapter
|
||||||
import androidx.databinding.InverseBindingListener
|
import androidx.databinding.InverseBindingListener
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
import androidx.recyclerview.widget.*
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.di.ServiceLocator
|
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||||
|
import com.topjohnwu.magisk.utils.TextHolder
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
import com.topjohnwu.widget.IndeterminateCheckBox
|
import com.topjohnwu.widget.IndeterminateCheckBox
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@@ -289,3 +296,13 @@ fun TextView.setTextColorAttr(attr: Int) {
|
|||||||
context.theme.resolveAttribute(attr, tv, true)
|
context.theme.resolveAttribute(attr, tv, true)
|
||||||
setTextColor(tv.data)
|
setTextColor(tv.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("android:text")
|
||||||
|
fun TextView.setText(text: TextHolder) {
|
||||||
|
this.text = text.getText(context.resources)
|
||||||
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("items", "layout")
|
||||||
|
fun Spinner.setAdapter(items: Array<Any>, layoutRes: Int) {
|
||||||
|
adapter = ArrayAdapter(context, layoutRes, items)
|
||||||
|
}
|
@@ -0,0 +1,156 @@
|
|||||||
|
package com.topjohnwu.magisk.databinding
|
||||||
|
|
||||||
|
import androidx.annotation.MainThread
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import androidx.databinding.ListChangeRegistry
|
||||||
|
import androidx.databinding.ObservableList
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListUpdateCallback
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.util.AbstractList
|
||||||
|
|
||||||
|
// Only expose the immutable List types
|
||||||
|
interface DiffList<T : DiffItem<*>> : List<T> {
|
||||||
|
fun calculateDiff(newItems: List<T>): DiffUtil.DiffResult
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
fun update(newItems: List<T>, diffResult: DiffUtil.DiffResult)
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
suspend fun update(newItems: List<T>)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FilterList<T : DiffItem<*>> : List<T> {
|
||||||
|
fun filter(filter: (T) -> Boolean)
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
fun set(newItems: List<T>)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : DiffItem<*>> diffList(): DiffList<T> = DiffObservableList()
|
||||||
|
|
||||||
|
fun <T : DiffItem<*>> filterList(scope: CoroutineScope): FilterList<T> =
|
||||||
|
FilterableDiffObservableList(scope)
|
||||||
|
|
||||||
|
private open class DiffObservableList<T : DiffItem<*>>
|
||||||
|
: AbstractList<T>(), ObservableList<T>, DiffList<T>, ListUpdateCallback {
|
||||||
|
|
||||||
|
protected var list: List<T> = emptyList()
|
||||||
|
private val listeners = ListChangeRegistry()
|
||||||
|
|
||||||
|
override val size: Int get() = list.size
|
||||||
|
|
||||||
|
override fun get(index: Int) = list[index]
|
||||||
|
|
||||||
|
override fun calculateDiff(newItems: List<T>): DiffUtil.DiffResult {
|
||||||
|
return doCalculateDiff(list, newItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun doCalculateDiff(oldItems: List<T>, newItems: List<T>): DiffUtil.DiffResult {
|
||||||
|
return DiffUtil.calculateDiff(object : DiffUtil.Callback() {
|
||||||
|
override fun getOldListSize() = oldItems.size
|
||||||
|
|
||||||
|
override fun getNewListSize() = newItems.size
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||||
|
val oldItem = oldItems[oldItemPosition]
|
||||||
|
val newItem = newItems[newItemPosition]
|
||||||
|
return (oldItem as DiffItem<Any>).itemSameAs(newItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||||
|
val oldItem = oldItems[oldItemPosition]
|
||||||
|
val newItem = newItems[newItemPosition]
|
||||||
|
return (oldItem as DiffItem<Any>).contentSameAs(newItem)
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
override fun update(newItems: List<T>, diffResult: DiffUtil.DiffResult) {
|
||||||
|
list = ArrayList(newItems)
|
||||||
|
diffResult.dispatchUpdatesTo(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override suspend fun update(newItems: List<T>) {
|
||||||
|
val diffResult = calculateDiff(newItems)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
update(newItems, diffResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addOnListChangedCallback(listener: ObservableList.OnListChangedCallback<out ObservableList<T>>) {
|
||||||
|
listeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeOnListChangedCallback(listener: ObservableList.OnListChangedCallback<out ObservableList<T>>) {
|
||||||
|
listeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||||
|
listeners.notifyChanged(this, position, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
||||||
|
listeners.notifyMoved(this, fromPosition, toPosition, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInserted(position: Int, count: Int) {
|
||||||
|
modCount += 1
|
||||||
|
listeners.notifyInserted(this, position, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRemoved(position: Int, count: Int) {
|
||||||
|
modCount += 1
|
||||||
|
listeners.notifyRemoved(this, position, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FilterableDiffObservableList<T : DiffItem<*>>(
|
||||||
|
private val scope: CoroutineScope
|
||||||
|
) : DiffObservableList<T>(), FilterList<T> {
|
||||||
|
|
||||||
|
private var sublist: List<T> = emptyList()
|
||||||
|
private var job: Job? = null
|
||||||
|
private var lastFilter: ((T) -> Boolean)? = null
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
override fun filter(filter: (T) -> Boolean) {
|
||||||
|
lastFilter = filter
|
||||||
|
job?.cancel()
|
||||||
|
job = scope.launch(Dispatchers.Default) {
|
||||||
|
val oldList = sublist
|
||||||
|
val newList = list.filter(filter)
|
||||||
|
val diff = doCalculateDiff(oldList, newList)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
sublist = newList
|
||||||
|
diff.dispatchUpdatesTo(this@FilterableDiffObservableList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
override fun get(index: Int): T {
|
||||||
|
return sublist[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
override val size: Int
|
||||||
|
get() = sublist.size
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
override fun set(newItems: List<T>) {
|
||||||
|
onRemoved(0, sublist.size)
|
||||||
|
list = newItems
|
||||||
|
sublist = emptyList()
|
||||||
|
lastFilter?.let { filter(it) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,162 @@
|
|||||||
|
package com.topjohnwu.magisk.databinding
|
||||||
|
|
||||||
|
import androidx.databinding.ListChangeRegistry
|
||||||
|
import androidx.databinding.ObservableList
|
||||||
|
import androidx.databinding.ObservableList.OnListChangedCallback
|
||||||
|
import java.util.AbstractList
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class MergeObservableList<T> : AbstractList<T>(), ObservableList<T> {
|
||||||
|
|
||||||
|
private val lists: MutableList<List<T>> = mutableListOf()
|
||||||
|
private val listeners = ListChangeRegistry()
|
||||||
|
private val callback = Callback<T>()
|
||||||
|
|
||||||
|
override fun addOnListChangedCallback(callback: OnListChangedCallback<out ObservableList<T>>) {
|
||||||
|
listeners.add(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeOnListChangedCallback(callback: OnListChangedCallback<out ObservableList<T>>) {
|
||||||
|
listeners.remove(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(index: Int): T {
|
||||||
|
if (index < 0)
|
||||||
|
throw IndexOutOfBoundsException()
|
||||||
|
var idx = index
|
||||||
|
for (list in lists) {
|
||||||
|
val size = list.size
|
||||||
|
if (idx < size) {
|
||||||
|
return list[idx]
|
||||||
|
}
|
||||||
|
idx -= size
|
||||||
|
}
|
||||||
|
throw IndexOutOfBoundsException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val size: Int
|
||||||
|
get() = lists.fold(0) { i, it -> i + it.size }
|
||||||
|
|
||||||
|
|
||||||
|
fun insertItem(obj: T): MergeObservableList<T> {
|
||||||
|
val idx = size
|
||||||
|
lists.add(listOf(obj))
|
||||||
|
++modCount
|
||||||
|
listeners.notifyInserted(this, idx, 1)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insertList(list: List<T>): MergeObservableList<T> {
|
||||||
|
val idx = size
|
||||||
|
lists.add(list)
|
||||||
|
++modCount
|
||||||
|
(list as? ObservableList<T>)?.addOnListChangedCallback(callback)
|
||||||
|
if (list.isNotEmpty())
|
||||||
|
listeners.notifyInserted(this, idx, list.size)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeItem(obj: T): Boolean {
|
||||||
|
var idx = 0
|
||||||
|
for ((i, list) in lists.withIndex()) {
|
||||||
|
if (list !is ObservableList<*>) {
|
||||||
|
if (obj == list[0]) {
|
||||||
|
lists.removeAt(i)
|
||||||
|
++modCount
|
||||||
|
listeners.notifyRemoved(this, idx, 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idx += list.size
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeList(listToRemove: List<T>): Boolean {
|
||||||
|
var idx = 0
|
||||||
|
for ((i, list) in lists.withIndex()) {
|
||||||
|
if (listToRemove === list) {
|
||||||
|
(list as? ObservableList<T>)?.removeOnListChangedCallback(callback)
|
||||||
|
lists.removeAt(i)
|
||||||
|
++modCount
|
||||||
|
listeners.notifyRemoved(this, idx, list.size)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
idx += list.size
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
val sz = size
|
||||||
|
for (list in lists) {
|
||||||
|
if (list is ObservableList) {
|
||||||
|
list.removeOnListChangedCallback(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++modCount
|
||||||
|
lists.clear()
|
||||||
|
if (sz > 0)
|
||||||
|
listeners.notifyRemoved(this, 0, sz)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun subIndexToIndex(subList: List<*>, index: Int): Int {
|
||||||
|
if (index < 0)
|
||||||
|
throw IndexOutOfBoundsException()
|
||||||
|
var idx = 0
|
||||||
|
for (list in lists) {
|
||||||
|
if (subList === list) {
|
||||||
|
return idx + index
|
||||||
|
}
|
||||||
|
idx += list.size
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException()
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class Callback<T> : OnListChangedCallback<ObservableList<T>>() {
|
||||||
|
override fun onChanged(sender: ObservableList<T>) {
|
||||||
|
++modCount
|
||||||
|
listeners.notifyChanged(this@MergeObservableList)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeChanged(
|
||||||
|
sender: ObservableList<T>,
|
||||||
|
positionStart: Int,
|
||||||
|
itemCount: Int
|
||||||
|
) {
|
||||||
|
listeners.notifyChanged(this@MergeObservableList,
|
||||||
|
subIndexToIndex(sender, positionStart), itemCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeInserted(
|
||||||
|
sender: ObservableList<T>,
|
||||||
|
positionStart: Int,
|
||||||
|
itemCount: Int
|
||||||
|
) {
|
||||||
|
++modCount
|
||||||
|
listeners.notifyInserted(this@MergeObservableList,
|
||||||
|
subIndexToIndex(sender, positionStart), itemCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeMoved(
|
||||||
|
sender: ObservableList<T>,
|
||||||
|
fromPosition: Int,
|
||||||
|
toPosition: Int,
|
||||||
|
itemCount: Int
|
||||||
|
) {
|
||||||
|
val idx = subIndexToIndex(sender, 0)
|
||||||
|
listeners.notifyMoved(this@MergeObservableList,
|
||||||
|
idx + fromPosition, idx + toPosition, itemCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeRemoved(
|
||||||
|
sender: ObservableList<T>,
|
||||||
|
positionStart: Int,
|
||||||
|
itemCount: Int
|
||||||
|
) {
|
||||||
|
++modCount
|
||||||
|
listeners.notifyRemoved(this@MergeObservableList,
|
||||||
|
subIndexToIndex(sender, positionStart), itemCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,35 @@
|
|||||||
|
package com.topjohnwu.magisk.databinding
|
||||||
|
|
||||||
|
import androidx.databinding.PropertyChangeRegistry
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
abstract class RvItem {
|
||||||
|
abstract val layoutRes: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ObservableRvItem : RvItem(), ObservableHost {
|
||||||
|
override var callbacks: PropertyChangeRegistry? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ItemWrapper<E> {
|
||||||
|
val item: E
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ViewAwareItem {
|
||||||
|
fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DiffItem<T : Any> {
|
||||||
|
|
||||||
|
fun itemSameAs(other: T): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
return when (this) {
|
||||||
|
is ItemWrapper<*> -> item == (other as ItemWrapper<*>).item
|
||||||
|
is Comparable<*> -> compareValues(this, other as Comparable<*>) == 0
|
||||||
|
else -> this == other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun contentSameAs(other: T) = true
|
||||||
|
}
|
@@ -0,0 +1,121 @@
|
|||||||
|
package com.topjohnwu.magisk.databinding
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.util.SparseArray
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.databinding.BindingAdapter
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.databinding.ObservableList
|
||||||
|
import androidx.databinding.ObservableList.OnListChangedCallback
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
|
||||||
|
class RvItemAdapter<T: RvItem>(
|
||||||
|
val items: List<T>,
|
||||||
|
val extraBindings: SparseArray<*>?
|
||||||
|
) : RecyclerView.Adapter<RvItemAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
private var lifecycleOwner: LifecycleOwner? = null
|
||||||
|
private var recyclerView: RecyclerView? = null
|
||||||
|
private val observer by lazy(LazyThreadSafetyMode.NONE) { ListObserver<T>() }
|
||||||
|
|
||||||
|
override fun onAttachedToRecyclerView(rv: RecyclerView) {
|
||||||
|
lifecycleOwner = rv.findViewTreeLifecycleOwner()
|
||||||
|
recyclerView = rv
|
||||||
|
if (items is ObservableList)
|
||||||
|
items.addOnListChangedCallback(observer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromRecyclerView(rv: RecyclerView) {
|
||||||
|
lifecycleOwner = null
|
||||||
|
recyclerView = null
|
||||||
|
if (items is ObservableList)
|
||||||
|
items.removeOnListChangedCallback(observer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, layoutRes: Int): ViewHolder {
|
||||||
|
val inflator = LayoutInflater.from(parent.context)
|
||||||
|
return ViewHolder(DataBindingUtil.inflate(inflator, layoutRes, parent, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val item = items[position]
|
||||||
|
holder.binding.setVariable(BR.item, item)
|
||||||
|
extraBindings?.let {
|
||||||
|
for (i in 0 until it.size()) {
|
||||||
|
holder.binding.setVariable(it.keyAt(i), it.valueAt(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
holder.binding.lifecycleOwner = lifecycleOwner
|
||||||
|
holder.binding.executePendingBindings()
|
||||||
|
recyclerView?.let {
|
||||||
|
if (item is ViewAwareItem)
|
||||||
|
item.onBind(holder.binding, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int) = items[position].layoutRes
|
||||||
|
|
||||||
|
class ViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
inner class ListObserver<T: RvItem> : OnListChangedCallback<ObservableList<T>>() {
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
override fun onChanged(sender: ObservableList<T>) {
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeChanged(
|
||||||
|
sender: ObservableList<T>,
|
||||||
|
positionStart: Int,
|
||||||
|
itemCount: Int
|
||||||
|
) {
|
||||||
|
notifyItemRangeChanged(positionStart, itemCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeInserted(
|
||||||
|
sender: ObservableList<T>?,
|
||||||
|
positionStart: Int,
|
||||||
|
itemCount: Int
|
||||||
|
) {
|
||||||
|
notifyItemRangeInserted(positionStart, itemCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeMoved(
|
||||||
|
sender: ObservableList<T>?,
|
||||||
|
fromPosition: Int,
|
||||||
|
toPosition: Int,
|
||||||
|
itemCount: Int
|
||||||
|
) {
|
||||||
|
for (i in 0 until itemCount) {
|
||||||
|
notifyItemMoved(fromPosition + i, toPosition + i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeRemoved(
|
||||||
|
sender: ObservableList<T>?,
|
||||||
|
positionStart: Int,
|
||||||
|
itemCount: Int
|
||||||
|
) {
|
||||||
|
notifyItemRangeRemoved(positionStart, itemCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun bindExtra(body: (SparseArray<Any?>) -> Unit) = SparseArray<Any?>().also(body)
|
||||||
|
|
||||||
|
@BindingAdapter("items", "extraBindings", requireAll = false)
|
||||||
|
fun <T: RvItem> RecyclerView.setAdapter(items: List<T>?, extraBindings: SparseArray<*>?) {
|
||||||
|
if (items != null) {
|
||||||
|
val rva = (adapter as? RvItemAdapter<*>)
|
||||||
|
if (rva == null || rva.items !== items || rva.extraBindings !== extraBindings) {
|
||||||
|
adapter = RvItemAdapter(items, extraBindings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,30 +1,33 @@
|
|||||||
package com.topjohnwu.magisk.events.dialog
|
package com.topjohnwu.magisk.dialog
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.arch.UIActivity
|
||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.events.DialogBuilder
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
class DarkThemeDialog : DialogEvent() {
|
class DarkThemeDialog : DialogBuilder {
|
||||||
|
|
||||||
override fun build(dialog: MagiskDialog) {
|
override fun build(dialog: MagiskDialog) {
|
||||||
val activity = dialog.ownerActivity!!
|
val activity = dialog.ownerActivity!!
|
||||||
dialog.apply {
|
dialog.apply {
|
||||||
setTitle(R.string.settings_dark_mode_title)
|
setTitle(CoreR.string.settings_dark_mode_title)
|
||||||
setMessage(R.string.settings_dark_mode_message)
|
setMessage(CoreR.string.settings_dark_mode_message)
|
||||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||||
text = R.string.settings_dark_mode_light
|
text = CoreR.string.settings_dark_mode_light
|
||||||
icon = R.drawable.ic_day
|
icon = R.drawable.ic_day
|
||||||
onClick { selectTheme(AppCompatDelegate.MODE_NIGHT_NO, activity) }
|
onClick { selectTheme(AppCompatDelegate.MODE_NIGHT_NO, activity) }
|
||||||
}
|
}
|
||||||
setButton(MagiskDialog.ButtonType.NEUTRAL) {
|
setButton(MagiskDialog.ButtonType.NEUTRAL) {
|
||||||
text = R.string.settings_dark_mode_system
|
text = CoreR.string.settings_dark_mode_system
|
||||||
icon = R.drawable.ic_day_night
|
icon = R.drawable.ic_day_night
|
||||||
onClick { selectTheme(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, activity) }
|
onClick { selectTheme(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, activity) }
|
||||||
}
|
}
|
||||||
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||||
text = R.string.settings_dark_mode_dark
|
text = CoreR.string.settings_dark_mode_dark
|
||||||
icon = R.drawable.ic_night
|
icon = R.drawable.ic_night
|
||||||
onClick { selectTheme(AppCompatDelegate.MODE_NIGHT_YES, activity) }
|
onClick { selectTheme(AppCompatDelegate.MODE_NIGHT_YES, activity) }
|
||||||
}
|
}
|
||||||
@@ -33,6 +36,6 @@ class DarkThemeDialog : DialogEvent() {
|
|||||||
|
|
||||||
private fun selectTheme(mode: Int, activity: Activity) {
|
private fun selectTheme(mode: Int, activity: Activity) {
|
||||||
Config.darkTheme = mode
|
Config.darkTheme = mode
|
||||||
activity.recreate()
|
(activity as UIActivity<*>).delegate.localNightMode = mode
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,16 +1,21 @@
|
|||||||
package com.topjohnwu.magisk.events.dialog
|
package com.topjohnwu.magisk.dialog
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.os.postDelayed
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.core.BuildConfig
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
import com.topjohnwu.magisk.core.R
|
||||||
|
import com.topjohnwu.magisk.core.ktx.reboot
|
||||||
|
import com.topjohnwu.magisk.core.ktx.toast
|
||||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||||
|
import com.topjohnwu.magisk.events.DialogBuilder
|
||||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class EnvFixDialog(private val vm: HomeViewModel) : DialogEvent() {
|
class EnvFixDialog(private val vm: HomeViewModel, private val code: Int) : DialogBuilder {
|
||||||
|
|
||||||
override fun build(dialog: MagiskDialog) {
|
override fun build(dialog: MagiskDialog) {
|
||||||
dialog.apply {
|
dialog.apply {
|
||||||
@@ -26,10 +31,16 @@ class EnvFixDialog(private val vm: HomeViewModel) : DialogEvent() {
|
|||||||
resetButtons()
|
resetButtons()
|
||||||
setCancelable(false)
|
setCancelable(false)
|
||||||
}
|
}
|
||||||
(dialog.ownerActivity as BaseActivity).lifecycleScope.launch {
|
dialog.activity.lifecycleScope.launch {
|
||||||
MagiskInstaller.FixEnv {
|
MagiskInstaller.FixEnv().exec { success ->
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}.exec()
|
context.toast(
|
||||||
|
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
)
|
||||||
|
if (success)
|
||||||
|
UiThreadHandler.handler.postDelayed(5000) { reboot() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,8 +49,10 @@ class EnvFixDialog(private val vm: HomeViewModel) : DialogEvent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Info.env.versionCode != BuildConfig.VERSION_CODE ||
|
if (code == 2 || // No rules block, module policy not loaded
|
||||||
Info.env.versionString != BuildConfig.VERSION_NAME) {
|
Info.env.versionCode != BuildConfig.APP_VERSION_CODE ||
|
||||||
|
Info.env.versionString != BuildConfig.APP_VERSION_NAME) {
|
||||||
|
dialog.setMessage(R.string.env_full_fix_msg)
|
||||||
dialog.setButton(MagiskDialog.ButtonType.POSITIVE) {
|
dialog.setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||||
text = android.R.string.ok
|
text = android.R.string.ok
|
||||||
onClick {
|
onClick {
|
@@ -0,0 +1,33 @@
|
|||||||
|
package com.topjohnwu.magisk.dialog
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import com.topjohnwu.magisk.MainDirections
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.R
|
||||||
|
import com.topjohnwu.magisk.events.DialogBuilder
|
||||||
|
import com.topjohnwu.magisk.ui.module.ModuleViewModel
|
||||||
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
|
|
||||||
|
class LocalModuleInstallDialog(
|
||||||
|
private val viewModel: ModuleViewModel,
|
||||||
|
private val uri: Uri,
|
||||||
|
private val displayName: String
|
||||||
|
) : DialogBuilder {
|
||||||
|
override fun build(dialog: MagiskDialog) {
|
||||||
|
dialog.apply {
|
||||||
|
setTitle(R.string.confirm_install_title)
|
||||||
|
setMessage(context.getString(R.string.confirm_install, displayName))
|
||||||
|
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||||
|
text = android.R.string.ok
|
||||||
|
onClick {
|
||||||
|
viewModel.apply {
|
||||||
|
MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, uri).navigate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||||
|
text = android.R.string.cancel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,11 @@
|
|||||||
package com.topjohnwu.magisk.events.dialog
|
package com.topjohnwu.magisk.dialog
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.core.AppContext
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.download.DownloadService
|
import com.topjohnwu.magisk.core.R
|
||||||
|
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||||
|
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||||
import com.topjohnwu.magisk.core.download.Subject
|
import com.topjohnwu.magisk.core.download.Subject
|
||||||
import com.topjohnwu.magisk.di.AppContext
|
|
||||||
import com.topjohnwu.magisk.di.ServiceLocator
|
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ class ManagerInstallDialog : MarkDownDialog() {
|
|||||||
setCancelable(true)
|
setCancelable(true)
|
||||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||||
text = R.string.install
|
text = R.string.install
|
||||||
onClick { DownloadService.start(context, Subject.Manager()) }
|
onClick { DownloadEngine.startWithActivity(activity, Subject.App()) }
|
||||||
}
|
}
|
||||||
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||||
text = android.R.string.cancel
|
text = android.R.string.cancel
|
@@ -1,20 +1,21 @@
|
|||||||
package com.topjohnwu.magisk.events.dialog
|
package com.topjohnwu.magisk.dialog
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.CallSuper
|
import androidx.annotation.CallSuper
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||||
import com.topjohnwu.magisk.di.ServiceLocator
|
import com.topjohnwu.magisk.events.DialogBuilder
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
abstract class MarkDownDialog : DialogEvent() {
|
abstract class MarkDownDialog : DialogBuilder {
|
||||||
|
|
||||||
abstract suspend fun getMarkdownText(): String
|
abstract suspend fun getMarkdownText(): String
|
||||||
|
|
||||||
@@ -24,13 +25,13 @@ abstract class MarkDownDialog : DialogEvent() {
|
|||||||
val view = LayoutInflater.from(context).inflate(R.layout.markdown_window_md2, null)
|
val view = LayoutInflater.from(context).inflate(R.layout.markdown_window_md2, null)
|
||||||
setView(view)
|
setView(view)
|
||||||
val tv = view.findViewById<TextView>(R.id.md_txt)
|
val tv = view.findViewById<TextView>(R.id.md_txt)
|
||||||
(ownerActivity as BaseActivity).lifecycleScope.launch {
|
activity.lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
val text = withContext(Dispatchers.IO) { getMarkdownText() }
|
val text = withContext(Dispatchers.IO) { getMarkdownText() }
|
||||||
ServiceLocator.markwon.setMarkdown(tv, text)
|
ServiceLocator.markwon.setMarkdown(tv, text)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
tv.setText(R.string.download_file_error)
|
tv.setText(CoreR.string.download_file_error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,14 +1,17 @@
|
|||||||
package com.topjohnwu.magisk.events.dialog
|
package com.topjohnwu.magisk.dialog
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R
|
import android.content.Context
|
||||||
import com.topjohnwu.magisk.core.download.Action
|
import com.topjohnwu.magisk.core.R
|
||||||
import com.topjohnwu.magisk.core.download.DownloadService
|
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||||
|
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||||
import com.topjohnwu.magisk.core.download.Subject
|
import com.topjohnwu.magisk.core.download.Subject
|
||||||
import com.topjohnwu.magisk.core.model.module.OnlineModule
|
import com.topjohnwu.magisk.core.model.module.OnlineModule
|
||||||
import com.topjohnwu.magisk.di.ServiceLocator
|
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
class ModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog() {
|
class OnlineModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog() {
|
||||||
|
|
||||||
private val svc get() = ServiceLocator.networkService
|
private val svc get() = ServiceLocator.networkService
|
||||||
|
|
||||||
@@ -17,14 +20,21 @@ class ModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog() {
|
|||||||
return if (str.length > 1000) str.substring(0, 1000) else str
|
return if (str.length > 1000) str.substring(0, 1000) else str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class Module(
|
||||||
|
override val module: OnlineModule,
|
||||||
|
override val autoLaunch: Boolean,
|
||||||
|
override val notifyId: Int = Notifications.nextId()
|
||||||
|
) : Subject.Module() {
|
||||||
|
override fun pendingIntent(context: Context) = FlashFragment.installIntent(context, file)
|
||||||
|
}
|
||||||
|
|
||||||
override fun build(dialog: MagiskDialog) {
|
override fun build(dialog: MagiskDialog) {
|
||||||
super.build(dialog)
|
super.build(dialog)
|
||||||
dialog.apply {
|
dialog.apply {
|
||||||
|
|
||||||
fun download(install: Boolean) {
|
fun download(install: Boolean) {
|
||||||
val action = if (install) Action.Flash else Action.Download
|
DownloadEngine.startWithActivity(activity, Module(item, install))
|
||||||
val subject = Subject.Module(item, action)
|
|
||||||
DownloadService.start(context, subject)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val title = context.getString(R.string.repo_install_title,
|
val title = context.getString(R.string.repo_install_title,
|
@@ -1,9 +1,10 @@
|
|||||||
package com.topjohnwu.magisk.events.dialog
|
package com.topjohnwu.magisk.dialog
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.core.R
|
||||||
|
import com.topjohnwu.magisk.events.DialogBuilder
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
|
|
||||||
class SecondSlotWarningDialog : DialogEvent() {
|
class SecondSlotWarningDialog : DialogBuilder {
|
||||||
|
|
||||||
override fun build(dialog: MagiskDialog) {
|
override fun build(dialog: MagiskDialog) {
|
||||||
dialog.apply {
|
dialog.apply {
|
@@ -0,0 +1,25 @@
|
|||||||
|
package com.topjohnwu.magisk.dialog
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.core.R
|
||||||
|
import com.topjohnwu.magisk.events.DialogBuilder
|
||||||
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
|
|
||||||
|
class SuperuserRevokeDialog(
|
||||||
|
private val appName: String,
|
||||||
|
private val onSuccess: () -> Unit
|
||||||
|
) : DialogBuilder {
|
||||||
|
|
||||||
|
override fun build(dialog: MagiskDialog) {
|
||||||
|
dialog.apply {
|
||||||
|
setTitle(R.string.su_revoke_title)
|
||||||
|
setMessage(R.string.su_revoke_msg, appName)
|
||||||
|
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||||
|
text = android.R.string.ok
|
||||||
|
onClick { onSuccess() }
|
||||||
|
}
|
||||||
|
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||||
|
text = android.R.string.cancel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,16 +1,19 @@
|
|||||||
package com.topjohnwu.magisk.events.dialog
|
package com.topjohnwu.magisk.dialog
|
||||||
|
|
||||||
import android.app.ProgressDialog
|
import android.app.ProgressDialog
|
||||||
import android.content.Context
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.topjohnwu.magisk.R
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.topjohnwu.magisk.arch.NavigationActivity
|
import com.topjohnwu.magisk.arch.NavigationActivity
|
||||||
|
import com.topjohnwu.magisk.arch.UIActivity
|
||||||
|
import com.topjohnwu.magisk.core.R
|
||||||
|
import com.topjohnwu.magisk.core.ktx.toast
|
||||||
|
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||||
|
import com.topjohnwu.magisk.events.DialogBuilder
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
import com.topjohnwu.superuser.Shell
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class UninstallDialog : DialogEvent() {
|
class UninstallDialog : DialogBuilder {
|
||||||
|
|
||||||
override fun build(dialog: MagiskDialog) {
|
override fun build(dialog: MagiskDialog) {
|
||||||
dialog.apply {
|
dialog.apply {
|
||||||
@@ -18,7 +21,7 @@ class UninstallDialog : DialogEvent() {
|
|||||||
setMessage(R.string.uninstall_magisk_msg)
|
setMessage(R.string.uninstall_magisk_msg)
|
||||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||||
text = R.string.restore_img
|
text = R.string.restore_img
|
||||||
onClick { restore(dialog.context) }
|
onClick { restore(dialog.activity) }
|
||||||
}
|
}
|
||||||
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||||
text = R.string.complete_uninstall
|
text = R.string.complete_uninstall
|
||||||
@@ -28,18 +31,20 @@ class UninstallDialog : DialogEvent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
private fun restore(context: Context) {
|
private fun restore(activity: UIActivity<*>) {
|
||||||
val dialog = ProgressDialog(context).apply {
|
val dialog = ProgressDialog(activity).apply {
|
||||||
setMessage(context.getString(R.string.restore_img_msg))
|
setMessage(activity.getString(R.string.restore_img_msg))
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
|
||||||
Shell.su("restore_imgs").submit { result ->
|
activity.lifecycleScope.launch {
|
||||||
dialog.dismiss()
|
MagiskInstaller.Restore().exec { success ->
|
||||||
if (result.isSuccess) {
|
dialog.dismiss()
|
||||||
Utils.toast(R.string.restore_done, Toast.LENGTH_SHORT)
|
if (success) {
|
||||||
} else {
|
activity.toast(R.string.restore_done, Toast.LENGTH_SHORT)
|
||||||
Utils.toast(R.string.restore_fail, Toast.LENGTH_LONG)
|
} else {
|
||||||
|
activity.toast(R.string.restore_fail, Toast.LENGTH_LONG)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
124
app/apk/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt
Normal file
124
app/apk/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package com.topjohnwu.magisk.events
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.navigation.NavDirections
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import com.topjohnwu.magisk.arch.ActivityExecutor
|
||||||
|
import com.topjohnwu.magisk.arch.ContextExecutor
|
||||||
|
import com.topjohnwu.magisk.arch.NavigationActivity
|
||||||
|
import com.topjohnwu.magisk.arch.UIActivity
|
||||||
|
import com.topjohnwu.magisk.arch.ViewEvent
|
||||||
|
import com.topjohnwu.magisk.core.base.ContentResultCallback
|
||||||
|
import com.topjohnwu.magisk.core.base.relaunch
|
||||||
|
import com.topjohnwu.magisk.utils.TextHolder
|
||||||
|
import com.topjohnwu.magisk.utils.asText
|
||||||
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
|
import com.topjohnwu.magisk.view.Shortcuts
|
||||||
|
|
||||||
|
class PermissionEvent(
|
||||||
|
private val permission: String,
|
||||||
|
private val callback: (Boolean) -> Unit
|
||||||
|
) : ViewEvent(), ActivityExecutor {
|
||||||
|
|
||||||
|
override fun invoke(activity: UIActivity<*>) =
|
||||||
|
activity.withPermission(permission, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackPressEvent : ViewEvent(), ActivityExecutor {
|
||||||
|
override fun invoke(activity: UIActivity<*>) {
|
||||||
|
activity.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DieEvent : ViewEvent(), ActivityExecutor {
|
||||||
|
override fun invoke(activity: UIActivity<*>) {
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShowUIEvent(private val delegate: View.AccessibilityDelegate?)
|
||||||
|
: ViewEvent(), ActivityExecutor {
|
||||||
|
override fun invoke(activity: UIActivity<*>) {
|
||||||
|
activity.setContentView()
|
||||||
|
activity.setAccessibilityDelegate(delegate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecreateEvent : ViewEvent(), ActivityExecutor {
|
||||||
|
override fun invoke(activity: UIActivity<*>) {
|
||||||
|
activity.relaunch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthEvent(
|
||||||
|
private val callback: () -> Unit
|
||||||
|
) : ViewEvent(), ActivityExecutor {
|
||||||
|
|
||||||
|
override fun invoke(activity: UIActivity<*>) {
|
||||||
|
activity.withAuthentication { if (it) callback() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetContentEvent(
|
||||||
|
private val type: String,
|
||||||
|
private val callback: ContentResultCallback
|
||||||
|
) : ViewEvent(), ActivityExecutor {
|
||||||
|
override fun invoke(activity: UIActivity<*>) {
|
||||||
|
activity.getContent(type, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NavigationEvent(
|
||||||
|
private val directions: NavDirections,
|
||||||
|
private val pop: Boolean
|
||||||
|
) : ViewEvent(), ActivityExecutor {
|
||||||
|
override fun invoke(activity: UIActivity<*>) {
|
||||||
|
(activity as? NavigationActivity<*>)?.apply {
|
||||||
|
if (pop) navigation.popBackStack()
|
||||||
|
directions.navigate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddHomeIconEvent : ViewEvent(), ContextExecutor {
|
||||||
|
override fun invoke(context: Context) {
|
||||||
|
Shortcuts.addHomeIcon(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnackbarEvent(
|
||||||
|
private val msg: TextHolder,
|
||||||
|
private val length: Int = Snackbar.LENGTH_SHORT,
|
||||||
|
private val builder: Snackbar.() -> Unit = {}
|
||||||
|
) : ViewEvent(), ActivityExecutor {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@StringRes res: Int,
|
||||||
|
length: Int = Snackbar.LENGTH_SHORT,
|
||||||
|
builder: Snackbar.() -> Unit = {}
|
||||||
|
) : this(res.asText(), length, builder)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
msg: String,
|
||||||
|
length: Int = Snackbar.LENGTH_SHORT,
|
||||||
|
builder: Snackbar.() -> Unit = {}
|
||||||
|
) : this(msg.asText(), length, builder)
|
||||||
|
|
||||||
|
override fun invoke(activity: UIActivity<*>) {
|
||||||
|
activity.showSnackbar(msg.getText(activity.resources), length, builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DialogEvent(
|
||||||
|
private val builder: DialogBuilder
|
||||||
|
) : ViewEvent(), ActivityExecutor {
|
||||||
|
override fun invoke(activity: UIActivity<*>) {
|
||||||
|
MagiskDialog(activity).apply(builder::build).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DialogBuilder {
|
||||||
|
fun build(dialog: MagiskDialog)
|
||||||
|
}
|
@@ -1,37 +1,58 @@
|
|||||||
package com.topjohnwu.magisk.ui
|
package com.topjohnwu.magisk.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.NavDirections
|
import androidx.navigation.NavDirections
|
||||||
import com.topjohnwu.magisk.MainDirections
|
import com.topjohnwu.magisk.MainDirections
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.BaseMainActivity
|
|
||||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||||
import com.topjohnwu.magisk.core.*
|
import com.topjohnwu.magisk.arch.NavigationActivity
|
||||||
|
import com.topjohnwu.magisk.arch.startAnimations
|
||||||
|
import com.topjohnwu.magisk.arch.viewModel
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.base.SplashController
|
||||||
|
import com.topjohnwu.magisk.core.base.SplashScreenHost
|
||||||
|
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||||
|
import com.topjohnwu.magisk.core.ktx.toast
|
||||||
|
import com.topjohnwu.magisk.core.model.module.LocalModule
|
||||||
|
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||||
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
|
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
|
||||||
import com.topjohnwu.magisk.di.viewModel
|
|
||||||
import com.topjohnwu.magisk.ktx.startAnimations
|
|
||||||
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
|
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.ui.theme.Theme
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
import com.topjohnwu.magisk.view.Shortcuts
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
class MainViewModel : BaseViewModel()
|
class MainViewModel : BaseViewModel()
|
||||||
|
|
||||||
class MainActivity : BaseMainActivity<ActivityMainMd2Binding>() {
|
class MainActivity : NavigationActivity<ActivityMainMd2Binding>(), SplashScreenHost {
|
||||||
|
|
||||||
override val layoutRes = R.layout.activity_main_md2
|
override val layoutRes = R.layout.activity_main_md2
|
||||||
override val viewModel by viewModel<MainViewModel>()
|
override val viewModel by viewModel<MainViewModel>()
|
||||||
override val navHostId: Int = R.id.main_nav_host
|
override val navHostId: Int = R.id.main_nav_host
|
||||||
|
override val splashController = SplashController(this)
|
||||||
|
override val snackbarView: View
|
||||||
|
get() {
|
||||||
|
val fragmentOverride = currentFragment?.snackbarView
|
||||||
|
return fragmentOverride ?: super.snackbarView
|
||||||
|
}
|
||||||
override val snackbarAnchorView: View?
|
override val snackbarAnchorView: View?
|
||||||
get() {
|
get() {
|
||||||
val fragmentAnchor = currentFragment?.snackbarAnchorView
|
val fragmentAnchor = currentFragment?.snackbarAnchorView
|
||||||
@@ -44,14 +65,34 @@ class MainActivity : BaseMainActivity<ActivityMainMd2Binding>() {
|
|||||||
|
|
||||||
private var isRootFragment = true
|
private var isRootFragment = true
|
||||||
|
|
||||||
override fun showMainUI(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
setTheme(Theme.selected.themeRes)
|
||||||
|
splashController.preOnCreate()
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
splashController.onCreate(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
splashController.onResume()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
override fun onCreateUi(savedInstanceState: Bundle?) {
|
||||||
setContentView()
|
setContentView()
|
||||||
showUnsupportedMessage()
|
showUnsupportedMessage()
|
||||||
askForHomeShortcut()
|
askForHomeShortcut()
|
||||||
|
|
||||||
|
// Ask permission to post notifications for background update check
|
||||||
|
if (Config.checkUpdate) {
|
||||||
|
withPermission(Manifest.permission.POST_NOTIFICATIONS) {
|
||||||
|
Config.checkUpdate = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||||
|
|
||||||
navigation?.addOnDestinationChangedListener { _, destination, _ ->
|
navigation.addOnDestinationChangedListener { _, destination, _ ->
|
||||||
isRootFragment = when (destination.id) {
|
isRootFragment = when (destination.id) {
|
||||||
R.id.homeFragment,
|
R.id.homeFragment,
|
||||||
R.id.modulesFragment,
|
R.id.modulesFragment,
|
||||||
@@ -80,8 +121,8 @@ class MainActivity : BaseMainActivity<ActivityMainMd2Binding>() {
|
|||||||
// https://issuetracker.google.com/issues/124538620
|
// https://issuetracker.google.com/issues/124538620
|
||||||
}
|
}
|
||||||
binding.mainNavigation.menu.apply {
|
binding.mainNavigation.menu.apply {
|
||||||
findItem(R.id.superuserFragment)?.isEnabled = Utils.showSuperUser()
|
findItem(R.id.superuserFragment)?.isEnabled = Info.showSuperUser
|
||||||
findItem(R.id.modulesFragment)?.isEnabled = Info.env.isActive
|
findItem(R.id.modulesFragment)?.isEnabled = Info.env.isActive && LocalModule.loaded()
|
||||||
}
|
}
|
||||||
|
|
||||||
val section =
|
val section =
|
||||||
@@ -147,11 +188,36 @@ class MainActivity : BaseMainActivity<ActivityMainMd2Binding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
override fun showInvalidStateMessage(): Unit = runOnUiThread {
|
||||||
|
MagiskDialog(this).apply {
|
||||||
|
setTitle(CoreR.string.unsupport_nonroot_stub_title)
|
||||||
|
setMessage(CoreR.string.unsupport_nonroot_stub_msg)
|
||||||
|
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||||
|
text = CoreR.string.install
|
||||||
|
onClick {
|
||||||
|
withPermission(REQUEST_INSTALL_PACKAGES) {
|
||||||
|
if (!it) {
|
||||||
|
toast(CoreR.string.install_unknown_denied, Toast.LENGTH_SHORT)
|
||||||
|
showInvalidStateMessage()
|
||||||
|
} else {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
AppMigration.restore(this@MainActivity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCancelable(false)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun showUnsupportedMessage() {
|
private fun showUnsupportedMessage() {
|
||||||
if (Info.env.isUnsupported) {
|
if (Info.env.isUnsupported) {
|
||||||
MagiskDialog(this).apply {
|
MagiskDialog(this).apply {
|
||||||
setTitle(R.string.unsupport_magisk_title)
|
setTitle(CoreR.string.unsupport_magisk_title)
|
||||||
setMessage(R.string.unsupport_magisk_msg, Const.Version.MIN_VERSION)
|
setMessage(CoreR.string.unsupport_magisk_msg, Const.Version.MIN_VERSION)
|
||||||
setButton(MagiskDialog.ButtonType.POSITIVE) { text = android.R.string.ok }
|
setButton(MagiskDialog.ButtonType.POSITIVE) { text = android.R.string.ok }
|
||||||
setCancelable(false)
|
setCancelable(false)
|
||||||
}.show()
|
}.show()
|
||||||
@@ -162,8 +228,8 @@ class MainActivity : BaseMainActivity<ActivityMainMd2Binding>() {
|
|||||||
?.filterNot { File("$it/magisk").exists() }
|
?.filterNot { File("$it/magisk").exists() }
|
||||||
?.any { File("$it/su").exists() } == true) {
|
?.any { File("$it/su").exists() } == true) {
|
||||||
MagiskDialog(this).apply {
|
MagiskDialog(this).apply {
|
||||||
setTitle(R.string.unsupport_general_title)
|
setTitle(CoreR.string.unsupport_general_title)
|
||||||
setMessage(R.string.unsupport_other_su_msg)
|
setMessage(CoreR.string.unsupport_other_su_msg)
|
||||||
setButton(MagiskDialog.ButtonType.POSITIVE) { text = android.R.string.ok }
|
setButton(MagiskDialog.ButtonType.POSITIVE) { text = android.R.string.ok }
|
||||||
setCancelable(false)
|
setCancelable(false)
|
||||||
}.show()
|
}.show()
|
||||||
@@ -171,8 +237,8 @@ class MainActivity : BaseMainActivity<ActivityMainMd2Binding>() {
|
|||||||
|
|
||||||
if (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0) {
|
if (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0) {
|
||||||
MagiskDialog(this).apply {
|
MagiskDialog(this).apply {
|
||||||
setTitle(R.string.unsupport_general_title)
|
setTitle(CoreR.string.unsupport_general_title)
|
||||||
setMessage(R.string.unsupport_system_app_msg)
|
setMessage(CoreR.string.unsupport_system_app_msg)
|
||||||
setButton(MagiskDialog.ButtonType.POSITIVE) { text = android.R.string.ok }
|
setButton(MagiskDialog.ButtonType.POSITIVE) { text = android.R.string.ok }
|
||||||
setCancelable(false)
|
setCancelable(false)
|
||||||
}.show()
|
}.show()
|
||||||
@@ -180,8 +246,8 @@ class MainActivity : BaseMainActivity<ActivityMainMd2Binding>() {
|
|||||||
|
|
||||||
if (applicationInfo.flags and ApplicationInfo.FLAG_EXTERNAL_STORAGE != 0) {
|
if (applicationInfo.flags and ApplicationInfo.FLAG_EXTERNAL_STORAGE != 0) {
|
||||||
MagiskDialog(this).apply {
|
MagiskDialog(this).apply {
|
||||||
setTitle(R.string.unsupport_general_title)
|
setTitle(CoreR.string.unsupport_general_title)
|
||||||
setMessage(R.string.unsupport_external_storage_msg)
|
setMessage(CoreR.string.unsupport_external_storage_msg)
|
||||||
setButton(MagiskDialog.ButtonType.POSITIVE) { text = android.R.string.ok }
|
setButton(MagiskDialog.ButtonType.POSITIVE) { text = android.R.string.ok }
|
||||||
setCancelable(false)
|
setCancelable(false)
|
||||||
}.show()
|
}.show()
|
||||||
@@ -194,8 +260,8 @@ class MainActivity : BaseMainActivity<ActivityMainMd2Binding>() {
|
|||||||
// Ask and show dialog
|
// Ask and show dialog
|
||||||
Config.askedHome = true
|
Config.askedHome = true
|
||||||
MagiskDialog(this).apply {
|
MagiskDialog(this).apply {
|
||||||
setTitle(R.string.add_shortcut_title)
|
setTitle(CoreR.string.add_shortcut_title)
|
||||||
setMessage(R.string.add_shortcut_msg)
|
setMessage(CoreR.string.add_shortcut_msg)
|
||||||
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||||
text = android.R.string.cancel
|
text = android.R.string.cancel
|
||||||
}
|
}
|
@@ -4,14 +4,20 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.ComponentInfo
|
import android.content.pm.ComponentInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.PackageManager.*
|
import android.content.pm.PackageManager.GET_ACTIVITIES
|
||||||
|
import android.content.pm.PackageManager.GET_PROVIDERS
|
||||||
|
import android.content.pm.PackageManager.GET_RECEIVERS
|
||||||
|
import android.content.pm.PackageManager.GET_SERVICES
|
||||||
|
import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS
|
||||||
|
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
|
||||||
import android.content.pm.ServiceInfo
|
import android.content.pm.ServiceInfo
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Build
|
||||||
import android.os.Build.VERSION.SDK_INT
|
import android.os.Build.VERSION.SDK_INT
|
||||||
import androidx.core.os.ProcessCompat
|
import androidx.core.os.ProcessCompat
|
||||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
import com.topjohnwu.magisk.core.ktx.getLabel
|
||||||
import com.topjohnwu.magisk.ktx.getLabel
|
import java.util.Locale
|
||||||
import java.util.*
|
import java.util.TreeSet
|
||||||
|
|
||||||
class CmdlineListItem(line: String) {
|
class CmdlineListItem(line: String) {
|
||||||
val packageName: String
|
val packageName: String
|
||||||
@@ -67,7 +73,8 @@ class AppProcessInfo(
|
|||||||
val proc = info.processName ?: info.packageName
|
val proc = info.processName ?: info.packageName
|
||||||
createProcess("${proc}_zygote")
|
createProcess("${proc}_zygote")
|
||||||
} else {
|
} else {
|
||||||
val proc = if (SDK_INT >= 29) "${it.getProcName()}:${it.name}" else it.getProcName()
|
val proc = if (SDK_INT >= Build.VERSION_CODES.Q)
|
||||||
|
"${it.getProcName()}:${it.name}" else it.getProcName()
|
||||||
createProcess(proc, ISOLATED_MAGIC)
|
createProcess(proc, ISOLATED_MAGIC)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -95,7 +102,7 @@ class AppProcessInfo(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val comparator = compareBy<AppProcessInfo>(
|
private val comparator = compareBy<AppProcessInfo>(
|
||||||
{ it.label.lowercase(currentLocale) },
|
{ it.label.lowercase(Locale.ROOT) },
|
||||||
{ it.info.packageName }
|
{ it.info.packageName }
|
||||||
)
|
)
|
||||||
}
|
}
|
@@ -1,33 +1,33 @@
|
|||||||
package com.topjohnwu.magisk.ui.deny
|
package com.topjohnwu.magisk.ui.deny
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
|
import androidx.core.view.MenuProvider
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.BaseFragment
|
import com.topjohnwu.magisk.arch.BaseFragment
|
||||||
|
import com.topjohnwu.magisk.arch.viewModel
|
||||||
|
import com.topjohnwu.magisk.core.ktx.hideKeyboard
|
||||||
import com.topjohnwu.magisk.databinding.FragmentDenyMd2Binding
|
import com.topjohnwu.magisk.databinding.FragmentDenyMd2Binding
|
||||||
import com.topjohnwu.magisk.di.viewModel
|
|
||||||
import com.topjohnwu.magisk.ktx.hideKeyboard
|
|
||||||
import rikka.recyclerview.addEdgeSpacing
|
import rikka.recyclerview.addEdgeSpacing
|
||||||
import rikka.recyclerview.addItemSpacing
|
import rikka.recyclerview.addItemSpacing
|
||||||
import rikka.recyclerview.fixEdgeEffect
|
import rikka.recyclerview.fixEdgeEffect
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
class DenyListFragment : BaseFragment<FragmentDenyMd2Binding>() {
|
class DenyListFragment : BaseFragment<FragmentDenyMd2Binding>(), MenuProvider {
|
||||||
|
|
||||||
override val layoutRes = R.layout.fragment_deny_md2
|
override val layoutRes = R.layout.fragment_deny_md2
|
||||||
override val viewModel by viewModel<DenyListViewModel>()
|
override val viewModel by viewModel<DenyListViewModel>()
|
||||||
|
|
||||||
private lateinit var searchView: SearchView
|
private lateinit var searchView: SearchView
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onStart() {
|
||||||
super.onAttach(context)
|
super.onStart()
|
||||||
activity.setTitle(R.string.denylist)
|
activity?.setTitle(CoreR.string.denylist)
|
||||||
setHasOptionsMenu(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -35,7 +35,7 @@ class DenyListFragment : BaseFragment<FragmentDenyMd2Binding>() {
|
|||||||
|
|
||||||
binding.appList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
binding.appList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||||
if (newState != RecyclerView.SCROLL_STATE_IDLE) hideKeyboard()
|
if (newState != RecyclerView.SCROLL_STATE_IDLE) activity?.hideKeyboard()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -56,10 +56,10 @@ class DenyListFragment : BaseFragment<FragmentDenyMd2Binding>() {
|
|||||||
return super.onBackPressed()
|
return super.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
inflater.inflate(R.menu.menu_deny_md2, menu)
|
inflater.inflate(R.menu.menu_deny_md2, menu)
|
||||||
searchView = menu.findItem(R.id.action_search).actionView as SearchView
|
searchView = menu.findItem(R.id.action_search).actionView as SearchView
|
||||||
searchView.queryHint = searchView.context.getString(R.string.hide_filter_hint)
|
searchView.queryHint = searchView.context.getString(CoreR.string.hide_filter_hint)
|
||||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
viewModel.query = query ?: ""
|
viewModel.query = query ?: ""
|
||||||
@@ -73,7 +73,7 @@ class DenyListFragment : BaseFragment<FragmentDenyMd2Binding>() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onMenuItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.action_show_system -> {
|
R.id.action_show_system -> {
|
||||||
val check = !item.isChecked
|
val check = !item.isChecked
|
||||||
@@ -91,7 +91,7 @@ class DenyListFragment : BaseFragment<FragmentDenyMd2Binding>() {
|
|||||||
return super.onOptionsItemSelected(item)
|
return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
override fun onPrepareMenu(menu: Menu) {
|
||||||
val showSystem = menu.findItem(R.id.action_show_system)
|
val showSystem = menu.findItem(R.id.action_show_system)
|
||||||
val showOS = menu.findItem(R.id.action_show_OS)
|
val showOS = menu.findItem(R.id.action_show_OS)
|
||||||
showOS.isEnabled = showSystem.isChecked
|
showOS.isEnabled = showSystem.isChecked
|
@@ -5,17 +5,17 @@ import android.view.ViewGroup
|
|||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.databinding.ComparableRv
|
import com.topjohnwu.magisk.arch.startAnimations
|
||||||
import com.topjohnwu.magisk.databinding.ObservableDiffRvItem
|
import com.topjohnwu.magisk.databinding.DiffItem
|
||||||
|
import com.topjohnwu.magisk.databinding.ObservableRvItem
|
||||||
import com.topjohnwu.magisk.databinding.addOnPropertyChangedCallback
|
import com.topjohnwu.magisk.databinding.addOnPropertyChangedCallback
|
||||||
import com.topjohnwu.magisk.databinding.set
|
import com.topjohnwu.magisk.databinding.set
|
||||||
import com.topjohnwu.magisk.ktx.startAnimations
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class DenyListRvItem(
|
class DenyListRvItem(
|
||||||
val info: AppProcessInfo
|
val info: AppProcessInfo
|
||||||
) : ObservableDiffRvItem<DenyListRvItem>(), ComparableRv<DenyListRvItem> {
|
) : ObservableRvItem(), DiffItem<DenyListRvItem>, Comparable<DenyListRvItem> {
|
||||||
|
|
||||||
override val layoutRes get() = R.layout.item_hide_md2
|
override val layoutRes get() = R.layout.item_hide_md2
|
||||||
|
|
||||||
@@ -44,9 +44,18 @@ class DenyListRvItem(
|
|||||||
processes
|
processes
|
||||||
.filterNot { it.isEnabled }
|
.filterNot { it.isEnabled }
|
||||||
.filter { isExpanded || it.defaultSelection }
|
.filter { isExpanded || it.defaultSelection }
|
||||||
|
.forEach { it.toggle() }
|
||||||
} else {
|
} else {
|
||||||
processes.filter { it.isEnabled }
|
Shell.cmd("magisk --denylist rm ${info.packageName}").submit()
|
||||||
}.forEach { it.toggle() }
|
processes.filter { it.isEnabled }.forEach {
|
||||||
|
if (it.process.isIsolated) {
|
||||||
|
it.toggle()
|
||||||
|
} else {
|
||||||
|
it.isEnabled = !it.isEnabled
|
||||||
|
notifyPropertyChanged(BR.enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -91,7 +100,7 @@ class DenyListRvItem(
|
|||||||
|
|
||||||
class ProcessRvItem(
|
class ProcessRvItem(
|
||||||
val process: ProcessInfo
|
val process: ProcessInfo
|
||||||
) : ObservableDiffRvItem<ProcessRvItem>() {
|
) : ObservableRvItem(), DiffItem<ProcessRvItem> {
|
||||||
|
|
||||||
override val layoutRes get() = R.layout.item_hide_process_md2
|
override val layoutRes get() = R.layout.item_hide_process_md2
|
||||||
|
|
||||||
@@ -103,7 +112,7 @@ class ProcessRvItem(
|
|||||||
set(value) = set(value, process.isEnabled, { process.isEnabled = it }, BR.enabled) {
|
set(value) = set(value, process.isEnabled, { process.isEnabled = it }, BR.enabled) {
|
||||||
val arg = if (it) "add" else "rm"
|
val arg = if (it) "add" else "rm"
|
||||||
val (name, pkg) = process
|
val (name, pkg) = process
|
||||||
Shell.su("magisk --denylist $arg $pkg \'$name\'").submit()
|
Shell.cmd("magisk --denylist $arg $pkg \'$name\'").submit()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggle() {
|
fun toggle() {
|
||||||
@@ -113,10 +122,9 @@ class ProcessRvItem(
|
|||||||
val defaultSelection get() =
|
val defaultSelection get() =
|
||||||
process.isIsolated || process.isAppZygote || process.name == process.packageName
|
process.isIsolated || process.isAppZygote || process.name == process.packageName
|
||||||
|
|
||||||
override fun contentSameAs(other: ProcessRvItem) =
|
|
||||||
process.isEnabled == other.process.isEnabled
|
|
||||||
|
|
||||||
override fun itemSameAs(other: ProcessRvItem) =
|
override fun itemSameAs(other: ProcessRvItem) =
|
||||||
process.name == other.process.name && process.packageName == other.process.packageName
|
process.name == other.process.name && process.packageName == other.process.packageName
|
||||||
|
|
||||||
|
override fun contentSameAs(other: ProcessRvItem) =
|
||||||
|
process.isEnabled == other.process.isEnabled
|
||||||
}
|
}
|
@@ -2,57 +2,57 @@ package com.topjohnwu.magisk.ui.deny
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
|
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
|
||||||
|
import androidx.databinding.Bindable
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
|
||||||
import com.topjohnwu.magisk.databinding.filterableListOf
|
import com.topjohnwu.magisk.core.AppContext
|
||||||
import com.topjohnwu.magisk.databinding.itemBindingOf
|
import com.topjohnwu.magisk.core.ktx.concurrentMap
|
||||||
import com.topjohnwu.magisk.di.AppContext
|
import com.topjohnwu.magisk.databinding.bindExtra
|
||||||
import com.topjohnwu.magisk.ktx.concurrentMap
|
import com.topjohnwu.magisk.databinding.filterList
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.databinding.set
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import java.util.*
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.toCollection
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class DenyListViewModel : BaseViewModel() {
|
class DenyListViewModel : AsyncLoadViewModel() {
|
||||||
|
|
||||||
var isShowSystem = false
|
var isShowSystem = false
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
query()
|
doQuery(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isShowOS = false
|
var isShowOS = false
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
query()
|
doQuery(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
var query = ""
|
var query = ""
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
query()
|
doQuery(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
val items = filterableListOf<DenyListRvItem>()
|
val items = filterList<DenyListRvItem>(viewModelScope)
|
||||||
val itemBinding = itemBindingOf<DenyListRvItem> {
|
val extraBindings = bindExtra {
|
||||||
it.bindExtra(BR.viewModel, this)
|
it.put(BR.viewModel, this)
|
||||||
}
|
|
||||||
val itemInternalBinding = itemBindingOf<ProcessRvItem> {
|
|
||||||
it.bindExtra(BR.viewModel, this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var loading = true
|
||||||
|
private set(value) = set(value, field, { field = it }, BR.loading)
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
override fun refresh() = viewModelScope.launch {
|
override suspend fun doLoadWork() {
|
||||||
if (!Utils.showSuperUser()) {
|
loading = true
|
||||||
state = State.LOADING_FAILED
|
val apps = withContext(Dispatchers.Default) {
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
state = State.LOADING
|
|
||||||
val (apps, diff) = withContext(Dispatchers.Default) {
|
|
||||||
val pm = AppContext.packageManager
|
val pm = AppContext.packageManager
|
||||||
val denyList = Shell.su("magisk --denylist ls").exec().out
|
val denyList = Shell.cmd("magisk --denylist ls").exec().out
|
||||||
.map { CmdlineListItem(it) }
|
.map { CmdlineListItem(it) }
|
||||||
val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES).run {
|
val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES).run {
|
||||||
asFlow()
|
asFlow()
|
||||||
@@ -63,27 +63,27 @@ class DenyListViewModel : BaseViewModel() {
|
|||||||
.toCollection(ArrayList(size))
|
.toCollection(ArrayList(size))
|
||||||
}
|
}
|
||||||
apps.sort()
|
apps.sort()
|
||||||
apps to items.calculateDiff(apps)
|
apps
|
||||||
}
|
}
|
||||||
items.update(apps, diff)
|
items.set(apps)
|
||||||
query()
|
doQuery(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun query() {
|
private fun doQuery(s: String) {
|
||||||
items.filter {
|
items.filter {
|
||||||
fun filterSystem() = isShowSystem || !it.info.isSystemApp()
|
fun filterSystem() = isShowSystem || !it.info.isSystemApp()
|
||||||
|
|
||||||
fun filterOS() = (isShowSystem && isShowOS) || it.info.isApp()
|
fun filterOS() = (isShowSystem && isShowOS) || it.info.isApp()
|
||||||
|
|
||||||
fun filterQuery(): Boolean {
|
fun filterQuery(): Boolean {
|
||||||
fun inName() = it.info.label.contains(query, true)
|
fun inName() = it.info.label.contains(s, true)
|
||||||
fun inPackage() = it.info.packageName.contains(query, true)
|
fun inPackage() = it.info.packageName.contains(s, true)
|
||||||
fun inProcesses() = it.processes.any { p -> p.process.name.contains(query, true) }
|
fun inProcesses() = it.processes.any { p -> p.process.name.contains(s, true) }
|
||||||
return inName() || inPackage() || inProcesses()
|
return inName() || inPackage() || inProcesses()
|
||||||
}
|
}
|
||||||
|
|
||||||
(it.isChecked || (filterSystem() && filterOS())) && filterQuery()
|
(it.isChecked || (filterSystem() && filterOS())) && filterQuery()
|
||||||
}
|
}
|
||||||
state = State.LOADED
|
loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -6,20 +6,20 @@ import androidx.core.view.updateLayoutParams
|
|||||||
import androidx.databinding.ViewDataBinding
|
import androidx.databinding.ViewDataBinding
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.databinding.DiffRvItem
|
import com.topjohnwu.magisk.databinding.DiffItem
|
||||||
import com.topjohnwu.magisk.databinding.LenientRvItem
|
import com.topjohnwu.magisk.databinding.ItemWrapper
|
||||||
import com.topjohnwu.magisk.databinding.RvContainer
|
import com.topjohnwu.magisk.databinding.RvItem
|
||||||
|
import com.topjohnwu.magisk.databinding.ViewAwareItem
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class ConsoleItem(
|
class ConsoleItem(
|
||||||
override val item: String
|
override val item: String
|
||||||
) : DiffRvItem<ConsoleItem>(), LenientRvItem,
|
) : RvItem(), ViewAwareItem, DiffItem<ConsoleItem>, ItemWrapper<String> {
|
||||||
RvContainer<String> {
|
|
||||||
override val layoutRes = R.layout.item_console_md2
|
override val layoutRes = R.layout.item_console_md2
|
||||||
|
|
||||||
private var parentWidth = -1
|
private var parentWidth = -1
|
||||||
|
|
||||||
override fun onBindingBound(binding: ViewDataBinding, recyclerView: RecyclerView) {
|
override fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView) {
|
||||||
if (parentWidth < 0)
|
if (parentWidth < 0)
|
||||||
parentWidth = (recyclerView.parent as View).width
|
parentWidth = (recyclerView.parent as View).width
|
||||||
|
|
@@ -5,21 +5,31 @@ import android.content.Context
|
|||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.*
|
import android.view.KeyEvent
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.MenuProvider
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.navigation.NavDeepLinkBuilder
|
import androidx.navigation.NavDeepLinkBuilder
|
||||||
import com.topjohnwu.magisk.MainDirections
|
import com.topjohnwu.magisk.MainDirections
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.BaseFragment
|
import com.topjohnwu.magisk.arch.BaseFragment
|
||||||
|
import com.topjohnwu.magisk.arch.viewModel
|
||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.cmp
|
import com.topjohnwu.magisk.core.cmp
|
||||||
import com.topjohnwu.magisk.databinding.FragmentFlashMd2Binding
|
import com.topjohnwu.magisk.databinding.FragmentFlashMd2Binding
|
||||||
import com.topjohnwu.magisk.di.viewModel
|
|
||||||
import com.topjohnwu.magisk.ui.MainActivity
|
import com.topjohnwu.magisk.ui.MainActivity
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
class FlashFragment : BaseFragment<FragmentFlashMd2Binding>() {
|
class FlashFragment : BaseFragment<FragmentFlashMd2Binding>(), MenuProvider {
|
||||||
|
|
||||||
override val layoutRes = R.layout.fragment_flash_md2
|
override val layoutRes = R.layout.fragment_flash_md2
|
||||||
override val viewModel by viewModel<FlashViewModel>()
|
override val viewModel by viewModel<FlashViewModel>()
|
||||||
|
override val snackbarView: View get() = binding.snackbarContainer
|
||||||
|
override val snackbarAnchorView: View?
|
||||||
|
get() = if (binding.restartBtn.isShown) binding.restartBtn else super.snackbarAnchorView
|
||||||
|
|
||||||
private var defaultOrientation = -1
|
private var defaultOrientation = -1
|
||||||
|
|
||||||
@@ -30,40 +40,53 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>() {
|
|||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
setHasOptionsMenu(true)
|
activity?.setTitle(CoreR.string.flash_screen_title)
|
||||||
activity.setTitle(R.string.flash_screen_title)
|
|
||||||
|
|
||||||
viewModel.subtitle.observe(this) {
|
viewModel.state.observe(this) {
|
||||||
activity.supportActionBar?.setSubtitle(it)
|
activity?.supportActionBar?.setSubtitle(
|
||||||
|
when (it) {
|
||||||
|
FlashViewModel.State.FLASHING -> CoreR.string.flashing
|
||||||
|
FlashViewModel.State.SUCCESS -> CoreR.string.done
|
||||||
|
FlashViewModel.State.FAILED -> CoreR.string.failure
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (it == FlashViewModel.State.SUCCESS && viewModel.showReboot) {
|
||||||
|
binding.restartBtn.apply {
|
||||||
|
if (!this.isVisible) this.show()
|
||||||
|
if (!this.isFocused) this.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
inflater.inflate(R.menu.menu_flash, menu)
|
inflater.inflate(R.menu.menu_flash, menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onMenuItemSelected(item: MenuItem): Boolean {
|
||||||
return viewModel.onMenuItemClicked(item)
|
return viewModel.onMenuItemClicked(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
defaultOrientation = activity.requestedOrientation
|
defaultOrientation = activity?.requestedOrientation ?: -1
|
||||||
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||||
viewModel.startFlashing()
|
if (savedInstanceState == null) {
|
||||||
|
viewModel.startFlashing()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("WrongConstant")
|
@SuppressLint("WrongConstant")
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
if (defaultOrientation != -1) {
|
if (defaultOrientation != -1) {
|
||||||
activity.requestedOrientation = defaultOrientation
|
activity?.requestedOrientation = defaultOrientation
|
||||||
}
|
}
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyEvent(event: KeyEvent): Boolean {
|
override fun onKeyEvent(event: KeyEvent): Boolean {
|
||||||
return when(event.keyCode) {
|
return when (event.keyCode) {
|
||||||
KeyEvent.KEYCODE_VOLUME_UP,
|
KeyEvent.KEYCODE_VOLUME_UP,
|
||||||
KeyEvent.KEYCODE_VOLUME_DOWN -> true
|
KeyEvent.KEYCODE_VOLUME_DOWN -> true
|
||||||
else -> false
|
else -> false
|
||||||
@@ -71,7 +94,8 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed(): Boolean {
|
override fun onBackPressed(): Boolean {
|
||||||
if (viewModel.loading) return true
|
if (viewModel.flashing.value == true)
|
||||||
|
return true
|
||||||
return super.onBackPressed()
|
return super.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
@@ -2,41 +2,45 @@ package com.topjohnwu.magisk.ui.flash
|
|||||||
|
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
|
import androidx.databinding.ObservableArrayList
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.map
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.ktx.reboot
|
||||||
|
import com.topjohnwu.magisk.core.ktx.synchronized
|
||||||
|
import com.topjohnwu.magisk.core.ktx.timeFormatStandard
|
||||||
|
import com.topjohnwu.magisk.core.ktx.toTime
|
||||||
import com.topjohnwu.magisk.core.tasks.FlashZip
|
import com.topjohnwu.magisk.core.tasks.FlashZip
|
||||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||||
import com.topjohnwu.magisk.databinding.RvBindingAdapter
|
|
||||||
import com.topjohnwu.magisk.databinding.diffListOf
|
|
||||||
import com.topjohnwu.magisk.databinding.itemBindingOf
|
|
||||||
import com.topjohnwu.magisk.databinding.set
|
import com.topjohnwu.magisk.databinding.set
|
||||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||||
import com.topjohnwu.magisk.ktx.*
|
|
||||||
import com.topjohnwu.superuser.CallbackList
|
import com.topjohnwu.superuser.CallbackList
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class FlashViewModel : BaseViewModel() {
|
class FlashViewModel : BaseViewModel() {
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
FLASHING, SUCCESS, FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _state = MutableLiveData(State.FLASHING)
|
||||||
|
val state: LiveData<State> get() = _state
|
||||||
|
val flashing = state.map { it == State.FLASHING }
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
var showReboot = Shell.rootAccess()
|
var showReboot = Info.isRooted
|
||||||
set(value) = set(value, field, { field = it }, BR.showReboot)
|
set(value) = set(value, field, { field = it }, BR.showReboot)
|
||||||
|
|
||||||
private val _subtitle = MutableLiveData(R.string.flashing)
|
val items = ObservableArrayList<ConsoleItem>()
|
||||||
val subtitle get() = _subtitle as LiveData<Int>
|
|
||||||
|
|
||||||
val adapter = RvBindingAdapter<ConsoleItem>()
|
|
||||||
val items = diffListOf<ConsoleItem>()
|
|
||||||
val itemBinding = itemBindingOf<ConsoleItem>()
|
|
||||||
lateinit var args: FlashFragmentArgs
|
lateinit var args: FlashFragmentArgs
|
||||||
|
|
||||||
private val logItems = mutableListOf<String>().synchronized()
|
private val logItems = mutableListOf<String>().synchronized()
|
||||||
@@ -54,7 +58,8 @@ class FlashViewModel : BaseViewModel() {
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val result = when (action) {
|
val result = when (action) {
|
||||||
Const.Value.FLASH_ZIP -> {
|
Const.Value.FLASH_ZIP -> {
|
||||||
FlashZip(uri!!, outItems, logItems).exec()
|
uri ?: return@launch
|
||||||
|
FlashZip(uri, outItems, logItems).exec()
|
||||||
}
|
}
|
||||||
Const.Value.UNINSTALL -> {
|
Const.Value.UNINSTALL -> {
|
||||||
showReboot = false
|
showReboot = false
|
||||||
@@ -67,6 +72,7 @@ class FlashViewModel : BaseViewModel() {
|
|||||||
MagiskInstaller.Direct(outItems, logItems).exec()
|
MagiskInstaller.Direct(outItems, logItems).exec()
|
||||||
}
|
}
|
||||||
Const.Value.FLASH_INACTIVE_SLOT -> {
|
Const.Value.FLASH_INACTIVE_SLOT -> {
|
||||||
|
showReboot = false
|
||||||
MagiskInstaller.SecondSlot(outItems, logItems).exec()
|
MagiskInstaller.SecondSlot(outItems, logItems).exec()
|
||||||
}
|
}
|
||||||
Const.Value.PATCH_FILE -> {
|
Const.Value.PATCH_FILE -> {
|
||||||
@@ -84,11 +90,7 @@ class FlashViewModel : BaseViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onResult(success: Boolean) {
|
private fun onResult(success: Boolean) {
|
||||||
state = if (success) State.LOADED else State.LOADING_FAILED
|
_state.value = if (success) State.SUCCESS else State.FAILED
|
||||||
when {
|
|
||||||
success -> _subtitle.postValue(R.string.done)
|
|
||||||
else -> _subtitle.postValue(R.string.failure)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onMenuItemClicked(item: MenuItem): Boolean {
|
fun onMenuItemClicked(item: MenuItem): Boolean {
|
||||||
@@ -100,12 +102,16 @@ class FlashViewModel : BaseViewModel() {
|
|||||||
|
|
||||||
private fun savePressed() = withExternalRW {
|
private fun savePressed() = withExternalRW {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val name = "magisk_install_log_%s.log".format(now.toTime(timeFormatStandard))
|
val name = "magisk_install_log_%s.log".format(
|
||||||
val file = MediaStoreUtils.getFile(name, true)
|
System.currentTimeMillis().toTime(timeFormatStandard)
|
||||||
|
)
|
||||||
|
val file = MediaStoreUtils.getFile(name)
|
||||||
file.uri.outputStream().bufferedWriter().use { writer ->
|
file.uri.outputStream().bufferedWriter().use { writer ->
|
||||||
logItems.forEach {
|
synchronized(logItems) {
|
||||||
writer.write(it)
|
logItems.forEach {
|
||||||
writer.newLine()
|
writer.write(it)
|
||||||
|
writer.newLine()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SnackbarEvent(file.toString()).publish()
|
SnackbarEvent(file.toString()).publish()
|
@@ -0,0 +1,127 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.home
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.databinding.RvItem
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
|
interface Dev {
|
||||||
|
val name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface JohnImpl : Dev {
|
||||||
|
override val name get() = "topjohnwu"
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface VvbImpl : Dev {
|
||||||
|
override val name get() = "vvb2060"
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface YUImpl : Dev {
|
||||||
|
override val name get() = "yujincheng08"
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface RikkaImpl : Dev {
|
||||||
|
override val name get() = "RikkaW"
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface CanyieImpl : Dev {
|
||||||
|
override val name get() = "canyie"
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class DeveloperItem : Dev {
|
||||||
|
|
||||||
|
abstract val items: List<IconLink>
|
||||||
|
val handle get() = "@${name}"
|
||||||
|
|
||||||
|
object John : DeveloperItem(), JohnImpl {
|
||||||
|
override val items =
|
||||||
|
listOf(
|
||||||
|
object : IconLink.Twitter(), JohnImpl {},
|
||||||
|
IconLink.Github.Project
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object Vvb : DeveloperItem(), VvbImpl {
|
||||||
|
override val items =
|
||||||
|
listOf<IconLink>(
|
||||||
|
object : IconLink.Twitter(), VvbImpl {},
|
||||||
|
object : IconLink.Github.User(), VvbImpl {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object YU : DeveloperItem(), YUImpl {
|
||||||
|
override val items =
|
||||||
|
listOf<IconLink>(
|
||||||
|
object : IconLink.Twitter() { override val name = "shanasaimoe" },
|
||||||
|
object : IconLink.Github.User(), YUImpl {},
|
||||||
|
object : IconLink.Sponsor(), YUImpl {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object Rikka : DeveloperItem(), RikkaImpl {
|
||||||
|
override val items =
|
||||||
|
listOf<IconLink>(
|
||||||
|
object : IconLink.Twitter() { override val name = "rikkawww" },
|
||||||
|
object : IconLink.Github.User(), RikkaImpl {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object Canyie : DeveloperItem(), CanyieImpl {
|
||||||
|
override val items =
|
||||||
|
listOf<IconLink>(
|
||||||
|
object : IconLink.Twitter() { override val name = "canyie2977" },
|
||||||
|
object : IconLink.Github.User(), CanyieImpl {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class IconLink : RvItem() {
|
||||||
|
|
||||||
|
abstract val icon: Int
|
||||||
|
abstract val title: Int
|
||||||
|
abstract val link: String
|
||||||
|
|
||||||
|
override val layoutRes get() = R.layout.item_icon_link
|
||||||
|
|
||||||
|
abstract class PayPal : IconLink(), Dev {
|
||||||
|
override val icon get() = CoreR.drawable.ic_paypal
|
||||||
|
override val title get() = CoreR.string.paypal
|
||||||
|
override val link get() = "https://paypal.me/$name"
|
||||||
|
|
||||||
|
object Project : PayPal() {
|
||||||
|
override val name: String get() = "magiskdonate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Patreon : IconLink() {
|
||||||
|
override val icon get() = CoreR.drawable.ic_patreon
|
||||||
|
override val title get() = CoreR.string.patreon
|
||||||
|
override val link get() = Const.Url.PATREON_URL
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Twitter : IconLink(), Dev {
|
||||||
|
override val icon get() = CoreR.drawable.ic_twitter
|
||||||
|
override val title get() = CoreR.string.twitter
|
||||||
|
override val link get() = "https://twitter.com/$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Github : IconLink() {
|
||||||
|
override val icon get() = CoreR.drawable.ic_github
|
||||||
|
override val title get() = CoreR.string.github
|
||||||
|
|
||||||
|
abstract class User : Github(), Dev {
|
||||||
|
override val link get() = "https://github.com/$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
object Project : Github() {
|
||||||
|
override val link get() = Const.Url.SOURCE_CODE_URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Sponsor : IconLink(), Dev {
|
||||||
|
override val icon get() = CoreR.drawable.ic_favorite
|
||||||
|
override val title get() = CoreR.string.github
|
||||||
|
override val link get() = "https://github.com/sponsors/$name"
|
||||||
|
}
|
||||||
|
}
|
@@ -1,27 +1,32 @@
|
|||||||
package com.topjohnwu.magisk.ui.home
|
package com.topjohnwu.magisk.ui.home
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.*
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.MenuProvider
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.BaseFragment
|
import com.topjohnwu.magisk.arch.BaseFragment
|
||||||
import com.topjohnwu.magisk.core.download.DownloadService
|
import com.topjohnwu.magisk.arch.viewModel
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||||
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
|
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
|
||||||
import com.topjohnwu.magisk.di.viewModel
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
import com.topjohnwu.magisk.events.RebootEvent
|
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
|
|
||||||
class HomeFragment : BaseFragment<FragmentHomeMd2Binding>() {
|
class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
|
||||||
|
|
||||||
override val layoutRes = R.layout.fragment_home_md2
|
override val layoutRes = R.layout.fragment_home_md2
|
||||||
override val viewModel by viewModel<HomeViewModel>()
|
override val viewModel by viewModel<HomeViewModel>()
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
activity.title = resources.getString(R.string.section_home)
|
activity?.setTitle(CoreR.string.section_home)
|
||||||
setHasOptionsMenu(true)
|
DownloadEngine.observeProgress(this, viewModel::onProgressUpdate)
|
||||||
DownloadService.observeProgress(this, viewModel::onProgressUpdate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkTitle(text: TextView, icon: ImageView) {
|
private fun checkTitle(text: TextView, icon: ImageView) {
|
||||||
@@ -54,17 +59,17 @@ class HomeFragment : BaseFragment<FragmentHomeMd2Binding>() {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
inflater.inflate(R.menu.menu_home_md2, menu)
|
inflater.inflate(R.menu.menu_home_md2, menu)
|
||||||
if (!Shell.rootAccess())
|
if (!Info.isRooted)
|
||||||
menu.removeItem(R.id.action_reboot)
|
menu.removeItem(R.id.action_reboot)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onMenuItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.action_settings ->
|
R.id.action_settings ->
|
||||||
HomeFragmentDirections.actionHomeFragmentToSettingsFragment().navigate()
|
HomeFragmentDirections.actionHomeFragmentToSettingsFragment().navigate()
|
||||||
R.id.action_reboot -> RebootEvent.inflateMenu(activity).show()
|
R.id.action_reboot -> activity?.let { RebootMenu.inflate(it).show() }
|
||||||
else -> return super.onOptionsItemSelected(item)
|
else -> return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
@@ -0,0 +1,167 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.home
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import androidx.databinding.Bindable
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.arch.ActivityExecutor
|
||||||
|
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
|
||||||
|
import com.topjohnwu.magisk.arch.ContextExecutor
|
||||||
|
import com.topjohnwu.magisk.arch.UIActivity
|
||||||
|
import com.topjohnwu.magisk.arch.ViewEvent
|
||||||
|
import com.topjohnwu.magisk.core.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.download.Subject
|
||||||
|
import com.topjohnwu.magisk.core.download.Subject.App
|
||||||
|
import com.topjohnwu.magisk.core.ktx.await
|
||||||
|
import com.topjohnwu.magisk.core.ktx.toast
|
||||||
|
import com.topjohnwu.magisk.core.repository.NetworkService
|
||||||
|
import com.topjohnwu.magisk.databinding.bindExtra
|
||||||
|
import com.topjohnwu.magisk.databinding.set
|
||||||
|
import com.topjohnwu.magisk.dialog.EnvFixDialog
|
||||||
|
import com.topjohnwu.magisk.dialog.ManagerInstallDialog
|
||||||
|
import com.topjohnwu.magisk.dialog.UninstallDialog
|
||||||
|
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||||
|
import com.topjohnwu.magisk.utils.asText
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
|
class HomeViewModel(
|
||||||
|
private val svc: NetworkService
|
||||||
|
) : AsyncLoadViewModel() {
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
LOADING, INVALID, OUTDATED, UP_TO_DATE
|
||||||
|
}
|
||||||
|
|
||||||
|
val magiskTitleBarrierIds =
|
||||||
|
intArrayOf(R.id.home_magisk_icon, R.id.home_magisk_title, R.id.home_magisk_button)
|
||||||
|
val appTitleBarrierIds =
|
||||||
|
intArrayOf(R.id.home_manager_icon, R.id.home_manager_title, R.id.home_manager_button)
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var isNoticeVisible = Config.safetyNotice
|
||||||
|
set(value) = set(value, field, { field = it }, BR.noticeVisible)
|
||||||
|
|
||||||
|
val magiskState
|
||||||
|
get() = when {
|
||||||
|
Info.isRooted && Info.env.isUnsupported -> State.OUTDATED
|
||||||
|
!Info.env.isActive -> State.INVALID
|
||||||
|
Info.env.versionCode < BuildConfig.APP_VERSION_CODE -> State.OUTDATED
|
||||||
|
else -> State.UP_TO_DATE
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var appState = State.LOADING
|
||||||
|
set(value) = set(value, field, { field = it }, BR.appState)
|
||||||
|
|
||||||
|
val magiskInstalledVersion
|
||||||
|
get() = Info.env.run {
|
||||||
|
if (isActive)
|
||||||
|
("$versionString ($versionCode)" + if (isDebug) " (D)" else "").asText()
|
||||||
|
else
|
||||||
|
CoreR.string.not_available.asText()
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var managerRemoteVersion = CoreR.string.loading.asText()
|
||||||
|
set(value) = set(value, field, { field = it }, BR.managerRemoteVersion)
|
||||||
|
|
||||||
|
val managerInstalledVersion
|
||||||
|
get() = "${BuildConfig.APP_VERSION_NAME} (${BuildConfig.APP_VERSION_CODE})" +
|
||||||
|
if (BuildConfig.DEBUG) " (D)" else ""
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var stateManagerProgress = 0
|
||||||
|
set(value) = set(value, field, { field = it }, BR.stateManagerProgress)
|
||||||
|
|
||||||
|
val extraBindings = bindExtra {
|
||||||
|
it.put(BR.viewModel, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var checkedEnv = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun doLoadWork() {
|
||||||
|
appState = State.LOADING
|
||||||
|
Info.getRemote(svc)?.apply {
|
||||||
|
appState = when {
|
||||||
|
BuildConfig.APP_VERSION_CODE < magisk.versionCode -> State.OUTDATED
|
||||||
|
else -> State.UP_TO_DATE
|
||||||
|
}
|
||||||
|
|
||||||
|
val isDebug = Config.updateChannel == Config.Value.DEBUG_CHANNEL
|
||||||
|
managerRemoteVersion =
|
||||||
|
("${magisk.version} (${magisk.versionCode})" +
|
||||||
|
if (isDebug) " (D)" else "").asText()
|
||||||
|
} ?: run {
|
||||||
|
appState = State.INVALID
|
||||||
|
managerRemoteVersion = CoreR.string.not_available.asText()
|
||||||
|
}
|
||||||
|
ensureEnv()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNetworkChanged(network: Boolean) = startLoading()
|
||||||
|
|
||||||
|
fun onProgressUpdate(progress: Float, subject: Subject) {
|
||||||
|
if (subject is App)
|
||||||
|
stateManagerProgress = progress.times(100f).roundToInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onLinkPressed(link: String) = object : ViewEvent(), ContextExecutor {
|
||||||
|
override fun invoke(context: Context) {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, link.toUri())
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
try {
|
||||||
|
context.startActivity(intent)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
context.toast(CoreR.string.open_link_failed_toast, Toast.LENGTH_SHORT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.publish()
|
||||||
|
|
||||||
|
fun onDeletePressed() = UninstallDialog().show()
|
||||||
|
|
||||||
|
fun onManagerPressed() = when (appState) {
|
||||||
|
State.LOADING -> SnackbarEvent(CoreR.string.loading).publish()
|
||||||
|
State.INVALID -> SnackbarEvent(CoreR.string.no_connection).publish()
|
||||||
|
else -> withExternalRW {
|
||||||
|
withInstallPermission {
|
||||||
|
ManagerInstallDialog().show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMagiskPressed() = withExternalRW {
|
||||||
|
HomeFragmentDirections.actionHomeFragmentToInstallFragment().navigate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideNotice() {
|
||||||
|
Config.safetyNotice = false
|
||||||
|
isNoticeVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun ensureEnv() {
|
||||||
|
if (magiskState == State.INVALID || checkedEnv) return
|
||||||
|
val cmd = "env_check ${Info.env.versionString} ${Info.env.versionCode}"
|
||||||
|
val code = Shell.cmd(cmd).await().code
|
||||||
|
if (code != 0) {
|
||||||
|
EnvFixDialog(this, code).show()
|
||||||
|
}
|
||||||
|
checkedEnv = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val showTest = false
|
||||||
|
fun onTestPressed() = object : ViewEvent(), ActivityExecutor {
|
||||||
|
override fun invoke(activity: UIActivity<*>) {
|
||||||
|
/* Entry point to trigger test events within the app */
|
||||||
|
}
|
||||||
|
}.publish()
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.events
|
package com.topjohnwu.magisk.ui.home
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.view.ContextThemeWrapper
|
import android.view.ContextThemeWrapper
|
||||||
@@ -7,11 +8,11 @@ import android.view.MenuItem
|
|||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.ktx.reboot as systemReboot
|
import com.topjohnwu.magisk.core.ktx.reboot as systemReboot
|
||||||
|
|
||||||
object RebootEvent {
|
object RebootMenu {
|
||||||
|
|
||||||
private fun reboot(item: MenuItem): Boolean {
|
private fun reboot(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
@@ -20,20 +21,31 @@ object RebootEvent {
|
|||||||
R.id.action_reboot_bootloader -> systemReboot("bootloader")
|
R.id.action_reboot_bootloader -> systemReboot("bootloader")
|
||||||
R.id.action_reboot_download -> systemReboot("download")
|
R.id.action_reboot_download -> systemReboot("download")
|
||||||
R.id.action_reboot_edl -> systemReboot("edl")
|
R.id.action_reboot_edl -> systemReboot("edl")
|
||||||
R.id.action_reboot_recovery -> Shell.su("/system/bin/reboot recovery").submit()
|
R.id.action_reboot_recovery -> systemReboot("recovery")
|
||||||
|
R.id.action_reboot_safe_mode -> {
|
||||||
|
val status = !item.isChecked
|
||||||
|
item.isChecked = status
|
||||||
|
Config.bootloop = if (status) 2 else 0
|
||||||
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun inflateMenu(activity: BaseActivity): PopupMenu {
|
fun inflate(activity: Activity): PopupMenu {
|
||||||
val themeWrapper = ContextThemeWrapper(activity, R.style.Foundation_PopupMenu)
|
val themeWrapper = ContextThemeWrapper(activity, R.style.Foundation_PopupMenu)
|
||||||
val menu = PopupMenu(themeWrapper, activity.findViewById(R.id.action_reboot))
|
val menu = PopupMenu(themeWrapper, activity.findViewById(R.id.action_reboot))
|
||||||
activity.menuInflater.inflate(R.menu.menu_reboot, menu.menu)
|
activity.menuInflater.inflate(R.menu.menu_reboot, menu.menu)
|
||||||
|
menu.setOnMenuItemClickListener(RebootMenu::reboot)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
|
||||||
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true)
|
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true) {
|
||||||
menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true
|
menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true
|
||||||
menu.setOnMenuItemClickListener(::reboot)
|
}
|
||||||
|
if (Const.Version.isCanary()) {
|
||||||
|
menu.menu.findItem(R.id.action_reboot_safe_mode).isChecked = Config.bootloop >= 2
|
||||||
|
} else {
|
||||||
|
menu.menu.findItem(R.id.action_reboot_safe_mode).isVisible = false
|
||||||
|
}
|
||||||
return menu
|
return menu
|
||||||
}
|
}
|
||||||
|
|
@@ -0,0 +1,18 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.install
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.arch.BaseFragment
|
||||||
|
import com.topjohnwu.magisk.arch.viewModel
|
||||||
|
import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
|
class InstallFragment : BaseFragment<FragmentInstallMd2Binding>() {
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.fragment_install_md2
|
||||||
|
override val viewModel by viewModel<InstallViewModel>()
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
requireActivity().setTitle(CoreR.string.install)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,145 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.install
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.SpannedString
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.databinding.Bindable
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.core.AppContext
|
||||||
|
import com.topjohnwu.magisk.core.BuildConfig
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.base.ContentResultCallback
|
||||||
|
import com.topjohnwu.magisk.core.ktx.toast
|
||||||
|
import com.topjohnwu.magisk.core.repository.NetworkService
|
||||||
|
import com.topjohnwu.magisk.databinding.set
|
||||||
|
import com.topjohnwu.magisk.dialog.SecondSlotWarningDialog
|
||||||
|
import com.topjohnwu.magisk.events.GetContentEvent
|
||||||
|
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||||
|
import io.noties.markwon.Markwon
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
|
class InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel() {
|
||||||
|
|
||||||
|
val isRooted get() = Info.isRooted
|
||||||
|
val skipOptions = Info.isEmulator || (Info.isSAR && !Info.isFDE && Info.ramdisk)
|
||||||
|
val noSecondSlot = !isRooted || !Info.isAB || Info.isEmulator
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var step = if (skipOptions) 1 else 0
|
||||||
|
set(value) = set(value, field, { field = it }, BR.step)
|
||||||
|
|
||||||
|
private var methodId = -1
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var method
|
||||||
|
get() = methodId
|
||||||
|
set(value) = set(value, methodId, { methodId = it }, BR.method) {
|
||||||
|
when (it) {
|
||||||
|
R.id.method_patch -> {
|
||||||
|
GetContentEvent("*/*", UriCallback()).publish()
|
||||||
|
}
|
||||||
|
R.id.method_inactive_slot -> {
|
||||||
|
SecondSlotWarningDialog().show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val data: LiveData<Uri?> get() = uri
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var notes: Spanned = SpannedString("")
|
||||||
|
set(value) = set(value, field, { field = it }, BR.notes)
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val file = File(AppContext.cacheDir, "${BuildConfig.APP_VERSION_CODE}.md")
|
||||||
|
val text = when {
|
||||||
|
file.exists() -> file.readText()
|
||||||
|
Const.Url.CHANGELOG_URL.isEmpty() -> ""
|
||||||
|
else -> {
|
||||||
|
val str = svc.fetchString(Const.Url.CHANGELOG_URL)
|
||||||
|
file.writeText(str)
|
||||||
|
str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val spanned = markwon.toMarkdown(text)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
notes = spanned
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun install() {
|
||||||
|
when (method) {
|
||||||
|
R.id.method_patch -> FlashFragment.patch(data.value!!).navigate(true)
|
||||||
|
R.id.method_direct -> FlashFragment.flash(false).navigate(true)
|
||||||
|
R.id.method_inactive_slot -> FlashFragment.flash(true).navigate(true)
|
||||||
|
else -> error("Unknown value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveState(state: Bundle) {
|
||||||
|
state.putParcelable(INSTALL_STATE_KEY, InstallState(
|
||||||
|
methodId,
|
||||||
|
step,
|
||||||
|
Config.keepVerity,
|
||||||
|
Config.keepEnc,
|
||||||
|
Config.recovery
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRestoreState(state: Bundle) {
|
||||||
|
state.getParcelable<InstallState>(INSTALL_STATE_KEY)?.let {
|
||||||
|
methodId = it.method
|
||||||
|
step = it.step
|
||||||
|
Config.keepVerity = it.keepVerity
|
||||||
|
Config.keepEnc = it.keepEnc
|
||||||
|
Config.recovery = it.recovery
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class UriCallback : ContentResultCallback {
|
||||||
|
override fun onActivityLaunch() {
|
||||||
|
AppContext.toast(CoreR.string.patch_file_msg, Toast.LENGTH_LONG)
|
||||||
|
}
|
||||||
|
override fun onActivityResult(result: Uri) {
|
||||||
|
uri.value = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class InstallState(
|
||||||
|
val method: Int,
|
||||||
|
val step: Int,
|
||||||
|
val keepVerity: Boolean,
|
||||||
|
val keepEnc: Boolean,
|
||||||
|
val recovery: Boolean,
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val INSTALL_STATE_KEY = "install_state"
|
||||||
|
private val uri = MutableLiveData<Uri?>()
|
||||||
|
}
|
||||||
|
}
|
@@ -5,21 +5,26 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.view.MenuProvider
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.BaseFragment
|
import com.topjohnwu.magisk.arch.BaseFragment
|
||||||
|
import com.topjohnwu.magisk.arch.viewModel
|
||||||
import com.topjohnwu.magisk.databinding.FragmentLogMd2Binding
|
import com.topjohnwu.magisk.databinding.FragmentLogMd2Binding
|
||||||
import com.topjohnwu.magisk.di.viewModel
|
|
||||||
import com.topjohnwu.magisk.ui.MainActivity
|
import com.topjohnwu.magisk.ui.MainActivity
|
||||||
import com.topjohnwu.magisk.utils.MotionRevealHelper
|
import com.topjohnwu.magisk.utils.MotionRevealHelper
|
||||||
import rikka.recyclerview.addEdgeSpacing
|
import rikka.recyclerview.addEdgeSpacing
|
||||||
import rikka.recyclerview.addItemSpacing
|
import rikka.recyclerview.addItemSpacing
|
||||||
import rikka.recyclerview.fixEdgeEffect
|
import rikka.recyclerview.fixEdgeEffect
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
class LogFragment : BaseFragment<FragmentLogMd2Binding>() {
|
class LogFragment : BaseFragment<FragmentLogMd2Binding>(), MenuProvider {
|
||||||
|
|
||||||
override val layoutRes = R.layout.fragment_log_md2
|
override val layoutRes = R.layout.fragment_log_md2
|
||||||
override val viewModel by viewModel<LogViewModel>()
|
override val viewModel by viewModel<LogViewModel>()
|
||||||
|
override val snackbarView: View?
|
||||||
|
get() = if (isMagiskLogVisible) binding.logFilterSuperuser.snackbarContainer
|
||||||
|
else super.snackbarView
|
||||||
override val snackbarAnchorView get() = binding.logFilterToggle
|
override val snackbarAnchorView get() = binding.logFilterToggle
|
||||||
|
|
||||||
private var actionSave: MenuItem? = null
|
private var actionSave: MenuItem? = null
|
||||||
@@ -37,8 +42,7 @@ class LogFragment : BaseFragment<FragmentLogMd2Binding>() {
|
|||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
setHasOptionsMenu(true)
|
activity?.setTitle(CoreR.string.logs)
|
||||||
activity.title = resources.getString(R.string.logs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -55,15 +59,14 @@ class LogFragment : BaseFragment<FragmentLogMd2Binding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater)
|
|
||||||
inflater.inflate(R.menu.menu_log_md2, menu)
|
inflater.inflate(R.menu.menu_log_md2, menu)
|
||||||
actionSave = menu.findItem(R.id.action_save)?.also {
|
actionSave = menu.findItem(R.id.action_save)?.also {
|
||||||
it.isVisible = !isMagiskLogVisible
|
it.isVisible = !isMagiskLogVisible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onMenuItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.action_save -> viewModel.saveMagiskLog()
|
R.id.action_save -> viewModel.saveMagiskLog()
|
||||||
R.id.action_clear ->
|
R.id.action_clear ->
|
@@ -0,0 +1,28 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.log
|
||||||
|
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.textview.MaterialTextView
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.DiffItem
|
||||||
|
import com.topjohnwu.magisk.databinding.ItemWrapper
|
||||||
|
import com.topjohnwu.magisk.databinding.ObservableRvItem
|
||||||
|
import com.topjohnwu.magisk.databinding.ViewAwareItem
|
||||||
|
|
||||||
|
class LogRvItem(
|
||||||
|
override val item: String
|
||||||
|
) : ObservableRvItem(), DiffItem<LogRvItem>, ItemWrapper<String>, ViewAwareItem {
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.item_log_textview
|
||||||
|
|
||||||
|
override fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView) {
|
||||||
|
val view = binding.root as MaterialTextView
|
||||||
|
view.measure(0, 0)
|
||||||
|
val desiredWidth = view.measuredWidth
|
||||||
|
val layoutParams = view.layoutParams
|
||||||
|
layoutParams.width = desiredWidth
|
||||||
|
if (recyclerView.width < desiredWidth) {
|
||||||
|
recyclerView.requestLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,22 +1,22 @@
|
|||||||
package com.topjohnwu.magisk.ui.log
|
package com.topjohnwu.magisk.ui.log
|
||||||
|
|
||||||
|
import android.system.Os
|
||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.core.BuildConfig
|
||||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.R
|
||||||
|
import com.topjohnwu.magisk.core.ktx.timeFormatStandard
|
||||||
|
import com.topjohnwu.magisk.core.ktx.toTime
|
||||||
|
import com.topjohnwu.magisk.core.repository.LogRepository
|
||||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
import com.topjohnwu.magisk.databinding.bindExtra
|
||||||
import com.topjohnwu.magisk.databinding.diffListOf
|
import com.topjohnwu.magisk.databinding.diffList
|
||||||
import com.topjohnwu.magisk.databinding.itemBindingOf
|
|
||||||
import com.topjohnwu.magisk.databinding.set
|
import com.topjohnwu.magisk.databinding.set
|
||||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||||
import com.topjohnwu.magisk.ktx.now
|
|
||||||
import com.topjohnwu.magisk.ktx.timeFormatStandard
|
|
||||||
import com.topjohnwu.magisk.ktx.toTime
|
|
||||||
import com.topjohnwu.magisk.view.TextItem
|
import com.topjohnwu.magisk.view.TextItem
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -25,7 +25,10 @@ import java.io.FileInputStream
|
|||||||
|
|
||||||
class LogViewModel(
|
class LogViewModel(
|
||||||
private val repo: LogRepository
|
private val repo: LogRepository
|
||||||
) : BaseViewModel() {
|
) : AsyncLoadViewModel() {
|
||||||
|
@get:Bindable
|
||||||
|
var loading = true
|
||||||
|
private set(value) = set(value, field, { field = it }, BR.loading)
|
||||||
|
|
||||||
// --- empty view
|
// --- empty view
|
||||||
|
|
||||||
@@ -34,52 +37,63 @@ class LogViewModel(
|
|||||||
|
|
||||||
// --- su log
|
// --- su log
|
||||||
|
|
||||||
val items = diffListOf<LogRvItem>()
|
val items = diffList<SuLogRvItem>()
|
||||||
val itemBinding = itemBindingOf<LogRvItem> {
|
val extraBindings = bindExtra {
|
||||||
it.bindExtra(BR.viewModel, this)
|
it.put(BR.viewModel, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- magisk log
|
// --- magisk log
|
||||||
@get:Bindable
|
val logs = diffList<LogRvItem>()
|
||||||
var consoleText = " "
|
var magiskLogRaw = " "
|
||||||
set(value) = set(value, field, { field = it }, BR.consoleText)
|
|
||||||
|
|
||||||
override fun refresh() = viewModelScope.launch {
|
override suspend fun doLoadWork() {
|
||||||
consoleText = repo.fetchMagiskLogs()
|
loading = true
|
||||||
val (suLogs, diff) = withContext(Dispatchers.Default) {
|
|
||||||
val suLogs = repo.fetchSuLogs().map { LogRvItem(it) }
|
val (suLogs, suDiff) = withContext(Dispatchers.Default) {
|
||||||
|
magiskLogRaw = repo.fetchMagiskLogs()
|
||||||
|
val newLogs = magiskLogRaw.split('\n').map { LogRvItem(it) }
|
||||||
|
logs.update(newLogs)
|
||||||
|
val suLogs = repo.fetchSuLogs().map { SuLogRvItem(it) }
|
||||||
suLogs to items.calculateDiff(suLogs)
|
suLogs to items.calculateDiff(suLogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
items.firstOrNull()?.isTop = false
|
items.firstOrNull()?.isTop = false
|
||||||
items.lastOrNull()?.isBottom = false
|
items.lastOrNull()?.isBottom = false
|
||||||
items.update(suLogs, diff)
|
items.update(suLogs, suDiff)
|
||||||
items.firstOrNull()?.isTop = true
|
items.firstOrNull()?.isTop = true
|
||||||
items.lastOrNull()?.isBottom = true
|
items.lastOrNull()?.isBottom = true
|
||||||
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveMagiskLog() = withExternalRW {
|
fun saveMagiskLog() = withExternalRW {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val filename = "magisk_log_%s.log".format(now.toTime(timeFormatStandard))
|
val filename = "magisk_log_%s.log".format(
|
||||||
val logFile = MediaStoreUtils.getFile(filename, true)
|
System.currentTimeMillis().toTime(timeFormatStandard))
|
||||||
|
val logFile = MediaStoreUtils.getFile(filename)
|
||||||
logFile.uri.outputStream().bufferedWriter().use { file ->
|
logFile.uri.outputStream().bufferedWriter().use { file ->
|
||||||
file.write("---Detected Device Info---\n\n")
|
file.write("---Detected Device Info---\n\n")
|
||||||
file.write("isAB=${Info.isAB}\n")
|
file.write("isAB=${Info.isAB}\n")
|
||||||
file.write("isSAR=${Info.isSAR}\n")
|
file.write("isSAR=${Info.isSAR}\n")
|
||||||
file.write("ramdisk=${Info.ramdisk}\n")
|
file.write("ramdisk=${Info.ramdisk}\n")
|
||||||
|
val uname = Os.uname()
|
||||||
|
file.write("kernel=${uname.sysname} ${uname.machine} ${uname.release} ${uname.version}\n")
|
||||||
|
|
||||||
file.write("\n\n---System Properties---\n\n")
|
file.write("\n\n---System Properties---\n\n")
|
||||||
ProcessBuilder("getprop").start()
|
ProcessBuilder("getprop").start()
|
||||||
.inputStream.reader().use { it.copyTo(file) }
|
.inputStream.reader().use { it.copyTo(file) }
|
||||||
|
|
||||||
|
file.write("\n\n---Environment Variables---\n\n")
|
||||||
|
System.getenv().forEach { (key, value) -> file.write("${key}=${value}\n") }
|
||||||
|
|
||||||
file.write("\n\n---System MountInfo---\n\n")
|
file.write("\n\n---System MountInfo---\n\n")
|
||||||
FileInputStream("/proc/self/mountinfo").reader().use { it.copyTo(file) }
|
FileInputStream("/proc/self/mountinfo").reader().use { it.copyTo(file) }
|
||||||
|
|
||||||
file.write("\n---Magisk Logs---\n")
|
file.write("\n---Magisk Logs---\n")
|
||||||
file.write("${Info.env.versionString} (${Info.env.versionCode})\n\n")
|
file.write("${Info.env.versionString} (${Info.env.versionCode})\n\n")
|
||||||
file.write(consoleText)
|
if (Info.env.isActive) file.write(magiskLogRaw)
|
||||||
|
|
||||||
file.write("\n---Manager Logs---\n")
|
file.write("\n---Manager Logs---\n")
|
||||||
file.write("${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})\n\n")
|
file.write("${BuildConfig.APP_VERSION_NAME} (${BuildConfig.APP_VERSION_CODE})\n\n")
|
||||||
ProcessBuilder("logcat", "-d").start()
|
ProcessBuilder("logcat", "-d").start()
|
||||||
.inputStream.reader().use { it.copyTo(file) }
|
.inputStream.reader().use { it.copyTo(file) }
|
||||||
}
|
}
|
||||||
@@ -89,12 +103,12 @@ class LogViewModel(
|
|||||||
|
|
||||||
fun clearMagiskLog() = repo.clearMagiskLogs {
|
fun clearMagiskLog() = repo.clearMagiskLogs {
|
||||||
SnackbarEvent(R.string.logs_cleared).publish()
|
SnackbarEvent(R.string.logs_cleared).publish()
|
||||||
requestRefresh()
|
startLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearLog() = viewModelScope.launch {
|
fun clearLog() = viewModelScope.launch {
|
||||||
repo.clearLogs()
|
repo.clearLogs()
|
||||||
SnackbarEvent(R.string.logs_cleared).publish()
|
SnackbarEvent(R.string.logs_cleared).publish()
|
||||||
requestRefresh()
|
startLoading()
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,54 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.log
|
||||||
|
|
||||||
|
import androidx.databinding.Bindable
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.AppContext
|
||||||
|
import com.topjohnwu.magisk.core.ktx.timeDateFormat
|
||||||
|
import com.topjohnwu.magisk.core.ktx.toTime
|
||||||
|
import com.topjohnwu.magisk.core.model.su.SuLog
|
||||||
|
import com.topjohnwu.magisk.databinding.DiffItem
|
||||||
|
import com.topjohnwu.magisk.databinding.ObservableRvItem
|
||||||
|
import com.topjohnwu.magisk.databinding.set
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
|
class SuLogRvItem(val log: SuLog) : ObservableRvItem(), DiffItem<SuLogRvItem> {
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.item_log_access_md2
|
||||||
|
|
||||||
|
val info = genInfo()
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var isTop = false
|
||||||
|
set(value) = set(value, field, { field = it }, BR.top)
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var isBottom = false
|
||||||
|
set(value) = set(value, field, { field = it }, BR.bottom)
|
||||||
|
|
||||||
|
override fun itemSameAs(other: SuLogRvItem) = log.appName == other.log.appName
|
||||||
|
|
||||||
|
private fun genInfo(): String {
|
||||||
|
val res = AppContext.resources
|
||||||
|
val sb = StringBuilder()
|
||||||
|
val date = log.time.toTime(timeDateFormat)
|
||||||
|
val toUid = res.getString(CoreR.string.target_uid, log.toUid)
|
||||||
|
val fromPid = res.getString(CoreR.string.pid, log.fromPid)
|
||||||
|
sb.append("$date\n$toUid $fromPid")
|
||||||
|
if (log.target != -1) {
|
||||||
|
val pid = if (log.target == 0) "magiskd" else log.target.toString()
|
||||||
|
val target = res.getString(CoreR.string.target_pid, pid)
|
||||||
|
sb.append(" $target")
|
||||||
|
}
|
||||||
|
if (log.context.isNotEmpty()) {
|
||||||
|
val context = res.getString(CoreR.string.selinux_context, log.context)
|
||||||
|
sb.append("\n$context")
|
||||||
|
}
|
||||||
|
if (log.gids.isNotEmpty()) {
|
||||||
|
val gids = res.getString(CoreR.string.supp_group, log.gids)
|
||||||
|
sb.append("\n$gids")
|
||||||
|
}
|
||||||
|
sb.append("\n${log.command}")
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,108 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.module
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewTreeObserver
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.view.MenuProvider
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.arch.BaseFragment
|
||||||
|
import com.topjohnwu.magisk.arch.viewModel
|
||||||
|
import com.topjohnwu.magisk.core.ktx.toast
|
||||||
|
import com.topjohnwu.magisk.databinding.FragmentActionMd2Binding
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
|
class ActionFragment : BaseFragment<FragmentActionMd2Binding>(), MenuProvider {
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.fragment_action_md2
|
||||||
|
override val viewModel by viewModel<ActionViewModel>()
|
||||||
|
override val snackbarView: View get() = binding.snackbarContainer
|
||||||
|
|
||||||
|
private var defaultOrientation = -1
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
viewModel.args = ActionFragmentArgs.fromBundle(requireArguments())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
activity?.setTitle(viewModel.args.name)
|
||||||
|
binding.closeBtn.setOnClickListener {
|
||||||
|
activity?.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.state.observe(this) {
|
||||||
|
if (it != ActionViewModel.State.RUNNING) {
|
||||||
|
binding.closeBtn.apply {
|
||||||
|
if (!this.isVisible) this.show()
|
||||||
|
if (!this.isFocused) this.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (it != ActionViewModel.State.SUCCESS) return@observe
|
||||||
|
view?.viewTreeObserver?.addOnWindowFocusChangeListener(
|
||||||
|
object : ViewTreeObserver.OnWindowFocusChangeListener {
|
||||||
|
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||||
|
if (hasFocus) return
|
||||||
|
view?.viewTreeObserver?.removeOnWindowFocusChangeListener(this)
|
||||||
|
view?.context?.apply {
|
||||||
|
toast(
|
||||||
|
getString(CoreR.string.done_action, viewModel.args.name),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
viewModel.back()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
inflater.inflate(R.menu.menu_flash, menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemSelected(item: MenuItem): Boolean {
|
||||||
|
return viewModel.onMenuItemClicked(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
defaultOrientation = activity?.requestedOrientation ?: -1
|
||||||
|
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
viewModel.startRunAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("WrongConstant")
|
||||||
|
override fun onDestroyView() {
|
||||||
|
if (defaultOrientation != -1) {
|
||||||
|
activity?.requestedOrientation = defaultOrientation
|
||||||
|
}
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onKeyEvent(event: KeyEvent): Boolean {
|
||||||
|
return when (event.keyCode) {
|
||||||
|
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> true
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed(): Boolean {
|
||||||
|
if (viewModel.state.value == ActionViewModel.State.RUNNING) return true
|
||||||
|
return super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPreBind(binding: FragmentActionMd2Binding) = Unit
|
||||||
|
}
|
@@ -0,0 +1,88 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.module
|
||||||
|
|
||||||
|
import android.view.MenuItem
|
||||||
|
import androidx.databinding.ObservableArrayList
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.core.ktx.synchronized
|
||||||
|
import com.topjohnwu.magisk.core.ktx.timeFormatStandard
|
||||||
|
import com.topjohnwu.magisk.core.ktx.toTime
|
||||||
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||||
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||||
|
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||||
|
import com.topjohnwu.magisk.ui.flash.ConsoleItem
|
||||||
|
import com.topjohnwu.superuser.CallbackList
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class ActionViewModel : BaseViewModel() {
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
RUNNING, SUCCESS, FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _state = MutableLiveData(State.RUNNING)
|
||||||
|
val state: LiveData<State> get() = _state
|
||||||
|
|
||||||
|
val items = ObservableArrayList<ConsoleItem>()
|
||||||
|
lateinit var args: ActionFragmentArgs
|
||||||
|
|
||||||
|
private val logItems = mutableListOf<String>().synchronized()
|
||||||
|
private val outItems = object : CallbackList<String>() {
|
||||||
|
override fun onAddElement(e: String?) {
|
||||||
|
e ?: return
|
||||||
|
items.add(ConsoleItem(e))
|
||||||
|
logItems.add(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startRunAction() = viewModelScope.launch {
|
||||||
|
onResult(withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
Shell.cmd("run_action \'${args.id}\'")
|
||||||
|
.to(outItems, logItems)
|
||||||
|
.exec().isSuccess
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Timber.e(e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onResult(success: Boolean) {
|
||||||
|
_state.value = if (success) State.SUCCESS else State.FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMenuItemClicked(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.action_save -> savePressed()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun savePressed() = withExternalRW {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val name = "%s_action_log_%s.log".format(
|
||||||
|
args.name,
|
||||||
|
System.currentTimeMillis().toTime(timeFormatStandard)
|
||||||
|
)
|
||||||
|
val file = MediaStoreUtils.getFile(name)
|
||||||
|
file.uri.outputStream().bufferedWriter().use { writer ->
|
||||||
|
synchronized(logItems) {
|
||||||
|
logItems.forEach {
|
||||||
|
writer.write(it)
|
||||||
|
writer.newLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SnackbarEvent(file.toString()).publish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -4,12 +4,14 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.BaseFragment
|
import com.topjohnwu.magisk.arch.BaseFragment
|
||||||
|
import com.topjohnwu.magisk.arch.viewModel
|
||||||
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
|
||||||
import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding
|
import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding
|
||||||
import com.topjohnwu.magisk.di.viewModel
|
|
||||||
import rikka.recyclerview.addEdgeSpacing
|
import rikka.recyclerview.addEdgeSpacing
|
||||||
import rikka.recyclerview.addInvalidateItemDecorationsObserver
|
import rikka.recyclerview.addInvalidateItemDecorationsObserver
|
||||||
import rikka.recyclerview.addItemSpacing
|
import rikka.recyclerview.addItemSpacing
|
||||||
import rikka.recyclerview.fixEdgeEffect
|
import rikka.recyclerview.fixEdgeEffect
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
class ModuleFragment : BaseFragment<FragmentModuleMd2Binding>() {
|
class ModuleFragment : BaseFragment<FragmentModuleMd2Binding>() {
|
||||||
|
|
||||||
@@ -18,8 +20,13 @@ class ModuleFragment : BaseFragment<FragmentModuleMd2Binding>() {
|
|||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
setHasOptionsMenu(true)
|
activity?.title = resources.getString(CoreR.string.modules)
|
||||||
activity.title = resources.getString(R.string.modules)
|
viewModel.data.observe(this) {
|
||||||
|
it ?: return@observe
|
||||||
|
val displayName = runCatching { it.displayName }.getOrNull() ?: return@observe
|
||||||
|
viewModel.requestInstallLocalModule(it, displayName)
|
||||||
|
viewModel.data.value = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
@@ -5,24 +5,27 @@ import com.topjohnwu.magisk.BR
|
|||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.model.module.LocalModule
|
import com.topjohnwu.magisk.core.model.module.LocalModule
|
||||||
import com.topjohnwu.magisk.databinding.DiffRvItem
|
import com.topjohnwu.magisk.databinding.DiffItem
|
||||||
import com.topjohnwu.magisk.databinding.ObservableDiffRvItem
|
import com.topjohnwu.magisk.databinding.ItemWrapper
|
||||||
import com.topjohnwu.magisk.databinding.RvContainer
|
import com.topjohnwu.magisk.databinding.ObservableRvItem
|
||||||
|
import com.topjohnwu.magisk.databinding.RvItem
|
||||||
import com.topjohnwu.magisk.databinding.set
|
import com.topjohnwu.magisk.databinding.set
|
||||||
import com.topjohnwu.magisk.utils.TextHolder
|
import com.topjohnwu.magisk.utils.TextHolder
|
||||||
import com.topjohnwu.magisk.utils.asText
|
import com.topjohnwu.magisk.utils.asText
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
object InstallModule : DiffRvItem<InstallModule>() {
|
object InstallModule : RvItem(), DiffItem<InstallModule> {
|
||||||
override val layoutRes = R.layout.item_module_download
|
override val layoutRes = R.layout.item_module_download
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalModuleRvItem(
|
class LocalModuleRvItem(
|
||||||
override val item: LocalModule
|
override val item: LocalModule
|
||||||
) : ObservableDiffRvItem<LocalModuleRvItem>(), RvContainer<LocalModule> {
|
) : ObservableRvItem(), DiffItem<LocalModuleRvItem>, ItemWrapper<LocalModule> {
|
||||||
|
|
||||||
override val layoutRes = R.layout.item_module_md2
|
override val layoutRes = R.layout.item_module_md2
|
||||||
|
|
||||||
val showNotice: Boolean
|
val showNotice: Boolean
|
||||||
|
val showAction: Boolean
|
||||||
val noticeText: TextHolder
|
val noticeText: TextHolder
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -33,11 +36,12 @@ class LocalModuleRvItem(
|
|||||||
showNotice = zygiskUnloaded ||
|
showNotice = zygiskUnloaded ||
|
||||||
(Info.isZygiskEnabled && isRiru) ||
|
(Info.isZygiskEnabled && isRiru) ||
|
||||||
(!Info.isZygiskEnabled && isZygisk)
|
(!Info.isZygiskEnabled && isZygisk)
|
||||||
|
showAction = item.hasAction && !showNotice
|
||||||
noticeText =
|
noticeText =
|
||||||
when {
|
when {
|
||||||
zygiskUnloaded -> R.string.zygisk_module_unloaded.asText()
|
zygiskUnloaded -> CoreR.string.zygisk_module_unloaded.asText()
|
||||||
isRiru -> R.string.suspend_text_riru.asText(R.string.zygisk.asText())
|
isRiru -> CoreR.string.suspend_text_riru.asText(CoreR.string.zygisk.asText())
|
||||||
else -> R.string.suspend_text_zygisk.asText(R.string.zygisk.asText())
|
else -> CoreR.string.suspend_text_zygisk.asText(CoreR.string.zygisk.asText())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@@ -0,0 +1,108 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.module
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.databinding.Bindable
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.MainDirections
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.base.ContentResultCallback
|
||||||
|
import com.topjohnwu.magisk.core.model.module.LocalModule
|
||||||
|
import com.topjohnwu.magisk.core.model.module.OnlineModule
|
||||||
|
import com.topjohnwu.magisk.databinding.MergeObservableList
|
||||||
|
import com.topjohnwu.magisk.databinding.RvItem
|
||||||
|
import com.topjohnwu.magisk.databinding.bindExtra
|
||||||
|
import com.topjohnwu.magisk.databinding.diffList
|
||||||
|
import com.topjohnwu.magisk.databinding.set
|
||||||
|
import com.topjohnwu.magisk.dialog.LocalModuleInstallDialog
|
||||||
|
import com.topjohnwu.magisk.dialog.OnlineModuleInstallDialog
|
||||||
|
import com.topjohnwu.magisk.events.GetContentEvent
|
||||||
|
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
|
class ModuleViewModel : AsyncLoadViewModel() {
|
||||||
|
|
||||||
|
val bottomBarBarrierIds = intArrayOf(R.id.module_update, R.id.module_remove)
|
||||||
|
|
||||||
|
private val itemsInstalled = diffList<LocalModuleRvItem>()
|
||||||
|
|
||||||
|
val items = MergeObservableList<RvItem>()
|
||||||
|
val extraBindings = bindExtra {
|
||||||
|
it.put(BR.viewModel, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
val data get() = uri
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var loading = true
|
||||||
|
private set(value) = set(value, field, { field = it }, BR.loading)
|
||||||
|
|
||||||
|
override suspend fun doLoadWork() {
|
||||||
|
loading = true
|
||||||
|
val moduleLoaded = Info.env.isActive &&
|
||||||
|
withContext(Dispatchers.IO) { LocalModule.loaded() }
|
||||||
|
if (moduleLoaded) {
|
||||||
|
loadInstalled()
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
items.insertItem(InstallModule)
|
||||||
|
.insertList(itemsInstalled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loading = false
|
||||||
|
loadUpdateInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNetworkChanged(network: Boolean) = startLoading()
|
||||||
|
|
||||||
|
private suspend fun loadInstalled() {
|
||||||
|
withContext(Dispatchers.Default) {
|
||||||
|
val installed = LocalModule.installed().map { LocalModuleRvItem(it) }
|
||||||
|
itemsInstalled.update(installed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loadUpdateInfo() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
itemsInstalled.forEach {
|
||||||
|
if (it.item.fetch())
|
||||||
|
it.fetchedUpdateInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun downloadPressed(item: OnlineModule?) =
|
||||||
|
if (item != null && Info.isConnected.value == true) {
|
||||||
|
withExternalRW { OnlineModuleInstallDialog(item).show() }
|
||||||
|
} else {
|
||||||
|
SnackbarEvent(CoreR.string.no_connection).publish()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun installPressed() = withExternalRW {
|
||||||
|
GetContentEvent("application/zip", UriCallback()).publish()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestInstallLocalModule(uri: Uri, displayName: String) {
|
||||||
|
LocalModuleInstallDialog(this, uri, displayName).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class UriCallback : ContentResultCallback {
|
||||||
|
override fun onActivityResult(result: Uri) {
|
||||||
|
uri.value = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun runAction(id: String, name: String) {
|
||||||
|
MainDirections.actionActionFragment(id, name).navigate()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val uri = MutableLiveData<Uri?>()
|
||||||
|
}
|
||||||
|
}
|
@@ -6,6 +6,7 @@ import android.view.View
|
|||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.ktx.activity
|
||||||
import com.topjohnwu.magisk.databinding.ObservableRvItem
|
import com.topjohnwu.magisk.databinding.ObservableRvItem
|
||||||
import com.topjohnwu.magisk.databinding.set
|
import com.topjohnwu.magisk.databinding.set
|
||||||
import com.topjohnwu.magisk.utils.TextHolder
|
import com.topjohnwu.magisk.utils.TextHolder
|
||||||
@@ -13,29 +14,34 @@ import com.topjohnwu.magisk.view.MagiskDialog
|
|||||||
|
|
||||||
sealed class BaseSettingsItem : ObservableRvItem() {
|
sealed class BaseSettingsItem : ObservableRvItem() {
|
||||||
|
|
||||||
|
interface Handler {
|
||||||
|
fun onItemPressed(view: View, item: BaseSettingsItem, andThen: () -> Unit)
|
||||||
|
fun onItemAction(view: View, item: BaseSettingsItem)
|
||||||
|
}
|
||||||
|
|
||||||
override val layoutRes get() = R.layout.item_settings
|
override val layoutRes get() = R.layout.item_settings
|
||||||
|
|
||||||
open val icon: Int get() = 0
|
open val icon: Int get() = 0
|
||||||
open val title: TextHolder get() = TextHolder.EMPTY
|
open val title: TextHolder get() = TextHolder.EMPTY
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
open val description: TextHolder get() = TextHolder.EMPTY
|
open val description: TextHolder get() = TextHolder.EMPTY
|
||||||
open val showSwitch get() = false
|
|
||||||
@get:Bindable
|
|
||||||
open val isChecked get() = false
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
var isEnabled = true
|
var isEnabled = true
|
||||||
set(value) = set(value, field, { field = it }, BR.enabled, BR.description)
|
set(value) = set(value, field, { field = it }, BR.enabled, BR.description)
|
||||||
|
|
||||||
open fun onToggle(view: View, handler: Handler, checked: Boolean) {}
|
|
||||||
open fun onPressed(view: View, handler: Handler) {
|
open fun onPressed(view: View, handler: Handler) {
|
||||||
handler.onItemPressed(view, this)
|
handler.onItemPressed(view, this) {
|
||||||
|
handler.onItemAction(view, this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
open fun refresh() {}
|
open fun refresh() {}
|
||||||
|
|
||||||
interface Handler {
|
// Only for toggle
|
||||||
fun onItemPressed(view: View, item: BaseSettingsItem, andThen: () -> Unit = {})
|
open val showSwitch get() = false
|
||||||
fun onItemAction(view: View, item: BaseSettingsItem)
|
@get:Bindable
|
||||||
}
|
open val isChecked get() = false
|
||||||
|
fun onToggle(view: View, handler: Handler, checked: Boolean) =
|
||||||
|
set(checked, isChecked, { onPressed(view, handler) })
|
||||||
|
|
||||||
abstract class Value<T> : BaseSettingsItem() {
|
abstract class Value<T> : BaseSettingsItem() {
|
||||||
|
|
||||||
@@ -53,10 +59,9 @@ sealed class BaseSettingsItem : ObservableRvItem() {
|
|||||||
override val showSwitch get() = true
|
override val showSwitch get() = true
|
||||||
override val isChecked get() = value
|
override val isChecked get() = value
|
||||||
|
|
||||||
override fun onToggle(view: View, handler: Handler, checked: Boolean) =
|
|
||||||
set(checked, value, { onPressed(view, handler) })
|
|
||||||
|
|
||||||
override fun onPressed(view: View, handler: Handler) {
|
override fun onPressed(view: View, handler: Handler) {
|
||||||
|
// Make sure the checked state is synced
|
||||||
|
notifyPropertyChanged(BR.checked)
|
||||||
handler.onItemPressed(view, this) {
|
handler.onItemPressed(view, this) {
|
||||||
value = !value
|
value = !value
|
||||||
notifyPropertyChanged(BR.checked)
|
notifyPropertyChanged(BR.checked)
|
||||||
@@ -72,7 +77,7 @@ sealed class BaseSettingsItem : ObservableRvItem() {
|
|||||||
|
|
||||||
override fun onPressed(view: View, handler: Handler) {
|
override fun onPressed(view: View, handler: Handler) {
|
||||||
handler.onItemPressed(view, this) {
|
handler.onItemPressed(view, this) {
|
||||||
MagiskDialog(view.context).apply {
|
MagiskDialog(view.activity).apply {
|
||||||
setTitle(title.getText(view.resources))
|
setTitle(title.getText(view.resources))
|
||||||
setView(getView(view.context))
|
setView(getView(view.context))
|
||||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||||
@@ -115,7 +120,7 @@ sealed class BaseSettingsItem : ObservableRvItem() {
|
|||||||
|
|
||||||
override fun onPressed(view: View, handler: Handler) {
|
override fun onPressed(view: View, handler: Handler) {
|
||||||
handler.onItemPressed(view, this) {
|
handler.onItemPressed(view, this) {
|
||||||
MagiskDialog(view.context).apply {
|
MagiskDialog(view.activity).apply {
|
||||||
setTitle(title.getText(view.resources))
|
setTitle(title.getText(view.resources))
|
||||||
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||||
text = android.R.string.cancel
|
text = android.R.string.cancel
|
||||||
@@ -137,5 +142,4 @@ sealed class BaseSettingsItem : ObservableRvItem() {
|
|||||||
abstract class Section : BaseSettingsItem() {
|
abstract class Section : BaseSettingsItem() {
|
||||||
override val layoutRes = R.layout.item_settings_section
|
override val layoutRes = R.layout.item_settings_section
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -4,21 +4,23 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.BaseFragment
|
import com.topjohnwu.magisk.arch.BaseFragment
|
||||||
|
import com.topjohnwu.magisk.arch.viewModel
|
||||||
import com.topjohnwu.magisk.databinding.FragmentSettingsMd2Binding
|
import com.topjohnwu.magisk.databinding.FragmentSettingsMd2Binding
|
||||||
import com.topjohnwu.magisk.di.viewModel
|
|
||||||
import rikka.recyclerview.addEdgeSpacing
|
import rikka.recyclerview.addEdgeSpacing
|
||||||
import rikka.recyclerview.addItemSpacing
|
import rikka.recyclerview.addItemSpacing
|
||||||
import rikka.recyclerview.fixEdgeEffect
|
import rikka.recyclerview.fixEdgeEffect
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
class SettingsFragment : BaseFragment<FragmentSettingsMd2Binding>() {
|
class SettingsFragment : BaseFragment<FragmentSettingsMd2Binding>() {
|
||||||
|
|
||||||
override val layoutRes = R.layout.fragment_settings_md2
|
override val layoutRes = R.layout.fragment_settings_md2
|
||||||
override val viewModel by viewModel<SettingsViewModel>()
|
override val viewModel by viewModel<SettingsViewModel>()
|
||||||
|
override val snackbarView: View get() = binding.snackbarContainer
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
|
|
||||||
activity.title = resources.getString(R.string.settings)
|
activity?.title = resources.getString(CoreR.string.settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
@@ -7,81 +7,70 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.BuildConfig
|
||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.JobService
|
import com.topjohnwu.magisk.core.ktx.activity
|
||||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
import com.topjohnwu.magisk.core.utils.LocaleSetting
|
||||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||||
import com.topjohnwu.magisk.core.utils.availableLocales
|
|
||||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
|
||||||
import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding
|
import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding
|
||||||
import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding
|
import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding
|
||||||
import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding
|
import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding
|
||||||
import com.topjohnwu.magisk.databinding.set
|
import com.topjohnwu.magisk.databinding.set
|
||||||
import com.topjohnwu.magisk.di.AppContext
|
import com.topjohnwu.magisk.utils.TextHolder
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
|
||||||
import com.topjohnwu.magisk.utils.asText
|
import com.topjohnwu.magisk.utils.asText
|
||||||
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
// --- Customization
|
// --- Customization
|
||||||
|
|
||||||
object Customization : BaseSettingsItem.Section() {
|
object Customization : BaseSettingsItem.Section() {
|
||||||
override val title = R.string.settings_customization.asText()
|
override val title = CoreR.string.settings_customization.asText()
|
||||||
}
|
}
|
||||||
|
|
||||||
object Language : BaseSettingsItem.Selector() {
|
object Language : BaseSettingsItem.Selector() {
|
||||||
override var value = -1
|
private val names: Array<String> get() = LocaleSetting.available.names
|
||||||
|
private val tags: Array<String> get() = LocaleSetting.available.tags
|
||||||
|
|
||||||
|
override var value
|
||||||
|
get() = tags.indexOf(Config.locale)
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
Config.locale = tags[value]
|
||||||
Config.locale = entryValues[value]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val title = R.string.language.asText()
|
override val title = CoreR.string.language.asText()
|
||||||
|
|
||||||
private var entries = emptyArray<String>()
|
override fun entries(res: Resources) = names
|
||||||
private var entryValues = emptyArray<String>()
|
override fun descriptions(res: Resources) = names
|
||||||
|
}
|
||||||
|
|
||||||
override fun entries(res: Resources) = entries
|
object LanguageSystem : BaseSettingsItem.Blank() {
|
||||||
override fun descriptions(res: Resources) = entries
|
override val title = CoreR.string.language.asText()
|
||||||
|
override val description: TextHolder
|
||||||
override fun onPressed(view: View, handler: Handler) {
|
get() {
|
||||||
if (entries.isNotEmpty())
|
val locale = LocaleSetting.instance.appLocale
|
||||||
super.onPressed(view, handler)
|
return locale?.getDisplayName(locale)?.asText() ?: CoreR.string.system_default.asText()
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun loadLanguages(scope: CoroutineScope) {
|
|
||||||
scope.launch {
|
|
||||||
availableLocales().let { (names, values) ->
|
|
||||||
entries = names
|
|
||||||
entryValues = values
|
|
||||||
val selectedLocale = currentLocale.getDisplayName(currentLocale)
|
|
||||||
value = names.indexOfFirst { it == selectedLocale }.let { if (it == -1) 0 else it }
|
|
||||||
notifyPropertyChanged(BR.description)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object Theme : BaseSettingsItem.Blank() {
|
object Theme : BaseSettingsItem.Blank() {
|
||||||
override val icon = R.drawable.ic_paint
|
override val icon = R.drawable.ic_paint
|
||||||
override val title = R.string.section_theme.asText()
|
override val title = CoreR.string.section_theme.asText()
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- App
|
// --- App
|
||||||
|
|
||||||
object AppSettings : BaseSettingsItem.Section() {
|
object AppSettings : BaseSettingsItem.Section() {
|
||||||
override val title = R.string.home_app_title.asText()
|
override val title = CoreR.string.home_app_title.asText()
|
||||||
}
|
}
|
||||||
|
|
||||||
object Hide : BaseSettingsItem.Input() {
|
object Hide : BaseSettingsItem.Input() {
|
||||||
override val title = R.string.settings_hide_app_title.asText()
|
override val title = CoreR.string.settings_hide_app_title.asText()
|
||||||
override val description = R.string.settings_hide_app_summary.asText()
|
override val description = CoreR.string.settings_hide_app_summary.asText()
|
||||||
override var value = ""
|
override var value = ""
|
||||||
|
|
||||||
override val inputResult
|
override val inputResult
|
||||||
@@ -92,7 +81,7 @@ object Hide : BaseSettingsItem.Input() {
|
|||||||
set(value) = set(value, field, { field = it }, BR.result, BR.error)
|
set(value) = set(value, field, { field = it }, BR.result, BR.error)
|
||||||
|
|
||||||
val maxLength
|
val maxLength
|
||||||
get() = HideAPK.MAX_LABEL_LENGTH
|
get() = AppMigration.MAX_LABEL_LENGTH
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
val isError
|
val isError
|
||||||
@@ -103,13 +92,33 @@ object Hide : BaseSettingsItem.Input() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object Restore : BaseSettingsItem.Blank() {
|
object Restore : BaseSettingsItem.Blank() {
|
||||||
override val title = R.string.settings_restore_app_title.asText()
|
override val title = CoreR.string.settings_restore_app_title.asText()
|
||||||
override val description = R.string.settings_restore_app_summary.asText()
|
override val description = CoreR.string.settings_restore_app_summary.asText()
|
||||||
|
|
||||||
|
override fun onPressed(view: View, handler: Handler) {
|
||||||
|
handler.onItemPressed(view, this) {
|
||||||
|
MagiskDialog(view.activity).apply {
|
||||||
|
setTitle(CoreR.string.settings_restore_app_title)
|
||||||
|
setMessage(CoreR.string.restore_app_confirmation)
|
||||||
|
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||||
|
text = android.R.string.ok
|
||||||
|
onClick {
|
||||||
|
handler.onItemAction(view, this@Restore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||||
|
text = android.R.string.cancel
|
||||||
|
}
|
||||||
|
setCancelable(true)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object AddShortcut : BaseSettingsItem.Blank() {
|
object AddShortcut : BaseSettingsItem.Blank() {
|
||||||
override val title = R.string.add_shortcut_title.asText()
|
override val title = CoreR.string.add_shortcut_title.asText()
|
||||||
override val description = R.string.setting_add_shortcut_summary.asText()
|
override val description = CoreR.string.setting_add_shortcut_summary.asText()
|
||||||
}
|
}
|
||||||
|
|
||||||
object DownloadPath : BaseSettingsItem.Input() {
|
object DownloadPath : BaseSettingsItem.Input() {
|
||||||
@@ -120,7 +129,7 @@ object DownloadPath : BaseSettingsItem.Input() {
|
|||||||
notifyPropertyChanged(BR.description)
|
notifyPropertyChanged(BR.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val title = R.string.settings_download_path_title.asText()
|
override val title = CoreR.string.settings_download_path_title.asText()
|
||||||
override val description get() = MediaStoreUtils.fullPath(value).asText()
|
override val description get() = MediaStoreUtils.fullPath(value).asText()
|
||||||
|
|
||||||
override var inputResult: String = value
|
override var inputResult: String = value
|
||||||
@@ -141,12 +150,12 @@ object UpdateChannel : BaseSettingsItem.Selector() {
|
|||||||
Info.remote = Info.EMPTY_REMOTE
|
Info.remote = Info.EMPTY_REMOTE
|
||||||
}
|
}
|
||||||
|
|
||||||
override val title = R.string.settings_update_channel_title.asText()
|
override val title = CoreR.string.settings_update_channel_title.asText()
|
||||||
|
|
||||||
override val entryRes = R.array.update_channel
|
override val entryRes = CoreR.array.update_channel
|
||||||
override fun entries(res: Resources): Array<String> {
|
override fun entries(res: Resources): Array<String> {
|
||||||
return super.entries(res).let {
|
return super.entries(res).let {
|
||||||
if (!BuildConfig.DEBUG)
|
if (!Const.APP_IS_CANARY && !BuildConfig.DEBUG)
|
||||||
it.copyOfRange(0, Config.Value.CANARY_CHANNEL)
|
it.copyOfRange(0, Config.Value.CANARY_CHANNEL)
|
||||||
else it
|
else it
|
||||||
}
|
}
|
||||||
@@ -154,7 +163,7 @@ object UpdateChannel : BaseSettingsItem.Selector() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object UpdateChannelUrl : BaseSettingsItem.Input() {
|
object UpdateChannelUrl : BaseSettingsItem.Input() {
|
||||||
override val title = R.string.settings_update_custom.asText()
|
override val title = CoreR.string.settings_update_custom.asText()
|
||||||
override val description get() = value.asText()
|
override val description get() = value.asText()
|
||||||
override var value
|
override var value
|
||||||
get() = Config.customChannelUrl
|
get() = Config.customChannelUrl
|
||||||
@@ -176,67 +185,57 @@ object UpdateChannelUrl : BaseSettingsItem.Input() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object UpdateChecker : BaseSettingsItem.Toggle() {
|
object UpdateChecker : BaseSettingsItem.Toggle() {
|
||||||
override val title = R.string.settings_check_update_title.asText()
|
override val title = CoreR.string.settings_check_update_title.asText()
|
||||||
override val description = R.string.settings_check_update_summary.asText()
|
override val description = CoreR.string.settings_check_update_summary.asText()
|
||||||
override var value
|
override var value by Config::checkUpdate
|
||||||
get() = Config.checkUpdate
|
|
||||||
set(value) {
|
|
||||||
Config.checkUpdate = value
|
|
||||||
JobService.schedule(AppContext)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object DoHToggle : BaseSettingsItem.Toggle() {
|
object DoHToggle : BaseSettingsItem.Toggle() {
|
||||||
override val title = R.string.settings_doh_title.asText()
|
override val title = CoreR.string.settings_doh_title.asText()
|
||||||
override val description = R.string.settings_doh_description.asText()
|
override val description = CoreR.string.settings_doh_description.asText()
|
||||||
override var value by Config::doh
|
override var value by Config::doh
|
||||||
}
|
}
|
||||||
|
|
||||||
object SystemlessHosts : BaseSettingsItem.Blank() {
|
object SystemlessHosts : BaseSettingsItem.Blank() {
|
||||||
override val title = R.string.settings_hosts_title.asText()
|
override val title = CoreR.string.settings_hosts_title.asText()
|
||||||
override val description = R.string.settings_hosts_summary.asText()
|
override val description = CoreR.string.settings_hosts_summary.asText()
|
||||||
|
}
|
||||||
|
|
||||||
|
object RandNameToggle : BaseSettingsItem.Toggle() {
|
||||||
|
override val title = CoreR.string.settings_random_name_title.asText()
|
||||||
|
override val description = CoreR.string.settings_random_name_description.asText()
|
||||||
|
override var value by Config::randName
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Magisk
|
// --- Magisk
|
||||||
|
|
||||||
object Magisk : BaseSettingsItem.Section() {
|
object Magisk : BaseSettingsItem.Section() {
|
||||||
override val title = R.string.magisk.asText()
|
override val title = CoreR.string.magisk.asText()
|
||||||
}
|
}
|
||||||
|
|
||||||
object Zygisk : BaseSettingsItem.Toggle() {
|
object Zygisk : BaseSettingsItem.Toggle() {
|
||||||
override val title = R.string.zygisk_beta.asText()
|
override val title = CoreR.string.zygisk.asText()
|
||||||
override val description get() =
|
override val description get() =
|
||||||
if (mismatch) R.string.reboot_apply_change.asText()
|
if (mismatch) CoreR.string.reboot_apply_change.asText()
|
||||||
else R.string.settings_zygisk_summary.asText()
|
else CoreR.string.settings_zygisk_summary.asText()
|
||||||
override var value
|
override var value
|
||||||
get() = Config.zygisk
|
get() = Config.zygisk
|
||||||
set(value) {
|
set(value) {
|
||||||
Config.zygisk = value
|
Config.zygisk = value
|
||||||
DenyList.isEnabled = value
|
|
||||||
DenyListConfig.isEnabled = value
|
|
||||||
notifyPropertyChanged(BR.description)
|
notifyPropertyChanged(BR.description)
|
||||||
DenyList.notifyPropertyChanged(BR.description)
|
|
||||||
}
|
}
|
||||||
val mismatch get() = value != Info.isZygiskEnabled
|
val mismatch get() = value != Info.isZygiskEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
object DenyList : BaseSettingsItem.Toggle() {
|
object DenyList : BaseSettingsItem.Toggle() {
|
||||||
override val title = R.string.settings_denylist_title.asText()
|
override val title = CoreR.string.settings_denylist_title.asText()
|
||||||
override val description get() =
|
override val description get() = CoreR.string.settings_denylist_summary.asText()
|
||||||
if (isEnabled) {
|
|
||||||
if (Zygisk.mismatch)
|
|
||||||
R.string.reboot_apply_change.asText()
|
|
||||||
else
|
|
||||||
R.string.settings_denylist_summary.asText()
|
|
||||||
} else {
|
|
||||||
R.string.settings_denylist_error.asText(R.string.zygisk.asText())
|
|
||||||
}
|
|
||||||
|
|
||||||
override var value = Config.denyList
|
override var value = Config.denyList
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
val cmd = if (value) "enable" else "disable"
|
val cmd = if (value) "enable" else "disable"
|
||||||
Shell.su("magisk --denylist $cmd").submit { result ->
|
Shell.cmd("magisk --denylist $cmd").submit { result ->
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
Config.denyList = value
|
Config.denyList = value
|
||||||
} else {
|
} else {
|
||||||
@@ -245,57 +244,48 @@ object DenyList : BaseSettingsItem.Toggle() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun refresh() {
|
|
||||||
isEnabled = Zygisk.value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object DenyListConfig : BaseSettingsItem.Blank() {
|
object DenyListConfig : BaseSettingsItem.Blank() {
|
||||||
override val title = R.string.settings_denylist_config_title.asText()
|
override val title = CoreR.string.settings_denylist_config_title.asText()
|
||||||
override val description = R.string.settings_denylist_config_summary.asText()
|
override val description = CoreR.string.settings_denylist_config_summary.asText()
|
||||||
override fun refresh() {
|
|
||||||
isEnabled = Zygisk.value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Superuser
|
// --- Superuser
|
||||||
|
|
||||||
object Tapjack : BaseSettingsItem.Toggle() {
|
object Tapjack : BaseSettingsItem.Toggle() {
|
||||||
override val title = R.string.settings_su_tapjack_title.asText()
|
override val title = CoreR.string.settings_su_tapjack_title.asText()
|
||||||
override val description = R.string.settings_su_tapjack_summary.asText()
|
override val description = CoreR.string.settings_su_tapjack_summary.asText()
|
||||||
override var value by Config::suTapjack
|
override var value by Config::suTapjack
|
||||||
}
|
}
|
||||||
|
|
||||||
object Biometrics : BaseSettingsItem.Toggle() {
|
object Authentication : BaseSettingsItem.Toggle() {
|
||||||
override val title = R.string.settings_su_biometric_title.asText()
|
override val title = CoreR.string.settings_su_auth_title.asText()
|
||||||
override var description = R.string.settings_su_biometric_summary.asText()
|
override var description = CoreR.string.settings_su_auth_summary.asText()
|
||||||
override var value by Config::suBiometric
|
override var value by Config::suAuth
|
||||||
|
|
||||||
override fun refresh() {
|
override fun refresh() {
|
||||||
isEnabled = BiometricHelper.isSupported
|
isEnabled = Info.isDeviceSecure
|
||||||
if (!isEnabled) {
|
if (!isEnabled) {
|
||||||
value = false
|
description = CoreR.string.settings_su_auth_insecure.asText()
|
||||||
description = R.string.no_biometric.asText()
|
|
||||||
notifyPropertyChanged(BR.checked)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Superuser : BaseSettingsItem.Section() {
|
object Superuser : BaseSettingsItem.Section() {
|
||||||
override val title = R.string.superuser.asText()
|
override val title = CoreR.string.superuser.asText()
|
||||||
}
|
}
|
||||||
|
|
||||||
object AccessMode : BaseSettingsItem.Selector() {
|
object AccessMode : BaseSettingsItem.Selector() {
|
||||||
override val title = R.string.superuser_access.asText()
|
override val title = CoreR.string.superuser_access.asText()
|
||||||
override val entryRes = R.array.su_access
|
override val entryRes = CoreR.array.su_access
|
||||||
override var value by Config::rootMode
|
override var value by Config::rootMode
|
||||||
}
|
}
|
||||||
|
|
||||||
object MultiuserMode : BaseSettingsItem.Selector() {
|
object MultiuserMode : BaseSettingsItem.Selector() {
|
||||||
override val title = R.string.multiuser_mode.asText()
|
override val title = CoreR.string.multiuser_mode.asText()
|
||||||
override val entryRes = R.array.multiuser_mode
|
override val entryRes = CoreR.array.multiuser_mode
|
||||||
override val descriptionRes = R.array.multiuser_summary
|
override val descriptionRes = CoreR.array.multiuser_summary
|
||||||
override var value by Config::suMultiuserMode
|
override var value by Config::suMultiuserMode
|
||||||
|
|
||||||
override fun refresh() {
|
override fun refresh() {
|
||||||
@@ -304,21 +294,21 @@ object MultiuserMode : BaseSettingsItem.Selector() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object MountNamespaceMode : BaseSettingsItem.Selector() {
|
object MountNamespaceMode : BaseSettingsItem.Selector() {
|
||||||
override val title = R.string.mount_namespace_mode.asText()
|
override val title = CoreR.string.mount_namespace_mode.asText()
|
||||||
override val entryRes = R.array.namespace
|
override val entryRes = CoreR.array.namespace
|
||||||
override val descriptionRes = R.array.namespace_summary
|
override val descriptionRes = CoreR.array.namespace_summary
|
||||||
override var value by Config::suMntNamespaceMode
|
override var value by Config::suMntNamespaceMode
|
||||||
}
|
}
|
||||||
|
|
||||||
object AutomaticResponse : BaseSettingsItem.Selector() {
|
object AutomaticResponse : BaseSettingsItem.Selector() {
|
||||||
override val title = R.string.auto_response.asText()
|
override val title = CoreR.string.auto_response.asText()
|
||||||
override val entryRes = R.array.auto_response
|
override val entryRes = CoreR.array.auto_response
|
||||||
override var value by Config::suAutoResponse
|
override var value by Config::suAutoResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
object RequestTimeout : BaseSettingsItem.Selector() {
|
object RequestTimeout : BaseSettingsItem.Selector() {
|
||||||
override val title = R.string.request_timeout.asText()
|
override val title = CoreR.string.request_timeout.asText()
|
||||||
override val entryRes = R.array.request_timeout
|
override val entryRes = CoreR.array.request_timeout
|
||||||
|
|
||||||
private val entryValues = listOf(10, 15, 20, 30, 45, 60)
|
private val entryValues = listOf(10, 15, 20, 30, 45, 60)
|
||||||
override var value = entryValues.indexOfFirst { it == Config.suDefaultTimeout }
|
override var value = entryValues.indexOfFirst { it == Config.suDefaultTimeout }
|
||||||
@@ -329,17 +319,17 @@ object RequestTimeout : BaseSettingsItem.Selector() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object SUNotification : BaseSettingsItem.Selector() {
|
object SUNotification : BaseSettingsItem.Selector() {
|
||||||
override val title = R.string.superuser_notification.asText()
|
override val title = CoreR.string.superuser_notification.asText()
|
||||||
override val entryRes = R.array.su_notification
|
override val entryRes = CoreR.array.su_notification
|
||||||
override var value by Config::suNotification
|
override var value by Config::suNotification
|
||||||
}
|
}
|
||||||
|
|
||||||
object Reauthenticate : BaseSettingsItem.Toggle() {
|
object Reauthenticate : BaseSettingsItem.Toggle() {
|
||||||
override val title = R.string.settings_su_reauth_title.asText()
|
override val title = CoreR.string.settings_su_reauth_title.asText()
|
||||||
override val description = R.string.settings_su_reauth_summary.asText()
|
override val description = CoreR.string.settings_su_reauth_summary.asText()
|
||||||
override var value by Config::suReAuth
|
override var value by Config::suReAuth
|
||||||
|
|
||||||
override fun refresh() {
|
override fun refresh() {
|
||||||
isEnabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.O && Utils.showSuperUser()
|
isEnabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.O && Info.showSuperUser
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,51 +1,48 @@
|
|||||||
package com.topjohnwu.magisk.ui.settings
|
package com.topjohnwu.magisk.ui.settings
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.provider.Settings
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.BuildConfig
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.core.AppContext
|
||||||
|
import com.topjohnwu.magisk.core.BuildConfig
|
||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.R
|
||||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
import com.topjohnwu.magisk.core.ktx.activity
|
||||||
import com.topjohnwu.magisk.databinding.adapterOf
|
import com.topjohnwu.magisk.core.ktx.toast
|
||||||
import com.topjohnwu.magisk.databinding.itemBindingOf
|
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||||
import com.topjohnwu.magisk.di.AppContext
|
import com.topjohnwu.magisk.core.utils.LocaleSetting
|
||||||
|
import com.topjohnwu.magisk.databinding.bindExtra
|
||||||
import com.topjohnwu.magisk.events.AddHomeIconEvent
|
import com.topjohnwu.magisk.events.AddHomeIconEvent
|
||||||
import com.topjohnwu.magisk.events.RecreateEvent
|
import com.topjohnwu.magisk.events.AuthEvent
|
||||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||||
import com.topjohnwu.magisk.events.dialog.BiometricEvent
|
|
||||||
import com.topjohnwu.magisk.events.dialog.RestoreAppDialog
|
|
||||||
import com.topjohnwu.magisk.ktx.activity
|
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
||||||
|
|
||||||
val adapter = adapterOf<BaseSettingsItem>()
|
|
||||||
val itemBinding = itemBindingOf<BaseSettingsItem> { it.bindExtra(BR.handler, this) }
|
|
||||||
val items = createItems()
|
val items = createItems()
|
||||||
|
val extraBindings = bindExtra {
|
||||||
init {
|
it.put(BR.handler, this)
|
||||||
viewModelScope.launch {
|
|
||||||
Language.loadLanguages(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createItems(): List<BaseSettingsItem> {
|
private fun createItems(): List<BaseSettingsItem> {
|
||||||
val context = AppContext
|
val context = AppContext
|
||||||
val hidden = context.packageName != BuildConfig.APPLICATION_ID
|
val hidden = context.packageName != BuildConfig.APP_PACKAGE_NAME
|
||||||
|
|
||||||
// Customization
|
// Customization
|
||||||
val list = mutableListOf(
|
val list = mutableListOf(
|
||||||
Customization,
|
Customization,
|
||||||
Theme, Language
|
Theme, if (LocaleSetting.useLocaleManager) LanguageSystem else Language
|
||||||
)
|
)
|
||||||
if (isRunningAsStub && ShortcutManagerCompat.isRequestPinShortcutSupported(context))
|
if (isRunningAsStub && ShortcutManagerCompat.isRequestPinShortcutSupported(context))
|
||||||
list.add(AddShortcut)
|
list.add(AddShortcut)
|
||||||
@@ -53,12 +50,10 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
|||||||
// Manager
|
// Manager
|
||||||
list.addAll(listOf(
|
list.addAll(listOf(
|
||||||
AppSettings,
|
AppSettings,
|
||||||
UpdateChannel, UpdateChannelUrl, DoHToggle, UpdateChecker, DownloadPath
|
UpdateChannel, UpdateChannelUrl, DoHToggle, UpdateChecker, DownloadPath, RandNameToggle
|
||||||
))
|
))
|
||||||
if (Info.env.isActive) {
|
if (Info.env.isActive && Const.USER_ID == 0) {
|
||||||
if (Const.USER_ID == 0) {
|
if (hidden) list.add(Restore) else list.add(Hide)
|
||||||
if (hidden) list.add(Restore) else list.add(Hide)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Magisk
|
// Magisk
|
||||||
@@ -73,21 +68,17 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Superuser
|
// Superuser
|
||||||
if (Utils.showSuperUser()) {
|
if (Info.showSuperUser) {
|
||||||
list.addAll(listOf(
|
list.addAll(listOf(
|
||||||
Superuser,
|
Superuser,
|
||||||
Tapjack, Biometrics, AccessMode, MultiuserMode, MountNamespaceMode,
|
Tapjack, Authentication, AccessMode, MultiuserMode, MountNamespaceMode,
|
||||||
AutomaticResponse, RequestTimeout, SUNotification
|
AutomaticResponse, RequestTimeout, SUNotification
|
||||||
))
|
))
|
||||||
if (Build.VERSION.SDK_INT < 23) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
// Biometric is only available on 6.0+
|
|
||||||
list.remove(Biometrics)
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT < 26) {
|
|
||||||
// Re-authenticate is not feasible on 8.0+
|
// Re-authenticate is not feasible on 8.0+
|
||||||
list.add(Reauthenticate)
|
list.add(Reauthenticate)
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= 31) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
// Can hide overlay windows on 12.0+
|
// Can hide overlay windows on 12.0+
|
||||||
list.remove(Tapjack)
|
list.remove(Tapjack)
|
||||||
}
|
}
|
||||||
@@ -96,29 +87,37 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemPressed(view: View, item: BaseSettingsItem, andThen: () -> Unit) {
|
override fun onItemPressed(view: View, item: BaseSettingsItem, doAction: () -> Unit) {
|
||||||
when (item) {
|
when (item) {
|
||||||
DownloadPath -> withExternalRW(andThen)
|
DownloadPath -> withExternalRW(doAction)
|
||||||
Biometrics -> authenticate(andThen)
|
UpdateChecker -> withPostNotificationPermission(doAction)
|
||||||
Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()
|
Authentication -> AuthEvent(doAction).publish()
|
||||||
DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate()
|
Hide, Restore -> withInstallPermission(doAction)
|
||||||
SystemlessHosts -> createHosts()
|
else -> doAction()
|
||||||
Restore -> RestoreAppDialog().publish()
|
|
||||||
AddShortcut -> AddHomeIconEvent().publish()
|
|
||||||
else -> andThen()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemAction(view: View, item: BaseSettingsItem) {
|
override fun onItemAction(view: View, item: BaseSettingsItem) {
|
||||||
when (item) {
|
when (item) {
|
||||||
Language -> RecreateEvent().publish()
|
Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()
|
||||||
|
LanguageSystem -> launchAppLocaleSettings(view.activity)
|
||||||
|
AddShortcut -> AddHomeIconEvent().publish()
|
||||||
|
SystemlessHosts -> createHosts()
|
||||||
|
DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate()
|
||||||
UpdateChannel -> openUrlIfNecessary(view)
|
UpdateChannel -> openUrlIfNecessary(view)
|
||||||
is Hide -> viewModelScope.launch { HideAPK.hide(view.activity, item.value) }
|
is Hide -> viewModelScope.launch { AppMigration.hide(view.activity, item.value) }
|
||||||
|
Restore -> viewModelScope.launch { AppMigration.restore(view.activity) }
|
||||||
Zygisk -> if (Zygisk.mismatch) SnackbarEvent(R.string.reboot_apply_change).publish()
|
Zygisk -> if (Zygisk.mismatch) SnackbarEvent(R.string.reboot_apply_change).publish()
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun launchAppLocaleSettings(activity: Activity) {
|
||||||
|
val intent = Intent(Settings.ACTION_APP_LOCALE_SETTINGS)
|
||||||
|
intent.data = Uri.fromParts("package", activity.packageName, null)
|
||||||
|
activity.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
private fun openUrlIfNecessary(view: View) {
|
private fun openUrlIfNecessary(view: View) {
|
||||||
UpdateChannelUrl.refresh()
|
UpdateChannelUrl.refresh()
|
||||||
if (UpdateChannelUrl.isEnabled && UpdateChannelUrl.value.isBlank()) {
|
if (UpdateChannelUrl.isEnabled && UpdateChannelUrl.value.isBlank()) {
|
||||||
@@ -126,16 +125,9 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun authenticate(callback: () -> Unit) {
|
|
||||||
BiometricEvent {
|
|
||||||
// allow the change on success
|
|
||||||
onSuccess { callback() }
|
|
||||||
}.publish()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createHosts() {
|
private fun createHosts() {
|
||||||
Shell.su("add_hosts_module").submit {
|
Shell.cmd("add_hosts_module").submit {
|
||||||
Utils.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
|
AppContext.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,80 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.superuser
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import androidx.databinding.Bindable
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||||
|
import com.topjohnwu.magisk.databinding.DiffItem
|
||||||
|
import com.topjohnwu.magisk.databinding.ItemWrapper
|
||||||
|
import com.topjohnwu.magisk.databinding.ObservableRvItem
|
||||||
|
import com.topjohnwu.magisk.databinding.set
|
||||||
|
|
||||||
|
class PolicyRvItem(
|
||||||
|
private val viewModel: SuperuserViewModel,
|
||||||
|
override val item: SuPolicy,
|
||||||
|
val packageName: String,
|
||||||
|
private val isSharedUid: Boolean,
|
||||||
|
val icon: Drawable,
|
||||||
|
val appName: String
|
||||||
|
) : ObservableRvItem(), DiffItem<PolicyRvItem>, ItemWrapper<SuPolicy> {
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.item_policy_md2
|
||||||
|
|
||||||
|
val title get() = if (isSharedUid) "[SharedUID] $appName" else appName
|
||||||
|
|
||||||
|
private inline fun <reified T> setImpl(new: T, old: T, setter: (T) -> Unit) {
|
||||||
|
if (old != new) {
|
||||||
|
setter(new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var isExpanded = false
|
||||||
|
set(value) = set(value, field, { field = it }, BR.expanded)
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var isEnabled
|
||||||
|
get() = item.policy == SuPolicy.ALLOW
|
||||||
|
set(value) = setImpl(value, isEnabled) {
|
||||||
|
notifyPropertyChanged(BR.enabled)
|
||||||
|
viewModel.togglePolicy(this, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var shouldNotify
|
||||||
|
get() = item.notification
|
||||||
|
private set(value) = setImpl(value, shouldNotify) {
|
||||||
|
item.notification = it
|
||||||
|
viewModel.updateNotify(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var shouldLog
|
||||||
|
get() = item.logging
|
||||||
|
private set(value) = setImpl(value, shouldLog) {
|
||||||
|
item.logging = it
|
||||||
|
viewModel.updateLogging(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleExpand() {
|
||||||
|
isExpanded = !isExpanded
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleNotify() {
|
||||||
|
shouldNotify = !shouldNotify
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleLog() {
|
||||||
|
shouldLog = !shouldLog
|
||||||
|
}
|
||||||
|
|
||||||
|
fun revoke() {
|
||||||
|
viewModel.deletePressed(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun itemSameAs(other: PolicyRvItem) = packageName == other.packageName
|
||||||
|
|
||||||
|
override fun contentSameAs(other: PolicyRvItem) = item.policy == other.item.policy
|
||||||
|
|
||||||
|
}
|
@@ -4,11 +4,12 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.BaseFragment
|
import com.topjohnwu.magisk.arch.BaseFragment
|
||||||
|
import com.topjohnwu.magisk.arch.viewModel
|
||||||
import com.topjohnwu.magisk.databinding.FragmentSuperuserMd2Binding
|
import com.topjohnwu.magisk.databinding.FragmentSuperuserMd2Binding
|
||||||
import com.topjohnwu.magisk.di.viewModel
|
|
||||||
import rikka.recyclerview.addEdgeSpacing
|
import rikka.recyclerview.addEdgeSpacing
|
||||||
import rikka.recyclerview.addItemSpacing
|
import rikka.recyclerview.addItemSpacing
|
||||||
import rikka.recyclerview.fixEdgeEffect
|
import rikka.recyclerview.fixEdgeEffect
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
class SuperuserFragment : BaseFragment<FragmentSuperuserMd2Binding>() {
|
class SuperuserFragment : BaseFragment<FragmentSuperuserMd2Binding>() {
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ class SuperuserFragment : BaseFragment<FragmentSuperuserMd2Binding>() {
|
|||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
activity.title = resources.getString(R.string.superuser)
|
activity?.title = resources.getString(CoreR.string.superuser)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
@@ -0,0 +1,179 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.superuser
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
|
||||||
|
import android.os.Process
|
||||||
|
import androidx.databinding.Bindable
|
||||||
|
import androidx.databinding.ObservableArrayList
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
|
||||||
|
import com.topjohnwu.magisk.core.AppContext
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.R
|
||||||
|
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
|
||||||
|
import com.topjohnwu.magisk.core.ktx.getLabel
|
||||||
|
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||||
|
import com.topjohnwu.magisk.databinding.MergeObservableList
|
||||||
|
import com.topjohnwu.magisk.databinding.RvItem
|
||||||
|
import com.topjohnwu.magisk.databinding.bindExtra
|
||||||
|
import com.topjohnwu.magisk.databinding.diffList
|
||||||
|
import com.topjohnwu.magisk.databinding.set
|
||||||
|
import com.topjohnwu.magisk.dialog.SuperuserRevokeDialog
|
||||||
|
import com.topjohnwu.magisk.events.AuthEvent
|
||||||
|
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||||
|
import com.topjohnwu.magisk.utils.asText
|
||||||
|
import com.topjohnwu.magisk.view.TextItem
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class SuperuserViewModel(
|
||||||
|
private val db: PolicyDao
|
||||||
|
) : AsyncLoadViewModel() {
|
||||||
|
|
||||||
|
private val itemNoData = TextItem(R.string.superuser_policy_none)
|
||||||
|
|
||||||
|
private val itemsHelpers = ObservableArrayList<TextItem>()
|
||||||
|
private val itemsPolicies = diffList<PolicyRvItem>()
|
||||||
|
|
||||||
|
val items = MergeObservableList<RvItem>()
|
||||||
|
.insertList(itemsHelpers)
|
||||||
|
.insertList(itemsPolicies)
|
||||||
|
val extraBindings = bindExtra {
|
||||||
|
it.put(BR.listener, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var loading = true
|
||||||
|
private set(value) = set(value, field, { field = it }, BR.loading)
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
override suspend fun doLoadWork() {
|
||||||
|
if (!Info.showSuperUser) {
|
||||||
|
loading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading = true
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.deleteOutdated()
|
||||||
|
db.delete(AppContext.applicationInfo.uid)
|
||||||
|
val policies = ArrayList<PolicyRvItem>()
|
||||||
|
val pm = AppContext.packageManager
|
||||||
|
for (policy in db.fetchAll()) {
|
||||||
|
val pkgs =
|
||||||
|
if (policy.uid == Process.SYSTEM_UID) arrayOf("android")
|
||||||
|
else pm.getPackagesForUid(policy.uid)
|
||||||
|
if (pkgs == null) {
|
||||||
|
db.delete(policy.uid)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val map = pkgs.mapNotNull { pkg ->
|
||||||
|
try {
|
||||||
|
val info = pm.getPackageInfo(pkg, MATCH_UNINSTALLED_PACKAGES)
|
||||||
|
PolicyRvItem(
|
||||||
|
this@SuperuserViewModel, policy,
|
||||||
|
info.packageName,
|
||||||
|
info.sharedUserId != null,
|
||||||
|
info.applicationInfo?.loadIcon(pm) ?: pm.defaultActivityIcon,
|
||||||
|
info.applicationInfo?.getLabel(pm) ?: info.packageName
|
||||||
|
)
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (map.isEmpty()) {
|
||||||
|
db.delete(policy.uid)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
policies.addAll(map)
|
||||||
|
}
|
||||||
|
policies.sortWith(compareBy(
|
||||||
|
{ it.appName.lowercase(Locale.ROOT) },
|
||||||
|
{ it.packageName }
|
||||||
|
))
|
||||||
|
itemsPolicies.update(policies)
|
||||||
|
}
|
||||||
|
if (itemsPolicies.isNotEmpty())
|
||||||
|
itemsHelpers.clear()
|
||||||
|
else if (itemsHelpers.isEmpty())
|
||||||
|
itemsHelpers.add(itemNoData)
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
fun deletePressed(item: PolicyRvItem) {
|
||||||
|
fun updateState() = viewModelScope.launch {
|
||||||
|
db.delete(item.item.uid)
|
||||||
|
val list = ArrayList(itemsPolicies)
|
||||||
|
list.removeAll { it.item.uid == item.item.uid }
|
||||||
|
itemsPolicies.update(list)
|
||||||
|
if (list.isEmpty() && itemsHelpers.isEmpty()) {
|
||||||
|
itemsHelpers.add(itemNoData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.suAuth) {
|
||||||
|
AuthEvent { updateState() }.publish()
|
||||||
|
} else {
|
||||||
|
SuperuserRevokeDialog(item.title) { updateState() }.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateNotify(item: PolicyRvItem) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
db.update(item.item)
|
||||||
|
val res = when {
|
||||||
|
item.item.notification -> R.string.su_snack_notif_on
|
||||||
|
else -> R.string.su_snack_notif_off
|
||||||
|
}
|
||||||
|
itemsPolicies.forEach {
|
||||||
|
if (it.item.uid == item.item.uid) {
|
||||||
|
it.notifyPropertyChanged(BR.shouldNotify)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SnackbarEvent(res.asText(item.appName)).publish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateLogging(item: PolicyRvItem) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
db.update(item.item)
|
||||||
|
val res = when {
|
||||||
|
item.item.logging -> R.string.su_snack_log_on
|
||||||
|
else -> R.string.su_snack_log_off
|
||||||
|
}
|
||||||
|
itemsPolicies.forEach {
|
||||||
|
if (it.item.uid == item.item.uid) {
|
||||||
|
it.notifyPropertyChanged(BR.shouldLog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SnackbarEvent(res.asText(item.appName)).publish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
|
||||||
|
val items = itemsPolicies.filter { it.item.uid == item.item.uid }
|
||||||
|
fun updateState() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val res = if (enable) R.string.su_snack_grant else R.string.su_snack_deny
|
||||||
|
item.item.policy = if (enable) SuPolicy.ALLOW else SuPolicy.DENY
|
||||||
|
db.update(item.item)
|
||||||
|
items.forEach {
|
||||||
|
it.notifyPropertyChanged(BR.enabled)
|
||||||
|
}
|
||||||
|
SnackbarEvent(res.asText(item.appName)).publish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.suAuth) {
|
||||||
|
AuthEvent { updateState() }.publish()
|
||||||
|
} else {
|
||||||
|
updateState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -7,26 +7,27 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.UIActivity
|
import com.topjohnwu.magisk.arch.UIActivity
|
||||||
|
import com.topjohnwu.magisk.arch.viewModel
|
||||||
|
import com.topjohnwu.magisk.core.base.UntrackedActivity
|
||||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST
|
import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST
|
||||||
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
||||||
import com.topjohnwu.magisk.di.viewModel
|
|
||||||
import com.topjohnwu.magisk.ui.theme.Theme
|
import com.topjohnwu.magisk.ui.theme.Theme
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
open class SuRequestActivity : UIActivity<ActivityRequestBinding>() {
|
open class SuRequestActivity : UIActivity<ActivityRequestBinding>(), UntrackedActivity {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.activity_request
|
override val layoutRes: Int = R.layout.activity_request
|
||||||
override val viewModel: SuRequestViewModel by viewModel()
|
override val viewModel: SuRequestViewModel by viewModel()
|
||||||
|
|
||||||
override fun onBackPressed() {
|
|
||||||
viewModel.denyPressed()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
|
supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
lockOrientation()
|
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
window.addFlags(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
|
window.addFlags(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
@@ -40,8 +41,12 @@ open class SuRequestActivity : UIActivity<ActivityRequestBinding>() {
|
|||||||
if (action == REQUEST) {
|
if (action == REQUEST) {
|
||||||
viewModel.handleRequest(intent)
|
viewModel.handleRequest(intent)
|
||||||
} else {
|
} else {
|
||||||
SuCallbackHandler.run(this, action, intent.extras)
|
lifecycleScope.launch {
|
||||||
finish()
|
withContext(Dispatchers.IO) {
|
||||||
|
SuCallbackHandler.run(this@SuRequestActivity, action, intent.extras)
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
finish()
|
finish()
|
||||||
@@ -54,7 +59,11 @@ open class SuRequestActivity : UIActivity<ActivityRequestBinding>() {
|
|||||||
return theme
|
return theme
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun lockOrientation() {
|
override fun onBackPressed() {
|
||||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
viewModel.denyPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun finish() {
|
||||||
|
super.finishAndRemoveTask()
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -17,24 +17,23 @@ import android.widget.Toast
|
|||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.core.AppContext
|
||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
import com.topjohnwu.magisk.core.R
|
||||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
|
||||||
|
import com.topjohnwu.magisk.core.ktx.getLabel
|
||||||
|
import com.topjohnwu.magisk.core.ktx.toast
|
||||||
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW
|
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW
|
||||||
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.DENY
|
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.DENY
|
||||||
import com.topjohnwu.magisk.core.su.SuRequestHandler
|
import com.topjohnwu.magisk.core.su.SuRequestHandler
|
||||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
|
||||||
import com.topjohnwu.magisk.databinding.set
|
import com.topjohnwu.magisk.databinding.set
|
||||||
import com.topjohnwu.magisk.di.AppContext
|
import com.topjohnwu.magisk.events.AuthEvent
|
||||||
import com.topjohnwu.magisk.events.DieEvent
|
import com.topjohnwu.magisk.events.DieEvent
|
||||||
import com.topjohnwu.magisk.events.ShowUIEvent
|
import com.topjohnwu.magisk.events.ShowUIEvent
|
||||||
import com.topjohnwu.magisk.events.dialog.BiometricEvent
|
|
||||||
import com.topjohnwu.magisk.utils.TextHolder
|
import com.topjohnwu.magisk.utils.TextHolder
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
|
||||||
import java.util.concurrent.TimeUnit.SECONDS
|
import java.util.concurrent.TimeUnit.SECONDS
|
||||||
|
|
||||||
class SuRequestViewModel(
|
class SuRequestViewModel(
|
||||||
@@ -63,26 +62,22 @@ class SuRequestViewModel(
|
|||||||
if (event.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0
|
if (event.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0
|
||||||
|| event.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0) {
|
|| event.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0) {
|
||||||
if (event.action == MotionEvent.ACTION_UP) {
|
if (event.action == MotionEvent.ACTION_UP) {
|
||||||
Utils.toast(R.string.touch_filtered_warning, Toast.LENGTH_SHORT)
|
AppContext.toast(R.string.touch_filtered_warning, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
return@OnTouchListener Config.suTapjack
|
return@OnTouchListener Config.suTapjack
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
val itemBinding = ItemBinding.of<String>(BR.item, R.layout.item_spinner)
|
|
||||||
|
|
||||||
private val handler = SuRequestHandler(AppContext.packageManager, policyDB)
|
private val handler = SuRequestHandler(AppContext.packageManager, policyDB)
|
||||||
private lateinit var timer: CountDownTimer
|
private val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
|
||||||
|
private var timer = SuTimer(millis, 1000)
|
||||||
|
private var initialized = false
|
||||||
|
|
||||||
fun grantPressed() {
|
fun grantPressed() {
|
||||||
cancelTimer()
|
cancelTimer()
|
||||||
if (BiometricHelper.isEnabled) {
|
if (Config.suAuth) {
|
||||||
BiometricEvent {
|
AuthEvent { respond(ALLOW) }.publish()
|
||||||
onSuccess {
|
|
||||||
respond(ALLOW)
|
|
||||||
}
|
|
||||||
}.publish()
|
|
||||||
} else {
|
} else {
|
||||||
respond(ALLOW)
|
respond(ALLOW)
|
||||||
}
|
}
|
||||||
@@ -98,37 +93,58 @@ class SuRequestViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun handleRequest(intent: Intent) {
|
fun handleRequest(intent: Intent) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
if (handler.start(intent))
|
if (handler.start(intent))
|
||||||
showDialog(handler.policy)
|
showDialog()
|
||||||
else
|
else
|
||||||
DieEvent().publish()
|
DieEvent().publish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showDialog(policy: SuPolicy) {
|
private fun showDialog() {
|
||||||
icon = policy.icon
|
val pm = handler.pm
|
||||||
title = policy.appName
|
val info = handler.pkgInfo
|
||||||
packageName = policy.packageName
|
val app = info.applicationInfo
|
||||||
selectedItemPosition = timeoutPrefs.getInt(policy.packageName, 0)
|
|
||||||
|
if (app == null) {
|
||||||
|
// The request is not coming from an app process, and the UID is a
|
||||||
|
// shared UID. We have no way to know where this request comes from.
|
||||||
|
icon = pm.defaultActivityIcon
|
||||||
|
title = "[SharedUID] ${info.sharedUserId}"
|
||||||
|
packageName = info.sharedUserId.toString()
|
||||||
|
} else {
|
||||||
|
val prefix = if (info.sharedUserId == null) "" else "[SharedUID] "
|
||||||
|
icon = app.loadIcon(pm)
|
||||||
|
title = "$prefix${app.getLabel(pm)}"
|
||||||
|
packageName = info.packageName
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedItemPosition = timeoutPrefs.getInt(packageName, 0)
|
||||||
|
|
||||||
// Set timer
|
// Set timer
|
||||||
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
|
timer.start()
|
||||||
timer = SuTimer(millis, 1000).apply { start() }
|
|
||||||
|
|
||||||
// Actually show the UI
|
// Actually show the UI
|
||||||
ShowUIEvent(if (Config.suTapjack) EmptyAccessibilityDelegate else null).publish()
|
ShowUIEvent(if (Config.suTapjack) EmptyAccessibilityDelegate else null).publish()
|
||||||
|
initialized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun respond(action: Int) {
|
private fun respond(action: Int) {
|
||||||
|
if (!initialized) {
|
||||||
|
// ignore the response until showDialog done
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
|
|
||||||
val pos = selectedItemPosition
|
val pos = selectedItemPosition
|
||||||
timeoutPrefs.edit().putInt(handler.policy.packageName, pos).apply()
|
timeoutPrefs.edit().putInt(packageName, pos).apply()
|
||||||
handler.respond(action, Config.Value.TIMEOUT_LIST[pos])
|
|
||||||
|
|
||||||
// Kill activity after response
|
viewModelScope.launch {
|
||||||
DieEvent().publish()
|
handler.respond(action, Config.Value.TIMEOUT_LIST[pos])
|
||||||
|
// Kill activity after response
|
||||||
|
DieEvent().publish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelTimer() {
|
private fun cancelTimer() {
|
||||||
@@ -169,15 +185,15 @@ class SuRequestViewModel(
|
|||||||
|
|
||||||
// Invisible for accessibility services
|
// Invisible for accessibility services
|
||||||
object EmptyAccessibilityDelegate : View.AccessibilityDelegate() {
|
object EmptyAccessibilityDelegate : View.AccessibilityDelegate() {
|
||||||
override fun sendAccessibilityEvent(host: View?, eventType: Int) {}
|
override fun sendAccessibilityEvent(host: View, eventType: Int) {}
|
||||||
override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?) = true
|
override fun performAccessibilityAction(host: View, action: Int, args: Bundle?) = true
|
||||||
override fun sendAccessibilityEventUnchecked(host: View?, event: AccessibilityEvent?) {}
|
override fun sendAccessibilityEventUnchecked(host: View, event: AccessibilityEvent) {}
|
||||||
override fun dispatchPopulateAccessibilityEvent(host: View?, event: AccessibilityEvent?) = true
|
override fun dispatchPopulateAccessibilityEvent(host: View, event: AccessibilityEvent) = true
|
||||||
override fun onPopulateAccessibilityEvent(host: View?, event: AccessibilityEvent?) {}
|
override fun onPopulateAccessibilityEvent(host: View, event: AccessibilityEvent) {}
|
||||||
override fun onInitializeAccessibilityEvent(host: View?, event: AccessibilityEvent?) {}
|
override fun onInitializeAccessibilityEvent(host: View, event: AccessibilityEvent) {}
|
||||||
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {}
|
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {}
|
||||||
override fun addExtraDataToAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo, extraDataKey: String, arguments: Bundle?) {}
|
override fun addExtraDataToAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo, extraDataKey: String, arguments: Bundle?) {}
|
||||||
override fun onRequestSendAccessibilityEvent(host: ViewGroup?, child: View?, event: AccessibilityEvent?): Boolean = false
|
override fun onRequestSendAccessibilityEvent(host: ViewGroup, child: View, event: AccessibilityEvent): Boolean = false
|
||||||
override fun getAccessibilityNodeProvider(host: View?): AccessibilityNodeProvider? = null
|
override fun getAccessibilityNodeProvider(host: View): AccessibilityNodeProvider? = null
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -9,9 +9,10 @@ import android.widget.FrameLayout
|
|||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.arch.BaseFragment
|
import com.topjohnwu.magisk.arch.BaseFragment
|
||||||
|
import com.topjohnwu.magisk.arch.viewModel
|
||||||
import com.topjohnwu.magisk.databinding.FragmentThemeMd2Binding
|
import com.topjohnwu.magisk.databinding.FragmentThemeMd2Binding
|
||||||
import com.topjohnwu.magisk.databinding.ItemThemeBindingImpl
|
import com.topjohnwu.magisk.databinding.ItemThemeBindingImpl
|
||||||
import com.topjohnwu.magisk.di.viewModel
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
class ThemeFragment : BaseFragment<FragmentThemeMd2Binding>() {
|
class ThemeFragment : BaseFragment<FragmentThemeMd2Binding>() {
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ class ThemeFragment : BaseFragment<FragmentThemeMd2Binding>() {
|
|||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
|
|
||||||
activity.title = getString(R.string.section_theme)
|
activity?.title = getString(CoreR.string.section_theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -1,8 +1,9 @@
|
|||||||
package com.topjohnwu.magisk.ui.theme
|
package com.topjohnwu.magisk.ui.theme
|
||||||
|
|
||||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
|
import com.topjohnwu.magisk.dialog.DarkThemeDialog
|
||||||
import com.topjohnwu.magisk.events.RecreateEvent
|
import com.topjohnwu.magisk.events.RecreateEvent
|
||||||
import com.topjohnwu.magisk.events.dialog.DarkThemeDialog
|
|
||||||
import com.topjohnwu.magisk.view.TappableHeadlineItem
|
import com.topjohnwu.magisk.view.TappableHeadlineItem
|
||||||
|
|
||||||
class ThemeViewModel : BaseViewModel(), TappableHeadlineItem.Listener {
|
class ThemeViewModel : BaseViewModel(), TappableHeadlineItem.Listener {
|
||||||
@@ -10,15 +11,13 @@ class ThemeViewModel : BaseViewModel(), TappableHeadlineItem.Listener {
|
|||||||
val themeHeadline = TappableHeadlineItem.ThemeMode
|
val themeHeadline = TappableHeadlineItem.ThemeMode
|
||||||
|
|
||||||
override fun onItemPressed(item: TappableHeadlineItem) = when (item) {
|
override fun onItemPressed(item: TappableHeadlineItem) = when (item) {
|
||||||
is TappableHeadlineItem.ThemeMode -> darkModePressed()
|
is TappableHeadlineItem.ThemeMode -> DarkThemeDialog().show()
|
||||||
else -> Unit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveTheme(theme: Theme) {
|
fun saveTheme(theme: Theme) {
|
||||||
theme.select()
|
if (!theme.isSelected) {
|
||||||
RecreateEvent().publish()
|
Config.themeOrdinal = theme.ordinal
|
||||||
|
RecreateEvent().publish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun darkModePressed() = DarkThemeDialog().publish()
|
|
||||||
|
|
||||||
}
|
}
|
@@ -14,7 +14,7 @@ import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
|||||||
import com.google.android.material.circularreveal.CircularRevealCompat
|
import com.google.android.material.circularreveal.CircularRevealCompat
|
||||||
import com.google.android.material.circularreveal.CircularRevealWidget
|
import com.google.android.material.circularreveal.CircularRevealWidget
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
import com.topjohnwu.magisk.core.utils.LocaleSetting
|
||||||
import kotlin.math.hypot
|
import kotlin.math.hypot
|
||||||
|
|
||||||
object MotionRevealHelper {
|
object MotionRevealHelper {
|
||||||
@@ -63,7 +63,9 @@ object MotionRevealHelper {
|
|||||||
it.interpolator = FastOutSlowInInterpolator()
|
it.interpolator = FastOutSlowInInterpolator()
|
||||||
it.addListener(onStart = { show() }, onEnd = { if (revealInfo.radius != 0f) hide() })
|
it.addListener(onStart = { show() }, onEnd = { if (revealInfo.radius != 0f) hide() })
|
||||||
|
|
||||||
val rtlMod = if (currentLocale.layoutDirection == View.LAYOUT_DIRECTION_RTL) 1f else -1f
|
val rtlMod =
|
||||||
|
if (LocaleSetting.instance.currentLocale.layoutDirection == View.LAYOUT_DIRECTION_RTL)
|
||||||
|
1f else -1f
|
||||||
val maxX = revealInfo.centerX - marginEnd - measuredWidth / 2f
|
val maxX = revealInfo.centerX - marginEnd - measuredWidth / 2f
|
||||||
val targetX = if (revealInfo.radius == 0f) 0f else maxX * rtlMod
|
val targetX = if (revealInfo.radius == 0f) 0f else maxX * rtlMod
|
||||||
val moveX = ObjectAnimator.ofFloat(this, View.TRANSLATION_X, targetX)
|
val moveX = ObjectAnimator.ofFloat(this, View.TRANSLATION_X, targetX)
|
@@ -1,8 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.databinding.BindingAdapter
|
|
||||||
|
|
||||||
abstract class TextHolder {
|
abstract class TextHolder {
|
||||||
|
|
||||||
@@ -46,9 +44,3 @@ abstract class TextHolder {
|
|||||||
fun Int.asText(): TextHolder = TextHolder.Resource(this)
|
fun Int.asText(): TextHolder = TextHolder.Resource(this)
|
||||||
fun Int.asText(vararg params: Any): TextHolder = TextHolder.ResourceArgs(this, *params)
|
fun Int.asText(vararg params: Any): TextHolder = TextHolder.ResourceArgs(this, *params)
|
||||||
fun CharSequence.asText(): TextHolder = TextHolder.String(this)
|
fun CharSequence.asText(): TextHolder = TextHolder.String(this)
|
||||||
|
|
||||||
|
|
||||||
@BindingAdapter("android:text")
|
|
||||||
fun TextView.setText(text: TextHolder) {
|
|
||||||
this.text = text.getText(context.resources)
|
|
||||||
}
|
|
@@ -1,6 +1,6 @@
|
|||||||
package com.topjohnwu.magisk.view
|
package com.topjohnwu.magisk.view
|
||||||
|
|
||||||
import android.content.Context
|
import android.app.Activity
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
@@ -21,24 +21,33 @@ import com.google.android.material.color.MaterialColors
|
|||||||
import com.google.android.material.shape.MaterialShapeDrawable
|
import com.google.android.material.shape.MaterialShapeDrawable
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.databinding.*
|
import com.topjohnwu.magisk.arch.UIActivity
|
||||||
|
import com.topjohnwu.magisk.databinding.DialogMagiskBaseBinding
|
||||||
|
import com.topjohnwu.magisk.databinding.DiffItem
|
||||||
|
import com.topjohnwu.magisk.databinding.ItemWrapper
|
||||||
|
import com.topjohnwu.magisk.databinding.ObservableHost
|
||||||
|
import com.topjohnwu.magisk.databinding.RvItem
|
||||||
|
import com.topjohnwu.magisk.databinding.bindExtra
|
||||||
|
import com.topjohnwu.magisk.databinding.set
|
||||||
|
import com.topjohnwu.magisk.databinding.setAdapter
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog.DialogClickListener
|
import com.topjohnwu.magisk.view.MagiskDialog.DialogClickListener
|
||||||
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapters
|
|
||||||
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
|
||||||
|
|
||||||
typealias DialogButtonClickListener = (DialogInterface) -> Unit
|
typealias DialogButtonClickListener = (DialogInterface) -> Unit
|
||||||
|
|
||||||
class MagiskDialog(
|
class MagiskDialog(
|
||||||
context: Context, theme: Int = 0
|
context: Activity, theme: Int = 0
|
||||||
) : AppCompatDialog(context, theme) {
|
) : AppCompatDialog(context, theme) {
|
||||||
|
|
||||||
private val binding: DialogMagiskBaseBinding =
|
private val binding: DialogMagiskBaseBinding =
|
||||||
DialogMagiskBaseBinding.inflate(LayoutInflater.from(context))
|
DialogMagiskBaseBinding.inflate(LayoutInflater.from(context))
|
||||||
private val data = Data()
|
private val data = Data()
|
||||||
|
|
||||||
|
val activity: UIActivity<*> get() = ownerActivity as UIActivity<*>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
binding.setVariable(BR.data, data)
|
binding.setVariable(BR.data, data)
|
||||||
setCancelable(true)
|
setCancelable(true)
|
||||||
|
setOwnerActivity(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class Data : ObservableHost {
|
inner class Data : ObservableHost {
|
||||||
@@ -121,15 +130,15 @@ class MagiskDialog(
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
super.setContentView(binding.root)
|
super.setContentView(binding.root)
|
||||||
|
|
||||||
val default = MaterialColors.getColor(context, R.attr.colorSurface, javaClass.canonicalName)
|
val default = MaterialColors.getColor(context, com.google.android.material.R.attr.colorSurface, javaClass.canonicalName)
|
||||||
val surfaceColor = MaterialColors.getColor(context, R.attr.colorSurfaceSurfaceVariant, default)
|
val surfaceColor = MaterialColors.getColor(context, R.attr.colorSurfaceSurfaceVariant, default)
|
||||||
val materialShapeDrawable = MaterialShapeDrawable(context, null, R.attr.alertDialogStyle, R.style.MaterialAlertDialog_MaterialComponents)
|
val materialShapeDrawable = MaterialShapeDrawable(context, null, androidx.appcompat.R.attr.alertDialogStyle, com.google.android.material.R.style.MaterialAlertDialog_MaterialComponents)
|
||||||
materialShapeDrawable.initializeElevationOverlay(context)
|
materialShapeDrawable.initializeElevationOverlay(context)
|
||||||
materialShapeDrawable.fillColor = ColorStateList.valueOf(surfaceColor)
|
materialShapeDrawable.fillColor = ColorStateList.valueOf(surfaceColor)
|
||||||
materialShapeDrawable.elevation = context.resources.getDimension(R.dimen.margin_generic)
|
materialShapeDrawable.elevation = context.resources.getDimension(R.dimen.margin_generic)
|
||||||
materialShapeDrawable.setCornerSize(context.resources.getDimension(R.dimen.l_50))
|
materialShapeDrawable.setCornerSize(context.resources.getDimension(R.dimen.l_50))
|
||||||
|
|
||||||
val inset = context.resources.getDimensionPixelSize(R.dimen.appcompat_dialog_background_inset)
|
val inset = context.resources.getDimensionPixelSize(com.google.android.material.R.dimen.appcompat_dialog_background_inset)
|
||||||
window?.apply {
|
window?.apply {
|
||||||
setBackgroundDrawable(InsetDrawable(materialShapeDrawable, inset, inset, inset, inset))
|
setBackgroundDrawable(InsetDrawable(materialShapeDrawable, inset, inset, inset, inset))
|
||||||
setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||||
@@ -164,7 +173,7 @@ class MagiskDialog(
|
|||||||
class DialogItem(
|
class DialogItem(
|
||||||
override val item: CharSequence,
|
override val item: CharSequence,
|
||||||
val position: Int
|
val position: Int
|
||||||
) : DiffRvItem<DialogItem>(), RvContainer<CharSequence> {
|
) : RvItem(), DiffItem<DialogItem>, ItemWrapper<CharSequence> {
|
||||||
override val layoutRes = R.layout.item_list_single_line
|
override val layoutRes = R.layout.item_list_single_line
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,14 +190,13 @@ class MagiskDialog(
|
|||||||
it.layoutManager = LinearLayoutManager(context)
|
it.layoutManager = LinearLayoutManager(context)
|
||||||
|
|
||||||
val items = list.mapIndexed { i, cs -> DialogItem(cs, i) }
|
val items = list.mapIndexed { i, cs -> DialogItem(cs, i) }
|
||||||
val binding = itemBindingOf<DialogItem> { item ->
|
val extraBindings = bindExtra { sa ->
|
||||||
item.bindExtra(BR.listener, DialogClickListener { pos ->
|
sa.put(BR.listener, DialogClickListener { pos ->
|
||||||
listener.onClick(pos)
|
listener.onClick(pos)
|
||||||
dismiss()
|
dismiss()
|
||||||
})
|
})
|
||||||
}.let { b -> ItemBinding.of(b) }
|
}
|
||||||
|
it.setAdapter(items, extraBindings)
|
||||||
BindingRecyclerViewAdapters.setAdapter(it, binding, items, null, null, null, null)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@@ -1,9 +1,11 @@
|
|||||||
package com.topjohnwu.magisk.view
|
package com.topjohnwu.magisk.view
|
||||||
|
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.databinding.DiffRvItem
|
import com.topjohnwu.magisk.databinding.DiffItem
|
||||||
|
import com.topjohnwu.magisk.databinding.RvItem
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
sealed class TappableHeadlineItem : DiffRvItem<TappableHeadlineItem>() {
|
sealed class TappableHeadlineItem : RvItem(), DiffItem<TappableHeadlineItem> {
|
||||||
|
|
||||||
abstract val title: Int
|
abstract val title: Int
|
||||||
abstract val icon: Int
|
abstract val icon: Int
|
||||||
@@ -21,7 +23,7 @@ sealed class TappableHeadlineItem : DiffRvItem<TappableHeadlineItem>() {
|
|||||||
// --- objects
|
// --- objects
|
||||||
|
|
||||||
object ThemeMode : TappableHeadlineItem() {
|
object ThemeMode : TappableHeadlineItem() {
|
||||||
override val title = R.string.settings_dark_mode_title
|
override val title = CoreR.string.settings_dark_mode_title
|
||||||
override val icon = R.drawable.ic_day_night
|
override val icon = R.drawable.ic_day_night
|
||||||
}
|
}
|
||||||
|
|
10
app/apk/src/main/java/com/topjohnwu/magisk/view/TextItem.kt
Normal file
10
app/apk/src/main/java/com/topjohnwu/magisk/view/TextItem.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package com.topjohnwu.magisk.view
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.DiffItem
|
||||||
|
import com.topjohnwu.magisk.databinding.ItemWrapper
|
||||||
|
import com.topjohnwu.magisk.databinding.RvItem
|
||||||
|
|
||||||
|
class TextItem(override val item: Int) : RvItem(), DiffItem<TextItem>, ItemWrapper<Int> {
|
||||||
|
override val layoutRes = R.layout.item_text
|
||||||
|
}
|
@@ -23,17 +23,16 @@ public class ConcealableBottomNavigationView extends BottomNavigationView {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private boolean isHidden;
|
private boolean isHidden;
|
||||||
|
|
||||||
public ConcealableBottomNavigationView(@NonNull Context context) {
|
public ConcealableBottomNavigationView(@NonNull Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConcealableBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
public ConcealableBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
this(context, attrs, R.attr.bottomNavigationStyle);
|
this(context, attrs, com.google.android.material.R.attr.bottomNavigationStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConcealableBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
public ConcealableBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
this(context, attrs, defStyleAttr, R.style.Widget_Design_BottomNavigationView);
|
this(context, attrs, defStyleAttr, com.google.android.material.R.style.Widget_Design_BottomNavigationView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConcealableBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public ConcealableBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
@@ -45,8 +44,8 @@ public class ConcealableBottomNavigationView extends BottomNavigationView {
|
|||||||
toHidden.setDuration(175);
|
toHidden.setDuration(175);
|
||||||
toHidden.setInterpolator(new FastOutLinearInInterpolator());
|
toHidden.setInterpolator(new FastOutLinearInInterpolator());
|
||||||
Animator toUnhidden = ObjectAnimator.ofFloat(this, "translationY", 0);
|
Animator toUnhidden = ObjectAnimator.ofFloat(this, "translationY", 0);
|
||||||
toHidden.setDuration(225);
|
toUnhidden.setDuration(225);
|
||||||
toHidden.setInterpolator(new FastOutLinearInInterpolator());
|
toUnhidden.setInterpolator(new FastOutLinearInInterpolator());
|
||||||
|
|
||||||
StateListAnimator animator = new StateListAnimator();
|
StateListAnimator animator = new StateListAnimator();
|
||||||
|
|
@@ -18,7 +18,7 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:duration="300"
|
android:duration="300"
|
||||||
android:interpolator="@interpolator/fast_out_slow_in"
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="pathData"
|
android:propertyName="pathData"
|
||||||
android:valueFrom="M 20 8 L 20 8 L 17.19 8 C 16.74 7.22 16.12 6.55 15.37 6.04 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.49 5 12 5 C 11.51 5 11.04 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6.04 C 7.88 6.55 7.26 7.22 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.04 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.04 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 7.85 19.79 9.78 21 12 21 C 14.22 21 16.15 19.79 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.96 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.96 10.33 17.91 10 L 20 10 L 20 8 M 14 16 C 14 15.43 14 14.859 14 14.289 L 14 14 C 13.869 14 13.739 14 13.608 14 C 12.405 14 11.203 14 10 14 C 10 14.509 10 15.017 10 15.526 C 10 15.684 10 15.842 10 16 L 10.33 16 C 10.392 16 10.454 16 10.515 16 C 11.677 16 12.838 16 14 16 C 14 16 14 16 14 16 M 14 10 L 14 12 L 14 12 L 10 12 L 10 10 L 14 10 M 12 15 L 12 15 L 12 15 L 12 15 L 12 15 L 12 15"
|
android:valueFrom="M 20 8 L 20 8 L 17.19 8 C 16.74 7.22 16.12 6.55 15.37 6.04 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.49 5 12 5 C 11.51 5 11.04 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6.04 C 7.88 6.55 7.26 7.22 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.04 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.04 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 7.85 19.79 9.78 21 12 21 C 14.22 21 16.15 19.79 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.96 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.96 10.33 17.91 10 L 20 10 L 20 8 M 14 16 C 14 15.43 14 14.859 14 14.289 L 14 14 C 13.869 14 13.739 14 13.608 14 C 12.405 14 11.203 14 10 14 C 10 14.509 10 15.017 10 15.526 C 10 15.684 10 15.842 10 16 L 10.33 16 C 10.392 16 10.454 16 10.515 16 C 11.677 16 12.838 16 14 16 C 14 16 14 16 14 16 M 14 10 L 14 12 L 14 12 L 10 12 L 10 10 L 14 10 M 12 15 L 12 15 L 12 15 L 12 15 L 12 15 L 12 15"
|
||||||
android:valueTo="M 20 8 L 18.595 8 L 17.19 8 C 16.74 7.2 16.12 6.5 15.37 6 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.5 5 12 5 C 11.5 5 11.05 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6 C 7.87 6.5 7.26 7.21 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.03 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.03 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 8.47 20.87 12.14 21.84 15 20.18 C 15.91 19.66 16.67 18.9 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.97 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.97 10.33 17.91 10 L 20 10 L 20 8 M 14.828 17.828 C 15.578 17.079 16 16.06 16 15 L 16 11 C 16 9.94 15.578 8.921 14.828 8.172 C 14.079 7.422 13.06 7 12 7 C 10.94 7 9.921 7.422 9.172 8.172 C 8.422 8.921 8 9.94 8 11 L 8 15 C 8 16.06 8.422 17.079 9.172 17.828 C 9.921 18.578 10.94 19 12 19 C 13.06 19 14.079 18.578 14.828 17.828 M 14 10 L 14 11 L 14 12 L 10 12 L 10 10 L 14 10 M 10 14 L 14 14 L 14 16 L 10 16 L 10 14 L 10 14"
|
android:valueTo="M 20 8 L 18.595 8 L 17.19 8 C 16.74 7.2 16.12 6.5 15.37 6 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.5 5 12 5 C 11.5 5 11.05 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6 C 7.87 6.5 7.26 7.21 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.03 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.03 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 8.47 20.87 12.14 21.84 15 20.18 C 15.91 19.66 16.67 18.9 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.97 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.97 10.33 17.91 10 L 20 10 L 20 8 M 14.828 17.828 C 15.578 17.079 16 16.06 16 15 L 16 11 C 16 9.94 15.578 8.921 14.828 8.172 C 14.079 7.422 13.06 7 12 7 C 10.94 7 9.921 7.422 9.172 8.172 C 8.422 8.921 8 9.94 8 11 L 8 15 C 8 16.06 8.422 17.079 9.172 17.828 C 9.921 18.578 10.94 19 12 19 C 13.06 19 14.079 18.578 14.828 17.828 M 14 10 L 14 11 L 14 12 L 10 12 L 10 10 L 14 10 M 10 14 L 14 14 L 14 16 L 10 16 L 10 14 L 10 14"
|
@@ -18,7 +18,7 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:duration="300"
|
android:duration="300"
|
||||||
android:interpolator="@interpolator/fast_out_slow_in"
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="pathData"
|
android:propertyName="pathData"
|
||||||
android:valueFrom="M 20 8 L 18.595 8 L 17.19 8 C 16.74 7.2 16.12 6.5 15.37 6 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.5 5 12 5 C 11.5 5 11.05 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6 C 7.87 6.5 7.26 7.21 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.03 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.03 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 8.47 20.87 12.14 21.84 15 20.18 C 15.91 19.66 16.67 18.9 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.97 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.97 10.33 17.91 10 L 20 10 L 20 8 M 14.828 17.828 C 15.578 17.079 16 16.06 16 15 L 16 11 C 16 9.94 15.578 8.921 14.828 8.172 C 14.079 7.422 13.06 7 12 7 C 10.94 7 9.921 7.422 9.172 8.172 C 8.422 8.921 8 9.94 8 11 L 8 15 C 8 16.06 8.422 17.079 9.172 17.828 C 9.921 18.578 10.94 19 12 19 C 13.06 19 14.079 18.578 14.828 17.828 M 14 10 L 14 11 L 14 12 L 10 12 L 10 10 L 14 10 M 10 14 L 14 14 L 14 16 L 10 16 L 10 14 L 10 14"
|
android:valueFrom="M 20 8 L 18.595 8 L 17.19 8 C 16.74 7.2 16.12 6.5 15.37 6 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.5 5 12 5 C 11.5 5 11.05 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6 C 7.87 6.5 7.26 7.21 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.03 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.03 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 8.47 20.87 12.14 21.84 15 20.18 C 15.91 19.66 16.67 18.9 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.97 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.97 10.33 17.91 10 L 20 10 L 20 8 M 14.828 17.828 C 15.578 17.079 16 16.06 16 15 L 16 11 C 16 9.94 15.578 8.921 14.828 8.172 C 14.079 7.422 13.06 7 12 7 C 10.94 7 9.921 7.422 9.172 8.172 C 8.422 8.921 8 9.94 8 11 L 8 15 C 8 16.06 8.422 17.079 9.172 17.828 C 9.921 18.578 10.94 19 12 19 C 13.06 19 14.079 18.578 14.828 17.828 M 14 10 L 14 11 L 14 12 L 10 12 L 10 10 L 14 10 M 10 14 L 14 14 L 14 16 L 10 16 L 10 14 L 10 14"
|
||||||
android:valueTo="M 20 8 L 20 8 L 17.19 8 C 16.74 7.22 16.12 6.55 15.37 6.04 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.49 5 12 5 C 11.51 5 11.04 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6.04 C 7.88 6.55 7.26 7.22 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.04 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.04 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 7.85 19.79 9.78 21 12 21 C 14.22 21 16.15 19.79 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.96 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.96 10.33 17.91 10 L 20 10 L 20 8 M 14 16 C 14 15.43 14 14.859 14 14.289 L 14 14 C 13.869 14 13.739 14 13.608 14 C 12.405 14 11.203 14 10 14 C 10 14.509 10 15.017 10 15.526 C 10 15.684 10 15.842 10 16 L 10.33 16 C 10.392 16 10.454 16 10.515 16 C 11.677 16 12.838 16 14 16 C 14 16 14 16 14 16 M 14 10 L 14 12 L 14 12 L 10 12 L 10 10 L 14 10 M 12 15 L 12 15 L 12 15 L 12 15 L 12 15 L 12 15"
|
android:valueTo="M 20 8 L 20 8 L 17.19 8 C 16.74 7.22 16.12 6.55 15.37 6.04 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.49 5 12 5 C 11.51 5 11.04 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6.04 C 7.88 6.55 7.26 7.22 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.04 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.04 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 7.85 19.79 9.78 21 12 21 C 14.22 21 16.15 19.79 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.96 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.96 10.33 17.91 10 L 20 10 L 20 8 M 14 16 C 14 15.43 14 14.859 14 14.289 L 14 14 C 13.869 14 13.739 14 13.608 14 C 12.405 14 11.203 14 10 14 C 10 14.509 10 15.017 10 15.526 C 10 15.684 10 15.842 10 16 L 10.33 16 C 10.392 16 10.454 16 10.515 16 C 11.677 16 12.838 16 14 16 C 14 16 14 16 14 16 M 14 10 L 14 12 L 14 12 L 10 12 L 10 10 L 14 10 M 12 15 L 12 15 L 12 15 L 12 15 L 12 15 L 12 15"
|
@@ -17,7 +17,7 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:duration="500"
|
android:duration="500"
|
||||||
android:interpolator="@interpolator/fast_out_slow_in"
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="pathData"
|
android:propertyName="pathData"
|
||||||
android:valueFrom="M 12 2 C 9.217 2 6.689 3.152 4.872 5.004 C 3.098 6.811 2 9.283 2 12 C 2 14.744 3.12 17.24 4.927 19.052 C 6.74 20.87 9.244 22 12 22 C 13.911 22 15.701 21.457 17.224 20.517 C 18.628 19.651 19.804 18.448 20.638 17.024 C 21.503 15.545 22 13.828 22 12 C 22 10.2 21.518 8.507 20.677 7.044 C 19.755 5.441 18.402 4.114 16.779 3.224 C 15.357 2.444 13.728 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 6 13 L 10 17 L 18 9 L 16.59 7.58 L 16.59 7.58 L 10 14.17 L 7.41 11.59 L 6 13"
|
android:valueFrom="M 12 2 C 9.217 2 6.689 3.152 4.872 5.004 C 3.098 6.811 2 9.283 2 12 C 2 14.744 3.12 17.24 4.927 19.052 C 6.74 20.87 9.244 22 12 22 C 13.911 22 15.701 21.457 17.224 20.517 C 18.628 19.651 19.804 18.448 20.638 17.024 C 21.503 15.545 22 13.828 22 12 C 22 10.2 21.518 8.507 20.677 7.044 C 19.755 5.441 18.402 4.114 16.779 3.224 C 15.357 2.444 13.728 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 6 13 L 10 17 L 18 9 L 16.59 7.58 L 16.59 7.58 L 10 14.17 L 7.41 11.59 L 6 13"
|
||||||
android:valueTo="M 12 2 C 9.349 2 6.804 3.054 4.929 4.929 C 3.054 6.804 2 9.349 2 12 C 2 14.651 3.054 17.196 4.929 19.071 C 6.804 20.946 9.349 22 12 22 C 13.755 22 15.48 21.538 17 20.66 C 18.52 19.783 19.783 18.52 20.66 17 C 21.538 15.48 22 13.755 22 12 C 22 10.245 21.538 8.52 20.66 7 C 19.783 5.48 18.52 4.217 17 3.34 C 15.48 2.462 13.755 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 7 13 L 7 13 L 17 13 L 17 11 L 17 11 L 7 11 L 7 11 L 7 11"
|
android:valueTo="M 12 2 C 9.349 2 6.804 3.054 4.929 4.929 C 3.054 6.804 2 9.349 2 12 C 2 14.651 3.054 17.196 4.929 19.071 C 6.804 20.946 9.349 22 12 22 C 13.755 22 15.48 21.538 17 20.66 C 18.52 19.783 19.783 18.52 20.66 17 C 21.538 15.48 22 13.755 22 12 C 22 10.245 21.538 8.52 20.66 7 C 19.783 5.48 18.52 4.217 17 3.34 C 15.48 2.462 13.755 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 7 13 L 7 13 L 17 13 L 17 11 L 17 11 L 7 11 L 7 11 L 7 11"
|
@@ -17,7 +17,7 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:duration="500"
|
android:duration="500"
|
||||||
android:interpolator="@interpolator/fast_out_slow_in"
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="pathData"
|
android:propertyName="pathData"
|
||||||
android:valueFrom="M 12 2 C 9.349 2 6.804 3.054 4.929 4.929 C 3.054 6.804 2 9.349 2 12 C 2 14.651 3.054 17.196 4.929 19.071 C 6.804 20.946 9.349 22 12 22 C 13.755 22 15.48 21.538 17 20.66 C 18.52 19.783 19.783 18.52 20.66 17 C 21.538 15.48 22 13.755 22 12 C 22 10.245 21.538 8.52 20.66 7 C 19.783 5.48 18.52 4.217 17 3.34 C 15.48 2.462 13.755 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 7 13 L 7 13 L 17 13 L 17 11 L 17 11 L 7 11 L 7 11 L 7 11"
|
android:valueFrom="M 12 2 C 9.349 2 6.804 3.054 4.929 4.929 C 3.054 6.804 2 9.349 2 12 C 2 14.651 3.054 17.196 4.929 19.071 C 6.804 20.946 9.349 22 12 22 C 13.755 22 15.48 21.538 17 20.66 C 18.52 19.783 19.783 18.52 20.66 17 C 21.538 15.48 22 13.755 22 12 C 22 10.245 21.538 8.52 20.66 7 C 19.783 5.48 18.52 4.217 17 3.34 C 15.48 2.462 13.755 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 7 13 L 7 13 L 17 13 L 17 11 L 17 11 L 7 11 L 7 11 L 7 11"
|
||||||
android:valueTo="M 12 2 C 9.217 2 6.689 3.152 4.872 5.004 C 3.098 6.811 2 9.283 2 12 C 2 14.856 3.213 17.442 5.149 19.268 C 6.942 20.96 9.356 22 12 22 C 14.061 22 15.982 21.368 17.578 20.288 C 19.114 19.249 20.349 17.796 21.119 16.092 C 21.685 14.841 22 13.456 22 12 C 22 10.122 21.475 8.361 20.566 6.856 C 19.691 5.408 18.46 4.197 16.997 3.347 C 15.524 2.491 13.817 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 6 13 L 10 17 L 18 9 L 16.59 7.58 L 16.59 7.58 L 10 14.17 L 7.41 11.59 L 6 13"
|
android:valueTo="M 12 2 C 9.217 2 6.689 3.152 4.872 5.004 C 3.098 6.811 2 9.283 2 12 C 2 14.856 3.213 17.442 5.149 19.268 C 6.942 20.96 9.356 22 12 22 C 14.061 22 15.982 21.368 17.578 20.288 C 19.114 19.249 20.349 17.796 21.119 16.092 C 21.685 14.841 22 13.456 22 12 C 22 10.122 21.475 8.361 20.566 6.856 C 19.691 5.408 18.46 4.197 16.997 3.347 C 15.524 2.491 13.817 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 6 13 L 10 17 L 18 9 L 16.59 7.58 L 16.59 7.58 L 10 14.17 L 7.41 11.59 L 6 13"
|
@@ -19,7 +19,7 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:duration="300"
|
android:duration="300"
|
||||||
android:interpolator="@interpolator/fast_out_slow_in"
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="pathData"
|
android:propertyName="pathData"
|
||||||
android:valueFrom="M 9 14 L 9 21 L 4 21 L 4 9 L 12 3 L 12 3 L 20 9 L 20 21 L 15 21 L 15 14 L 9 14 M 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4"
|
android:valueFrom="M 9 14 L 9 21 L 4 21 L 4 9 L 12 3 L 12 3 L 20 9 L 20 21 L 15 21 L 15 14 L 9 14 M 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4"
|
||||||
android:valueTo="M 9 13 L 9 19 L 6 19 L 6 10 L 12 5.5 L 15 7.75 L 18 10 L 18 19 L 15 19 L 15 13 L 9 13 M 4 21 L 4 9 L 12 3 L 20 9 L 20 21 L 4 21 L 4 21"
|
android:valueTo="M 9 13 L 9 19 L 6 19 L 6 10 L 12 5.5 L 15 7.75 L 18 10 L 18 19 L 15 19 L 15 13 L 9 13 M 4 21 L 4 9 L 12 3 L 20 9 L 20 21 L 4 21 L 4 21"
|
@@ -19,7 +19,7 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:duration="300"
|
android:duration="300"
|
||||||
android:interpolator="@interpolator/fast_out_slow_in"
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="pathData"
|
android:propertyName="pathData"
|
||||||
android:valueFrom="M 9 13 L 9 19 L 6 19 L 6 10 L 12 5.5 L 15 7.75 L 18 10 L 18 19 L 15 19 L 15 13 L 9 13 M 4 21 L 4 9 L 12 3 L 20 9 L 20 21 L 4 21 L 4 21"
|
android:valueFrom="M 9 13 L 9 19 L 6 19 L 6 10 L 12 5.5 L 15 7.75 L 18 10 L 18 19 L 15 19 L 15 13 L 9 13 M 4 21 L 4 9 L 12 3 L 20 9 L 20 21 L 4 21 L 4 21"
|
||||||
android:valueTo="M 9 14 L 9 21 L 4 21 L 4 9 L 12 3 L 12 3 L 20 9 L 20 21 L 15 21 L 15 14 L 9 14 M 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4"
|
android:valueTo="M 9 14 L 9 21 L 4 21 L 4 9 L 12 3 L 12 3 L 20 9 L 20 21 L 15 21 L 15 14 L 9 14 M 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4"
|
@@ -18,7 +18,7 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:duration="300"
|
android:duration="300"
|
||||||
android:interpolator="@interpolator/fast_out_slow_in"
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="pathData"
|
android:propertyName="pathData"
|
||||||
android:valueFrom="M 23 13.5 C 23 14.163 22.736 14.799 22.268 15.268 C 21.799 15.736 21.163 16 20.5 16 C 20 16 19.5 16 19 16 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 15.1 22 L 13.2 22 C 13.2 21.5 13.2 21 13.2 20.5 C 13.2 19 12 17.8 10.5 17.8 C 9 17.8 7.8 19 7.8 20.5 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 3.5 16.2 C 5 16.2 6.2 15 6.2 13.5 C 6.2 12 5 10.8 3.5 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 8 5 C 8 4.5 8 4 8 3.5 C 8 2.837 8.264 2.201 8.732 1.732 C 9.201 1.264 9.837 1 10.5 1 C 11.163 1 11.799 1.264 12.268 1.732 C 12.736 2.201 13 2.837 13 3.5 C 13 4 13 4.5 13 5 L 17 5 C 17.55 5 18.05 5.223 18.413 5.584 C 18.775 5.945 19 6.445 19 7 L 19 11 C 19.5 11 20 11 20.5 11 C 20.5 11 20.5 11 20.5 11 C 21.163 11 21.799 11.264 22.268 11.732 C 22.736 12.201 23 12.837 23 13.5 M 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12"
|
android:valueFrom="M 23 13.5 C 23 14.163 22.736 14.799 22.268 15.268 C 21.799 15.736 21.163 16 20.5 16 C 20 16 19.5 16 19 16 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 15.1 22 L 13.2 22 C 13.2 21.5 13.2 21 13.2 20.5 C 13.2 19 12 17.8 10.5 17.8 C 9 17.8 7.8 19 7.8 20.5 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 3.5 16.2 C 5 16.2 6.2 15 6.2 13.5 C 6.2 12 5 10.8 3.5 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 8 5 C 8 4.5 8 4 8 3.5 C 8 2.837 8.264 2.201 8.732 1.732 C 9.201 1.264 9.837 1 10.5 1 C 11.163 1 11.799 1.264 12.268 1.732 C 12.736 2.201 13 2.837 13 3.5 C 13 4 13 4.5 13 5 L 17 5 C 17.55 5 18.05 5.223 18.413 5.584 C 18.775 5.945 19 6.445 19 7 L 19 11 C 19.5 11 20 11 20.5 11 C 20.5 11 20.5 11 20.5 11 C 21.163 11 21.799 11.264 22.268 11.732 C 22.736 12.201 23 12.837 23 13.5 M 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12"
|
||||||
android:valueTo="M 22 13.5 C 22 14.087 21.856 14.64 21.6 15.126 C 21.344 15.612 20.978 16.03 20.533 16.347 C 20.089 16.664 19.567 16.88 19 16.96 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 13.2 22 L 13.2 21.7 C 13.2 20.984 12.915 20.297 12.409 19.791 C 11.903 19.285 11.216 19 10.5 19 C 9 19 7.8 20.21 7.8 21.7 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 2.3 16.2 C 3.79 16.2 5 15 5 13.5 C 5 12 3.79 10.8 2.3 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 7.04 5 C 7.12 4.433 7.336 3.911 7.653 3.467 C 7.97 3.022 8.388 2.656 8.874 2.4 C 9.36 2.144 9.913 2 10.5 2 C 11.087 2 11.64 2.144 12.126 2.4 C 12.612 2.656 13.03 3.022 13.347 3.467 C 13.664 3.911 13.88 4.433 13.96 5 L 17 5 C 17.53 5 18.039 5.211 18.414 5.586 C 18.789 5.961 19 6.47 19 7 L 19 10.04 C 19.425 10.1 19.825 10.236 20.186 10.434 C 20.547 10.633 20.869 10.893 21.137 11.2 C 21.406 11.508 21.622 11.863 21.77 12.251 C 21.919 12.639 22 13.06 22 13.5 M 17 12 L 18.5 12 C 18.898 12 19.279 12.158 19.561 12.439 C 19.842 12.721 20 13.102 20 13.5 C 20 13.898 19.842 14.279 19.561 14.561 C 19.279 14.842 18.898 15 18.5 15 L 17 15 L 17 15 L 17 20 L 14.88 20 C 14.2 18.25 12.5 17 10.5 17 C 8.5 17 6.8 18.25 6.12 20 L 4 20 L 4 17.88 C 5.75 17.2 7 15.5 7 13.5 C 7 11.5 5.76 9.8 4 9.12 L 4 7 L 9 7 L 9 5.5 C 9 5.102 9.158 4.721 9.439 4.439 C 9.721 4.158 10.102 4 10.5 4 C 10.898 4 11.279 4.158 11.561 4.439 C 11.842 4.721 12 5.102 12 5.5 L 12 7 L 17 7 L 17 12"
|
android:valueTo="M 22 13.5 C 22 14.087 21.856 14.64 21.6 15.126 C 21.344 15.612 20.978 16.03 20.533 16.347 C 20.089 16.664 19.567 16.88 19 16.96 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 13.2 22 L 13.2 21.7 C 13.2 20.984 12.915 20.297 12.409 19.791 C 11.903 19.285 11.216 19 10.5 19 C 9 19 7.8 20.21 7.8 21.7 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 2.3 16.2 C 3.79 16.2 5 15 5 13.5 C 5 12 3.79 10.8 2.3 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 7.04 5 C 7.12 4.433 7.336 3.911 7.653 3.467 C 7.97 3.022 8.388 2.656 8.874 2.4 C 9.36 2.144 9.913 2 10.5 2 C 11.087 2 11.64 2.144 12.126 2.4 C 12.612 2.656 13.03 3.022 13.347 3.467 C 13.664 3.911 13.88 4.433 13.96 5 L 17 5 C 17.53 5 18.039 5.211 18.414 5.586 C 18.789 5.961 19 6.47 19 7 L 19 10.04 C 19.425 10.1 19.825 10.236 20.186 10.434 C 20.547 10.633 20.869 10.893 21.137 11.2 C 21.406 11.508 21.622 11.863 21.77 12.251 C 21.919 12.639 22 13.06 22 13.5 M 17 12 L 18.5 12 C 18.898 12 19.279 12.158 19.561 12.439 C 19.842 12.721 20 13.102 20 13.5 C 20 13.898 19.842 14.279 19.561 14.561 C 19.279 14.842 18.898 15 18.5 15 L 17 15 L 17 15 L 17 20 L 14.88 20 C 14.2 18.25 12.5 17 10.5 17 C 8.5 17 6.8 18.25 6.12 20 L 4 20 L 4 17.88 C 5.75 17.2 7 15.5 7 13.5 C 7 11.5 5.76 9.8 4 9.12 L 4 7 L 9 7 L 9 5.5 C 9 5.102 9.158 4.721 9.439 4.439 C 9.721 4.158 10.102 4 10.5 4 C 10.898 4 11.279 4.158 11.561 4.439 C 11.842 4.721 12 5.102 12 5.5 L 12 7 L 17 7 L 17 12"
|
@@ -18,7 +18,7 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:duration="300"
|
android:duration="300"
|
||||||
android:interpolator="@interpolator/fast_out_slow_in"
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="pathData"
|
android:propertyName="pathData"
|
||||||
android:valueFrom="M 22 13.5 C 22 14.087 21.856 14.64 21.6 15.126 C 21.344 15.612 20.978 16.03 20.533 16.347 C 20.089 16.664 19.567 16.88 19 16.96 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 13.2 22 L 13.2 21.7 C 13.2 20.984 12.915 20.297 12.409 19.791 C 11.903 19.285 11.216 19 10.5 19 C 9 19 7.8 20.21 7.8 21.7 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 2.3 16.2 C 3.79 16.2 5 15 5 13.5 C 5 12 3.79 10.8 2.3 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 7.04 5 C 7.12 4.433 7.336 3.911 7.653 3.467 C 7.97 3.022 8.388 2.656 8.874 2.4 C 9.36 2.144 9.913 2 10.5 2 C 11.087 2 11.64 2.144 12.126 2.4 C 12.612 2.656 13.03 3.022 13.347 3.467 C 13.664 3.911 13.88 4.433 13.96 5 L 17 5 C 17.53 5 18.039 5.211 18.414 5.586 C 18.789 5.961 19 6.47 19 7 L 19 10.04 C 19.425 10.1 19.825 10.236 20.186 10.434 C 20.547 10.633 20.869 10.893 21.137 11.2 C 21.406 11.508 21.622 11.863 21.77 12.251 C 21.919 12.639 22 13.06 22 13.5 M 17 12 L 18.5 12 C 18.898 12 19.279 12.158 19.561 12.439 C 19.842 12.721 20 13.102 20 13.5 C 20 13.898 19.842 14.279 19.561 14.561 C 19.279 14.842 18.898 15 18.5 15 L 17 15 L 17 15 L 17 20 L 14.88 20 C 14.2 18.25 12.5 17 10.5 17 C 8.5 17 6.8 18.25 6.12 20 L 4 20 L 4 17.88 C 5.75 17.2 7 15.5 7 13.5 C 7 11.5 5.76 9.8 4 9.12 L 4 7 L 9 7 L 9 5.5 C 9 5.102 9.158 4.721 9.439 4.439 C 9.721 4.158 10.102 4 10.5 4 C 10.898 4 11.279 4.158 11.561 4.439 C 11.842 4.721 12 5.102 12 5.5 L 12 7 L 17 7 L 17 12"
|
android:valueFrom="M 22 13.5 C 22 14.087 21.856 14.64 21.6 15.126 C 21.344 15.612 20.978 16.03 20.533 16.347 C 20.089 16.664 19.567 16.88 19 16.96 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 13.2 22 L 13.2 21.7 C 13.2 20.984 12.915 20.297 12.409 19.791 C 11.903 19.285 11.216 19 10.5 19 C 9 19 7.8 20.21 7.8 21.7 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 2.3 16.2 C 3.79 16.2 5 15 5 13.5 C 5 12 3.79 10.8 2.3 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 7.04 5 C 7.12 4.433 7.336 3.911 7.653 3.467 C 7.97 3.022 8.388 2.656 8.874 2.4 C 9.36 2.144 9.913 2 10.5 2 C 11.087 2 11.64 2.144 12.126 2.4 C 12.612 2.656 13.03 3.022 13.347 3.467 C 13.664 3.911 13.88 4.433 13.96 5 L 17 5 C 17.53 5 18.039 5.211 18.414 5.586 C 18.789 5.961 19 6.47 19 7 L 19 10.04 C 19.425 10.1 19.825 10.236 20.186 10.434 C 20.547 10.633 20.869 10.893 21.137 11.2 C 21.406 11.508 21.622 11.863 21.77 12.251 C 21.919 12.639 22 13.06 22 13.5 M 17 12 L 18.5 12 C 18.898 12 19.279 12.158 19.561 12.439 C 19.842 12.721 20 13.102 20 13.5 C 20 13.898 19.842 14.279 19.561 14.561 C 19.279 14.842 18.898 15 18.5 15 L 17 15 L 17 15 L 17 20 L 14.88 20 C 14.2 18.25 12.5 17 10.5 17 C 8.5 17 6.8 18.25 6.12 20 L 4 20 L 4 17.88 C 5.75 17.2 7 15.5 7 13.5 C 7 11.5 5.76 9.8 4 9.12 L 4 7 L 9 7 L 9 5.5 C 9 5.102 9.158 4.721 9.439 4.439 C 9.721 4.158 10.102 4 10.5 4 C 10.898 4 11.279 4.158 11.561 4.439 C 11.842 4.721 12 5.102 12 5.5 L 12 7 L 17 7 L 17 12"
|
||||||
android:valueTo="M 23 13.5 C 23 14.163 22.736 14.799 22.268 15.268 C 21.799 15.736 21.163 16 20.5 16 C 20 16 19.5 16 19 16 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 15.1 22 L 13.2 22 C 13.2 21.5 13.2 21 13.2 20.5 C 13.2 19 12 17.8 10.5 17.8 C 9 17.8 7.8 19 7.8 20.5 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 3.5 16.2 C 5 16.2 6.2 15 6.2 13.5 C 6.2 12 5 10.8 3.5 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 8 5 C 8 4.5 8 4 8 3.5 C 8 2.837 8.264 2.201 8.732 1.732 C 9.201 1.264 9.837 1 10.5 1 C 11.163 1 11.799 1.264 12.268 1.732 C 12.736 2.201 13 2.837 13 3.5 C 13 4 13 4.5 13 5 L 17 5 C 17.55 5 18.05 5.223 18.413 5.584 C 18.775 5.945 19 6.445 19 7 L 19 11 C 19.5 11 20 11 20.5 11 C 20.5 11 20.5 11 20.5 11 C 21.163 11 21.799 11.264 22.268 11.732 C 22.736 12.201 23 12.837 23 13.5 M 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12"
|
android:valueTo="M 23 13.5 C 23 14.163 22.736 14.799 22.268 15.268 C 21.799 15.736 21.163 16 20.5 16 C 20 16 19.5 16 19 16 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 15.1 22 L 13.2 22 C 13.2 21.5 13.2 21 13.2 20.5 C 13.2 19 12 17.8 10.5 17.8 C 9 17.8 7.8 19 7.8 20.5 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 3.5 16.2 C 5 16.2 6.2 15 6.2 13.5 C 6.2 12 5 10.8 3.5 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 8 5 C 8 4.5 8 4 8 3.5 C 8 2.837 8.264 2.201 8.732 1.732 C 9.201 1.264 9.837 1 10.5 1 C 11.163 1 11.799 1.264 12.268 1.732 C 12.736 2.201 13 2.837 13 3.5 C 13 4 13 4.5 13 5 L 17 5 C 17.55 5 18.05 5.223 18.413 5.584 C 18.775 5.945 19 6.445 19 7 L 19 11 C 19.5 11 20 11 20.5 11 C 20.5 11 20.5 11 20.5 11 C 21.163 11 21.799 11.264 22.268 11.732 C 22.736 12.201 23 12.837 23 13.5 M 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12"
|
@@ -18,7 +18,7 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:duration="300"
|
android:duration="300"
|
||||||
android:interpolator="@interpolator/fast_out_slow_in"
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="pathData"
|
android:propertyName="pathData"
|
||||||
android:valueFrom="M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.53 11.34 4.5 11.67 4.5 12 C 4.5 12.33 4.53 12.65 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.67 16.04 18.34 16.56 17.94 L 19.05 18.95 C 19.27 19.03 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.78 15.05 21.73 14.78 21.54 14.63 L 19.43 12.97 L 19.43 12.97 C 19.47 12.65 19.5 12.33 19.5 12 C 19.5 11.67 19.47 11.34 19.43 11 L 21.54 9.37 C 21.73 9.22 21.78 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8.5 C 12.614 8.5 13.218 8.662 13.75 8.969 C 14.282 9.276 14.724 9.718 15.031 10.25 C 15.338 10.782 15.5 11.386 15.5 12 C 15.5 12.614 15.338 13.218 15.031 13.75 C 14.724 14.282 14.282 14.724 13.75 15.031 C 13.218 15.338 12.614 15.5 12 15.5 C 11.072 15.5 10.181 15.131 9.525 14.475 C 8.869 13.819 8.5 12.928 8.5 12 C 8.5 11.072 8.869 10.181 9.525 9.525 C 10.181 8.869 11.072 8.5 12 8.5 M 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 M 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12"
|
android:valueFrom="M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.53 11.34 4.5 11.67 4.5 12 C 4.5 12.33 4.53 12.65 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.67 16.04 18.34 16.56 17.94 L 19.05 18.95 C 19.27 19.03 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.78 15.05 21.73 14.78 21.54 14.63 L 19.43 12.97 L 19.43 12.97 C 19.47 12.65 19.5 12.33 19.5 12 C 19.5 11.67 19.47 11.34 19.43 11 L 21.54 9.37 C 21.73 9.22 21.78 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8.5 C 12.614 8.5 13.218 8.662 13.75 8.969 C 14.282 9.276 14.724 9.718 15.031 10.25 C 15.338 10.782 15.5 11.386 15.5 12 C 15.5 12.614 15.338 13.218 15.031 13.75 C 14.724 14.282 14.282 14.724 13.75 15.031 C 13.218 15.338 12.614 15.5 12 15.5 C 11.072 15.5 10.181 15.131 9.525 14.475 C 8.869 13.819 8.5 12.928 8.5 12 C 8.5 11.072 8.869 10.181 9.525 9.525 C 10.181 8.869 11.072 8.5 12 8.5 M 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 M 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12"
|
||||||
android:valueTo="M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.547 11.333 4.523 11.667 4.5 12 C 4.523 12.323 4.547 12.647 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.68 16.04 18.34 16.56 17.95 L 19.05 18.95 C 19.27 19.04 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.79 15.05 21.73 14.78 21.54 14.63 L 19.43 13 L 19.465 12.499 C 19.477 12.333 19.488 12.166 19.5 12 C 19.477 11.667 19.453 11.333 19.43 11 L 21.54 9.37 C 21.73 9.22 21.79 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8 C 12.53 8 13.05 8.105 13.531 8.305 C 14.011 8.504 14.454 8.797 14.828 9.172 C 15.578 9.921 16 10.94 16 12 C 16 12.53 15.895 13.05 15.695 13.531 C 15.496 14.011 15.203 14.454 14.828 14.828 C 14.079 15.578 13.06 16 12 16 C 10.94 16 9.921 15.578 9.172 14.828 C 8.422 14.079 8 13.06 8 12 C 8 10.94 8.422 9.921 9.172 9.172 C 9.921 8.422 10.94 8 12 8 M 12 10 C 11.912 10 11.824 10.006 11.737 10.017 C 11.651 10.029 11.565 10.046 11.481 10.069 C 11.397 10.091 11.315 10.119 11.235 10.152 C 11.155 10.186 11.077 10.224 11.001 10.267 C 10.926 10.311 10.854 10.359 10.784 10.412 C 10.715 10.466 10.649 10.524 10.586 10.586 C 10.524 10.649 10.466 10.715 10.412 10.784 C 10.359 10.854 10.311 10.926 10.267 11.001 C 10.224 11.077 10.186 11.155 10.152 11.235 C 10.119 11.315 10.091 11.397 10.069 11.481 C 10.046 11.565 10.029 11.651 10.017 11.737 C 10.006 11.824 10 11.912 10 12 C 10 12.088 10.006 12.176 10.017 12.263 C 10.029 12.349 10.046 12.435 10.069 12.519 C 10.091 12.603 10.119 12.685 10.152 12.765 C 10.186 12.845 10.224 12.923 10.267 12.999 C 10.311 13.074 10.359 13.146 10.412 13.216 C 10.466 13.285 10.524 13.351 10.586 13.414 C 10.649 13.476 10.715 13.534 10.784 13.588 C 10.854 13.641 10.926 13.689 11.001 13.733 C 11.077 13.776 11.155 13.814 11.235 13.848 C 11.315 13.881 11.397 13.909 11.481 13.931 C 11.565 13.954 11.651 13.971 11.737 13.983 C 11.824 13.994 11.912 14 12 14 C 12.53 14 13.039 13.789 13.414 13.414 C 13.468 13.36 13.518 13.304 13.565 13.245 C 13.611 13.187 13.655 13.126 13.694 13.062 C 13.734 12.999 13.77 12.934 13.802 12.867 C 13.834 12.8 13.863 12.731 13.887 12.661 C 13.912 12.591 13.933 12.519 13.949 12.447 C 13.966 12.374 13.979 12.3 13.987 12.226 C 13.996 12.151 14 12.076 14 12 C 14 11.912 13.994 11.824 13.983 11.737 C 13.971 11.651 13.954 11.565 13.931 11.481 C 13.909 11.397 13.881 11.315 13.848 11.235 C 13.814 11.155 13.776 11.077 13.733 11.001 C 13.689 10.926 13.641 10.854 13.588 10.784 C 13.534 10.715 13.476 10.649 13.414 10.586 C 13.039 10.211 12.53 10 12 10 M 11.25 4 L 11.25 4 L 12.75 4 L 13.12 6.62 C 14.32 6.86 15.38 7.5 16.15 8.39 L 18.56 7.35 L 19.31 8.65 L 17.2 10.2 C 17.6 11.37 17.6 12.64 17.2 13.81 L 19.32 15.36 L 18.57 16.66 L 16.14 15.62 C 15.37 16.5 14.32 17.14 13.13 17.39 L 12.76 20 L 11.24 20 L 10.87 17.38 C 9.68 17.14 8.63 16.5 7.86 15.62 L 5.43 16.66 L 4.68 15.36 L 6.8 13.8 C 6.4 12.64 6.4 11.37 6.8 10.2 L 4.69 8.65 L 5.44 7.35 L 7.85 8.39 C 8.62 7.5 9.68 6.86 10.88 6.61 L 11.25 4"
|
android:valueTo="M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.547 11.333 4.523 11.667 4.5 12 C 4.523 12.323 4.547 12.647 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.68 16.04 18.34 16.56 17.95 L 19.05 18.95 C 19.27 19.04 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.79 15.05 21.73 14.78 21.54 14.63 L 19.43 13 L 19.465 12.499 C 19.477 12.333 19.488 12.166 19.5 12 C 19.477 11.667 19.453 11.333 19.43 11 L 21.54 9.37 C 21.73 9.22 21.79 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8 C 12.53 8 13.05 8.105 13.531 8.305 C 14.011 8.504 14.454 8.797 14.828 9.172 C 15.578 9.921 16 10.94 16 12 C 16 12.53 15.895 13.05 15.695 13.531 C 15.496 14.011 15.203 14.454 14.828 14.828 C 14.079 15.578 13.06 16 12 16 C 10.94 16 9.921 15.578 9.172 14.828 C 8.422 14.079 8 13.06 8 12 C 8 10.94 8.422 9.921 9.172 9.172 C 9.921 8.422 10.94 8 12 8 M 12 10 C 11.912 10 11.824 10.006 11.737 10.017 C 11.651 10.029 11.565 10.046 11.481 10.069 C 11.397 10.091 11.315 10.119 11.235 10.152 C 11.155 10.186 11.077 10.224 11.001 10.267 C 10.926 10.311 10.854 10.359 10.784 10.412 C 10.715 10.466 10.649 10.524 10.586 10.586 C 10.524 10.649 10.466 10.715 10.412 10.784 C 10.359 10.854 10.311 10.926 10.267 11.001 C 10.224 11.077 10.186 11.155 10.152 11.235 C 10.119 11.315 10.091 11.397 10.069 11.481 C 10.046 11.565 10.029 11.651 10.017 11.737 C 10.006 11.824 10 11.912 10 12 C 10 12.088 10.006 12.176 10.017 12.263 C 10.029 12.349 10.046 12.435 10.069 12.519 C 10.091 12.603 10.119 12.685 10.152 12.765 C 10.186 12.845 10.224 12.923 10.267 12.999 C 10.311 13.074 10.359 13.146 10.412 13.216 C 10.466 13.285 10.524 13.351 10.586 13.414 C 10.649 13.476 10.715 13.534 10.784 13.588 C 10.854 13.641 10.926 13.689 11.001 13.733 C 11.077 13.776 11.155 13.814 11.235 13.848 C 11.315 13.881 11.397 13.909 11.481 13.931 C 11.565 13.954 11.651 13.971 11.737 13.983 C 11.824 13.994 11.912 14 12 14 C 12.53 14 13.039 13.789 13.414 13.414 C 13.468 13.36 13.518 13.304 13.565 13.245 C 13.611 13.187 13.655 13.126 13.694 13.062 C 13.734 12.999 13.77 12.934 13.802 12.867 C 13.834 12.8 13.863 12.731 13.887 12.661 C 13.912 12.591 13.933 12.519 13.949 12.447 C 13.966 12.374 13.979 12.3 13.987 12.226 C 13.996 12.151 14 12.076 14 12 C 14 11.912 13.994 11.824 13.983 11.737 C 13.971 11.651 13.954 11.565 13.931 11.481 C 13.909 11.397 13.881 11.315 13.848 11.235 C 13.814 11.155 13.776 11.077 13.733 11.001 C 13.689 10.926 13.641 10.854 13.588 10.784 C 13.534 10.715 13.476 10.649 13.414 10.586 C 13.039 10.211 12.53 10 12 10 M 11.25 4 L 11.25 4 L 12.75 4 L 13.12 6.62 C 14.32 6.86 15.38 7.5 16.15 8.39 L 18.56 7.35 L 19.31 8.65 L 17.2 10.2 C 17.6 11.37 17.6 12.64 17.2 13.81 L 19.32 15.36 L 18.57 16.66 L 16.14 15.62 C 15.37 16.5 14.32 17.14 13.13 17.39 L 12.76 20 L 11.24 20 L 10.87 17.38 C 9.68 17.14 8.63 16.5 7.86 15.62 L 5.43 16.66 L 4.68 15.36 L 6.8 13.8 C 6.4 12.64 6.4 11.37 6.8 10.2 L 4.69 8.65 L 5.44 7.35 L 7.85 8.39 C 8.62 7.5 9.68 6.86 10.88 6.61 L 11.25 4"
|
@@ -18,7 +18,7 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:duration="300"
|
android:duration="300"
|
||||||
android:interpolator="@interpolator/fast_out_slow_in"
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="pathData"
|
android:propertyName="pathData"
|
||||||
android:valueFrom="M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.547 11.333 4.523 11.667 4.5 12 C 4.523 12.323 4.547 12.647 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.68 16.04 18.34 16.56 17.95 L 19.05 18.95 C 19.27 19.04 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.79 15.05 21.73 14.78 21.54 14.63 L 19.43 13 L 19.465 12.499 C 19.477 12.333 19.488 12.166 19.5 12 C 19.477 11.667 19.453 11.333 19.43 11 L 21.54 9.37 C 21.73 9.22 21.79 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8 C 12.53 8 13.05 8.105 13.531 8.305 C 14.011 8.504 14.454 8.797 14.828 9.172 C 15.578 9.921 16 10.94 16 12 C 16 12.53 15.895 13.05 15.695 13.531 C 15.496 14.011 15.203 14.454 14.828 14.828 C 14.079 15.578 13.06 16 12 16 C 10.94 16 9.921 15.578 9.172 14.828 C 8.422 14.079 8 13.06 8 12 C 8 10.94 8.422 9.921 9.172 9.172 C 9.921 8.422 10.94 8 12 8 M 12 10 C 11.912 10 11.824 10.006 11.737 10.017 C 11.651 10.029 11.565 10.046 11.481 10.069 C 11.397 10.091 11.315 10.119 11.235 10.152 C 11.155 10.186 11.077 10.224 11.001 10.267 C 10.926 10.311 10.854 10.359 10.784 10.412 C 10.715 10.466 10.649 10.524 10.586 10.586 C 10.524 10.649 10.466 10.715 10.412 10.784 C 10.359 10.854 10.311 10.926 10.267 11.001 C 10.224 11.077 10.186 11.155 10.152 11.235 C 10.119 11.315 10.091 11.397 10.069 11.481 C 10.046 11.565 10.029 11.651 10.017 11.737 C 10.006 11.824 10 11.912 10 12 C 10 12.088 10.006 12.176 10.017 12.263 C 10.029 12.349 10.046 12.435 10.069 12.519 C 10.091 12.603 10.119 12.685 10.152 12.765 C 10.186 12.845 10.224 12.923 10.267 12.999 C 10.311 13.074 10.359 13.146 10.412 13.216 C 10.466 13.285 10.524 13.351 10.586 13.414 C 10.649 13.476 10.715 13.534 10.784 13.588 C 10.854 13.641 10.926 13.689 11.001 13.733 C 11.077 13.776 11.155 13.814 11.235 13.848 C 11.315 13.881 11.397 13.909 11.481 13.931 C 11.565 13.954 11.651 13.971 11.737 13.983 C 11.824 13.994 11.912 14 12 14 C 12.53 14 13.039 13.789 13.414 13.414 C 13.468 13.36 13.518 13.304 13.565 13.245 C 13.611 13.187 13.655 13.126 13.694 13.062 C 13.734 12.999 13.77 12.934 13.802 12.867 C 13.834 12.8 13.863 12.731 13.887 12.661 C 13.912 12.591 13.933 12.519 13.949 12.447 C 13.966 12.374 13.979 12.3 13.987 12.226 C 13.996 12.151 14 12.076 14 12 C 14 11.912 13.994 11.824 13.983 11.737 C 13.971 11.651 13.954 11.565 13.931 11.481 C 13.909 11.397 13.881 11.315 13.848 11.235 C 13.814 11.155 13.776 11.077 13.733 11.001 C 13.689 10.926 13.641 10.854 13.588 10.784 C 13.534 10.715 13.476 10.649 13.414 10.586 C 13.039 10.211 12.53 10 12 10 M 11.25 4 L 11.25 4 L 12.75 4 L 13.12 6.62 C 14.32 6.86 15.38 7.5 16.15 8.39 L 18.56 7.35 L 19.31 8.65 L 17.2 10.2 C 17.6 11.37 17.6 12.64 17.2 13.81 L 19.32 15.36 L 18.57 16.66 L 16.14 15.62 C 15.37 16.5 14.32 17.14 13.13 17.39 L 12.76 20 L 11.24 20 L 10.87 17.38 C 9.68 17.14 8.63 16.5 7.86 15.62 L 5.43 16.66 L 4.68 15.36 L 6.8 13.8 C 6.4 12.64 6.4 11.37 6.8 10.2 L 4.69 8.65 L 5.44 7.35 L 7.85 8.39 C 8.62 7.5 9.68 6.86 10.88 6.61 L 11.25 4"
|
android:valueFrom="M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.547 11.333 4.523 11.667 4.5 12 C 4.523 12.323 4.547 12.647 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.68 16.04 18.34 16.56 17.95 L 19.05 18.95 C 19.27 19.04 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.79 15.05 21.73 14.78 21.54 14.63 L 19.43 13 L 19.465 12.499 C 19.477 12.333 19.488 12.166 19.5 12 C 19.477 11.667 19.453 11.333 19.43 11 L 21.54 9.37 C 21.73 9.22 21.79 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8 C 12.53 8 13.05 8.105 13.531 8.305 C 14.011 8.504 14.454 8.797 14.828 9.172 C 15.578 9.921 16 10.94 16 12 C 16 12.53 15.895 13.05 15.695 13.531 C 15.496 14.011 15.203 14.454 14.828 14.828 C 14.079 15.578 13.06 16 12 16 C 10.94 16 9.921 15.578 9.172 14.828 C 8.422 14.079 8 13.06 8 12 C 8 10.94 8.422 9.921 9.172 9.172 C 9.921 8.422 10.94 8 12 8 M 12 10 C 11.912 10 11.824 10.006 11.737 10.017 C 11.651 10.029 11.565 10.046 11.481 10.069 C 11.397 10.091 11.315 10.119 11.235 10.152 C 11.155 10.186 11.077 10.224 11.001 10.267 C 10.926 10.311 10.854 10.359 10.784 10.412 C 10.715 10.466 10.649 10.524 10.586 10.586 C 10.524 10.649 10.466 10.715 10.412 10.784 C 10.359 10.854 10.311 10.926 10.267 11.001 C 10.224 11.077 10.186 11.155 10.152 11.235 C 10.119 11.315 10.091 11.397 10.069 11.481 C 10.046 11.565 10.029 11.651 10.017 11.737 C 10.006 11.824 10 11.912 10 12 C 10 12.088 10.006 12.176 10.017 12.263 C 10.029 12.349 10.046 12.435 10.069 12.519 C 10.091 12.603 10.119 12.685 10.152 12.765 C 10.186 12.845 10.224 12.923 10.267 12.999 C 10.311 13.074 10.359 13.146 10.412 13.216 C 10.466 13.285 10.524 13.351 10.586 13.414 C 10.649 13.476 10.715 13.534 10.784 13.588 C 10.854 13.641 10.926 13.689 11.001 13.733 C 11.077 13.776 11.155 13.814 11.235 13.848 C 11.315 13.881 11.397 13.909 11.481 13.931 C 11.565 13.954 11.651 13.971 11.737 13.983 C 11.824 13.994 11.912 14 12 14 C 12.53 14 13.039 13.789 13.414 13.414 C 13.468 13.36 13.518 13.304 13.565 13.245 C 13.611 13.187 13.655 13.126 13.694 13.062 C 13.734 12.999 13.77 12.934 13.802 12.867 C 13.834 12.8 13.863 12.731 13.887 12.661 C 13.912 12.591 13.933 12.519 13.949 12.447 C 13.966 12.374 13.979 12.3 13.987 12.226 C 13.996 12.151 14 12.076 14 12 C 14 11.912 13.994 11.824 13.983 11.737 C 13.971 11.651 13.954 11.565 13.931 11.481 C 13.909 11.397 13.881 11.315 13.848 11.235 C 13.814 11.155 13.776 11.077 13.733 11.001 C 13.689 10.926 13.641 10.854 13.588 10.784 C 13.534 10.715 13.476 10.649 13.414 10.586 C 13.039 10.211 12.53 10 12 10 M 11.25 4 L 11.25 4 L 12.75 4 L 13.12 6.62 C 14.32 6.86 15.38 7.5 16.15 8.39 L 18.56 7.35 L 19.31 8.65 L 17.2 10.2 C 17.6 11.37 17.6 12.64 17.2 13.81 L 19.32 15.36 L 18.57 16.66 L 16.14 15.62 C 15.37 16.5 14.32 17.14 13.13 17.39 L 12.76 20 L 11.24 20 L 10.87 17.38 C 9.68 17.14 8.63 16.5 7.86 15.62 L 5.43 16.66 L 4.68 15.36 L 6.8 13.8 C 6.4 12.64 6.4 11.37 6.8 10.2 L 4.69 8.65 L 5.44 7.35 L 7.85 8.39 C 8.62 7.5 9.68 6.86 10.88 6.61 L 11.25 4"
|
||||||
android:valueTo="M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.53 11.34 4.5 11.67 4.5 12 C 4.5 12.33 4.53 12.65 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.67 16.04 18.34 16.56 17.94 L 19.05 18.95 C 19.27 19.03 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.78 15.05 21.73 14.78 21.54 14.63 L 19.43 12.97 L 19.43 12.97 C 19.47 12.65 19.5 12.33 19.5 12 C 19.5 11.67 19.47 11.34 19.43 11 L 21.54 9.37 C 21.73 9.22 21.78 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8.5 C 12.614 8.5 13.218 8.662 13.75 8.969 C 14.282 9.276 14.724 9.718 15.031 10.25 C 15.338 10.782 15.5 11.386 15.5 12 C 15.5 12.614 15.338 13.218 15.031 13.75 C 14.724 14.282 14.282 14.724 13.75 15.031 C 13.218 15.338 12.614 15.5 12 15.5 C 11.072 15.5 10.181 15.131 9.525 14.475 C 8.869 13.819 8.5 12.928 8.5 12 C 8.5 11.072 8.869 10.181 9.525 9.525 C 10.181 8.869 11.072 8.5 12 8.5 M 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 M 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12"
|
android:valueTo="M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.53 11.34 4.5 11.67 4.5 12 C 4.5 12.33 4.53 12.65 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.67 16.04 18.34 16.56 17.94 L 19.05 18.95 C 19.27 19.03 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.78 15.05 21.73 14.78 21.54 14.63 L 19.43 12.97 L 19.43 12.97 C 19.47 12.65 19.5 12.33 19.5 12 C 19.5 11.67 19.47 11.34 19.43 11 L 21.54 9.37 C 21.73 9.22 21.78 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8.5 C 12.614 8.5 13.218 8.662 13.75 8.969 C 14.282 9.276 14.724 9.718 15.031 10.25 C 15.338 10.782 15.5 11.386 15.5 12 C 15.5 12.614 15.338 13.218 15.031 13.75 C 14.724 14.282 14.282 14.724 13.75 15.031 C 13.218 15.338 12.614 15.5 12 15.5 C 11.072 15.5 10.181 15.131 9.525 14.475 C 8.869 13.819 8.5 12.928 8.5 12 C 8.5 11.072 8.869 10.181 9.525 9.525 C 10.181 8.869 11.072 8.5 12 8.5 M 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 M 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12"
|
@@ -18,7 +18,7 @@
|
|||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator
|
||||||
android:duration="300"
|
android:duration="300"
|
||||||
android:interpolator="@interpolator/fast_out_slow_in"
|
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||||
android:propertyName="pathData"
|
android:propertyName="pathData"
|
||||||
android:valueFrom="M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 12 1 L 12 1 L 21 5 L 21 11 M 12 10.18 L 12 10.18 C 12 10.18 12 10.18 12 10.18 L 12 10.18 L 12 10.18 L 12 10.18 L 12 10.18 C 12 10.18 12 10.18 12 10.18"
|
android:valueFrom="M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 12 1 L 12 1 L 21 5 L 21 11 M 12 10.18 L 12 10.18 C 12 10.18 12 10.18 12 10.18 L 12 10.18 L 12 10.18 L 12 10.18 L 12 10.18 C 12 10.18 12 10.18 12 10.18"
|
||||||
android:valueTo="M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 7.5 3 L 12 1 L 21 5 L 21 11 M 12 21 L 12 21 C 8.25 20 5 15.54 5 11.22 L 5 6.3 L 12 3.18 L 19 6.3 L 19 11.22 C 19 15.54 15.75 20 12 21"
|
android:valueTo="M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 7.5 3 L 12 1 L 21 5 L 21 11 M 12 21 L 12 21 C 8.25 20 5 15.54 5 11.22 L 5 6.3 L 12 3.18 L 19 6.3 L 19 11.22 C 19 15.54 15.75 20 12 21"
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user