Compare commits
1474
Commits
1.8.9.2-gms
...
1.11.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85b538bbd3 | ||
|
|
d00d700a4b | ||
|
|
ae66318843 | ||
|
|
1b9aa6a762 | ||
|
|
306ce83de4 | ||
|
|
89f9735ab9 | ||
|
|
843e1cc6a0 | ||
|
|
1a53d0d207 | ||
|
|
45c02d4fd6 | ||
|
|
862dee0845 | ||
|
|
809bbfeba7 | ||
|
|
959c740175 | ||
|
|
a718510c89 | ||
|
|
f7c2fd8af3 | ||
|
|
c76440f167 | ||
|
|
c3bd959ee5 | ||
|
|
0a25643b87 | ||
|
|
c805ca8aff | ||
|
|
699a1ab801 | ||
|
|
ee091aee0f | ||
|
|
673c1837e9 | ||
|
|
3998913c17 | ||
|
|
1e83d7128e | ||
|
|
355289a576 | ||
|
|
ba3a41f0ee | ||
|
|
fc6d0bd6ef | ||
|
|
15ef0ae362 | ||
|
|
a598f8562d | ||
|
|
2860fed5ab | ||
|
|
f247544439 | ||
|
|
fdc3d94d5c | ||
|
|
afc2ff7478 | ||
|
|
83072d4eee | ||
|
|
7e7be8d40a | ||
|
|
90d7555333 | ||
|
|
ba268cfe6d | ||
|
|
0a7a9beb84 | ||
|
|
b18b89db29 | ||
|
|
cb2d99bdc1 | ||
|
|
4dd6d68163 | ||
|
|
28ecc9d479 | ||
|
|
c917117db3 | ||
|
|
5c0d65dd60 | ||
|
|
dfc59aa643 | ||
|
|
6ab77e541f | ||
|
|
6224f8f4ab | ||
|
|
3d269c4220 | ||
|
|
176a802e96 | ||
|
|
417a06e140 | ||
|
|
5c4f034532 | ||
|
|
1d50855f52 | ||
|
|
0280450797 | ||
|
|
d0c907c7ff | ||
|
|
1302e17c01 | ||
|
|
33f83b7325 | ||
|
|
d2c49f5022 | ||
|
|
084883a308 | ||
|
|
67a73da720 | ||
|
|
f2447fd122 | ||
|
|
8ddcb003e0 | ||
|
|
0bff011f72 | ||
|
|
f41d1d7306 | ||
|
|
a30aa23b67 | ||
|
|
583d30eb5f | ||
|
|
c53b0986a9 | ||
|
|
85703c8c47 | ||
|
|
025ba0bf5b | ||
|
|
ac2def22d2 | ||
|
|
895cfc7bc6 | ||
|
|
49eb9d5077 | ||
|
|
5a3c165ff1 | ||
|
|
3946428df6 | ||
|
|
471d9784d6 | ||
|
|
0ba0afc0d0 | ||
|
|
5049b791fb | ||
|
|
9a3715c516 | ||
|
|
4ee276f636 | ||
|
|
d528b2d5d0 | ||
|
|
b3ee69d193 | ||
|
|
f882b78ff6 | ||
|
|
7e58101a3f | ||
|
|
5450eb9b2b | ||
|
|
69695e3c99 | ||
|
|
5b15059c04 | ||
|
|
f50d69cd60 | ||
|
|
df6f783a69 | ||
|
|
f0291d94f9 | ||
|
|
593f24b86f | ||
|
|
5bce898f99 | ||
|
|
2b444c262d | ||
|
|
e40a7c75d9 | ||
|
|
4a7c057260 | ||
|
|
229a00cd53 | ||
|
|
d2d0d880f1 | ||
|
|
c5e17c1d5b | ||
|
|
33ff2ebe06 | ||
|
|
5c13e188de | ||
|
|
60b03dcd0e | ||
|
|
4604a25f58 | ||
|
|
99029b8b04 | ||
|
|
70f46442c6 | ||
|
|
13cf42437f | ||
|
|
1e29f6dcb4 | ||
|
|
f25981792f | ||
|
|
934c8be3e4 | ||
|
|
b9994eacbc | ||
|
|
704ab0ee7e | ||
|
|
6d1d737f9f | ||
|
|
bc75156232 | ||
|
|
2101ea7a36 | ||
|
|
26921c8ae1 | ||
|
|
be1eecf3b5 | ||
|
|
2d4f154c9b | ||
|
|
d82cbe51ba | ||
|
|
d13ffa93ba | ||
|
|
2cce55953f | ||
|
|
07d100fcc0 | ||
|
|
cc04371181 | ||
|
|
ee50c88765 | ||
|
|
efc5b55ea7 | ||
|
|
94af66156a | ||
|
|
4861359f9e | ||
|
|
5af167dab4 | ||
|
|
db1f0bc6d7 | ||
|
|
b94a843ec5 | ||
|
|
b27d214f24 | ||
|
|
a81eb9d17b | ||
|
|
ec9b85f30f | ||
|
|
8ac2f33949 | ||
|
|
d84198ab28 | ||
|
|
34d339d9f1 | ||
|
|
bcfefc7c76 | ||
|
|
62539dc48e | ||
|
|
453ab11395 | ||
|
|
b2246b980a | ||
|
|
822a461acd | ||
|
|
25e5c0ab8e | ||
|
|
2a55db95f4 | ||
|
|
a144866182 | ||
|
|
be7a04a4cb | ||
|
|
fd1b861dab | ||
|
|
957ebd35aa | ||
|
|
29d8b11771 | ||
|
|
c87059e9d7 | ||
|
|
4babe2eec4 | ||
|
|
48fdaaca0c | ||
|
|
f0ee88b4d6 | ||
|
|
9210780b6b | ||
|
|
b479e28543 | ||
|
|
f93b98fc5e | ||
|
|
4721d1011b | ||
|
|
4da4443e67 | ||
|
|
e5343f4360 | ||
|
|
86323220dc | ||
|
|
ada8f3af75 | ||
|
|
fb940d3982 | ||
|
|
8e8729c25f | ||
|
|
ffdd063ca1 | ||
|
|
dfd5c6c70e | ||
|
|
3068aab681 | ||
|
|
e402587f8f | ||
|
|
00b1be879d | ||
|
|
e63bd49a4c | ||
|
|
8131170eb1 | ||
|
|
dc1abd3806 | ||
|
|
468fbd50da | ||
|
|
f2011f9b6f | ||
|
|
1e53a9b389 | ||
|
|
dc2504b67f | ||
|
|
2ae3e3b64c | ||
|
|
e6bdaf1172 | ||
|
|
36a66d9199 | ||
|
|
fb6fbb8df9 | ||
|
|
ef8449dc02 | ||
|
|
b59f8e9ade | ||
|
|
a49506f4a0 | ||
|
|
4ce3d25eed | ||
|
|
81b166bee1 | ||
|
|
37a587859a | ||
|
|
b7544630bb | ||
|
|
05c7a0fa39 | ||
|
|
eee7655f80 | ||
|
|
9390813c48 | ||
|
|
d032fbdd67 | ||
|
|
adcd73a590 | ||
|
|
87b405bf1f | ||
|
|
d8306e2c80 | ||
|
|
c03fe9b51e | ||
|
|
9099014d28 | ||
|
|
d7b3872486 | ||
|
|
4ae2af0bef | ||
|
|
c9bcd46b77 | ||
|
|
e531653634 | ||
|
|
a29e4c72b1 | ||
|
|
9e1f7ca051 | ||
|
|
fbbc347411 | ||
|
|
ea364536a4 | ||
|
|
575fc9e94c | ||
|
|
703c63125a | ||
|
|
b205b5e120 | ||
|
|
e27f36d2c8 | ||
|
|
d582eddc03 | ||
|
|
3b83d8744c | ||
|
|
68a5217b1c | ||
|
|
c01082b822 | ||
|
|
bb95c86320 | ||
|
|
d398926aeb | ||
|
|
7b23efb0ce | ||
|
|
7818ff197c | ||
|
|
95bfc4f8c5 | ||
|
|
c750016034 | ||
|
|
6298e501cd | ||
|
|
7c39b95025 | ||
|
|
02c1e630d1 | ||
|
|
8f3b0cc536 | ||
|
|
d0c5ef8e8a | ||
|
|
d628024637 | ||
|
|
fe1249630b | ||
|
|
cdbe3cf728 | ||
|
|
f5e36bc0cf | ||
|
|
4f9bfe13cb | ||
|
|
959da21b9d | ||
|
|
fae2b1f416 | ||
|
|
a054633d0a | ||
|
|
a4619547f1 | ||
|
|
5d53febb15 | ||
|
|
3da1587924 | ||
|
|
971d0307fe | ||
|
|
705d3afb78 | ||
|
|
bc34b4b9c1 | ||
|
|
a2ac01fcdc | ||
|
|
645ebddd4f | ||
|
|
d5d8a828ef | ||
|
|
96d4193d2d | ||
|
|
18e4afdff9 | ||
|
|
19a702ffc4 | ||
|
|
cfd44c256f | ||
|
|
4cea8d0090 | ||
|
|
30e166b29c | ||
|
|
c50f3d8c5b | ||
|
|
d9249eeedb | ||
|
|
51699cafe6 | ||
|
|
ce97976bdd | ||
|
|
7b8a12e3eb | ||
|
|
7276c37c54 | ||
|
|
4ec7de611c | ||
|
|
0295c40b02 | ||
|
|
035fa44523 | ||
|
|
60c6618db1 | ||
|
|
f35643b2b6 | ||
|
|
b25661c436 | ||
|
|
cea06f0c7e | ||
|
|
7de6a821d9 | ||
|
|
13be4e4eb1 | ||
|
|
691e7c3ba7 | ||
|
|
31aea1cdbf | ||
|
|
97d5642427 | ||
|
|
c68272c1a4 | ||
|
|
a69dc994b3 | ||
|
|
d79a71c764 | ||
|
|
35183de477 | ||
|
|
21b9fe3cb2 | ||
|
|
5762b0bfc3 | ||
|
|
3473e91747 | ||
|
|
6277d7d41d | ||
|
|
f22da2b771 | ||
|
|
3466bbd64a | ||
|
|
49fc87621c | ||
|
|
7e558f4478 | ||
|
|
307624ca9a | ||
|
|
40e66b919a | ||
|
|
cf2e0aadd5 | ||
|
|
1b523f6a78 | ||
|
|
6a9f6bb645 | ||
|
|
45602b05c6 | ||
|
|
8dc842e125 | ||
|
|
9dfc3a49fe | ||
|
|
97d4a61140 | ||
|
|
8b596e2274 | ||
|
|
123b86830b | ||
|
|
e11f5615a1 | ||
|
|
31fb2ed2b1 | ||
|
|
9c12d11c85 | ||
|
|
aea0e885c3 | ||
|
|
f34ff71145 | ||
|
|
820c16e86b | ||
|
|
a335348b1a | ||
|
|
7f134e1995 | ||
|
|
9e13c1c3db | ||
|
|
61161cef0f | ||
|
|
9ff74e789c | ||
|
|
45183df640 | ||
|
|
22f11994f5 | ||
|
|
bc7aa832b7 | ||
|
|
b32f78ea9f | ||
|
|
78002dac3a | ||
|
|
aca70354e7 | ||
|
|
d7a3471b9b | ||
|
|
e81092d42c | ||
|
|
d06df3c819 | ||
|
|
050bfea8f2 | ||
|
|
7b84c74abf | ||
|
|
009c49cd89 | ||
|
|
ca023f73f4 | ||
|
|
456dfd2aac | ||
|
|
0c0291127c | ||
|
|
fe0c74de23 | ||
|
|
df1efa7604 | ||
|
|
73122fc517 | ||
|
|
b506ba277c | ||
|
|
ea3aed8fae | ||
|
|
481bb8e7d4 | ||
|
|
0ed8c40dbf | ||
|
|
d267d7c3b0 | ||
|
|
a04206f600 | ||
|
|
8d64a6f1b2 | ||
|
|
b3f614e150 | ||
|
|
988fae56fd | ||
|
|
fa439fc62d | ||
|
|
c58b2d6156 | ||
|
|
62c56b7918 | ||
|
|
73882ab55d | ||
|
|
490ea2b521 | ||
|
|
7116871652 | ||
|
|
cca374c774 | ||
|
|
3e0dbf92d0 | ||
|
|
2747ae674c | ||
|
|
e85ec5aaba | ||
|
|
d4894e066b | ||
|
|
71a4e84d7c | ||
|
|
d6c36146ba | ||
|
|
9013778f58 | ||
|
|
1af03dd145 | ||
|
|
b83e0fd092 | ||
|
|
1f8d786d05 | ||
|
|
5d87a4ada0 | ||
|
|
4d4fb1e398 | ||
|
|
05eea54f06 | ||
|
|
a00d9ef765 | ||
|
|
aa6ddac9c5 | ||
|
|
9a6f17c745 | ||
|
|
980977aaf3 | ||
|
|
3ed0afaf91 | ||
|
|
250adb4b33 | ||
|
|
0baf09cb53 | ||
|
|
d04a914b9b | ||
|
|
59775cb27c | ||
|
|
fb76cd7b1b | ||
|
|
52fb5306b6 | ||
|
|
74f7d95718 | ||
|
|
bcc1b78c7c | ||
|
|
3978abea87 | ||
|
|
556b726349 | ||
|
|
e0f16f31e6 | ||
|
|
b39e24467c | ||
|
|
65613f07ba | ||
|
|
666a7e4d77 | ||
|
|
cf08bfdd9b | ||
|
|
1f47d56c55 | ||
|
|
1a7dde848f | ||
|
|
c8207308a8 | ||
|
|
f23020cc6f | ||
|
|
555e487f1e | ||
|
|
107bb0b15d | ||
|
|
87a2de393e | ||
|
|
a7e4412454 | ||
|
|
7dce4cfa8e | ||
|
|
2b30aca47c | ||
|
|
0073d2862b | ||
|
|
b182c97185 | ||
|
|
a32119bada | ||
|
|
a928cfd066 | ||
|
|
8c7a371336 | ||
|
|
6f61eb4946 | ||
|
|
1096f05344 | ||
|
|
26f96d3e38 | ||
|
|
4a02189a59 | ||
|
|
7a5bab41f6 | ||
|
|
84c32fb28d | ||
|
|
a3cb6d2782 | ||
|
|
45be957df5 | ||
|
|
8e45014e3f | ||
|
|
d84e948352 | ||
|
|
68bd835038 | ||
|
|
c297bebea1 | ||
|
|
6c0a823216 | ||
|
|
067a701bdc | ||
|
|
c3fb008c92 | ||
|
|
bc4321da2e | ||
|
|
8dec040803 | ||
|
|
29e4e77c44 | ||
|
|
11429ef7fe | ||
|
|
f5fcd96a1e | ||
|
|
19120038c5 | ||
|
|
9021f28ead | ||
|
|
62934f72e3 | ||
|
|
6c534e1049 | ||
|
|
f80cd90792 | ||
|
|
3999c0c114 | ||
|
|
59399453c4 | ||
|
|
694bb19e2a | ||
|
|
9928ecd4d9 | ||
|
|
2c5293af6e | ||
|
|
9569ca774d | ||
|
|
3a04460926 | ||
|
|
acb4fb6166 | ||
|
|
7d137bb016 | ||
|
|
8c2ed37b72 | ||
|
|
749b230250 | ||
|
|
23531cb6e0 | ||
|
|
0ac11dbe0b | ||
|
|
9b0e017542 | ||
|
|
f2f5fcacf9 | ||
|
|
52ded265f3 | ||
|
|
e77f905c4a | ||
|
|
2d14d34d53 | ||
|
|
78facda18a | ||
|
|
1fe1b43015 | ||
|
|
63164dc70a | ||
|
|
0d39092a7f | ||
|
|
c50db8a327 | ||
|
|
7e107cc5e2 | ||
|
|
260949e3a7 | ||
|
|
d9ec108721 | ||
|
|
1e97407936 | ||
|
|
73afe05a18 | ||
|
|
23475de932 | ||
|
|
6fb2172c2c | ||
|
|
9226b71119 | ||
|
|
58e38b58bf | ||
|
|
db0d110627 | ||
|
|
08812bf7ff | ||
|
|
daf7f8ffca | ||
|
|
74564fa07f | ||
|
|
fc51d3fc81 | ||
|
|
c5d5b8abd2 | ||
|
|
193badccb8 | ||
|
|
2653d0568a | ||
|
|
adf5a65371 | ||
|
|
5b59dabc66 | ||
|
|
f7259c5b40 | ||
|
|
6f9fceaef8 | ||
|
|
6a64ff51ed | ||
|
|
ea9d9cfba7 | ||
|
|
75c6561896 | ||
|
|
8f8db82e9d | ||
|
|
a114a8ac72 | ||
|
|
2c6216057f | ||
|
|
c5cbcf7418 | ||
|
|
5746e3bb57 | ||
|
|
fd3c87be22 | ||
|
|
866fff3e45 | ||
|
|
06df7befb6 | ||
|
|
8021f86473 | ||
|
|
c9919a8b39 | ||
|
|
386015a9ec | ||
|
|
cbce98b34e | ||
|
|
f69521fc1a | ||
|
|
01b4054368 | ||
|
|
e07aab4434 | ||
|
|
18caa87512 | ||
|
|
5676de24da | ||
|
|
57bbb1fe38 | ||
|
|
dcbfbc2e5f | ||
|
|
e1f473d301 | ||
|
|
a33256d29f | ||
|
|
6a81877b09 | ||
|
|
6cd7b85b94 | ||
|
|
df97a9c4b9 | ||
|
|
5cb91e9b45 | ||
|
|
94794cb0db | ||
|
|
a16c021000 | ||
|
|
49d4ae078f | ||
|
|
eb45068df3 | ||
|
|
1aa750d676 | ||
|
|
af85a68e3d | ||
|
|
a8fc4003a3 | ||
|
|
675df44745 | ||
|
|
7e271d7e8e | ||
|
|
acb2c720af | ||
|
|
5d0c2e3f05 | ||
|
|
3d5e3620dd | ||
|
|
43aefeedae | ||
|
|
c2cbcc009d | ||
|
|
2aa494d9b7 | ||
|
|
8f3a11f374 | ||
|
|
76298f15e4 | ||
|
|
58839e0def | ||
|
|
6fe62db5e4 | ||
|
|
9b9f25d405 | ||
|
|
e47d7378ec | ||
|
|
51b385408d | ||
|
|
8e7cced8d2 | ||
|
|
affeb3e106 | ||
|
|
a2377857e9 | ||
|
|
7714ff8938 | ||
|
|
352853b88c | ||
|
|
23d282e354 | ||
|
|
b93c12de89 | ||
|
|
e0e5a155aa | ||
|
|
e1f34a7b1b | ||
|
|
298e80eb9d | ||
|
|
3ecfd83f1e | ||
|
|
1dcf4e7f46 | ||
|
|
f1a856cb45 | ||
|
|
af32d7eea7 | ||
|
|
47be2754e5 | ||
|
|
39b16b6650 | ||
|
|
0a809a564c | ||
|
|
07e24013c8 | ||
|
|
8e8394985d | ||
|
|
e54b33aaaa | ||
|
|
e6a22d8ef4 | ||
|
|
ae25018574 | ||
|
|
28160608ec | ||
|
|
38651eba1d | ||
|
|
cc53fa5f1c | ||
|
|
70f8181752 | ||
|
|
9e506f6258 | ||
|
|
1304c728cf | ||
|
|
80e11c54e7 | ||
|
|
ff5d58b101 | ||
|
|
bf550d99bc | ||
|
|
7738b7bc29 | ||
|
|
01f51b1000 | ||
|
|
16dfacb285 | ||
|
|
a253ddf7e4 | ||
|
|
ffb488482c | ||
|
|
0e29754ce9 | ||
|
|
ee97720e1f | ||
|
|
29d4e6370c | ||
|
|
9040cb54d4 | ||
|
|
f082766f06 | ||
|
|
e2372891cb | ||
|
|
9dc5b5c539 | ||
|
|
886e025ac8 | ||
|
|
feb13cbd79 | ||
|
|
66baaad86f | ||
|
|
4142f28822 | ||
|
|
c578b16e27 | ||
|
|
32887acecd | ||
|
|
5cf5cc5e34 | ||
|
|
4dc0f7e02e | ||
|
|
6e9c6e6269 | ||
|
|
a22880a9b1 | ||
|
|
7357724392 | ||
|
|
fb1b08e7e8 | ||
|
|
1405b99ab8 | ||
|
|
f7aec69dae | ||
|
|
f15a0fc4bb | ||
|
|
6330fc3bdc | ||
|
|
01e3f3e9b7 | ||
|
|
e0c614cf71 | ||
|
|
b34764d4c2 | ||
|
|
f46f84e533 | ||
|
|
ca0eaf2053 | ||
|
|
25f283ee9c | ||
|
|
8ee23ab520 | ||
|
|
6964baf3f6 | ||
|
|
01aa7af175 | ||
|
|
5982bd2d4b | ||
|
|
d0fbb3539f | ||
|
|
284fb4242b | ||
|
|
beeb4bdbd4 | ||
|
|
76a52903e5 | ||
|
|
a19b8782a8 | ||
|
|
31f66fd05b | ||
|
|
bc3c4e969c | ||
|
|
4231402e54 | ||
|
|
ff4f76dfa5 | ||
|
|
2ff9df4c07 | ||
|
|
7cbc641178 | ||
|
|
08d5621b59 | ||
|
|
d39c2fdfa2 | ||
|
|
27e02ad642 | ||
|
|
11e9faef7c | ||
|
|
cbb8971dd1 | ||
|
|
1077f4d8cf | ||
|
|
594f9412e3 | ||
|
|
7674f6a0f7 | ||
|
|
aea6fbf4ab | ||
|
|
962f9bfff5 | ||
|
|
60532e2622 | ||
|
|
5c7a834342 | ||
|
|
ca940080a9 | ||
|
|
2273dd541a | ||
|
|
422062226d | ||
|
|
6a6da6f059 | ||
|
|
4cbb0eca4f | ||
|
|
ebb3eb1ab3 | ||
|
|
ebf645acaf | ||
|
|
ec787ebde2 | ||
|
|
4f190cb0ef | ||
|
|
2a36868d11 | ||
|
|
176d8be565 | ||
|
|
7b581e45e1 | ||
|
|
4888e29f79 | ||
|
|
e5f6d1b1aa | ||
|
|
d544bbfbeb | ||
|
|
ed20d6022b | ||
|
|
4191e2091c | ||
|
|
1eeb5a51f6 | ||
|
|
dd0776758f | ||
|
|
cf3ccf6155 | ||
|
|
fa6e4acad6 | ||
|
|
db33a72a8e | ||
|
|
0f8a0549b1 | ||
|
|
26ff30af6c | ||
|
|
c2fa32f430 | ||
|
|
2734de0588 | ||
|
|
aa219e4b71 | ||
|
|
645656b4cd | ||
|
|
ef16bdeb75 | ||
|
|
84299b4db7 | ||
|
|
953e216dde | ||
|
|
c57fd0a923 | ||
|
|
f9cb3d769f | ||
|
|
dd62571943 | ||
|
|
9edd33d88d | ||
|
|
b2fded76d5 | ||
|
|
1761493c8e | ||
|
|
6aa6ee2810 | ||
|
|
84728a1490 | ||
|
|
a1568467f0 | ||
|
|
32e2a03c31 | ||
|
|
d4609593e2 | ||
|
|
a2af7437c0 | ||
|
|
ab5af22fc3 | ||
|
|
39a159e5ca | ||
|
|
ec8a54906c | ||
|
|
73c0ce02ea | ||
|
|
7d2e0f7f7e | ||
|
|
7515a049c2 | ||
|
|
a317d5303e | ||
|
|
c4045ded70 | ||
|
|
3cb1b1eee4 | ||
|
|
5423af46c5 | ||
|
|
a2883840e0 | ||
|
|
14810b4c2c | ||
|
|
f5a9a41790 | ||
|
|
500d00d214 | ||
|
|
49810f8fd1 | ||
|
|
6ad3ab228f | ||
|
|
3a29ce291b | ||
|
|
1e28a4c7ab | ||
|
|
645467d20f | ||
|
|
b2a5fe9f52 | ||
|
|
2ea3c03360 | ||
|
|
0c147967d2 | ||
|
|
de1fab2595 | ||
|
|
b86eebf0a7 | ||
|
|
07345f90e6 | ||
|
|
5fe520e575 | ||
|
|
a9dd705ec5 | ||
|
|
e36520faad | ||
|
|
edaf0a4275 | ||
|
|
7735237ea7 | ||
|
|
7bbc4ea618 | ||
|
|
db99392082 | ||
|
|
7bf6a9f545 | ||
|
|
51c0efebc4 | ||
|
|
7ef70aa16e | ||
|
|
c5c0c6c4bd | ||
|
|
b26cba8ea9 | ||
|
|
90b4c16166 | ||
|
|
9b0f6fe433 | ||
|
|
e24735c341 | ||
|
|
9680ada9ee | ||
|
|
028a7c2352 | ||
|
|
80e5f36c4e | ||
|
|
e0b65184e2 | ||
|
|
395a48ddeb | ||
|
|
e22a39f549 | ||
|
|
e17d934ebf | ||
|
|
10746417b6 | ||
|
|
3ef45ba0cc | ||
|
|
a3e7b6ef43 | ||
|
|
c444a3b435 | ||
|
|
5161802465 | ||
|
|
8c27feec7a | ||
|
|
37e895cdca | ||
|
|
e1ad4e3b90 | ||
|
|
5f0be5ae43 | ||
|
|
9a7cabb221 | ||
|
|
6acc552058 | ||
|
|
b4f4d442eb | ||
|
|
21016dda53 | ||
|
|
576e5b1b58 | ||
|
|
17437a6882 | ||
|
|
04f9f087da | ||
|
|
c508947728 | ||
|
|
ce1dc55d3c | ||
|
|
e9f615819f | ||
|
|
8da3546558 | ||
|
|
d8f8dcffbd | ||
|
|
e79113b19e | ||
|
|
2a7f65a1bd | ||
|
|
e322878b95 | ||
|
|
11b09f187c | ||
|
|
926309733c | ||
|
|
c4b2bcab66 | ||
|
|
b88c96894d | ||
|
|
8502671203 | ||
|
|
4a7d6f7489 | ||
|
|
aec34c81aa | ||
|
|
1d4fe30e04 | ||
|
|
a2cfecd922 | ||
|
|
d36aef3214 | ||
|
|
c632231813 | ||
|
|
d8dd68c9b6 | ||
|
|
d6a58d5d34 | ||
|
|
41f8168c28 | ||
|
|
4513ead30a | ||
|
|
f4e23a5738 | ||
|
|
79b167002c | ||
|
|
a9a763ae5f | ||
|
|
a5fc29bc66 | ||
|
|
02c568fd7a | ||
|
|
ce55891bc2 | ||
|
|
87b1160c72 | ||
|
|
ccc6d004de | ||
|
|
20b145bee5 | ||
|
|
a37cc59a56 | ||
|
|
7cbffff12c | ||
|
|
4e5ae53775 | ||
|
|
a53de63695 | ||
|
|
8020c139ec | ||
|
|
3c5375a9a6 | ||
|
|
1d9008a1a6 | ||
|
|
fb577dc026 | ||
|
|
675eb1b7e2 | ||
|
|
6a3d522dfa | ||
|
|
47aa5c64e7 | ||
|
|
5ecf5c41ca | ||
|
|
bc6d12a873 | ||
|
|
4d983830df | ||
|
|
2f1e0d030e | ||
|
|
b7c873a172 | ||
|
|
9d5eb55290 | ||
|
|
78ffb1c4fe | ||
|
|
031d622d5d | ||
|
|
34da120a43 | ||
|
|
5285e1ccf2 | ||
|
|
e28efc1f50 | ||
|
|
1c043a81a6 | ||
|
|
9a769cc7b6 | ||
|
|
045c6abe40 | ||
|
|
4ad5cef412 | ||
|
|
c3ffab3c79 | ||
|
|
c82de0b779 | ||
|
|
4e36cbabc0 | ||
|
|
94f87b14b7 | ||
|
|
6bc80d1afa | ||
|
|
fe4d004083 | ||
|
|
28410befa3 | ||
|
|
515f7deb7e | ||
|
|
800e66261e | ||
|
|
65658c4a02 | ||
|
|
0488c7ebf4 | ||
|
|
c30c1b29ef | ||
|
|
876001ad99 | ||
|
|
60dc59adbc | ||
|
|
c994890f28 | ||
|
|
76223a2cd2 | ||
|
|
6274b39b68 | ||
|
|
cca3a3e350 | ||
|
|
86ef8f2b48 | ||
|
|
5d116d9477 | ||
|
|
6e73fcc8c9 | ||
|
|
3bd12a3047 | ||
|
|
c99d8e8275 | ||
|
|
4a12e71877 | ||
|
|
3043901b5a | ||
|
|
7cc28955d6 | ||
|
|
ef44bb4857 | ||
|
|
37660ef697 | ||
|
|
9821b3aaab | ||
|
|
1f5331af5a | ||
|
|
3ea6c82a6d | ||
|
|
ba53d01fa1 | ||
|
|
df8ae8d11a | ||
|
|
c817726ab3 | ||
|
|
9eb606810f | ||
|
|
fbb030ec8e | ||
|
|
28afebefb1 | ||
|
|
da2610657c | ||
|
|
6e3471ba1b | ||
|
|
5a9ab6c4eb | ||
|
|
1fe553d039 | ||
|
|
42e0b0aeab | ||
|
|
9e0ea32296 | ||
|
|
d3f5274b7e | ||
|
|
faff0733e9 | ||
|
|
7b32cde7dd | ||
|
|
648d1d10b3 | ||
|
|
52727100a0 | ||
|
|
8a2dbb99f0 | ||
|
|
d880b30ea9 | ||
|
|
0e2ca4e150 | ||
|
|
e0eef82ed8 | ||
|
|
09839086a2 | ||
|
|
3ca3ab7273 | ||
|
|
8386bf9c69 | ||
|
|
99b8ee1f78 | ||
|
|
0333a8c679 | ||
|
|
e0bb09e902 | ||
|
|
77677cead3 | ||
|
|
d4b2ff7de9 | ||
|
|
b81bae09d1 | ||
|
|
846e66a56c | ||
|
|
5cec635e52 | ||
|
|
8d0dc88125 | ||
|
|
51b58bb797 | ||
|
|
f79aa2350c | ||
|
|
719dddeab9 | ||
|
|
c1522568fd | ||
|
|
3ad6b9106b | ||
|
|
549387ac91 | ||
|
|
7d2f985ffa | ||
|
|
7e8b0a5800 | ||
|
|
69252ae8a4 | ||
|
|
eeed56b211 | ||
|
|
8aaad071ea | ||
|
|
7ef67e1bf9 | ||
|
|
1c15d9a956 | ||
|
|
70082090a1 | ||
|
|
dbc1a32496 | ||
|
|
e8c9ee5dd0 | ||
|
|
5c3657736b | ||
|
|
5191408d06 | ||
|
|
347ce6854d | ||
|
|
f86dec9b2e | ||
|
|
c74b128b1a | ||
|
|
8ee28a4b4f | ||
|
|
3b07b6997d | ||
|
|
f59023ed03 | ||
|
|
3fc13e61c3 | ||
|
|
ae22f448a8 | ||
|
|
450c15d51e | ||
|
|
d9e5bb4399 | ||
|
|
369904f659 | ||
|
|
96302b2344 | ||
|
|
eeffb12e3b | ||
|
|
57c12f7df0 | ||
|
|
37eb2f1aed | ||
|
|
c70ec5f354 | ||
|
|
7139af4629 | ||
|
|
a2bac029f5 | ||
|
|
6c68074e8b | ||
|
|
991caf73f4 | ||
|
|
0ba11641fa | ||
|
|
b51120e914 | ||
|
|
b02fb2d1d7 | ||
|
|
c6247230da | ||
|
|
d64be78b4e | ||
|
|
e75f633165 | ||
|
|
27c5598133 | ||
|
|
a9449bfbc7 | ||
|
|
4fecbe259b | ||
|
|
81d72edbf0 | ||
|
|
ea7e6e4624 | ||
|
|
6335d5bca4 | ||
|
|
227b430dbb | ||
|
|
0bba56a29f | ||
|
|
c07d9da7b0 | ||
|
|
900c5ed007 | ||
|
|
ba0788f6df | ||
|
|
c517856e89 | ||
|
|
791ee0ddbf | ||
|
|
0746938cb9 | ||
|
|
bbae24aba9 | ||
|
|
677edb0e02 | ||
|
|
a4328c5111 | ||
|
|
27cf341fef | ||
|
|
bc654e98b8 | ||
|
|
588f78e598 | ||
|
|
70f952285a | ||
|
|
a6f5e4f98d | ||
|
|
6fe5c1d666 | ||
|
|
1dba12ec94 | ||
|
|
aeb3f689d1 | ||
|
|
0a2268d4de | ||
|
|
9a32b020cf | ||
|
|
c5b01a7089 | ||
|
|
0634fbb724 | ||
|
|
445e9511a0 | ||
|
|
ac532acb5b | ||
|
|
7a8e21fbef | ||
|
|
58b0f3a41e | ||
|
|
ab373990cd | ||
|
|
272c157fe0 | ||
|
|
e5e047ee10 | ||
|
|
bf0b529018 | ||
|
|
acf2392406 | ||
|
|
cecd6fb359 | ||
|
|
7b67210876 | ||
|
|
e40c4f2c71 | ||
|
|
b0717b38b1 | ||
|
|
18c9b95b9b | ||
|
|
fc63c41ab0 | ||
|
|
d1c7434993 | ||
|
|
da15ba8b9a | ||
|
|
1d2a2a3608 | ||
|
|
145cd7a7c0 | ||
|
|
048415f3e0 | ||
|
|
95ccabc756 | ||
|
|
1f08c0e08c | ||
|
|
b036aeef23 | ||
|
|
d469a8404c | ||
|
|
5b16b6aed9 | ||
|
|
151949aecf | ||
|
|
de2c4ca3f5 | ||
|
|
e5bebcfe80 | ||
|
|
0080a482e4 | ||
|
|
756867416b | ||
|
|
124ff2f190 | ||
|
|
828a3d09b6 | ||
|
|
fc3b9d73ec | ||
|
|
ba943a31d4 | ||
|
|
8ee1ac7617 | ||
|
|
113f2a1794 | ||
|
|
f6028813e2 | ||
|
|
b094a32d12 | ||
|
|
fd3e4bdc60 | ||
|
|
142092d930 | ||
|
|
75e3e78ecb | ||
|
|
053832681f | ||
|
|
29e33d6e69 | ||
|
|
2d878e2f1c | ||
|
|
0fdbfbb1ae | ||
|
|
52f314f6b0 | ||
|
|
ff8d8916aa | ||
|
|
b2368b312f | ||
|
|
3cade906d6 | ||
|
|
e7cb33ef6f | ||
|
|
8c331c9577 | ||
|
|
8560a050ba | ||
|
|
583637b692 | ||
|
|
791359e949 | ||
|
|
e238b6aec0 | ||
|
|
17a143c4de | ||
|
|
dfb0aa823e | ||
|
|
b5cad36967 | ||
|
|
86378630bb | ||
|
|
730035aff7 | ||
|
|
64ac770c56 | ||
|
|
557a5599e2 | ||
|
|
ffd24015ac | ||
|
|
5eb1f12113 | ||
|
|
8425fed5a7 | ||
|
|
1e34aa4d9a | ||
|
|
f383594050 | ||
|
|
086db7572d | ||
|
|
082e05584a | ||
|
|
fb20f899e2 | ||
|
|
c0c07ff2cf | ||
|
|
2f67b741ff | ||
|
|
5a96048051 | ||
|
|
5d8b38ecf9 | ||
|
|
2941ac8285 | ||
|
|
07e316d9a9 | ||
|
|
5bd9fa3a62 | ||
|
|
0b5da2b2dd | ||
|
|
714a8ccf65 | ||
|
|
5c731cc6b2 | ||
|
|
eda8115fe5 | ||
|
|
cf0a408b90 | ||
|
|
f9786377a5 | ||
|
|
93f234b16d | ||
|
|
afb986937b | ||
|
|
61fecaffe1 | ||
|
|
6f87afc1bf | ||
|
|
2e5950a1a8 | ||
|
|
4bf0c4ecec | ||
|
|
3a8dbcdb32 | ||
|
|
1f3546f3cc | ||
|
|
f864d65d98 | ||
|
|
3479498c6c | ||
|
|
ac319330be | ||
|
|
9b3595e45f | ||
|
|
d855858543 | ||
|
|
ada52f1687 | ||
|
|
ef68374f52 | ||
|
|
0bf8049021 | ||
|
|
3cff3f837a | ||
|
|
ca94a63762 | ||
|
|
81b8bc9029 | ||
|
|
9d38520b85 | ||
|
|
800e09adad | ||
|
|
30d6c11220 | ||
|
|
e465356df8 | ||
|
|
ff8e1b7d28 | ||
|
|
175e1d7df9 | ||
|
|
271c1b1390 | ||
|
|
a128356fc4 | ||
|
|
2ed957b5f2 | ||
|
|
85bb9b9f97 | ||
|
|
050b5c0451 | ||
|
|
a14df3fc0c | ||
|
|
939136476e | ||
|
|
7e2af653ee | ||
|
|
d357a55307 | ||
|
|
23509ac2c9 | ||
|
|
118a4f75c0 | ||
|
|
2b8a7fafe6 | ||
|
|
10eda7e5f3 | ||
|
|
c23cadec8a | ||
|
|
0114081a2b | ||
|
|
2611601fb9 | ||
|
|
4194087f0b | ||
|
|
673adc41ba | ||
|
|
b195ae25a8 | ||
|
|
556c49fbfd | ||
|
|
358a5249a4 | ||
|
|
d5b837e6af | ||
|
|
61100d0c83 | ||
|
|
8473ca9fff | ||
|
|
37a2227c5c | ||
|
|
d6a4ab7471 | ||
|
|
6291288c42 | ||
|
|
6c926419e9 | ||
|
|
f292f31ac1 | ||
|
|
86a49f764e | ||
|
|
5dad21f4ea | ||
|
|
a4e9291bf9 | ||
|
|
2a667e62f4 | ||
|
|
6bd8063976 | ||
|
|
0aba5907b0 | ||
|
|
32182a7791 | ||
|
|
198960f93c | ||
|
|
618f14a732 | ||
|
|
fec2c39364 | ||
|
|
dd06d82cd9 | ||
|
|
4c98ee5350 | ||
|
|
2800a7bc15 | ||
|
|
04818e5afb | ||
|
|
122089eb1e | ||
|
|
b991e5d70a | ||
|
|
0d00ae207a | ||
|
|
9e451c18d9 | ||
|
|
71ebd99115 | ||
|
|
116dbead4b | ||
|
|
ef8adb00f4 | ||
|
|
7d74781f96 | ||
|
|
b7627dc576 | ||
|
|
cf42acd65c | ||
|
|
4163c895df | ||
|
|
63ddd5ea89 | ||
|
|
216dfded4b | ||
|
|
08b5eac6bd | ||
|
|
6e541be6ca | ||
|
|
17a9c02f96 | ||
|
|
ed640ab00e | ||
|
|
d69df5f7ab | ||
|
|
6f6461e855 | ||
|
|
682d959688 | ||
|
|
179686de16 | ||
|
|
32e35622bd | ||
|
|
c9c06202bb | ||
|
|
42d17621f9 | ||
|
|
629044c241 | ||
|
|
9be19489a4 | ||
|
|
cde4f9e405 | ||
|
|
16fddcb377 | ||
|
|
35cff4647a | ||
|
|
80ad749fc2 | ||
|
|
1a1d444b20 | ||
|
|
bb2d500067 | ||
|
|
c3c7e88c15 | ||
|
|
8dc3b1f376 | ||
|
|
b49a80bb35 | ||
|
|
21bfd4dd2d | ||
|
|
3551ed7c7a | ||
|
|
5f8ab98e9e | ||
|
|
8c6b2f5366 | ||
|
|
bac60fbec8 | ||
|
|
4eed03ebed | ||
|
|
e33295d0a6 | ||
|
|
07a495c499 | ||
|
|
e69eba6023 | ||
|
|
8be6ad84fc | ||
|
|
59e46aeeea | ||
|
|
4404531171 | ||
|
|
99db84fdd0 | ||
|
|
aa50b69754 | ||
|
|
fa2933df7c | ||
|
|
c51a26b9b7 | ||
|
|
9859a1eaac | ||
|
|
cd3858ace8 | ||
|
|
0c54e8b0b3 | ||
|
|
e32a7b61b6 | ||
|
|
ea2977268b | ||
|
|
6b529bcb18 | ||
|
|
941e7d18f3 | ||
|
|
d0fe9fac8c | ||
|
|
8f37c5ba25 | ||
|
|
b63b36e6d2 | ||
|
|
081357cbb4 | ||
|
|
0f7aa5f9ea | ||
|
|
a565b428e8 | ||
|
|
71f4df6f05 | ||
|
|
5da2f91e6a | ||
|
|
a6e9867596 | ||
|
|
9e1cff014d | ||
|
|
9c06f10d43 | ||
|
|
3f11df1dc6 | ||
|
|
6dd4505cec | ||
|
|
8faabd7759 | ||
|
|
288e56d7e0 | ||
|
|
7bcddb5f51 | ||
|
|
7b0bd309e4 | ||
|
|
237938b1f0 | ||
|
|
07009f8bd6 | ||
|
|
f7ef768c8f | ||
|
|
d1c3c9a37a | ||
|
|
78f15860e0 | ||
|
|
6b11773da9 | ||
|
|
ed26cb8f35 | ||
|
|
337f27dc38 | ||
|
|
97fab45877 | ||
|
|
8e8749ed6e | ||
|
|
a0e999576e | ||
|
|
b29802c260 | ||
|
|
665e5a3d39 | ||
|
|
c2a2dafb47 | ||
|
|
1d12a16e3d | ||
|
|
f3fe5f0a75 | ||
|
|
3bad1d06f4 | ||
|
|
70f2bfb760 | ||
|
|
9e86d42972 | ||
|
|
c8ade4d750 | ||
|
|
11b7bbd7ef | ||
|
|
513bce51ea | ||
|
|
f2c8c5d66d | ||
|
|
4e08f4f962 | ||
|
|
a9bd55dff5 | ||
|
|
5cec825dbe | ||
|
|
14c5b28cdc | ||
|
|
7693b11e0a | ||
|
|
f9cb49e4d3 | ||
|
|
892a614246 | ||
|
|
4a482d996e | ||
|
|
af9baa8ce4 | ||
|
|
39a11525a9 | ||
|
|
2337dda4eb | ||
|
|
c0a610a02b | ||
|
|
85f7b54719 | ||
|
|
e0aea2fedf | ||
|
|
ee83dbb877 | ||
|
|
1de4bc11eb | ||
|
|
04a7656afa | ||
|
|
77ec7e76e9 | ||
|
|
9ca7be081a | ||
|
|
fb36346f0b | ||
|
|
95679f3fb9 | ||
|
|
addd4124c1 | ||
|
|
74b79d9faa | ||
|
|
9daa05ef02 | ||
|
|
5ee884e0a2 | ||
|
|
dba7f4e6c0 | ||
|
|
8221514805 | ||
|
|
2ae3effc0e | ||
|
|
5b450ff18f | ||
|
|
92be6e9340 | ||
|
|
8884aeec11 | ||
|
|
8e15eda4e3 | ||
|
|
0dbf220655 | ||
|
|
fe69d59ef1 | ||
|
|
1e1f8350b5 | ||
|
|
afa2d03b56 | ||
|
|
1293d7fe28 | ||
|
|
62e879f2de | ||
|
|
a771994f6f | ||
|
|
8c2ba051a0 | ||
|
|
ae2f289122 | ||
|
|
703d63d106 | ||
|
|
2d7b024293 | ||
|
|
26906f6709 | ||
|
|
e9ec97403f | ||
|
|
34fb72707b | ||
|
|
fb7eb64c3a | ||
|
|
ef65720999 | ||
|
|
3bc71d6798 | ||
|
|
c4be269fb7 | ||
|
|
1238edfe2b | ||
|
|
747fd23b96 | ||
|
|
022f7a3ef1 | ||
|
|
e0cad8600c | ||
|
|
9510ebbc31 | ||
|
|
b63f2fd393 | ||
|
|
95a0e9f03b | ||
|
|
8265dd7f02 | ||
|
|
67feb19cab | ||
|
|
614e847de0 | ||
|
|
c2ff4b6a0b | ||
|
|
b273f1039c | ||
|
|
bea0c6a1f0 | ||
|
|
dcfaa361ec | ||
|
|
9904e166d2 | ||
|
|
5f02769012 | ||
|
|
19e5636dd2 | ||
|
|
9a220f89d9 | ||
|
|
1c76ec813c | ||
|
|
d36c189430 | ||
|
|
61e2ace5c8 | ||
|
|
d6a3f21754 | ||
|
|
4e54d7f66f | ||
|
|
f2dff038cd | ||
|
|
a7b30640e0 | ||
|
|
45048b1f46 | ||
|
|
498129314a | ||
|
|
3ee4b4a61e | ||
|
|
ee59c0f22e | ||
|
|
c56134cd92 | ||
|
|
b3d1b66a06 | ||
|
|
c9bb81bf63 | ||
|
|
d176810dae | ||
|
|
17bdca71b1 | ||
|
|
22fd3730df | ||
|
|
23d1271949 | ||
|
|
bf6aa3e8ae | ||
|
|
75fcfde892 | ||
|
|
89be27995e | ||
|
|
6738dc6cdc | ||
|
|
9ff0decb98 | ||
|
|
717d3717ba | ||
|
|
fdbca869a8 | ||
|
|
aafa750ca3 | ||
|
|
afe2bac672 | ||
|
|
b6f5d72db5 | ||
|
|
f0021a7601 | ||
|
|
ea5bfbb346 | ||
|
|
38fbcf4457 | ||
|
|
7c43e8f349 | ||
|
|
93df4e3dd9 | ||
|
|
f7d45a52a7 | ||
|
|
328f96d115 | ||
|
|
22338a72d8 | ||
|
|
256dedcb7c | ||
|
|
95d2438b84 | ||
|
|
f8d27e165e | ||
|
|
e2f024ae29 | ||
|
|
9c28fbeaf8 | ||
|
|
6458eb090a | ||
|
|
f1ca2b6958 | ||
|
|
60f031e7e3 | ||
|
|
e6a44ee0e1 | ||
|
|
7a3a641a07 | ||
|
|
1021efb239 | ||
|
|
edbe6509e1 | ||
|
|
44a6253bfc | ||
|
|
3d6e5df71e | ||
|
|
2733d2e58c | ||
|
|
0c78673870 | ||
|
|
d618948429 | ||
|
|
7584704d22 | ||
|
|
4a4658cc54 | ||
|
|
e9875f1a24 | ||
|
|
cc11257d80 | ||
|
|
4fd64601ef | ||
|
|
7eea490adf | ||
|
|
b2b12f7b93 | ||
|
|
66d026cffa | ||
|
|
5a15a20dd6 | ||
|
|
70450a9725 | ||
|
|
470a3c6a09 | ||
|
|
15f8b2f15d | ||
|
|
717cbe7630 | ||
|
|
d34075d29d | ||
|
|
80d4d66fb5 | ||
|
|
6b20b46761 | ||
|
|
cd764a4a3c | ||
|
|
5422b3d8bd | ||
|
|
0273390d79 | ||
|
|
8f7242b939 | ||
|
|
246dee63b4 | ||
|
|
697918a73a | ||
|
|
67e480220e | ||
|
|
5784eb008e | ||
|
|
82d66c4d57 | ||
|
|
ab8671cc3b | ||
|
|
2e52dda208 | ||
|
|
1ab08d0ae1 | ||
|
|
702be0ed43 | ||
|
|
4067543701 | ||
|
|
074689c60e | ||
|
|
2beb715df3 | ||
|
|
7ea2597899 | ||
|
|
34a8f92865 | ||
|
|
6e2a2de13f | ||
|
|
e073232352 | ||
|
|
6f49b4d23e | ||
|
|
97cad20ab1 | ||
|
|
9300a3d9ce | ||
|
|
28b7f80a8f | ||
|
|
a06d23753a | ||
|
|
98d66b3e6c | ||
|
|
6e59583dea | ||
|
|
2f0571e384 | ||
|
|
64a6074e02 | ||
|
|
0b7cb4cf41 | ||
|
|
e620832e33 | ||
|
|
f82e4af0e3 | ||
|
|
02baa793f8 | ||
|
|
9311a9e7f9 | ||
|
|
00a60ad6ab | ||
|
|
e740c25211 | ||
|
|
79c6b63c56 | ||
|
|
bc914566dd | ||
|
|
2c6ccfb23d | ||
|
|
646767e712 | ||
|
|
754b14d349 | ||
|
|
38c18149aa | ||
|
|
35b448a274 | ||
|
|
c1e63b5a5d | ||
|
|
37f984829a | ||
|
|
65942021d0 | ||
|
|
ecef20d52b | ||
|
|
1a092bb200 | ||
|
|
a880f801fb | ||
|
|
f00360e81c | ||
|
|
1737163364 | ||
|
|
f9a3c3d266 | ||
|
|
7db04e4359 | ||
|
|
934629d829 | ||
|
|
cd65bb057d | ||
|
|
f2d94f1ed6 | ||
|
|
e29281ce39 | ||
|
|
1e719a8738 | ||
|
|
92926487b6 | ||
|
|
69f744131a | ||
|
|
83cd9bdd00 | ||
|
|
1130f4f114 | ||
|
|
d99d9008dd | ||
|
|
78f61b2396 | ||
|
|
32829bbf15 | ||
|
|
a2d3046070 | ||
|
|
95641e398a | ||
|
|
97c39d07e8 | ||
|
|
14fa2fab71 | ||
|
|
ef8c31ae52 | ||
|
|
353080867a | ||
|
|
4d09b895d4 | ||
|
|
6c00d66334 | ||
|
|
77de187207 | ||
|
|
79eec13856 | ||
|
|
562c6fa9a4 | ||
|
|
9fa3ddd254 | ||
|
|
d11a0b9b8a | ||
|
|
41cf3fbe89 | ||
|
|
ac12ab93e5 | ||
|
|
8b4e978cf4 | ||
|
|
c15f22400e | ||
|
|
1773116313 | ||
|
|
62424fbad1 | ||
|
|
d07bd1aa29 | ||
|
|
8433df09e3 | ||
|
|
0634898cb6 | ||
|
|
2ebafc3eeb | ||
|
|
8288559e44 | ||
|
|
2caf3f0174 | ||
|
|
e88e48906d | ||
|
|
8fb5153983 | ||
|
|
03725b0ab4 | ||
|
|
b0896ea465 | ||
|
|
654e9a79c5 | ||
|
|
7f541e2623 | ||
|
|
198e905050 | ||
|
|
3744018af8 | ||
|
|
a21ba04e95 | ||
|
|
bf4d39421b | ||
|
|
2e4935715f | ||
|
|
cf8ce9642a | ||
|
|
cac65d9030 | ||
|
|
4a83829dfd | ||
|
|
c0348484fe | ||
|
|
213133ab45 | ||
|
|
8469eac18c | ||
|
|
5aaa1bb5b3 | ||
|
|
9f500b85e6 | ||
|
|
0d6fd8d7ad | ||
|
|
7e4b6dbf31 | ||
|
|
8bf3f0f094 | ||
|
|
198acef666 | ||
|
|
1d0c76b2b2 | ||
|
|
567875cf00 | ||
|
|
58069e8925 | ||
|
|
a90cf3c72b | ||
|
|
2fbf226ccd | ||
|
|
d674672ce2 | ||
|
|
dab7968ce3 | ||
|
|
510719f890 | ||
|
|
96b92239b0 | ||
|
|
363029cbdd | ||
|
|
7783a8de15 | ||
|
|
26a589fc1d | ||
|
|
4c467c304f | ||
|
|
9496b9396a | ||
|
|
669a9ee1e3 | ||
|
|
d08b7c2d94 | ||
|
|
534f6aa22c | ||
|
|
8ea731159f | ||
|
|
7704fa9318 | ||
|
|
d0e3de7121 | ||
|
|
40af1862a0 | ||
|
|
33e24aa46c | ||
|
|
5a97561389 | ||
|
|
26701d84f7 | ||
|
|
d13b09aaf3 | ||
|
|
6a65b61785 | ||
|
|
2b8bfab44b | ||
|
|
b9e1d79be6 | ||
|
|
0f9e1819c1 | ||
|
|
eda97347fc | ||
|
|
2bd24b6a8b | ||
|
|
8306342ab6 | ||
|
|
6b6ba91721 | ||
|
|
68155e6a6b | ||
|
|
bfbceefef2 | ||
|
|
21b39135fe | ||
|
|
6adc7f7221 | ||
|
|
6bfa8d1ea4 | ||
|
|
6f22269d93 | ||
|
|
b68dc98ae0 | ||
|
|
f9ad1f724b | ||
|
|
afd22635bd | ||
|
|
a9f9706be3 | ||
|
|
41f35498fe | ||
|
|
58628b6556 | ||
|
|
875858618e | ||
|
|
70b7bf390a | ||
|
|
de3d6de4cf | ||
|
|
e3b4caa37d | ||
|
|
f8e19f7772 | ||
|
|
ea5dd8b1bf | ||
|
|
bd7db1a016 | ||
|
|
02ac048d74 | ||
|
|
9a3c9b9cbd | ||
|
|
80c77c740e | ||
|
|
0f62662963 | ||
|
|
576d6515db | ||
|
|
30d29bfa80 | ||
|
|
c7c0446f23 | ||
|
|
134777584d | ||
|
|
0b64eadb0c | ||
|
|
ea93960bf4 | ||
|
|
71032e3010 | ||
|
|
d6a3a351f5 | ||
|
|
a574bca48e | ||
|
|
785faf1bf4 | ||
|
|
fbcc32b3ee | ||
|
|
46b19a9de5 | ||
|
|
d2f47d51f9 | ||
|
|
db380507f5 | ||
|
|
84cf67d21a | ||
|
|
8f53591800 | ||
|
|
65ffd78844 | ||
|
|
c00b5e6635 | ||
|
|
8160991086 | ||
|
|
f0c933d97a | ||
|
|
3fb8d3e73b | ||
|
|
d8efff9921 | ||
|
|
32170688b6 | ||
|
|
ca74bfe2e4 | ||
|
|
41767f0fd7 | ||
|
|
c62ca4dd23 | ||
|
|
9c5eddb903 | ||
|
|
4a0df07805 | ||
|
|
37a3edd44b | ||
|
|
f117abd483 | ||
|
|
4593714298 | ||
|
|
a148fd6ec3 | ||
|
|
23a44e2f3b | ||
|
|
01e03ec0f0 |
No files matched your search
@@ -0,0 +1,4 @@
|
|||||||
|
[*.{kt,kts}]
|
||||||
|
ij_kotlin_imports_layout = *
|
||||||
|
ktlint_code_style = intellij_idea
|
||||||
|
ktlint_function_naming_ignore_when_annotated_with = Composable
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
*.sh eol=lf
|
||||||
@@ -6,10 +6,10 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
感谢您愿意为 Ehviewer-Overhauled 做出贡献!
|
感谢您愿意为 EhViewer 做出贡献!
|
||||||
提交问题报告前,还请首先完成文末的自查步骤
|
提交问题报告前,还请首先完成文末的自查步骤
|
||||||
|
|
||||||
Thanks for your contribution on Ehviewer-Overhauled Project!
|
Thanks for your contribution on EhViewer Project!
|
||||||
Please finish verify steps which list in the end first before create bug report
|
Please finish verify steps which list in the end first before create bug report
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
@@ -64,7 +64,7 @@ body:
|
|||||||
description: 您可以通过设置-高级-导出日志来获得日志文件 / You can get logs file in Settings - Advanced - Dump logcat
|
description: 您可以通过设置-高级-导出日志来获得日志文件 / You can get logs file in Settings - Advanced - Dump logcat
|
||||||
placeholder: 06-15 17:44:53.704 23382 23382 E ...
|
placeholder: 06-15 17:44:53.704 23382 23382 E ...
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: more
|
id: more
|
||||||
@@ -79,6 +79,15 @@ body:
|
|||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: site
|
||||||
|
attributes:
|
||||||
|
label: 浏览站点 / Browsing site
|
||||||
|
description: E-Hentai / ExHentai
|
||||||
|
placeholder: E-Hentai
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: version
|
id: version
|
||||||
attributes:
|
attributes:
|
||||||
@@ -132,17 +141,11 @@ body:
|
|||||||
请确认您已经遵守所有必选项
|
请确认您已经遵守所有必选项
|
||||||
Please ensure you have obtained all needed options
|
Please ensure you have obtained all needed options
|
||||||
options:
|
options:
|
||||||
- label: 如果您有足够的时间和能力,并愿意为此提交 PR ,请勾上此复选框 / Pull request is welcome. Check this if you want to start a pull request
|
- label: 您已搜索过 [Issue Tracker](https://github.com/FooIbar/EhViewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/FooIbar/EhViewer/issues), No duplicate or related open issue has been found
|
||||||
required: false
|
|
||||||
|
|
||||||
- label: 您已仔细查看并知情 [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) carefully
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- label: 您已搜索过 [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues), No duplicate or related open issue has been found
|
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- label: 您确保这个 Issue 只提及一个问题。如果您有多个问题报告,烦请发起多个 Issue / Ensure there is only one bug report in this issue. Please make mutiply issue for mutiply bugs
|
- label: 您确保这个 Issue 只提及一个问题。如果您有多个问题报告,烦请发起多个 Issue / Ensure there is only one bug report in this issue. Please make mutiply issue for mutiply bugs
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- label: 您确保已使用最新 CI 版本测试,并且该问题在最新 CI 版本中并未解决 / This bug have not solved in latest CI version
|
- label: 您确保已使用最新 [CI 版本](https://github.com/FooIbar/EhViewer/actions)测试,并且该问题在最新 CI 版本中并未解决 / This bug have not solved in latest [CI version](https://github.com/FooIbar/EhViewer/actions)
|
||||||
required: true
|
required: true
|
||||||
@@ -1,5 +1 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
|
||||||
- name: 所有其他问题 / All other questions
|
|
||||||
url: https://github.com/Ehviewer-Overhauled/Ehviewer/discussions
|
|
||||||
about: 转到 Discussions / Turn to discussions
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
name: 功能请求 / Feature request
|
|
||||||
description: 提出一个功能建议 / Suggest an idea
|
|
||||||
labels:
|
|
||||||
- "enhancement"
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
感谢您愿意为 Ehviewer-Overhauled 做出贡献!
|
|
||||||
提交功能建议前,还请首先完成文末的自查步骤
|
|
||||||
|
|
||||||
Thanks for your contribution on Ehviewer-Overhauled Project!
|
|
||||||
Please finish verify steps which list in the end first before suggest an idea
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: request
|
|
||||||
attributes:
|
|
||||||
label: 需求 / Requirement
|
|
||||||
description: |
|
|
||||||
在此处描述您的需求。这通常会是一个您想要的功能
|
|
||||||
Ddescribe what you need here.
|
|
||||||
placeholder: |
|
|
||||||
我需要 XXX 功能 ...
|
|
||||||
I want ABC feature ...
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: impl
|
|
||||||
attributes:
|
|
||||||
label: 建议实现 / Suggested implements
|
|
||||||
description: |
|
|
||||||
在此处表述您建议的实现方式。如有可能,UI 类功能请求还请尽量附上图示
|
|
||||||
Describe your suggested implements here, It's recommend to add a photo if you are making a UI feature request.
|
|
||||||
placeholder: |
|
|
||||||
建议在 XX 处添加 XX ...
|
|
||||||
I recommend add ABC feature to DEF...
|
|
||||||
图片(如果有)/ Photos (if exists)
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: more
|
|
||||||
attributes:
|
|
||||||
label: 备注 / Addition details
|
|
||||||
description: |
|
|
||||||
在此处写下其他您想说的内容
|
|
||||||
Describe addition details here
|
|
||||||
placeholder: |
|
|
||||||
其他有用的信息与附件
|
|
||||||
Additional details and attachments
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: version
|
|
||||||
attributes:
|
|
||||||
label: EhViewer 版本号 / EhViewer version code
|
|
||||||
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
|
|
||||||
placeholder: 1.6.9.13
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: ci
|
|
||||||
attributes:
|
|
||||||
label: EhViewer CI 版本 / EhViewer CI Version
|
|
||||||
description: 位于设置 - 关于中的版本号后 / Next to the version code in Settings - About
|
|
||||||
placeholder: 26f05a0
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: check
|
|
||||||
attributes:
|
|
||||||
label: 自查步骤 / Verify steps
|
|
||||||
description: |
|
|
||||||
请确认您已经遵守所有必选项
|
|
||||||
Please ensure you have obtained all needed options
|
|
||||||
options:
|
|
||||||
- label: 如果您有足够的时间和能力,并愿意为此提交 PR ,请勾上此复选框 / Pull request is welcome. Check this if you want to start a pull request
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- label: 您已仔细查看并知情 [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) carefully
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- label: 您已搜索过 [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues), No duplicate or related open issue has been found
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- label: 您确保这个 Issue 只提及一个功能。如果您有多个功能请求,烦请发起多个 Issue / Ensure there is only one feature request in this issue. Please make mutiply issue for mutiply feature request
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- label: 您确保已使用最新 CI 版本测试,并且该功能在最新 CI 版本中并未按照预期实现 / This feature don't implement correctly in latest CI version
|
|
||||||
required: true
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
name: Baseline profile generation
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 0 * * 0'
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'benchmark/**'
|
||||||
|
- '.github/workflows/baseline-profile.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
baseline-profile:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: 21
|
||||||
|
|
||||||
|
- name: Free disk space
|
||||||
|
run: rm -rf ${ANDROID_SDK_ROOT}/ndk
|
||||||
|
|
||||||
|
- name: Install NDK
|
||||||
|
run: echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;27.0.11902837"
|
||||||
|
|
||||||
|
- name: Install Rust Toolchain
|
||||||
|
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android x86_64-linux-android && rustup component add rustfmt clippy
|
||||||
|
|
||||||
|
- name: Rust Cache
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: "./app/src/main/rust"
|
||||||
|
shared-key: ${{ runner.os }}
|
||||||
|
save-if: "false"
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
with:
|
||||||
|
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
|
||||||
|
cache-read-only: true
|
||||||
|
|
||||||
|
- name: CMake Cache
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: app/.cxx
|
||||||
|
key: cmake-
|
||||||
|
|
||||||
|
- 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: Build
|
||||||
|
run: ./gradlew generateBaselineProfile
|
||||||
|
-Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
|
||||||
|
|
||||||
|
- name: Update baseline profile
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config push.autoSetupRemote true
|
||||||
|
git switch -c chore/baseline-profile
|
||||||
|
if git commit -am "Update baseline profile"; then
|
||||||
|
git push
|
||||||
|
gh pr create -f
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload reports
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: reports
|
||||||
|
path: benchmark/build/reports
|
||||||
+162
-43
@@ -4,31 +4,30 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- '*'
|
- '*'
|
||||||
tags-ignore:
|
pull_request:
|
||||||
- "*"
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linux:
|
check:
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: 'recursive'
|
|
||||||
|
|
||||||
- name: Setup Java 17
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 17
|
java-version: 21
|
||||||
|
|
||||||
- name: Install NDK
|
|
||||||
run: |
|
|
||||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;26.0.10404224" --channel=1 --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
|
||||||
|
|
||||||
- name: Install Rust Toolchain
|
- name: Install Rust Toolchain
|
||||||
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android && rustup component add rustfmt clippy
|
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android x86_64-linux-android && rustup component add rustfmt clippy
|
||||||
|
|
||||||
|
- name: Rust Cache
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: "./app/src/main/rust"
|
||||||
|
shared-key: ${{ runner.os }}
|
||||||
|
|
||||||
- name: Rustfmt Check
|
- name: Rustfmt Check
|
||||||
working-directory: ./app/src/main/rust
|
working-directory: ./app/src/main/rust
|
||||||
@@ -38,42 +37,162 @@ jobs:
|
|||||||
working-directory: ./app/src/main/rust
|
working-directory: ./app/src/main/rust
|
||||||
run: cargo clippy --all-targets --all-features -- -D warnings
|
run: cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
- name: Ktlint Check
|
- name: Setup Gradle
|
||||||
run: ./gradlew ktlintCheck
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
|
||||||
- name: Gradle Build
|
|
||||||
uses: gradle/gradle-build-action@v2
|
|
||||||
with:
|
with:
|
||||||
|
cache-read-only: true
|
||||||
|
|
||||||
|
- name: Install NDK
|
||||||
|
run: echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;27.0.11902837"
|
||||||
|
|
||||||
|
- name: Spotless Check
|
||||||
|
run: ./gradlew spotlessCheck
|
||||||
|
|
||||||
|
- name: Lint Check
|
||||||
|
run: ./gradlew lintMarshmallowRelease
|
||||||
|
|
||||||
|
default:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: 21
|
||||||
|
|
||||||
|
- name: Install NDK
|
||||||
|
run: echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;27.0.11902837"
|
||||||
|
|
||||||
|
- name: Install Rust Toolchain
|
||||||
|
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android x86_64-linux-android && rustup component add rustfmt clippy
|
||||||
|
|
||||||
|
- name: Rust Cache
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: "./app/src/main/rust"
|
||||||
|
shared-key: ${{ runner.os }}
|
||||||
|
save-if: "false"
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
with:
|
||||||
|
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
|
||||||
gradle-home-cache-cleanup: true
|
gradle-home-cache-cleanup: true
|
||||||
arguments: assembleRelease
|
|
||||||
|
|
||||||
- name: Update ARM64
|
- name: CMake Cache
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
name: arm64-v8a-${{ github.sha }}
|
path: app/.cxx
|
||||||
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
|
key: cmake-default-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
|
||||||
|
restore-keys: cmake-default-
|
||||||
|
|
||||||
- name: Update x86_64
|
- name: Build
|
||||||
uses: actions/upload-artifact@v3
|
env:
|
||||||
with:
|
GITHUB_TOKEN: ${{ secrets.GithubToken }}
|
||||||
name: x86_64-${{ github.sha }}
|
run: ./gradlew assembleDefaultRelease
|
||||||
path: app/build/outputs/apk/release/app-x86_64-release.apk
|
|
||||||
|
|
||||||
- name: Update ARM32
|
- name: Upload ARM64
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: armeabi-v7a-${{ github.sha }}
|
name: default-arm64-v8a-${{ github.sha }}
|
||||||
path: app/build/outputs/apk/release/app-armeabi-v7a-release.apk
|
path: app/build/outputs/apk/default/release/app-default-arm64-v8a-release.apk
|
||||||
|
|
||||||
- name: Update x86
|
- name: Upload x86_64
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: x86-${{ github.sha }}
|
name: default-x86_64-${{ github.sha }}
|
||||||
path: app/build/outputs/apk/release/app-x86-release.apk
|
path: app/build/outputs/apk/default/release/app-default-x86_64-release.apk
|
||||||
|
|
||||||
- name: Update mapping
|
- name: Upload ARM32
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: mapping-${{ github.sha }}
|
name: default-armeabi-v7a-${{ github.sha }}
|
||||||
path: app/build/outputs/mapping/release/mapping.txt
|
path: app/build/outputs/apk/default/release/app-default-armeabi-v7a-release.apk
|
||||||
retention-days: 5
|
|
||||||
|
- name: Upload mapping
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: default-mapping-${{ github.sha }}
|
||||||
|
path: app/build/outputs/mapping/defaultRelease
|
||||||
|
|
||||||
|
- name: Upload native debug symbols
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: default-native-debug-symbols-${{ github.sha }}
|
||||||
|
path: app/build/outputs/native-debug-symbols/defaultRelease/native-debug-symbols.zip
|
||||||
|
|
||||||
|
marshmallow:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: 21
|
||||||
|
|
||||||
|
- name: Install NDK
|
||||||
|
run: echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;27.0.11902837"
|
||||||
|
|
||||||
|
- name: Install Rust Toolchain
|
||||||
|
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android x86_64-linux-android && rustup component add rustfmt clippy
|
||||||
|
|
||||||
|
- name: Rust Cache
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: "./app/src/main/rust"
|
||||||
|
shared-key: ${{ runner.os }}
|
||||||
|
save-if: "false"
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
with:
|
||||||
|
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
|
||||||
|
gradle-home-cache-cleanup: true
|
||||||
|
|
||||||
|
- name: CMake Cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: app/.cxx
|
||||||
|
key: cmake-marshmallow-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
|
||||||
|
restore-keys: cmake-marshmallow-
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GithubToken }}
|
||||||
|
run: ./gradlew assembleMarshmallowRelease
|
||||||
|
|
||||||
|
- name: Upload ARM64
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: marshmallow-arm64-v8a-${{ github.sha }}
|
||||||
|
path: app/build/outputs/apk/marshmallow/release/app-marshmallow-arm64-v8a-release.apk
|
||||||
|
|
||||||
|
- name: Upload x86_64
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: marshmallow-x86_64-${{ github.sha }}
|
||||||
|
path: app/build/outputs/apk/marshmallow/release/app-marshmallow-x86_64-release.apk
|
||||||
|
|
||||||
|
- name: Upload ARM32
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: marshmallow-armeabi-v7a-${{ github.sha }}
|
||||||
|
path: app/build/outputs/apk/marshmallow/release/app-marshmallow-armeabi-v7a-release.apk
|
||||||
|
|
||||||
|
- name: Upload mapping
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: marshmallow-mapping-${{ github.sha }}
|
||||||
|
path: app/build/outputs/mapping/marshmallowRelease
|
||||||
|
|
||||||
|
- name: Upload native debug symbols
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: marshmallow-native-debug-symbols-${{ github.sha }}
|
||||||
|
path: app/build/outputs/native-debug-symbols/marshmallowRelease/native-debug-symbols.zip
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
name: 'Lock Threads'
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 8 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: lock-threads
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
action:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dessant/lock-threads@v5
|
||||||
|
with:
|
||||||
|
issue-inactive-days: '1'
|
||||||
|
process-only: 'issues'
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
name: Pull Request Check
|
|
||||||
|
|
||||||
on: [ pull_request ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
linux:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: 'recursive'
|
|
||||||
|
|
||||||
- name: Setup Java 17
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
distribution: temurin
|
|
||||||
java-version: 17
|
|
||||||
|
|
||||||
- name: Install NDK
|
|
||||||
run: |
|
|
||||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;26.0.10404224" --channel=1 --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
|
||||||
|
|
||||||
- name: Install Rust Toolchain
|
|
||||||
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android && rustup component add rustfmt clippy
|
|
||||||
|
|
||||||
- name: Rustfmt Check
|
|
||||||
working-directory: ./app/src/main/rust
|
|
||||||
run: cargo fmt --all -- --check
|
|
||||||
|
|
||||||
- name: Clippy Check
|
|
||||||
working-directory: ./app/src/main/rust
|
|
||||||
run: cargo clippy --all-targets --all-features -- -D warnings
|
|
||||||
|
|
||||||
- name: Ktlint Check
|
|
||||||
run: ./gradlew ktlintCheck
|
|
||||||
|
|
||||||
- name: Gradle Build
|
|
||||||
uses: gradle/gradle-build-action@v2
|
|
||||||
with:
|
|
||||||
arguments: assembleRelease
|
|
||||||
|
|
||||||
- name: Update ARM64
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: arm64-v8a-${{ github.sha }}
|
|
||||||
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
|
|
||||||
|
|
||||||
- name: Update x86_64
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: x86_64-${{ github.sha }}
|
|
||||||
path: app/build/outputs/apk/release/app-x86_64-release.apk
|
|
||||||
|
|
||||||
- name: Update ARM32
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: armeabi-v7a-${{ github.sha }}
|
|
||||||
path: app/build/outputs/apk/release/app-armeabi-v7a-release.apk
|
|
||||||
|
|
||||||
- name: Update x86
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: x86-${{ github.sha }}
|
|
||||||
path: app/build/outputs/apk/release/app-x86-release.apk
|
|
||||||
@@ -6,71 +6,74 @@ on:
|
|||||||
- "*"
|
- "*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linux:
|
build:
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: 'recursive'
|
|
||||||
|
|
||||||
- name: Setup Java 17
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 17
|
java-version: 21
|
||||||
|
|
||||||
- name: Install NDK
|
- name: Install NDK
|
||||||
run: |
|
run: echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;27.0.11902837"
|
||||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;26.0.10404224" --channel=1 --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
|
||||||
|
|
||||||
- name: Install Rust Toolchain
|
- name: Install Rust Toolchain
|
||||||
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
|
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android x86_64-linux-android
|
||||||
|
|
||||||
|
- name: Rust Cache
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: "./app/src/main/rust"
|
||||||
|
|
||||||
|
- name: CMake Cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: app/.cxx
|
||||||
|
key: cmake-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
|
||||||
|
restore-keys: cmake-
|
||||||
|
|
||||||
- name: Gradle Build
|
- name: Gradle Build
|
||||||
uses: gradle/gradle-build-action@v2
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GithubToken }}
|
||||||
with:
|
with:
|
||||||
arguments: assembleRelease
|
arguments: assembleRelease
|
||||||
|
|
||||||
- name: Rename Apks
|
- name: Rename Apks
|
||||||
run: |
|
run: |
|
||||||
cp app/build/outputs/apk/release/app-universal-release.apk app/build/outputs/apk/release/EhViewer-universal.apk
|
mv app/build/outputs/apk/default/release/app-default-universal-release.apk EhViewer-${{ github.ref_name }}-default-universal.apk
|
||||||
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a.apk
|
mv app/build/outputs/apk/default/release/app-default-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-default-arm64-v8a.apk
|
||||||
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64.apk
|
mv app/build/outputs/apk/default/release/app-default-x86_64-release.apk EhViewer-${{ github.ref_name }}-default-x86_64.apk
|
||||||
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86.apk
|
mv app/build/outputs/apk/default/release/app-default-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-default-armeabi-v7a.apk
|
||||||
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
|
mv app/build/outputs/mapping/defaultRelease/mapping.txt EhViewer-${{ github.ref_name }}-default-mapping.txt
|
||||||
|
mv app/build/outputs/native-debug-symbols/defaultRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-default-native-debug-symbols.zip
|
||||||
|
|
||||||
|
mv app/build/outputs/apk/marshmallow/release/app-marshmallow-universal-release.apk EhViewer-${{ github.ref_name }}-marshmallow-universal.apk
|
||||||
|
mv app/build/outputs/apk/marshmallow/release/app-marshmallow-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-arm64-v8a.apk
|
||||||
|
mv app/build/outputs/apk/marshmallow/release/app-marshmallow-x86_64-release.apk EhViewer-${{ github.ref_name }}-marshmallow-x86_64.apk
|
||||||
|
mv app/build/outputs/apk/marshmallow/release/app-marshmallow-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-armeabi-v7a.apk
|
||||||
|
mv app/build/outputs/mapping/marshmallowRelease/mapping.txt EhViewer-${{ github.ref_name }}-marshmallow-mapping.txt
|
||||||
|
mv app/build/outputs/native-debug-symbols/marshmallowRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-marshmallow-native-debug-symbols.zip
|
||||||
|
|
||||||
- name: Releases
|
- name: Releases
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
body: Bump Version
|
body: Bump Version
|
||||||
discussion_category_name: Announcements
|
|
||||||
files: |
|
files: |
|
||||||
app/build/outputs/apk/release/EhViewer-universal.apk
|
EhViewer-${{ github.ref_name }}-default-universal.apk
|
||||||
app/build/outputs/apk/release/EhViewer-arm64-v8a.apk
|
EhViewer-${{ github.ref_name }}-default-arm64-v8a.apk
|
||||||
app/build/outputs/apk/release/EhViewer-x86_64.apk
|
EhViewer-${{ github.ref_name }}-default-x86_64.apk
|
||||||
app/build/outputs/apk/release/EhViewer-x86.apk
|
EhViewer-${{ github.ref_name }}-default-armeabi-v7a.apk
|
||||||
app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
|
EhViewer-${{ github.ref_name }}-default-mapping.txt
|
||||||
app/build/outputs/mapping/release/mapping.txt
|
EhViewer-${{ github.ref_name }}-default-native-debug-symbols.zip
|
||||||
|
|
||||||
- name: Collect Apks
|
EhViewer-${{ github.ref_name }}-marshmallow-universal.apk
|
||||||
run: |
|
EhViewer-${{ github.ref_name }}-marshmallow-arm64-v8a.apk
|
||||||
mkdir app/build/outputs/apk/release/fdroid
|
EhViewer-${{ github.ref_name }}-marshmallow-x86_64.apk
|
||||||
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/fdroid/EhViewer-v$GITHUB_REF_NAME.apk
|
EhViewer-${{ github.ref_name }}-marshmallow-armeabi-v7a.apk
|
||||||
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/fdroid/EhViewer-v$GITHUB_REF_NAME-armv7.apk
|
EhViewer-${{ github.ref_name }}-marshmallow-mapping.txt
|
||||||
|
EhViewer-${{ github.ref_name }}-marshmallow-native-debug-symbols.zip
|
||||||
- name: Push Apks
|
|
||||||
uses: Ehviewer-Overhauled/github-action-push-to-another-repository@main
|
|
||||||
env:
|
|
||||||
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
|
|
||||||
with:
|
|
||||||
source-directory: app/build/outputs/apk/release/fdroid
|
|
||||||
destination-github-username: Ehviewer-Overhauled
|
|
||||||
destination-repository-name: fdroid-repo
|
|
||||||
user-name: 'github-actions[bot]'
|
|
||||||
user-email: 'github-actions[bot]@users.noreply.github.com'
|
|
||||||
target-branch: deploy
|
|
||||||
create-target-branch: true
|
|
||||||
commit-message: Add apks from $GITHUB_REF
|
|
||||||
target-directory: fdroid/tmp-apks
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
name: 'Update Chrome Version'
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 1 * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
action:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Update Chrome Version
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
cd chrome-for-testing
|
||||||
|
curl -OLf https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_STABLE
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config push.autoSetupRemote true
|
||||||
|
git switch -c chore/chrome
|
||||||
|
if git commit -am "Update Chrome version for default User-Agent"; then
|
||||||
|
git push
|
||||||
|
gh pr create -f
|
||||||
|
gh pr merge chore/chrome --auto -dr
|
||||||
|
fi
|
||||||
+1
-3
@@ -2,9 +2,7 @@
|
|||||||
/local.properties
|
/local.properties
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/src
|
/.kotlin
|
||||||
*.iml
|
*.iml
|
||||||
.idea
|
.idea
|
||||||
/captures
|
/captures
|
||||||
release
|
|
||||||
google-services.json
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
[submodule "app/src/main/cpp/nettle/nettle"]
|
|
||||||
path = app/src/main/cpp/nettle/nettle
|
|
||||||
url = https://github.com/Ehviewer-Overhauled/nettle
|
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
# 变更日志
|
||||||
|
|
||||||
|
## [1.11.6] - 2024-06-24
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
* 某些设备上无法加载长图时可尝试减小 设置-高级 中的硬件位图阈值 [#1321](https://github.com/FooIbar/EhViewer/issues/1321)
|
||||||
|
* 清理下载冗余时显示确认对话框 [#1369](https://github.com/FooIbar/EhViewer/issues/1369)
|
||||||
|
* 修改收藏时显示收藏备注 [#1392](https://github.com/FooIbar/EhViewer/issues/1392)
|
||||||
|
|
||||||
|
### Bug 修复
|
||||||
|
|
||||||
|
* 图片完整性校验误报 [#1286](https://github.com/FooIbar/EhViewer/issues/1286)
|
||||||
|
* 某些情况下应用崩溃 [#1290](https://github.com/FooIbar/EhViewer/issues/1290) [#1302](https://github.com/FooIbar/EhViewer/issues/1302)
|
||||||
|
* 某些情况下渲染问题 [#1284](https://github.com/FooIbar/EhViewer/issues/1284)
|
||||||
|
* 无法下载标题过长的画廊 [#1340](https://github.com/FooIbar/EhViewer/issues/1340)
|
||||||
|
* 某些界面拖动滚动条时黑屏 [#1329](https://github.com/FooIbar/EhViewer/issues/1329)
|
||||||
|
* 搜索栏多行文本遮挡其他内容 [#1365](https://github.com/FooIbar/EhViewer/issues/1365)
|
||||||
|
* CIFS Documents Provider 兼容问题 [#1356](https://github.com/FooIbar/EhViewer/issues/1356)
|
||||||
|
* 清理下载冗余未按预期运行 [#1369](https://github.com/FooIbar/EhViewer/issues/1369)
|
||||||
|
* 读取压缩包失败时崩溃
|
||||||
|
* 某些设备上崩溃 [#1276](https://github.com/FooIbar/EhViewer/issues/1276)
|
||||||
|
* 检查更新失效
|
||||||
|
|
||||||
|
|
||||||
|
## [1.11.6-RC2] - 2024-06-06
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
* 下载图片时执行完整性校验以防止文件损坏/被篡改
|
||||||
|
|
||||||
|
|
||||||
|
## [1.11.6-RC1] - 2024-06-02
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
* 下载列表默认显示全部下载项目
|
||||||
|
* 替换搜索文本中的换行符为空格 [#1258](https://github.com/FooIbar/EhViewer/issues/1258)
|
||||||
|
|
||||||
|
### Bug 修复
|
||||||
|
|
||||||
|
* 评价画廊时未使用输入的评分
|
||||||
|
* 搜索时意外选择 Non-H 类别 [#1246](https://github.com/FooIbar/EhViewer/issues/1246)
|
||||||
|
* 侧边栏与导航抽屉重叠 [#1262](https://github.com/FooIbar/EhViewer/issues/1262)
|
||||||
|
* 快速搜索/下载标签排序失效 [#1267](https://github.com/FooIbar/EhViewer/issues/1267)
|
||||||
|
* 加载压缩包/种子列表出错时 UI 冻结
|
||||||
|
* 低版本 Android 上某些 UI 被遮挡
|
||||||
|
|
||||||
|
|
||||||
|
## [1.11.5] - 2024-05-19
|
||||||
|
|
||||||
|
### 重大变更
|
||||||
|
|
||||||
|
* 不再支持 32 位 x86
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
|
||||||
|
* 支持按作者对下载分组
|
||||||
|
* 由于 E 站不再强制要求人机验证,重新支持账号密码登录
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
* 在登录时加载收藏名称 [#1077](https://github.com/FooIbar/EhViewer/issues/1077)
|
||||||
|
* 优化归档和种子界面 UI
|
||||||
|
* 禁止在 cookie 界面截图以防止泄露
|
||||||
|
* 在缩略图模式下显示画廊页数
|
||||||
|
* 记住最低评分和页数搜索参数
|
||||||
|
* 更新 User-Agent 以缓解 IP 封禁 [#1182](https://github.com/FooIbar/EhViewer/issues/1182)
|
||||||
|
* 账号密码登录支持自动填充
|
||||||
|
* 更新中文(台灣)翻译
|
||||||
|
|
||||||
|
### Bug 修复
|
||||||
|
|
||||||
|
* 非触摸模式下无法退出搜索界面 [#1060](https://github.com/FooIbar/EhViewer/issues/1060)
|
||||||
|
* 某些设备上无法安装更新 [#1067](https://github.com/FooIbar/EhViewer/issues/1067)
|
||||||
|
* 某些设备上崩溃/卡顿 [#996](https://github.com/FooIbar/EhViewer/issues/996) [#1023](https://github.com/FooIbar/EhViewer/issues/1023)
|
||||||
|
* 搜索记录中存在重复条目时崩溃 [#1130](https://github.com/FooIbar/EhViewer/issues/1130)
|
||||||
|
* 条漫模式下缩小后点按区域偏移 [#127](https://github.com/FooIbar/EhViewer/issues/127)
|
||||||
|
* 多窗口模式下点按区域偏移
|
||||||
|
* 点击下载通知时未清除状态
|
||||||
|
* 保存未完成加载的图片时崩溃 [#1154](https://github.com/FooIbar/EhViewer/issues/1154)
|
||||||
|
* 历史记录界面无法在项目上滑动打开抽屉 [#464](https://github.com/FooIbar/EhViewer/issues/464)
|
||||||
|
* 导入数据失败时显示导入成功 [#1174](https://github.com/FooIbar/EhViewer/issues/1174)
|
||||||
|
* 某些条件下收藏界面崩溃 [#1190](https://github.com/FooIbar/EhViewer/issues/1190)
|
||||||
|
* 反转点按区域功能失效 [#1217](https://github.com/FooIbar/EhViewer/issues/1217)
|
||||||
|
|
||||||
|
### 已知问题
|
||||||
|
|
||||||
|
* 某些设备上从后台返回时某些 UI 消失,可通过关闭列表项目动画部分缓解 [#1184](https://github.com/FooIbar/EhViewer/issues/1184)
|
||||||
|
|
||||||
|
|
||||||
|
## [1.11.4] - 2024-04-21
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
|
||||||
|
* 更新前自动备份数据
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
* 在 设置-高级 中加入了禁用列表项目动画的选项,该功能在部分设备上导致崩溃/卡顿 [#996](https://github.com/FooIbar/EhViewer/issues/996) [#1023](https://github.com/FooIbar/EhViewer/issues/1023)
|
||||||
|
* 下载列表多选模式下避免误触 [#1016](https://github.com/FooIbar/EhViewer/issues/1016)
|
||||||
|
* 杂项 UI 优化
|
||||||
|
|
||||||
|
### Bug 修复
|
||||||
|
|
||||||
|
* 重命名下载标签后删除该标签时崩溃 [#1008](https://github.com/FooIbar/EhViewer/issues/1008)
|
||||||
|
* 搜索栏中某些输入法长按退格无法连续删除 [#606](https://github.com/FooIbar/EhViewer/issues/606)
|
||||||
|
* 评论/屏蔽/搜索建议/下载标签/快速搜索列表卡顿 [#1041](https://github.com/FooIbar/EhViewer/issues/1041)
|
||||||
|
|
||||||
|
|
||||||
|
## [1.11.3] - 2024-04-06
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
* 优化预加载逻辑和图片加载顺序
|
||||||
|
* 优化画廊详情和评论页面 UI
|
||||||
|
|
||||||
|
### Bug 修复
|
||||||
|
|
||||||
|
* 某些模式下搜索结果不显示画廊语言 [#923](https://github.com/FooIbar/EhViewer/issues/923)
|
||||||
|
* 某些情况下在搜索结果中直接阅读画廊后崩溃 [#927](https://github.com/FooIbar/EhViewer/issues/927)
|
||||||
|
* 更新至最新版本后仍提示有新版本 [#927](https://github.com/FooIbar/EhViewer/issues/927)
|
||||||
|
* 从旧版本升级时包含不兼容数据导致崩溃/无法导出数据 [#940](https://github.com/FooIbar/EhViewer/issues/940) [#974](https://github.com/FooIbar/EhViewer/issues/974)
|
||||||
|
* Android 10 以下的设备打开 设置-隐私 时崩溃 [#953](https://github.com/FooIbar/EhViewer/issues/953)
|
||||||
|
* 下载标签无法拖动
|
||||||
|
* 刷新 和 刷新(原图) 无法在已下载的图片上使用
|
||||||
|
* 临时 IP 封禁错误显示为解析失败
|
||||||
|
* 某些情况下下载时闪退 [#980](https://github.com/FooIbar/EhViewer/issues/980)
|
||||||
|
* 某些设备上下载列表中的缩略图无法显示 [#977](https://github.com/FooIbar/EhViewer/issues/977)
|
||||||
|
|
||||||
|
|
||||||
|
## [1.11.2] - 2024-03-17
|
||||||
|
|
||||||
|
### 重大变更
|
||||||
|
|
||||||
|
* Default 变种最低支持版本更改为 Android 8.0,重新添加了最低支持 Android 6.0 的 Marshmallow 变种
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
* 重新添加了删除快速搜索/下载标签时的确认对话框
|
||||||
|
|
||||||
|
### Bug 修复
|
||||||
|
|
||||||
|
* 收藏页面 搜索栏中文标签转英文异常 [#902](https://github.com/FooIbar/EhViewer/issues/902)
|
||||||
|
* 在收藏或者取消收藏之后,收藏按钮会无反应 [#908](https://github.com/FooIbar/EhViewer/issues/908)
|
||||||
|
* 横屏页面下阅读设置UI重合 [#909](https://github.com/FooIbar/EhViewer/issues/909)
|
||||||
|
* Search bar does not work properly in Japanese [#906](https://github.com/FooIbar/EhViewer/issues/906)
|
||||||
|
* 搜索结果中的 Disowned 画廊不显示上传者
|
||||||
|
* 使用 CI 频道更新时,有时会获取到错误的更新链接
|
||||||
|
|
||||||
|
|
||||||
|
## [1.11.1] - 2024-03-12
|
||||||
|
|
||||||
|
### 重大变更
|
||||||
|
|
||||||
|
* 由于 E 站强制要求人机验证,仅支持通过网页登录
|
||||||
|
* 默认支持 Android 6,不再提供 Marshmallow 变种
|
||||||
|
* 集成 Cronet 库,不再提供 GMS 变种
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
* 支持在排序下载列表前按下载标签分组
|
||||||
|
|
||||||
|
### Bug 修复
|
||||||
|
|
||||||
|
* 下载画廊时崩溃 [#893](https://github.com/FooIbar/EhViewer/issues/893)
|
||||||
|
|
||||||
|
|
||||||
|
## [1.11.0] - 2024-03-09
|
||||||
|
|
||||||
|
### 重大变更
|
||||||
|
|
||||||
|
* 由于 E 站 Cookie 策略变更,不再支持 Cookie 登录
|
||||||
|
* 登录时自动选择 exhentai (如果可用)
|
||||||
|
* 不再提供默认下载路径
|
||||||
|
* 手动排序更改为以添加时间/上传时间/标题/下载标签/页数对下载列表排序
|
||||||
|
* 由于数据库结构变更,此版本导出的数据将无法被旧版本导入
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
|
||||||
|
* 下载画廊时生成 ComicInfo.xml 格式元数据
|
||||||
|
* 归档下载时生成 ComicInfo.xml 格式元数据 [#711](https://github.com/FooIbar/EhViewer/issues/711)
|
||||||
|
* 归档下载支持第三方下载管理器 [#79](https://github.com/FooIbar/EhViewer/issues/79)
|
||||||
|
* 保存下载结果为 CBZ 压缩包 [#660](https://github.com/FooIbar/EhViewer/issues/660)
|
||||||
|
* 清除 `igneous` Cookie (将于下次搜索时重新获取)
|
||||||
|
* 随机打开本地收藏/下载中的画廊 [#695](https://github.com/FooIbar/EhViewer/issues/695)
|
||||||
|
* 随机在搜索结果中跳转
|
||||||
|
* 以网格模式查看下载列表
|
||||||
|
* 本地收藏/历史/下载中的标签搜索 [#860](https://github.com/FooIbar/EhViewer/issues/860)
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
* 搜索时优先显示已选择的类别卡片
|
||||||
|
* 多处性能优化
|
||||||
|
|
||||||
|
### Bug 修复
|
||||||
|
|
||||||
|
* 显示模式为 Thumbnail 时无法点击上传者卡片 [#653](https://github.com/FooIbar/EhViewer/issues/653)
|
||||||
|
* [Marshmallow] GIF 只显示第一帧
|
||||||
|
* 无法在 `定时请求新闻页面` 关闭时开启 `隐藏 HV 事件通知`
|
||||||
|
* 响应正文含有无效 UTF-8 字符导致解码失败 [#643](https://github.com/FooIbar/EhViewer/issues/643)
|
||||||
|
* 某些情况下应用崩溃 [#710](https://github.com/FooIbar/EhViewer/issues/710)
|
||||||
|
* 画廊名称包含非法路径字符时无法保存图片 [#815](https://github.com/FooIbar/EhViewer/issues/815)
|
||||||
|
* 无法解码超长图片 [#732](https://github.com/FooIbar/EhViewer/issues/732)
|
||||||
|
* E 站变更导致种子解析失败 [#880](https://github.com/FooIbar/EhViewer/issues/880)
|
||||||
|
* E 站不再支持相似扫描导致图片搜索无法使用
|
||||||
|
* 杂项修复
|
||||||
|
|
||||||
|
### 已知问题
|
||||||
|
|
||||||
|
* Android 14 上使用手势导航返回时过渡动画过早结束 [#775](https://github.com/FooIbar/EhViewer/issues/775)
|
||||||
|
|
||||||
|
|
||||||
|
## [1.10.3] - 2024-01-15
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
|
||||||
|
* [Marshmallow] 支持裁剪边缘、打开压缩包和 GIF
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
* 由于部分设备上的非预期行为 [#578](https://github.com/FooIbar/EhViewer/issues/578) [#596](https://github.com/FooIbar/EhViewer/issues/596),默认禁用预见式返回导航动画,可于 设置-高级 中开启
|
||||||
|
* 杂项优化
|
||||||
|
|
||||||
|
### Bug 修复
|
||||||
|
|
||||||
|
* 刷新后页面无响应 [#579](https://github.com/FooIbar/EhViewer/issues/579)
|
||||||
|
* 浏览中保存图片后点击操作被阻挡 [#581](https://github.com/FooIbar/EhViewer/issues/581)
|
||||||
|
* 在画廊详情页面切换至后台后点击评论闪退 [#580](https://github.com/FooIbar/EhViewer/issues/580)
|
||||||
|
* 已下载的画廊中未阅读缓存过的图片解码失败 [#592](https://github.com/FooIbar/EhViewer/issues/592)
|
||||||
|
* 在收藏页多选并下载会使得下载状态出现问题 [#603](https://github.com/FooIbar/EhViewer/issues/603)
|
||||||
|
* 未能在不重启情况下切换 Http 引擎 [#610](https://github.com/FooIbar/EhViewer/issues/610)
|
||||||
|
* 在有进度的画廊阅读界面无法直接回到第一页 [#614](https://github.com/FooIbar/EhViewer/issues/614)
|
||||||
|
* 请求新闻界面失效 [#605](https://github.com/FooIbar/EhViewer/issues/605)
|
||||||
|
* 收藏夹为空解析失败 [#630](https://github.com/FooIbar/EhViewer/issues/630)
|
||||||
|
|
||||||
|
|
||||||
|
## [1.10.2] - 2023-12-31
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
|
||||||
|
* [阅读器] 支持裁剪图片黑色/白色边缘
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
* 切换 HTTP 引擎无需重启
|
||||||
|
* 优化过渡动画
|
||||||
|
* 杂项性能优化
|
||||||
|
|
||||||
|
### Bug 修复
|
||||||
|
|
||||||
|
* 修复搜索建议中出现关键词相同的条目时崩溃 [#533](https://github.com/FooIbar/EhViewer/issues/533) [#555](https://github.com/FooIbar/EhViewer/issues/555)
|
||||||
|
* 修复重命名下载标签时不显示原标签 [#534](https://github.com/FooIbar/EhViewer/issues/534)
|
||||||
|
* 修复调整下载项目顺序时卡顿 [#552](https://github.com/FooIbar/EhViewer/issues/552)
|
||||||
|
* 修复新增或修改评论后黑屏 [#554](https://github.com/FooIbar/EhViewer/issues/554)
|
||||||
|
* 修复剪贴板读取bug [#566](https://github.com/FooIbar/EhViewer/issues/566)
|
||||||
|
* 修复画廊信息对话框无法通过返回按钮/手势关闭 [#413](https://github.com/FooIbar/EhViewer/issues/413)
|
||||||
|
|
||||||
|
|
||||||
|
## [1.10.1] - 2023-12-19
|
||||||
|
|
||||||
|
### 重大变更
|
||||||
|
|
||||||
|
* 由于 Apache HttpClient 5 在 Android 10 以下无法使用,后备 HTTP 引擎恢复为 OkHttp [#514](https://github.com/FooIbar/EhViewer/issues/514)
|
||||||
|
|
||||||
|
### Bug 修复
|
||||||
|
|
||||||
|
* 修复获取 pToken 失败导致无限加载 [#508](https://github.com/FooIbar/EhViewer/issues/508)
|
||||||
|
* 修复阅读页快速滑动滑块导致下载延时过多累计 [#512](https://github.com/FooIbar/EhViewer/issues/512)
|
||||||
|
* 修复 Android 9 以下的设备上无法退出搜索栏 [#513](https://github.com/FooIbar/EhViewer/issues/513)
|
||||||
|
|
||||||
|
|
||||||
|
## [1.10.0] - 2023-12-14 [YANKED]
|
||||||
|
|
||||||
|
### 重大变更
|
||||||
|
|
||||||
|
* 后备 HTTP 引擎变更为 Apache HttpClient 5
|
||||||
|
* 类别和高级搜索选项移至搜索栏下方
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
|
||||||
|
* 支持在搜索时指定画廊语言
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
* 在下载中阅读画廊时会更新历史记录
|
||||||
|
* 多处性能改进
|
||||||
|
|
||||||
|
### Bug 修复
|
||||||
|
|
||||||
|
* 修复无法由外部应用进入画廊页面 [#436](https://github.com/FooIbar/EhViewer/issues/436)
|
||||||
|
* 修复点击精确到页的画廊链接闪退 [#430](https://github.com/FooIbar/EhViewer/issues/430)
|
||||||
|
* 修复无法搜索本地收藏 [#491](https://github.com/FooIbar/EhViewer/issues/491)
|
||||||
|
* 修复 CI 版本不显示变更日志
|
||||||
|
|
||||||
|
|
||||||
|
## [1.9.0] - 2023-12-01
|
||||||
|
|
||||||
|
### 重大变更
|
||||||
|
|
||||||
|
* 除阅读器外全部迁移至 Compose
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
|
||||||
|
* 设置-高级 中增加实验性滑动手势敏感度设置
|
||||||
|
* 支持通过滑动手势打开右侧抽屉
|
||||||
|
* 支持预见式返回动画,相关设置位于 设置-高级
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
* [阅读器] 画廊页数过多时隐藏进度条刻度并禁用触感反馈
|
||||||
|
* 优化原图下载重试策略
|
||||||
|
* 优化小屏设备上的搜索类别布局
|
||||||
|
* 优化过渡动画
|
||||||
|
* 优化历史记录删除动画
|
||||||
|
* 设置-EH 中的缩略图大小设置改为缩略图列数
|
||||||
|
|
||||||
|
### Bug 修复
|
||||||
|
|
||||||
|
* 修复点击通知栏的下载通知闪退 [#175](https://github.com/FooIbar/EhViewer/issues/175)
|
||||||
|
* 修复收藏状态显示错误 [#189](https://github.com/FooIbar/EhViewer/issues/189)
|
||||||
|
* 修复双击导航按钮后无法打开导航抽屉
|
||||||
|
* 修复某些情况下闪退 [#142](https://github.com/FooIbar/EhViewer/issues/142) [#208](https://github.com/FooIbar/EhViewer/issues/208)
|
||||||
|
* 修复收藏界面点击重试崩溃 [#193](https://github.com/FooIbar/EhViewer/issues/193)
|
||||||
|
* 修复本地收藏滚动位置丢失 [#192](https://github.com/FooIbar/EhViewer/issues/192)
|
||||||
|
* 修复手速过快页面切换过快使加载圈圈不自动消失 [#209](https://github.com/FooIbar/EhViewer/issues/209)
|
||||||
|
* 修复打开并关闭右侧抽屉后,状态栏一片白色 [#101](https://github.com/FooIbar/EhViewer/issues/101)
|
||||||
|
* 修复使用深色主题时,切换界面会闪过亮色界面 [#121](https://github.com/FooIbar/EhViewer/issues/121)
|
||||||
|
* [Marshmallow] 修复小内存设备上图片解码失败 [#229](https://github.com/FooIbar/EhViewer/issues/229) [#372](https://github.com/FooIbar/EhViewer/issues/372)
|
||||||
|
* 修复已舍弃画廊上传者解析
|
||||||
|
|
||||||
|
### 已知问题
|
||||||
|
|
||||||
|
* 画廊信息对话框无法通过返回关闭 [#413](https://github.com/FooIbar/EhViewer/issues/413)
|
||||||
@@ -1,60 +1,57 @@
|
|||||||
<p align="right">
|
<p align="right">
|
||||||
<strong>English</strong>
|
<strong>English</strong>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/docs/README/zh-cn.md">
|
<a href="/docs/README/zh-cn.md">
|
||||||
简体中文
|
简体中文
|
||||||
</a>
|
</a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/docs/README/zh-tw.md">
|
<a href="/docs/README/zh-tw.md">
|
||||||
正體中文
|
正體中文
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
<img src="https://github.com/Ehviewer-Overhauled/Art/blob/master/launcher_icon-web.svg" width="200">
|
<img src="https://github.com/Ehviewer-Overhauled/Art/blob/master/launcher_icon-web.svg" width="200" alt="EhViewer">
|
||||||
<br>EhViewer<br>
|
<br>EhViewer<br>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
|
<a href="https://github.com/FooIbar/EhViewer/actions/workflows/ci.yml">
|
||||||
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
|
<img src="https://github.com/FooIbar/EhViewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/LICENSE">
|
<a href="/LICENSE">
|
||||||
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
|
<img src="https://img.shields.io/github/license/FooIbar/EhViewer" alt="LICENSE">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
|
<a href="https://www.codefactor.io/repository/github/FooIbar/EhViewer">
|
||||||
<img src="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer/badge" alt="CodeFactor">
|
<img src="https://www.codefactor.io/repository/github/FooIbar/EhViewer/badge" alt="CodeFactor">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases">
|
<a href="https://github.com/FooIbar/EhViewer/releases">
|
||||||
<img src="https://img.shields.io/github/downloads/Ehviewer-Overhauled/EhViewer/total.svg" alt="Downloads">
|
<img src="https://img.shields.io/github/v/release/FooIbar/EhViewer" alt="Release">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases">
|
<a href="https://github.com/FooIbar/EhViewer/issues">
|
||||||
<img src="https://img.shields.io/github/v/release/Ehviewer-Overhauled/ehviewer?include_prereleases" alt="Release">
|
<img src="https://img.shields.io/github/issues/FooIbar/EhViewer" alt="Issues">
|
||||||
</a>
|
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/issues">
|
|
||||||
<img src="https://img.shields.io/github/issues/Ehviewer-Overhauled/ehviewer" alt="Issues">
|
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h3>
|
<h3>
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#description">
|
<a href="#description">
|
||||||
Description
|
Description
|
||||||
</a>
|
</a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#download">
|
<a href="#download">
|
||||||
Download
|
Download
|
||||||
</a>
|
</a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#screenshot">
|
<a href="#screenshot">
|
||||||
Screenshot
|
Screenshot
|
||||||
</a>
|
</a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#thanks">
|
<a href="#thanks">
|
||||||
Thanks
|
Thanks
|
||||||
</a>
|
</a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#license">
|
<a href="#license">
|
||||||
License
|
License
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
@@ -69,7 +66,14 @@ and [Dynamic Color](https://m3.material.io/styles/color/dynamic-color/overview)
|
|||||||
|
|
||||||
# Download
|
# Download
|
||||||
|
|
||||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on GitHub" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-github.svg" width="200px"/></a> <a href="https://github.com/Ehviewer-Overhauled/fdroid-repo"><img alt="Get it on F-Droid" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-fdroid.svg" width="200px"/></a>
|
| Flavor | Feature |
|
||||||
|
|-------------|----------------------------------|
|
||||||
|
| Default | Android 8.0+, full support |
|
||||||
|
| Marshmallow | Android 6.0-7.1, limited support |
|
||||||
|
|
||||||
|
<a href="https://github.com/FooIbar/EhViewer/releases">
|
||||||
|
<img alt="Get it on GitHub" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-github.svg" width="200px"/>
|
||||||
|
</a>
|
||||||
|
|
||||||
# Screenshot
|
# Screenshot
|
||||||
|
|
||||||
@@ -81,16 +85,14 @@ and [Dynamic Color](https://m3.material.io/styles/color/dynamic-color/overview)
|
|||||||
Here is the libraries
|
Here is the libraries
|
||||||
|
|
||||||
- [Arrow](https://arrow-kt.io/)
|
- [Arrow](https://arrow-kt.io/)
|
||||||
- [AOSP & AndroidX](http://source.android.com/)
|
- [AOSP & AndroidX](https://source.android.com/)
|
||||||
- [Kotlin & KotlinX](https://kotlinlang.org/)
|
- [Kotlin & KotlinX](https://kotlinlang.org/)
|
||||||
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
|
- [Material Icons](https://github.com/google/material-design-icons)
|
||||||
- [MCA](https://github.com/material-components/material-components-android)
|
- [Ktor](https://ktor.io/)
|
||||||
- [material-design-icons](https://github.com/google/material-design-icons)
|
|
||||||
- [Okhttp](https://square.github.io/okhttp/)
|
|
||||||
- [RikkaX](https://github.com/RikkaApps/RikkaX)
|
|
||||||
- [Libarchive](http://www.libarchive.org/)
|
|
||||||
- [Coil](https://coil-kt.github.io/coil/)
|
- [Coil](https://coil-kt.github.io/coil/)
|
||||||
- [Jsoup](https://jsoup.org/)
|
- [Compose Destinations](https://composedestinations.rafaelcosta.xyz/)
|
||||||
|
- [libarchive](https://www.libarchive.org/)
|
||||||
|
- [jsoup](https://jsoup.org/)
|
||||||
|
|
||||||
Specially thanks [Tachiyomi](https://tachiyomi.org/) for its reader implementation!
|
Specially thanks [Tachiyomi](https://tachiyomi.org/) for its reader implementation!
|
||||||
|
|
||||||
@@ -99,6 +101,7 @@ Specially thanks [Tachiyomi](https://tachiyomi.org/) for its reader implementati
|
|||||||
Copyright 2014-2019 Hippo Seven
|
Copyright 2014-2019 Hippo Seven
|
||||||
Copyright 2020-2022 NekoInverter
|
Copyright 2020-2022 NekoInverter
|
||||||
Copyright 2022-2023 Tarsin Norbin
|
Copyright 2022-2023 Tarsin Norbin
|
||||||
|
Copyright 2023-2024 Foolbar
|
||||||
|
|
||||||
EhViewer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
EhViewer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,2 @@
|
|||||||
/build
|
/build
|
||||||
/src/main/java-gen
|
|
||||||
manifest-merger-release-report.txt
|
|
||||||
/src/main/libs
|
|
||||||
/src/main/obj
|
|
||||||
# Intellij
|
|
||||||
*.iml
|
|
||||||
/.cxx
|
/.cxx
|
||||||
+187
-160
@@ -1,37 +1,40 @@
|
|||||||
|
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
|
||||||
import com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE
|
import com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE
|
||||||
import com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP
|
import com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP
|
||||||
import java.time.Instant
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
|
|
||||||
val isRelease: Boolean
|
val isRelease: Boolean
|
||||||
get() = gradle.startParameter.taskNames.any { it.contains("Release") }
|
get() = gradle.startParameter.taskNames.any { it.contains("Release") }
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
alias(libs.plugins.android.application)
|
||||||
kotlin("android")
|
alias(libs.plugins.kotlin.android)
|
||||||
id("kotlin-parcelize")
|
alias(libs.plugins.kotlin.compose)
|
||||||
kotlin("plugin.serialization")
|
alias(libs.plugins.kotlin.parcelize)
|
||||||
id("com.google.devtools.ksp")
|
alias(libs.plugins.kotlin.serialization)
|
||||||
id("com.mikepenz.aboutlibraries.plugin")
|
alias(libs.plugins.ksp)
|
||||||
id("org.mozilla.rust-android-gradle.rust-android")
|
alias(libs.plugins.spotless)
|
||||||
id("dev.shreyaspatil.compose-compiler-report-generator")
|
alias(libs.plugins.aboutlibrariesPlugin)
|
||||||
|
alias(libs.plugins.composeCompilerReportGenerator)
|
||||||
|
alias(libs.plugins.baselineprofile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val supportedAbis = arrayOf("arm64-v8a", "x86_64", "armeabi-v7a")
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk = 34
|
compileSdk = if (isRelease) 35 else 34
|
||||||
buildToolsVersion = "34.0.0"
|
buildToolsVersion = "35.0.0"
|
||||||
ndkVersion = "26.0.10404224"
|
ndkVersion = "27.0.11902837-rc1"
|
||||||
|
androidResources.generateLocaleConfig = true
|
||||||
|
|
||||||
splits {
|
splits {
|
||||||
abi {
|
abi {
|
||||||
isEnable = true
|
isEnable = true
|
||||||
reset()
|
reset()
|
||||||
if (isRelease) {
|
if (isRelease) {
|
||||||
include("arm64-v8a", "x86_64", "armeabi-v7a", "x86")
|
include(*supportedAbis)
|
||||||
isUniversalApk = true
|
isUniversalApk = true
|
||||||
} else {
|
} else {
|
||||||
include("arm64-v8a")
|
include("arm64-v8a", "x86_64")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,22 +52,28 @@ android {
|
|||||||
commandLine = "git rev-parse --short=7 HEAD".split(' ')
|
commandLine = "git rev-parse --short=7 HEAD".split(' ')
|
||||||
}.standardOutput.asText.get().trim()
|
}.standardOutput.asText.get().trim()
|
||||||
|
|
||||||
val buildTime by lazy {
|
val commitTime = providers.exec {
|
||||||
val formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm").withZone(ZoneOffset.UTC)
|
commandLine = "git log -1 --format=%ct".split(' ')
|
||||||
formatter.format(Instant.now())
|
}.standardOutput.asText.get().trim()
|
||||||
}
|
|
||||||
|
|
||||||
val repoName = providers.exec {
|
val repoName = providers.exec {
|
||||||
commandLine = "git remote get-url origin".split(' ')
|
commandLine = "git remote get-url origin".split(' ')
|
||||||
}.standardOutput.asText.get().trim().removePrefix("https://github.com/").removePrefix("git@github.com:")
|
}.standardOutput.asText.get().trim().removePrefix("https://github.com/").removePrefix("git@github.com:")
|
||||||
.removeSuffix(".git")
|
.removeSuffix(".git")
|
||||||
|
|
||||||
|
val chromeVersion = rootProject.layout.projectDirectory.file("chrome-for-testing/LATEST_RELEASE_STABLE").asFile
|
||||||
|
.readText().substringBefore('.')
|
||||||
|
|
||||||
|
val githubToken = gradleLocalProperties(rootDir, providers)["GITHUB_TOKEN"] as? String
|
||||||
|
?: System.getenv("GITHUB_TOKEN").orEmpty()
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "moe.tarsin.ehviewer"
|
applicationId = "moe.tarsin.ehviewer"
|
||||||
minSdk = 28
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 35
|
||||||
versionCode = 180042
|
versionCode = 180057
|
||||||
versionName = "1.8.9.0-SNAPSHOT"
|
versionName = "1.11.6"
|
||||||
|
// versionNameSuffix = "-SNAPSHOT"
|
||||||
resourceConfigurations.addAll(
|
resourceConfigurations.addAll(
|
||||||
listOf(
|
listOf(
|
||||||
"zh",
|
"zh",
|
||||||
@@ -81,9 +90,34 @@ android {
|
|||||||
"nb-rNO",
|
"nb-rNO",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
buildConfigField("String", "RAW_VERSION_NAME", "\"$versionName${versionNameSuffix.orEmpty()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"$commitSha\"")
|
buildConfigField("String", "COMMIT_SHA", "\"$commitSha\"")
|
||||||
buildConfigField("String", "BUILD_TIME", "\"$buildTime\"")
|
buildConfigField("long", "COMMIT_TIME", commitTime)
|
||||||
buildConfigField("String", "REPO_NAME", "\"$repoName\"")
|
buildConfigField("String", "REPO_NAME", "\"$repoName\"")
|
||||||
|
buildConfigField("String", "CHROME_VERSION", "\"$chromeVersion\"")
|
||||||
|
buildConfigField("String", "GITHUB_TOKEN", "\"$githubToken\"")
|
||||||
|
ndk {
|
||||||
|
if (isRelease) {
|
||||||
|
abiFilters.addAll(supportedAbis)
|
||||||
|
}
|
||||||
|
debugSymbolLevel = "FULL"
|
||||||
|
}
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flavorDimensions += "api"
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
create("default")
|
||||||
|
create("marshmallow") {
|
||||||
|
minSdk = 23
|
||||||
|
applicationIdSuffix = ".m"
|
||||||
|
versionNameSuffix = "-M"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
@@ -93,48 +127,18 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
isCoreLibraryDesugaringEnabled = true
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "17"
|
|
||||||
freeCompilerArgs = listOf(
|
|
||||||
// https://kotlinlang.org/docs/compiler-reference.html#progressive
|
|
||||||
"-progressive",
|
|
||||||
"-Xjvm-default=all",
|
|
||||||
"-Xlambdas=indy",
|
|
||||||
"-XXLanguage:+BreakContinueInInlineLambdas",
|
|
||||||
|
|
||||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
|
||||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
|
||||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
|
||||||
"-opt-in=androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi",
|
|
||||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
|
||||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
|
||||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
|
||||||
"-opt-in=androidx.paging.ExperimentalPagingApi",
|
|
||||||
"-opt-in=kotlin.contracts.ExperimentalContracts",
|
|
||||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
|
||||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
|
||||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
|
||||||
"-opt-in=splitties.experimental.ExperimentalSplittiesApi",
|
|
||||||
"-opt-in=splitties.preferences.DataStorePreferencesPreview",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lint {
|
lint {
|
||||||
abortOnError = true
|
|
||||||
checkReleaseBuilds = false
|
checkReleaseBuilds = false
|
||||||
disable.add("MissingTranslation")
|
disable += setOf("MissingTranslation", "MissingQuantity")
|
||||||
|
error += setOf("InlinedApi")
|
||||||
}
|
}
|
||||||
|
|
||||||
packaging {
|
packaging {
|
||||||
resources {
|
dex {
|
||||||
excludes += "/META-INF/**"
|
useLegacyPackaging = false
|
||||||
excludes += "/kotlin/**"
|
|
||||||
excludes += "**.txt"
|
|
||||||
excludes += "**.bin"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +153,16 @@ android {
|
|||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
|
lint {
|
||||||
|
abortOnError = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
create("benchmarkRelease") {
|
||||||
|
initWith(buildTypes.getByName("release"))
|
||||||
|
matchingFallbacks += listOf("release")
|
||||||
|
applicationIdSuffix = ".benchmark"
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
isDebuggable = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,146 +172,159 @@ android {
|
|||||||
viewBinding = true
|
viewBinding = true
|
||||||
}
|
}
|
||||||
|
|
||||||
composeOptions {
|
|
||||||
kotlinCompilerExtensionVersion = "1.5.1"
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace = "com.hippo.ehviewer"
|
namespace = "com.hippo.ehviewer"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composeCompiler {
|
||||||
|
enableNonSkippingGroupOptimization = true
|
||||||
|
enableStrongSkippingMode = true
|
||||||
|
}
|
||||||
|
|
||||||
|
androidComponents {
|
||||||
|
onVariants(selector().withBuildType("release")) {
|
||||||
|
it.packaging.resources.excludes.addAll(
|
||||||
|
"/META-INF/**",
|
||||||
|
"/kotlin/**",
|
||||||
|
"**.txt",
|
||||||
|
"**.bin",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
baselineProfile {
|
||||||
|
mergeIntoMain = true
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// https://developer.android.com/jetpack/androidx/releases/activity
|
// https://developer.android.com/jetpack/androidx/releases/activity
|
||||||
implementation("androidx.activity:activity-compose:1.8.0-alpha06")
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
implementation(libs.androidx.appcompat)
|
||||||
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha05")
|
implementation(libs.androidx.biometric)
|
||||||
implementation("androidx.browser:browser:1.6.0")
|
implementation(libs.androidx.browser)
|
||||||
implementation("androidx.collection:collection-ktx:1.3.0-beta01")
|
|
||||||
|
|
||||||
// https://developer.android.com/jetpack/androidx/releases/compose-material3
|
// https://developer.android.com/jetpack/androidx/releases/compose-material3
|
||||||
// api(platform("androidx.compose:compose-bom:2023.05.00"))
|
api(platform(libs.compose.bom))
|
||||||
api(platform("dev.chrisbanes.compose:compose-bom:2023.07.00-alpha02"))
|
implementation(libs.bundles.compose)
|
||||||
implementation("androidx.compose.material:material-icons-extended")
|
|
||||||
implementation("androidx.compose.material3:material3")
|
|
||||||
implementation("androidx.compose.material3:material3-window-size-class")
|
|
||||||
implementation("androidx.compose.ui:ui-util")
|
|
||||||
|
|
||||||
implementation("androidx.core:core-ktx:1.12.0-rc01")
|
implementation(libs.compose.destinations.core)
|
||||||
|
ksp(libs.compose.destinations.compiler)
|
||||||
|
|
||||||
|
implementation(libs.androidx.core)
|
||||||
|
implementation(libs.androidx.core.splashscreen)
|
||||||
|
|
||||||
|
implementation(libs.androidx.constraintlayout.compose)
|
||||||
|
implementation(libs.androidx.datastore)
|
||||||
|
implementation(libs.androidx.graphics.path)
|
||||||
|
|
||||||
implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha12")
|
|
||||||
implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
|
|
||||||
implementation("androidx.fragment:fragment-ktx:1.7.0-alpha02")
|
|
||||||
// https://developer.android.com/jetpack/androidx/releases/lifecycle
|
// https://developer.android.com/jetpack/androidx/releases/lifecycle
|
||||||
implementation("androidx.lifecycle:lifecycle-process:2.6.1")
|
implementation(libs.androidx.lifecycle.process)
|
||||||
|
implementation(libs.androidx.lifecycle.compose)
|
||||||
// https://developer.android.com/jetpack/androidx/releases/navigation
|
|
||||||
val nav_version = "2.7.0"
|
|
||||||
implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
|
|
||||||
implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
|
|
||||||
implementation("androidx.navigation:navigation-compose:$nav_version")
|
|
||||||
|
|
||||||
// https://developer.android.com/jetpack/androidx/releases/paging
|
// https://developer.android.com/jetpack/androidx/releases/paging
|
||||||
val paging = "3.2.0"
|
implementation(libs.androidx.paging.compose)
|
||||||
implementation("androidx.paging:paging-compose:$paging")
|
|
||||||
implementation("androidx.paging:paging-runtime-ktx:$paging")
|
implementation(libs.androidx.recyclerview)
|
||||||
implementation("androidx.recyclerview:recyclerview:1.3.1")
|
implementation(libs.androidx.viewpager2)
|
||||||
implementation("androidx.recyclerview:recyclerview-selection:1.2.0-alpha01")
|
|
||||||
|
|
||||||
// https://developer.android.com/jetpack/androidx/releases/room
|
// https://developer.android.com/jetpack/androidx/releases/room
|
||||||
val room_version = "2.6.0-alpha03"
|
ksp(libs.androidx.room.compiler)
|
||||||
ksp("androidx.room:room-compiler:$room_version")
|
implementation(libs.androidx.room.paging)
|
||||||
implementation("androidx.room:room-paging:$room_version")
|
|
||||||
|
|
||||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
|
implementation(libs.androidx.work.runtime)
|
||||||
implementation("androidx.work:work-runtime-ktx:2.8.1")
|
implementation(libs.photoview)
|
||||||
implementation("com.drakeet.drawer:drawer:1.0.3")
|
implementation(libs.material.motion.core)
|
||||||
implementation("com.github.chrisbanes:PhotoView:2.3.0") // Dead Dependency
|
|
||||||
implementation("com.github.tachiyomiorg:DirectionalViewPager:1.0.0") // Dead Dependency
|
|
||||||
// https://github.com/google/accompanist/releases
|
|
||||||
val accompanist_version = "0.31.6-rc"
|
|
||||||
implementation("com.google.accompanist:accompanist-themeadapter-material3:$accompanist_version")
|
|
||||||
implementation("com.google.accompanist:accompanist-webview:$accompanist_version")
|
|
||||||
implementation("com.google.android.material:material:1.11.0-alpha02")
|
|
||||||
|
|
||||||
val splitties = "3.0.0"
|
implementation(libs.bundles.splitties)
|
||||||
implementation("com.louiscad.splitties:splitties-appctx:$splitties")
|
|
||||||
implementation("com.louiscad.splitties:splitties-systemservices:$splitties")
|
|
||||||
implementation("com.louiscad.splitties:splitties-preferences:$splitties")
|
|
||||||
implementation("com.louiscad.splitties:splitties-arch-room:$splitties")
|
|
||||||
|
|
||||||
// https://square.github.io/okhttp/changelogs/changelog/
|
implementation(libs.okio.jvm)
|
||||||
implementation(platform("com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.11"))
|
|
||||||
implementation("com.squareup.okhttp3:okhttp-android")
|
|
||||||
implementation("com.squareup.okhttp3:okhttp-coroutines")
|
|
||||||
|
|
||||||
implementation("com.squareup.okio:okio-jvm:3.5.0")
|
implementation(libs.logcat)
|
||||||
|
|
||||||
implementation("com.mikepenz:aboutlibraries-core:10.8.3")
|
implementation(libs.diff)
|
||||||
|
|
||||||
implementation("dev.chrisbanes.insetter:insetter:0.6.1") // Dead Dependency
|
implementation(libs.aboutlibraries.core)
|
||||||
implementation("dev.rikka.rikkax.core:core-ktx:1.4.1")
|
implementation(libs.aboutlibraries.compose.m3)
|
||||||
implementation("dev.rikka.rikkax.insets:insets:1.3.0")
|
implementation(libs.accompanist.drawable.painter)
|
||||||
implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0")
|
|
||||||
|
|
||||||
implementation(platform("io.arrow-kt:arrow-stack:1.2.0"))
|
implementation(libs.insetter) // Dead Dependency
|
||||||
implementation("io.arrow-kt:arrow-fx-coroutines")
|
|
||||||
|
// implementation(libs.reorderable)
|
||||||
|
|
||||||
|
implementation(platform(libs.arrow.stack))
|
||||||
|
implementation(libs.arrow.fx.coroutines)
|
||||||
|
implementation(libs.arrow.resilience)
|
||||||
|
|
||||||
// https://coil-kt.github.io/coil/changelog/
|
// https://coil-kt.github.io/coil/changelog/
|
||||||
implementation(platform("io.coil-kt:coil-bom:2.4.0"))
|
implementation(platform(libs.coil.bom))
|
||||||
implementation("io.coil-kt:coil-compose")
|
implementation(libs.bundles.coil)
|
||||||
implementation("io.coil-kt:coil-gif")
|
|
||||||
|
|
||||||
val ktor = "2.3.3"
|
implementation(libs.telephoto.zoomable)
|
||||||
implementation("io.ktor:ktor-io-jvm:$ktor")
|
|
||||||
implementation("io.ktor:ktor-utils-jvm:$ktor")
|
|
||||||
|
|
||||||
implementation("org.chromium.net:cronet-embedded:113.5672.61")
|
implementation(libs.ktor.client.core)
|
||||||
|
|
||||||
val serialization = "1.5.1"
|
implementation(libs.bundles.kotlinx.serialization)
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:$serialization")
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization")
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-okio:$serialization")
|
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
implementation(libs.kotlinx.coroutines.android)
|
||||||
implementation("org.jsoup:jsoup:1.16.1")
|
implementation(libs.kotlinx.datetime)
|
||||||
|
implementation(libs.jsoup)
|
||||||
|
|
||||||
val chunker = "4.0.0"
|
coreLibraryDesugaring(libs.desugar)
|
||||||
debugImplementation("com.github.chuckerteam.chucker:library:$chunker")
|
|
||||||
releaseImplementation("com.github.chuckerteam.chucker:library-no-op:$chunker")
|
|
||||||
|
|
||||||
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.12")
|
implementation(libs.cronet.embedded)
|
||||||
|
|
||||||
|
implementation(libs.androidx.profileinstaller)
|
||||||
|
"baselineProfile"(project(":benchmark"))
|
||||||
|
|
||||||
|
debugImplementation(libs.compose.ui.tooling)
|
||||||
|
implementation(libs.compose.ui.tooling.preview)
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(21)
|
||||||
|
compilerOptions {
|
||||||
|
freeCompilerArgs = listOf(
|
||||||
|
// https://kotlinlang.org/docs/compiler-reference.html#progressive
|
||||||
|
"-progressive",
|
||||||
|
"-Xjvm-default=all",
|
||||||
|
"-Xcontext-receivers",
|
||||||
|
|
||||||
|
"-opt-in=coil3.annotation.ExperimentalCoilApi",
|
||||||
|
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||||
|
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||||
|
"-opt-in=androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi",
|
||||||
|
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||||
|
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||||
|
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||||
|
"-opt-in=androidx.paging.ExperimentalPagingApi",
|
||||||
|
"-opt-in=kotlin.contracts.ExperimentalContracts",
|
||||||
|
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
|
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||||
|
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||||
|
"-opt-in=splitties.experimental.ExperimentalSplittiesApi",
|
||||||
|
"-opt-in=splitties.preferences.DataStorePreferencesPreview",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ksp {
|
ksp {
|
||||||
arg("room.schemaLocation", "$projectDir/schemas")
|
arg("room.schemaLocation", "$projectDir/schemas")
|
||||||
arg("room.generateKotlin", "true")
|
arg("compose-destinations.codeGenPackageName", "com.hippo.ehviewer.ui")
|
||||||
}
|
}
|
||||||
|
|
||||||
aboutLibraries {
|
aboutLibraries {
|
||||||
duplicationMode = MERGE
|
duplicationMode = MERGE
|
||||||
duplicationRule = GROUP
|
duplicationRule = GROUP
|
||||||
exclusionPatterns = listOf(
|
|
||||||
"androidx\\..*".toPattern(),
|
|
||||||
".*annotations".toPattern(),
|
|
||||||
"com\\.google\\.code\\..*".toPattern(),
|
|
||||||
"com\\.google\\.guava:listenablefuture".toPattern(),
|
|
||||||
"com\\.google\\.protobuf:protobuf-javalite".toPattern(),
|
|
||||||
"org\\.checkerframework:checker-qual".toPattern(),
|
|
||||||
"org\\.chromium\\.net:cronet-embedded".toPattern(),
|
|
||||||
"org\\.slf4j:slf4j-api".toPattern(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cargo {
|
val ktlintVersion = libs.ktlint.get().version
|
||||||
module = "src/main/rust"
|
|
||||||
libname = "ehviewer_rust"
|
|
||||||
targets = if (isRelease) listOf("arm", "x86", "arm64", "x86_64") else listOf("arm64")
|
|
||||||
if (isRelease) profile = "release"
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.configureEach {
|
spotless {
|
||||||
if ((name == "mergeDebugJniLibFolders" || name == "mergeReleaseJniLibFolders")) {
|
kotlin {
|
||||||
dependsOn("cargoBuild")
|
// https://github.com/diffplug/spotless/issues/111
|
||||||
// fix mergeDebugJniLibFolders UP-TO-DATE
|
target("src/**/*.kt")
|
||||||
inputs.dir(buildDir.resolve("rustJniLibs/android"))
|
ktlint(ktlintVersion)
|
||||||
|
}
|
||||||
|
kotlinGradle {
|
||||||
|
ktlint(ktlintVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vendored
+18
-16
@@ -1,29 +1,31 @@
|
|||||||
-keepclassmembers class * implements android.os.Parcelable {
|
|
||||||
public static final ** CREATOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||||
native <methods>;
|
native <methods>;
|
||||||
}
|
}
|
||||||
|
|
||||||
# https://issuetracker.google.com/issues/190382641
|
-keepclassmembers enum * {
|
||||||
-keepclassmembers class kotlin.SafePublicationLazyImpl {
|
public static **[] values();
|
||||||
java.lang.Object _value;
|
public static ** valueOf(java.lang.String);
|
||||||
}
|
}
|
||||||
|
|
||||||
-keep class androidx.viewpager.widget.ViewPager$LayoutParams { int position; }
|
-keepclassmembers class * implements android.os.Parcelable {
|
||||||
|
public static final ** CREATOR;
|
||||||
|
}
|
||||||
|
|
||||||
-keep class com.hippo.ehviewer.client.parser.Torrent { *; }
|
# https://issuetracker.google.com/347963245
|
||||||
-keep class com.hippo.ehviewer.client.parser.GalleryListResult { *; }
|
-keep class * extends androidx.room.RoomDatabase {
|
||||||
-keep class com.hippo.ehviewer.client.parser.Limits { *; }
|
<init>();
|
||||||
-keep class com.hippo.ehviewer.client.data.BaseGalleryInfo { *; }
|
}
|
||||||
|
|
||||||
# Ktor logger
|
# Ktor logger
|
||||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||||
|
|
||||||
-keepattributes LineNumberTable,SourceFile
|
-dontwarn org.conscrypt.Conscrypt
|
||||||
-renamesourcefileattribute SourceFile
|
|
||||||
|
# https://issuetracker.google.com/222232895
|
||||||
|
-dontwarn androidx.window.extensions.**
|
||||||
|
-dontwarn androidx.window.sidecar.Sidecar*
|
||||||
|
|
||||||
|
-keepattributes LineNumberTable
|
||||||
|
|
||||||
-repackageclasses
|
|
||||||
-allowaccessmodification
|
-allowaccessmodification
|
||||||
-overloadaggressively
|
-repackageclasses
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
{
|
|
||||||
"formatVersion": 1,
|
|
||||||
"database": {
|
|
||||||
"version": 1,
|
|
||||||
"identityHash": "c801ee62729ba20a203b150211234d5c",
|
|
||||||
"entities": [
|
|
||||||
{
|
|
||||||
"tableName": "OK_HTTP_3_COOKIE",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`NAME` TEXT, `VALUE` TEXT, `EXPIRES_AT` INTEGER NOT NULL, `DOMAIN` TEXT, `PATH` TEXT, `SECURE` INTEGER NOT NULL, `HTTP_ONLY` INTEGER NOT NULL, `PERSISTENT` INTEGER NOT NULL, `HOST_ONLY` INTEGER NOT NULL, `_id` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "name",
|
|
||||||
"columnName": "NAME",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "value",
|
|
||||||
"columnName": "VALUE",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "expiresAt",
|
|
||||||
"columnName": "EXPIRES_AT",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "domain",
|
|
||||||
"columnName": "DOMAIN",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "path",
|
|
||||||
"columnName": "PATH",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "secure",
|
|
||||||
"columnName": "SECURE",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "httpOnly",
|
|
||||||
"columnName": "HTTP_ONLY",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "persistent",
|
|
||||||
"columnName": "PERSISTENT",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "hostOnly",
|
|
||||||
"columnName": "HOST_ONLY",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "id",
|
|
||||||
"columnName": "_id",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"autoGenerate": false,
|
|
||||||
"columnNames": [
|
|
||||||
"_id"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"views": [],
|
|
||||||
"setupQueries": [
|
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c801ee62729ba20a203b150211234d5c')"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
{
|
|
||||||
"formatVersion": 1,
|
|
||||||
"database": {
|
|
||||||
"version": 2,
|
|
||||||
"identityHash": "1b80cd29939b0f43934721f1289ba94d",
|
|
||||||
"entities": [
|
|
||||||
{
|
|
||||||
"tableName": "OK_HTTP_3_COOKIE",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`NAME` TEXT NOT NULL, `VALUE` TEXT NOT NULL, `EXPIRES_AT` INTEGER NOT NULL, `DOMAIN` TEXT NOT NULL, `PATH` TEXT NOT NULL, `SECURE` INTEGER NOT NULL, `HTTP_ONLY` INTEGER NOT NULL, `PERSISTENT` INTEGER NOT NULL, `HOST_ONLY` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "name",
|
|
||||||
"columnName": "NAME",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "value",
|
|
||||||
"columnName": "VALUE",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "expiresAt",
|
|
||||||
"columnName": "EXPIRES_AT",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "domain",
|
|
||||||
"columnName": "DOMAIN",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "path",
|
|
||||||
"columnName": "PATH",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "secure",
|
|
||||||
"columnName": "SECURE",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "httpOnly",
|
|
||||||
"columnName": "HTTP_ONLY",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "persistent",
|
|
||||||
"columnName": "PERSISTENT",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "hostOnly",
|
|
||||||
"columnName": "HOST_ONLY",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "id",
|
|
||||||
"columnName": "_id",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"autoGenerate": false,
|
|
||||||
"columnNames": [
|
|
||||||
"_id"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"views": [],
|
|
||||||
"setupQueries": [
|
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1b80cd29939b0f43934721f1289ba94d')"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,471 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 17,
|
||||||
|
"identityHash": "d6375317a9298b7feae5925a23272bb9",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "GALLERIES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "token",
|
||||||
|
"columnName": "TOKEN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "TITLE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "titleJpn",
|
||||||
|
"columnName": "TITLE_JPN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "thumbKey",
|
||||||
|
"columnName": "THUMB",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "posted",
|
||||||
|
"columnName": "POSTED",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uploader",
|
||||||
|
"columnName": "UPLOADER",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "rating",
|
||||||
|
"columnName": "RATING",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "simpleLanguage",
|
||||||
|
"columnName": "SIMPLE_LANGUAGE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "favoriteSlot",
|
||||||
|
"columnName": "FAVORITE_SLOT",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_LABELS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOAD_LABELS_LABEL",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DOWNLOAD_LABELS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOADS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`LABEL`) REFERENCES `DOWNLOAD_LABELS`(`LABEL`) ON UPDATE CASCADE ON DELETE SET NULL )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "state",
|
||||||
|
"columnName": "STATE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legacy",
|
||||||
|
"columnName": "LEGACY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOADS_LABEL",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_DOWNLOADS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "DOWNLOAD_LABELS",
|
||||||
|
"onDelete": "SET NULL",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"LABEL"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_DIRNAME",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dirname",
|
||||||
|
"columnName": "DIRNAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "FILTER",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "text",
|
||||||
|
"columnName": "TEXT",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "enable",
|
||||||
|
"columnName": "ENABLE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "HISTORY",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "LOCAL_FAVORITES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "PROGRESS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `PAGE` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "page",
|
||||||
|
"columnName": "PAGE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "QUICK_SEARCH",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "NAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "keyword",
|
||||||
|
"columnName": "KEYWORD",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "advanceSearch",
|
||||||
|
"columnName": "ADVANCE_SEARCH",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "minRating",
|
||||||
|
"columnName": "MIN_RATING",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageFrom",
|
||||||
|
"columnName": "PAGE_FROM",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageTo",
|
||||||
|
"columnName": "PAGE_TO",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd6375317a9298b7feae5925a23272bb9')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,471 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 18,
|
||||||
|
"identityHash": "bc90acf4ffb4e7cce9a16cd5a0f662f3",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "GALLERIES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT NOT NULL, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "token",
|
||||||
|
"columnName": "TOKEN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "TITLE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "titleJpn",
|
||||||
|
"columnName": "TITLE_JPN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "thumbKey",
|
||||||
|
"columnName": "THUMB",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "posted",
|
||||||
|
"columnName": "POSTED",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uploader",
|
||||||
|
"columnName": "UPLOADER",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "rating",
|
||||||
|
"columnName": "RATING",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "simpleLanguage",
|
||||||
|
"columnName": "SIMPLE_LANGUAGE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "favoriteSlot",
|
||||||
|
"columnName": "FAVORITE_SLOT",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_LABELS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOAD_LABELS_LABEL",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DOWNLOAD_LABELS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOADS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`LABEL`) REFERENCES `DOWNLOAD_LABELS`(`LABEL`) ON UPDATE CASCADE ON DELETE SET NULL )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "state",
|
||||||
|
"columnName": "STATE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legacy",
|
||||||
|
"columnName": "LEGACY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOADS_LABEL",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_DOWNLOADS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "DOWNLOAD_LABELS",
|
||||||
|
"onDelete": "SET NULL",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"LABEL"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_DIRNAME",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dirname",
|
||||||
|
"columnName": "DIRNAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "FILTER",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "text",
|
||||||
|
"columnName": "TEXT",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "enable",
|
||||||
|
"columnName": "ENABLE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "HISTORY",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "LOCAL_FAVORITES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "PROGRESS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `PAGE` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "page",
|
||||||
|
"columnName": "PAGE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "QUICK_SEARCH",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "NAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "keyword",
|
||||||
|
"columnName": "KEYWORD",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "advanceSearch",
|
||||||
|
"columnName": "ADVANCE_SEARCH",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "minRating",
|
||||||
|
"columnName": "MIN_RATING",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageFrom",
|
||||||
|
"columnName": "PAGE_FROM",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageTo",
|
||||||
|
"columnName": "PAGE_TO",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bc90acf4ffb4e7cce9a16cd5a0f662f3')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,465 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 19,
|
||||||
|
"identityHash": "edd2f8e628072889f7702b50eb2c44a1",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "GALLERIES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT NOT NULL, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "token",
|
||||||
|
"columnName": "TOKEN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "TITLE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "titleJpn",
|
||||||
|
"columnName": "TITLE_JPN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "thumbKey",
|
||||||
|
"columnName": "THUMB",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "posted",
|
||||||
|
"columnName": "POSTED",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uploader",
|
||||||
|
"columnName": "UPLOADER",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "rating",
|
||||||
|
"columnName": "RATING",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "simpleLanguage",
|
||||||
|
"columnName": "SIMPLE_LANGUAGE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "favoriteSlot",
|
||||||
|
"columnName": "FAVORITE_SLOT",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_LABELS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOAD_LABELS_LABEL",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DOWNLOAD_LABELS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOADS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`LABEL`) REFERENCES `DOWNLOAD_LABELS`(`LABEL`) ON UPDATE CASCADE ON DELETE SET NULL )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "state",
|
||||||
|
"columnName": "STATE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legacy",
|
||||||
|
"columnName": "LEGACY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOADS_LABEL",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_DOWNLOADS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "DOWNLOAD_LABELS",
|
||||||
|
"onDelete": "SET NULL",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"LABEL"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_DIRNAME",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dirname",
|
||||||
|
"columnName": "DIRNAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "FILTER",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "text",
|
||||||
|
"columnName": "TEXT",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "enable",
|
||||||
|
"columnName": "ENABLE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "HISTORY",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "LOCAL_FAVORITES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "PROGRESS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `PAGE` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "page",
|
||||||
|
"columnName": "PAGE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "QUICK_SEARCH",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "NAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "keyword",
|
||||||
|
"columnName": "KEYWORD",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "advanceSearch",
|
||||||
|
"columnName": "ADVANCE_SEARCH",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "minRating",
|
||||||
|
"columnName": "MIN_RATING",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageFrom",
|
||||||
|
"columnName": "PAGE_FROM",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageTo",
|
||||||
|
"columnName": "PAGE_TO",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'edd2f8e628072889f7702b50eb2c44a1')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,472 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 20,
|
||||||
|
"identityHash": "a8dd0226b286cc57a8c99ba7ad416730",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "GALLERIES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT NOT NULL, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `PAGES` INTEGER NOT NULL DEFAULT 0, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "token",
|
||||||
|
"columnName": "TOKEN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "TITLE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "titleJpn",
|
||||||
|
"columnName": "TITLE_JPN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "thumbKey",
|
||||||
|
"columnName": "THUMB",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "posted",
|
||||||
|
"columnName": "POSTED",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uploader",
|
||||||
|
"columnName": "UPLOADER",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "rating",
|
||||||
|
"columnName": "RATING",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pages",
|
||||||
|
"columnName": "PAGES",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "simpleLanguage",
|
||||||
|
"columnName": "SIMPLE_LANGUAGE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "favoriteSlot",
|
||||||
|
"columnName": "FAVORITE_SLOT",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_LABELS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOAD_LABELS_LABEL",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DOWNLOAD_LABELS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOADS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`LABEL`) REFERENCES `DOWNLOAD_LABELS`(`LABEL`) ON UPDATE CASCADE ON DELETE SET NULL )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "state",
|
||||||
|
"columnName": "STATE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legacy",
|
||||||
|
"columnName": "LEGACY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOADS_LABEL",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_DOWNLOADS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "DOWNLOAD_LABELS",
|
||||||
|
"onDelete": "SET NULL",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"LABEL"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_DIRNAME",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dirname",
|
||||||
|
"columnName": "DIRNAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "FILTER",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "text",
|
||||||
|
"columnName": "TEXT",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "enable",
|
||||||
|
"columnName": "ENABLE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "HISTORY",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "LOCAL_FAVORITES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "PROGRESS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `PAGE` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "page",
|
||||||
|
"columnName": "PAGE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "QUICK_SEARCH",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "NAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "keyword",
|
||||||
|
"columnName": "KEYWORD",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "advanceSearch",
|
||||||
|
"columnName": "ADVANCE_SEARCH",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "minRating",
|
||||||
|
"columnName": "MIN_RATING",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageFrom",
|
||||||
|
"columnName": "PAGE_FROM",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageTo",
|
||||||
|
"columnName": "PAGE_TO",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a8dd0226b286cc57a8c99ba7ad416730')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,513 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 21,
|
||||||
|
"identityHash": "6a9926f6722f8fc6f9c4426b31260e33",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "GALLERIES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT NOT NULL, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `PAGES` INTEGER NOT NULL DEFAULT 0, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "token",
|
||||||
|
"columnName": "TOKEN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "TITLE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "titleJpn",
|
||||||
|
"columnName": "TITLE_JPN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "thumbKey",
|
||||||
|
"columnName": "THUMB",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "posted",
|
||||||
|
"columnName": "POSTED",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uploader",
|
||||||
|
"columnName": "UPLOADER",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "rating",
|
||||||
|
"columnName": "RATING",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pages",
|
||||||
|
"columnName": "PAGES",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "simpleLanguage",
|
||||||
|
"columnName": "SIMPLE_LANGUAGE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "favoriteSlot",
|
||||||
|
"columnName": "FAVORITE_SLOT",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_LABELS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOAD_LABELS_LABEL",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DOWNLOAD_LABELS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOADS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`LABEL`) REFERENCES `DOWNLOAD_LABELS`(`LABEL`) ON UPDATE CASCADE ON DELETE SET NULL )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "state",
|
||||||
|
"columnName": "STATE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legacy",
|
||||||
|
"columnName": "LEGACY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOADS_LABEL",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_DOWNLOADS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "DOWNLOAD_LABELS",
|
||||||
|
"onDelete": "SET NULL",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"LABEL"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_DIRNAME",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dirname",
|
||||||
|
"columnName": "DIRNAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "FILTER",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "text",
|
||||||
|
"columnName": "TEXT",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "enable",
|
||||||
|
"columnName": "ENABLE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "HISTORY",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "LOCAL_FAVORITES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "PROGRESS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `PAGE` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "page",
|
||||||
|
"columnName": "PAGE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "QUICK_SEARCH",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "NAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "keyword",
|
||||||
|
"columnName": "KEYWORD",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "advanceSearch",
|
||||||
|
"columnName": "ADVANCE_SEARCH",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "minRating",
|
||||||
|
"columnName": "MIN_RATING",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageFrom",
|
||||||
|
"columnName": "PAGE_FROM",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageTo",
|
||||||
|
"columnName": "PAGE_TO",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ftsVersion": "FTS4",
|
||||||
|
"ftsOptions": {
|
||||||
|
"tokenizer": "simple",
|
||||||
|
"tokenizerArgs": [],
|
||||||
|
"contentTable": "GALLERIES",
|
||||||
|
"languageIdColumnName": "",
|
||||||
|
"matchInfo": "FTS4",
|
||||||
|
"notIndexedColumns": [],
|
||||||
|
"prefixSizes": [],
|
||||||
|
"preferredOrder": "ASC"
|
||||||
|
},
|
||||||
|
"contentSyncTriggers": [
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GalleryFts_BEFORE_UPDATE BEFORE UPDATE ON `GALLERIES` BEGIN DELETE FROM `GalleryFts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GalleryFts_BEFORE_DELETE BEFORE DELETE ON `GALLERIES` BEGIN DELETE FROM `GalleryFts` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GalleryFts_AFTER_UPDATE AFTER UPDATE ON `GALLERIES` BEGIN INSERT INTO `GalleryFts`(`docid`, `TITLE`, `TITLE_JPN`) VALUES (NEW.`rowid`, NEW.`TITLE`, NEW.`TITLE_JPN`); END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GalleryFts_AFTER_INSERT AFTER INSERT ON `GALLERIES` BEGIN INSERT INTO `GalleryFts`(`docid`, `TITLE`, `TITLE_JPN`) VALUES (NEW.`rowid`, NEW.`TITLE`, NEW.`TITLE_JPN`); END"
|
||||||
|
],
|
||||||
|
"tableName": "GalleryFts",
|
||||||
|
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`TITLE` TEXT NOT NULL, `TITLE_JPN` TEXT NOT NULL, content=`GALLERIES`)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "TITLE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "titleJpn",
|
||||||
|
"columnName": "TITLE_JPN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": []
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6a9926f6722f8fc6f9c4426b31260e33')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,525 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 22,
|
||||||
|
"identityHash": "f61ae3beef3739c0de7db945d2c9a4a4",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "GALLERIES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT NOT NULL, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_TAGS` TEXT, `PAGES` INTEGER NOT NULL DEFAULT 0, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "token",
|
||||||
|
"columnName": "TOKEN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "TITLE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "titleJpn",
|
||||||
|
"columnName": "TITLE_JPN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "thumbKey",
|
||||||
|
"columnName": "THUMB",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "posted",
|
||||||
|
"columnName": "POSTED",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uploader",
|
||||||
|
"columnName": "UPLOADER",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "rating",
|
||||||
|
"columnName": "RATING",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "simpleTags",
|
||||||
|
"columnName": "SIMPLE_TAGS",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pages",
|
||||||
|
"columnName": "PAGES",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "simpleLanguage",
|
||||||
|
"columnName": "SIMPLE_LANGUAGE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "favoriteSlot",
|
||||||
|
"columnName": "FAVORITE_SLOT",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_LABELS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOAD_LABELS_LABEL",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DOWNLOAD_LABELS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOADS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`LABEL`) REFERENCES `DOWNLOAD_LABELS`(`LABEL`) ON UPDATE CASCADE ON DELETE SET NULL )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "state",
|
||||||
|
"columnName": "STATE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legacy",
|
||||||
|
"columnName": "LEGACY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOADS_LABEL",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_DOWNLOADS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "DOWNLOAD_LABELS",
|
||||||
|
"onDelete": "SET NULL",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"LABEL"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_DIRNAME",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dirname",
|
||||||
|
"columnName": "DIRNAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "FILTER",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "text",
|
||||||
|
"columnName": "TEXT",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "enable",
|
||||||
|
"columnName": "ENABLE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "HISTORY",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "LOCAL_FAVORITES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "PROGRESS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `PAGE` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "page",
|
||||||
|
"columnName": "PAGE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "QUICK_SEARCH",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "NAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "keyword",
|
||||||
|
"columnName": "KEYWORD",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "advanceSearch",
|
||||||
|
"columnName": "ADVANCE_SEARCH",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "minRating",
|
||||||
|
"columnName": "MIN_RATING",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageFrom",
|
||||||
|
"columnName": "PAGE_FROM",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageTo",
|
||||||
|
"columnName": "PAGE_TO",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ftsVersion": "FTS4",
|
||||||
|
"ftsOptions": {
|
||||||
|
"tokenizer": "simple",
|
||||||
|
"tokenizerArgs": [],
|
||||||
|
"contentTable": "GALLERIES",
|
||||||
|
"languageIdColumnName": "",
|
||||||
|
"matchInfo": "FTS4",
|
||||||
|
"notIndexedColumns": [],
|
||||||
|
"prefixSizes": [],
|
||||||
|
"preferredOrder": "ASC"
|
||||||
|
},
|
||||||
|
"contentSyncTriggers": [
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GALLERIES_FTS_BEFORE_UPDATE BEFORE UPDATE ON `GALLERIES` BEGIN DELETE FROM `GALLERIES_FTS` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GALLERIES_FTS_BEFORE_DELETE BEFORE DELETE ON `GALLERIES` BEGIN DELETE FROM `GALLERIES_FTS` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GALLERIES_FTS_AFTER_UPDATE AFTER UPDATE ON `GALLERIES` BEGIN INSERT INTO `GALLERIES_FTS`(`docid`, `TITLE`, `TITLE_JPN`, `SIMPLE_TAGS`) VALUES (NEW.`rowid`, NEW.`TITLE`, NEW.`TITLE_JPN`, NEW.`SIMPLE_TAGS`); END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GALLERIES_FTS_AFTER_INSERT AFTER INSERT ON `GALLERIES` BEGIN INSERT INTO `GALLERIES_FTS`(`docid`, `TITLE`, `TITLE_JPN`, `SIMPLE_TAGS`) VALUES (NEW.`rowid`, NEW.`TITLE`, NEW.`TITLE_JPN`, NEW.`SIMPLE_TAGS`); END"
|
||||||
|
],
|
||||||
|
"tableName": "GALLERIES_FTS",
|
||||||
|
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`TITLE` TEXT NOT NULL, `TITLE_JPN` TEXT NOT NULL, `SIMPLE_TAGS` TEXT, content=`GALLERIES`)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "TITLE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "titleJpn",
|
||||||
|
"columnName": "TITLE_JPN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "simpleTags",
|
||||||
|
"columnName": "SIMPLE_TAGS",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": []
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f61ae3beef3739c0de7db945d2c9a4a4')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,564 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 23,
|
||||||
|
"identityHash": "7d1cfc4e9a9d506a73eb0520ae414d25",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "GALLERIES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TOKEN` TEXT NOT NULL, `TITLE` TEXT, `TITLE_JPN` TEXT, `THUMB` TEXT, `CATEGORY` INTEGER NOT NULL, `POSTED` TEXT, `UPLOADER` TEXT, `RATING` REAL NOT NULL, `SIMPLE_TAGS` TEXT, `PAGES` INTEGER NOT NULL DEFAULT 0, `SIMPLE_LANGUAGE` TEXT, `FAVORITE_SLOT` INTEGER NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "token",
|
||||||
|
"columnName": "TOKEN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "TITLE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "titleJpn",
|
||||||
|
"columnName": "TITLE_JPN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "thumbKey",
|
||||||
|
"columnName": "THUMB",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "posted",
|
||||||
|
"columnName": "POSTED",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uploader",
|
||||||
|
"columnName": "UPLOADER",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "rating",
|
||||||
|
"columnName": "RATING",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "simpleTags",
|
||||||
|
"columnName": "SIMPLE_TAGS",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pages",
|
||||||
|
"columnName": "PAGES",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "simpleLanguage",
|
||||||
|
"columnName": "SIMPLE_LANGUAGE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "favoriteSlot",
|
||||||
|
"columnName": "FAVORITE_SLOT",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_LABELS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LABEL` TEXT NOT NULL, `POSITION` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOAD_LABELS_LABEL",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_DOWNLOAD_LABELS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOADS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `STATE` INTEGER NOT NULL, `LEGACY` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, `LABEL` TEXT, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`LABEL`) REFERENCES `DOWNLOAD_LABELS`(`LABEL`) ON UPDATE CASCADE ON DELETE SET NULL )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "state",
|
||||||
|
"columnName": "STATE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "legacy",
|
||||||
|
"columnName": "LEGACY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "LABEL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_DOWNLOADS_LABEL",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_DOWNLOADS_LABEL` ON `${TABLE_NAME}` (`LABEL`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "DOWNLOAD_LABELS",
|
||||||
|
"onDelete": "SET NULL",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"LABEL"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"LABEL"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_DIRNAME",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `DIRNAME` TEXT NOT NULL, PRIMARY KEY(`GID`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dirname",
|
||||||
|
"columnName": "DIRNAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "DOWNLOAD_ARTISTS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `ARTIST` TEXT NOT NULL, PRIMARY KEY(`GID`, `ARTIST`), FOREIGN KEY(`GID`) REFERENCES `DOWNLOADS`(`GID`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "ARTIST",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID",
|
||||||
|
"ARTIST"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "DOWNLOADS",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "FILTER",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`MODE` INTEGER NOT NULL, `TEXT` TEXT NOT NULL, `ENABLE` INTEGER NOT NULL, `_id` INTEGER, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "text",
|
||||||
|
"columnName": "TEXT",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "enable",
|
||||||
|
"columnName": "ENABLE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "HISTORY",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "LOCAL_FAVORITES",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `TIME` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "time",
|
||||||
|
"columnName": "TIME",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "NO ACTION",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "PROGRESS",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GID` INTEGER NOT NULL, `PAGE` INTEGER NOT NULL, PRIMARY KEY(`GID`), FOREIGN KEY(`GID`) REFERENCES `GALLERIES`(`GID`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "gid",
|
||||||
|
"columnName": "GID",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "page",
|
||||||
|
"columnName": "PAGE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "GALLERIES",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"GID"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"GID"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "QUICK_SEARCH",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `NAME` TEXT NOT NULL, `MODE` INTEGER NOT NULL, `CATEGORY` INTEGER NOT NULL, `KEYWORD` TEXT, `ADVANCE_SEARCH` INTEGER NOT NULL, `MIN_RATING` INTEGER NOT NULL, `PAGE_FROM` INTEGER NOT NULL, `PAGE_TO` INTEGER NOT NULL, `POSITION` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "NAME",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "mode",
|
||||||
|
"columnName": "MODE",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "category",
|
||||||
|
"columnName": "CATEGORY",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "keyword",
|
||||||
|
"columnName": "KEYWORD",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "advanceSearch",
|
||||||
|
"columnName": "ADVANCE_SEARCH",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "minRating",
|
||||||
|
"columnName": "MIN_RATING",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageFrom",
|
||||||
|
"columnName": "PAGE_FROM",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pageTo",
|
||||||
|
"columnName": "PAGE_TO",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "position",
|
||||||
|
"columnName": "POSITION",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ftsVersion": "FTS4",
|
||||||
|
"ftsOptions": {
|
||||||
|
"tokenizer": "simple",
|
||||||
|
"tokenizerArgs": [],
|
||||||
|
"contentTable": "GALLERIES",
|
||||||
|
"languageIdColumnName": "",
|
||||||
|
"matchInfo": "FTS4",
|
||||||
|
"notIndexedColumns": [],
|
||||||
|
"prefixSizes": [],
|
||||||
|
"preferredOrder": "ASC"
|
||||||
|
},
|
||||||
|
"contentSyncTriggers": [
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GALLERIES_FTS_BEFORE_UPDATE BEFORE UPDATE ON `GALLERIES` BEGIN DELETE FROM `GALLERIES_FTS` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GALLERIES_FTS_BEFORE_DELETE BEFORE DELETE ON `GALLERIES` BEGIN DELETE FROM `GALLERIES_FTS` WHERE `docid`=OLD.`rowid`; END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GALLERIES_FTS_AFTER_UPDATE AFTER UPDATE ON `GALLERIES` BEGIN INSERT INTO `GALLERIES_FTS`(`docid`, `TITLE`, `TITLE_JPN`, `SIMPLE_TAGS`) VALUES (NEW.`rowid`, NEW.`TITLE`, NEW.`TITLE_JPN`, NEW.`SIMPLE_TAGS`); END",
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_GALLERIES_FTS_AFTER_INSERT AFTER INSERT ON `GALLERIES` BEGIN INSERT INTO `GALLERIES_FTS`(`docid`, `TITLE`, `TITLE_JPN`, `SIMPLE_TAGS`) VALUES (NEW.`rowid`, NEW.`TITLE`, NEW.`TITLE_JPN`, NEW.`SIMPLE_TAGS`); END"
|
||||||
|
],
|
||||||
|
"tableName": "GALLERIES_FTS",
|
||||||
|
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`TITLE` TEXT NOT NULL, `TITLE_JPN` TEXT NOT NULL, `SIMPLE_TAGS` TEXT, content=`GALLERIES`)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "TITLE",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "titleJpn",
|
||||||
|
"columnName": "TITLE_JPN",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "simpleTags",
|
||||||
|
"columnName": "SIMPLE_TAGS",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": []
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7d1cfc4e9a9d506a73eb0520ae414d25')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,27 +23,26 @@
|
|||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:appCategory="image"
|
android:appCategory="image"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:fullBackupContent="@xml/backup_scheme"
|
android:fullBackupContent="@xml/backup_scheme"
|
||||||
android:hasFragileUserData="true"
|
android:hasFragileUserData="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:localeConfig="@xml/locale_config"
|
android:largeHeap="@bool/large_heap"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
tools:targetApi="tiramisu">
|
tools:targetApi="tiramisu">
|
||||||
<activity
|
<profileable android:shell="true" />
|
||||||
android:name=".ui.ConfigureActivity"
|
|
||||||
android:exported="false"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:windowSoftInputMode="adjustResize" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.MainActivity"
|
android:name=".ui.MainActivity"
|
||||||
android:banner="@mipmap/ic_leanback_banner"
|
android:banner="@mipmap/ic_leanback_banner"
|
||||||
android:configChanges="screenSize"
|
android:configChanges="screenSize"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@style/AppTheme.Splash"
|
||||||
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
|
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
@@ -101,23 +100,24 @@
|
|||||||
<data android:scheme="content" />
|
<data android:scheme="content" />
|
||||||
<data android:host="*" />
|
<data android:host="*" />
|
||||||
<data android:mimeType="application/octet-stream" />
|
<data android:mimeType="application/octet-stream" />
|
||||||
<data android:mimeType="application/7z" />
|
|
||||||
<data android:mimeType="application/rar" />
|
|
||||||
<data android:mimeType="application/zip" />
|
<data android:mimeType="application/zip" />
|
||||||
<data android:mimeType="application/x-7z-compressed" />
|
<data android:mimeType="application/x-7z-compressed" />
|
||||||
<data android:mimeType="application/x-rar-compressed" />
|
|
||||||
<data android:mimeType="application/x-zip-compressed" />
|
|
||||||
<data android:mimeType="application/x-tar" />
|
<data android:mimeType="application/x-tar" />
|
||||||
<data android:mimeType="application/x-xz" />
|
<data android:mimeType="application/x-xz" />
|
||||||
<data android:mimeType="application/gzip" />
|
<data android:mimeType="application/gzip" />
|
||||||
|
<data android:mimeType="application/vnd.rar" />
|
||||||
|
<data android:mimeType="application/vnd.comicbook+zip" />
|
||||||
|
<data android:mimeType="application/vnd.comicbook-rar" />
|
||||||
|
|
||||||
|
<!-- unofficial -->
|
||||||
|
<data android:mimeType="application/7z" />
|
||||||
|
<data android:mimeType="application/rar" />
|
||||||
<data android:mimeType="application/x-cbz" />
|
<data android:mimeType="application/x-cbz" />
|
||||||
<data android:mimeType="application/x-cbr" />
|
<data android:mimeType="application/x-cbr" />
|
||||||
<data android:mimeType="application/x-cbt" />
|
<data android:mimeType="application/x-cbt" />
|
||||||
<data android:mimeType="application/x-cb7" />
|
<data android:mimeType="application/x-cb7" />
|
||||||
|
<data android:mimeType="application/x-zip-compressed" />
|
||||||
<!-- untested -->
|
<data android:mimeType="application/x-rar-compressed" />
|
||||||
<data android:mimeType="application/vnd.comicbook+zip" />
|
|
||||||
<data android:mimeType="application/vnd.comicbook-rar" />
|
|
||||||
<data android:mimeType="application/x-compressed-tar" />
|
<data android:mimeType="application/x-compressed-tar" />
|
||||||
<data android:mimeType="application/x-bzip-compressed-tar" />
|
<data android:mimeType="application/x-bzip-compressed-tar" />
|
||||||
<data android:mimeType="application/x-lzma-compressed-tar" />
|
<data android:mimeType="application/x-lzma-compressed-tar" />
|
||||||
@@ -134,8 +134,6 @@
|
|||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||||
<activity
|
|
||||||
android:name=".ui.WebViewActivity" />
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
From d1db03ebd9989d10a5a905b792b712c6487a0057 Mon Sep 17 00:00:00 2001
|
||||||
|
From: FooIbar <118464521+FooIbar@users.noreply.github.com>
|
||||||
|
Date: Sat, 24 Feb 2024 12:03:15 +0000
|
||||||
|
Subject: [PATCH] Don't build other binaries
|
||||||
|
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 8 ++++----
|
||||||
|
1 file changed, 4 insertions(+), 4 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index a346dd02..52b5d0d8 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -1567,7 +1567,7 @@ endif()
|
||||||
|
# xzdec and lzmadec
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
-if(HAVE_DECODERS AND (NOT MSVC OR MSVC_VERSION GREATER_EQUAL 1900))
|
||||||
|
+if(0)
|
||||||
|
foreach(XZDEC xzdec lzmadec)
|
||||||
|
add_executable("${XZDEC}"
|
||||||
|
src/common/sysdefs.h
|
||||||
|
@@ -1624,7 +1624,7 @@ endif()
|
||||||
|
# lzmainfo
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
-if(HAVE_DECODERS AND (NOT MSVC OR MSVC_VERSION GREATER_EQUAL 1900))
|
||||||
|
+if(0)
|
||||||
|
add_executable(lzmainfo
|
||||||
|
src/common/sysdefs.h
|
||||||
|
src/common/tuklib_common.h
|
||||||
|
@@ -1680,7 +1680,7 @@ endif()
|
||||||
|
# xz
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
-if(NOT MSVC OR MSVC_VERSION GREATER_EQUAL 1900)
|
||||||
|
+if(0)
|
||||||
|
add_executable(xz
|
||||||
|
src/common/mythread.h
|
||||||
|
src/common/sysdefs.h
|
||||||
|
@@ -1914,7 +1914,7 @@ endif()
|
||||||
|
# Scripts
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
-if(UNIX)
|
||||||
|
+if(0)
|
||||||
|
# NOTE: This isn't as sophisticated as in the Autotools build which
|
||||||
|
# uses posix-shell.m4 but hopefully this doesn't need to be either.
|
||||||
|
# CMake likely won't be used on as many (old) obscure systems as the
|
||||||
|
--
|
||||||
|
2.34.1
|
||||||
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
From 2bda36800a5424c44378bbc0cc8bb5d26a0f34fa Mon Sep 17 00:00:00 2001
|
|
||||||
From: tarsin <yuanqingxiang233@163.com>
|
|
||||||
Date: Sat, 22 Jul 2023 08:30:20 +0800
|
|
||||||
Subject: [PATCH] Don't build other binarys
|
|
||||||
|
|
||||||
---
|
|
||||||
CMakeLists.txt | 4 ++--
|
|
||||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
|
||||||
index 55cd358..52b57aa 100644
|
|
||||||
--- a/CMakeLists.txt
|
|
||||||
+++ b/CMakeLists.txt
|
|
||||||
@@ -962,7 +962,7 @@ check_symbol_exists(getopt_long getopt.h HAVE_GETOPT_LONG)
|
|
||||||
# xzdec
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
-if(HAVE_GETOPT_LONG AND HAVE_DECODERS)
|
|
||||||
+if(0)
|
|
||||||
add_executable(xzdec
|
|
||||||
src/common/sysdefs.h
|
|
||||||
src/common/tuklib_common.h
|
|
||||||
@@ -1008,7 +1008,7 @@ endif()
|
|
||||||
# xz
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
-if(NOT MSVC AND HAVE_GETOPT_LONG)
|
|
||||||
+if(0)
|
|
||||||
add_executable(xz
|
|
||||||
src/common/mythread.h
|
|
||||||
src/common/sysdefs.h
|
|
||||||
--
|
|
||||||
2.41.0
|
|
||||||
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
From 57da49f9b0ed8538573f6cfe289f835ee0078eec Mon Sep 17 00:00:00 2001
|
|
||||||
From: tarsin <yuanqingxiang233@163.com>
|
|
||||||
Date: Sat, 22 Jul 2023 17:50:08 +0800
|
|
||||||
Subject: [PATCH] Hide symbols
|
|
||||||
|
|
||||||
---
|
|
||||||
CMakeLists.txt | 4 ++--
|
|
||||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
|
||||||
index 646484b7..67a71272 100644
|
|
||||||
--- a/CMakeLists.txt
|
|
||||||
+++ b/CMakeLists.txt
|
|
||||||
@@ -2060,10 +2060,10 @@ SET(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
|
|
||||||
SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -fvisibility=hidden -Werror")
|
|
||||||
CHECK_C_SOURCE_COMPILES("void __attribute__((visibility(\"default\"))) foo(void);
|
|
||||||
int main() { return 0; }" HAVE_VISIBILITY_ATTR)
|
|
||||||
-IF (HAVE_VISIBILITY_ATTR)
|
|
||||||
+IF (0)
|
|
||||||
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
|
|
||||||
ADD_DEFINITIONS(-D__LIBARCHIVE_ENABLE_VISIBILITY)
|
|
||||||
-ENDIF(HAVE_VISIBILITY_ATTR)
|
|
||||||
+ENDIF(0)
|
|
||||||
SET(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}")
|
|
||||||
|
|
||||||
# Generate "config.h" from "build/cmake/config.h.in"
|
|
||||||
--
|
|
||||||
2.41.0
|
|
||||||
|
|
||||||
+7
-7
@@ -1,17 +1,17 @@
|
|||||||
From c9298fd6dc0a8b04dd4b2247118b9cb895c46e58 Mon Sep 17 00:00:00 2001
|
From aee8f7147ddb262fd9e5feee8d5b17094cf3470f Mon Sep 17 00:00:00 2001
|
||||||
From: tarsin <yuanqingxiang233@163.com>
|
From: FooIbar <118464521+FooIbar@users.noreply.github.com>
|
||||||
Date: Sat, 23 Jul 2022 16:05:36 +0800
|
Date: Tue, 26 Sep 2023 00:09:25 +0800
|
||||||
Subject: [PATCH 2/2] Insert link libs
|
Subject: [PATCH] Insert link libs
|
||||||
|
|
||||||
---
|
---
|
||||||
CMakeLists.txt | 2 +-
|
CMakeLists.txt | 2 +-
|
||||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
index 713e3bc5..7247640d 100644
|
index ec97e4c7..420f204c 100644
|
||||||
--- a/CMakeLists.txt
|
--- a/CMakeLists.txt
|
||||||
+++ b/CMakeLists.txt
|
+++ b/CMakeLists.txt
|
||||||
@@ -415,7 +415,7 @@ IF(DEFINED __GNUWIN32PATH AND EXISTS "${__GNUWIN32PATH}")
|
@@ -438,7 +438,7 @@ IF(DEFINED __GNUWIN32PATH AND EXISTS "${__GNUWIN32PATH}")
|
||||||
# # endif
|
# # endif
|
||||||
ENDIF(DEFINED __GNUWIN32PATH AND EXISTS "${__GNUWIN32PATH}")
|
ENDIF(DEFINED __GNUWIN32PATH AND EXISTS "${__GNUWIN32PATH}")
|
||||||
|
|
||||||
@@ -21,5 +21,5 @@ index 713e3bc5..7247640d 100644
|
|||||||
# Find ZLIB
|
# Find ZLIB
|
||||||
#
|
#
|
||||||
--
|
--
|
||||||
2.39.2
|
2.34.1
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
cmake_minimum_required(VERSION 3.14)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
project(ehviewer C ASM)
|
project(ehviewer C)
|
||||||
|
include(ExternalProject)
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
@@ -16,27 +17,49 @@ if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
|||||||
-mllvm -polly-invariant-load-hoisting")
|
-mllvm -polly-invariant-load-hoisting")
|
||||||
endif (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
endif (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
|
||||||
# Change the default path of fetch content to avoid downloading the same dependencies per architecture
|
option(BUILD_TESTING OFF)
|
||||||
get_filename_component(deps "../_deps" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
|
||||||
set(FETCHCONTENT_BASE_DIR ${deps})
|
|
||||||
|
|
||||||
set(BUILD_TESTING 0)
|
set(XZ_PATCH ${CMAKE_CURRENT_LIST_DIR}/0001-Don-t-build-other-binaries.patch)
|
||||||
|
FetchContent_Declare(
|
||||||
FetchContent_Declare(liblzma
|
liblzma
|
||||||
GIT_REPOSITORY https://git.tukaani.org/xz.git
|
GIT_REPOSITORY https://github.com/tukaani-project/xz.git
|
||||||
GIT_TAG v5.4.3
|
GIT_TAG v5.6.2
|
||||||
PATCH_COMMAND git apply ${CMAKE_CURRENT_LIST_DIR}/0001-Don-t-build-other-binarys.patch || cd .
|
PATCH_COMMAND git apply --check -R ${XZ_PATCH} || git apply ${XZ_PATCH}
|
||||||
)
|
)
|
||||||
|
|
||||||
FetchContent_MakeAvailable(liblzma)
|
FetchContent_MakeAvailable(liblzma)
|
||||||
include_directories(${liblzma_SOURCE_DIR}/src/liblzma/api)
|
include_directories(${liblzma_SOURCE_DIR}/src/liblzma/api)
|
||||||
|
|
||||||
# Build GNUTLS libnettle
|
# Build GNUTLS libnettle
|
||||||
add_subdirectory(nettle)
|
if (CMAKE_HOST_SYSTEM_NAME STREQUAL Windows)
|
||||||
|
execute_process(
|
||||||
|
COMMAND wsl wslpath ${CMAKE_CURRENT_LIST_DIR}
|
||||||
|
OUTPUT_VARIABLE CURRENT_DIR
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
)
|
||||||
|
else ()
|
||||||
|
set(CURRENT_DIR ${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
endif ()
|
||||||
|
set(NDK_VERSION "${ANDROID_NDK_MAJOR}.${ANDROID_NDK_MINOR}.${ANDROID_NDK_BUILD}")
|
||||||
|
ExternalProject_Add(
|
||||||
|
nettle
|
||||||
|
PREFIX nettle
|
||||||
|
URL https://ftp.gnu.org/gnu/nettle/nettle-3.10.tar.gz
|
||||||
|
URL_MD5 c61453139d5fb44e9cdcc5b684b26e55
|
||||||
|
CONFIGURE_COMMAND bash ${CURRENT_DIR}/configure_nettle.sh ${NDK_VERSION} ${CMAKE_C_COMPILER_TARGET}
|
||||||
|
BUILD_COMMAND bash -c "make libnettle.a CFLAGS='${CMAKE_C_FLAGS}'"
|
||||||
|
INSTALL_COMMAND ""
|
||||||
|
BUILD_IN_SOURCE 1
|
||||||
|
BUILD_BYPRODUCTS nettle/src/nettle/libnettle.a
|
||||||
|
)
|
||||||
|
|
||||||
|
ExternalProject_Get_Property(nettle SOURCE_DIR)
|
||||||
|
include_directories(${SOURCE_DIR}/..)
|
||||||
|
add_library(libnettle STATIC IMPORTED)
|
||||||
|
set_property(TARGET libnettle PROPERTY IMPORTED_LOCATION ${SOURCE_DIR}/libnettle.a)
|
||||||
|
add_dependencies(libnettle nettle)
|
||||||
|
|
||||||
# Configure libnettle support for libarchive
|
# Configure libnettle support for libarchive
|
||||||
include_directories(nettle)
|
|
||||||
include_directories(.)
|
|
||||||
set(HAVE_LIBNETTLE 1)
|
set(HAVE_LIBNETTLE 1)
|
||||||
set(HAVE_NETTLE_AES_H 1)
|
set(HAVE_NETTLE_AES_H 1)
|
||||||
set(HAVE_NETTLE_HMAC_H 1)
|
set(HAVE_NETTLE_HMAC_H 1)
|
||||||
@@ -52,6 +75,7 @@ SET(HAVE_LZMA_STREAM_ENCODER_MT 1)
|
|||||||
SET(HAVE_LZMADEC_H 1)
|
SET(HAVE_LZMADEC_H 1)
|
||||||
SET(HAVE_LIBLZMADEC 1)
|
SET(HAVE_LIBLZMADEC 1)
|
||||||
|
|
||||||
|
option(ENABLE_OPENSSL OFF)
|
||||||
option(ENABLE_TAR OFF)
|
option(ENABLE_TAR OFF)
|
||||||
option(ENABLE_CPIO OFF)
|
option(ENABLE_CPIO OFF)
|
||||||
option(ENABLE_CAT OFF)
|
option(ENABLE_CAT OFF)
|
||||||
@@ -59,17 +83,29 @@ option(ENABLE_UNZIP OFF)
|
|||||||
option(ENABLE_TEST OFF)
|
option(ENABLE_TEST OFF)
|
||||||
|
|
||||||
# Configure libarchive link's static lib
|
# Configure libarchive link's static lib
|
||||||
SET(LIBARCHIVE_CUSTOM_LIBS "nettle" "liblzma")
|
SET(LIBARCHIVE_CUSTOM_LIBS "libnettle" "liblzma")
|
||||||
|
|
||||||
FetchContent_Declare(libarchive
|
set(LIBARCHIVE_PATCH ${CMAKE_CURRENT_LIST_DIR}/0001-Insert-link-libs.patch)
|
||||||
GIT_REPOSITORY https://github.com/libarchive/libarchive
|
FetchContent_Declare(
|
||||||
GIT_TAG v3.7.0
|
libarchive
|
||||||
PATCH_COMMAND git apply ${CMAKE_CURRENT_LIST_DIR}/0001-Hide-symbols.patch ${CMAKE_CURRENT_LIST_DIR}/0002-Insert-link-libs.patch || cd .
|
GIT_REPOSITORY https://github.com/libarchive/libarchive.git
|
||||||
)
|
GIT_TAG v3.7.4
|
||||||
|
PATCH_COMMAND git apply --check -R ${LIBARCHIVE_PATCH} || git apply ${LIBARCHIVE_PATCH}
|
||||||
|
)
|
||||||
|
|
||||||
FetchContent_MakeAvailable(libarchive)
|
FetchContent_MakeAvailable(libarchive)
|
||||||
include_directories(${libarchive_SOURCE_DIR}/libarchive)
|
include_directories(${libarchive_SOURCE_DIR}/libarchive)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
Corrosion
|
||||||
|
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
|
||||||
|
GIT_TAG v0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(Corrosion)
|
||||||
|
corrosion_import_crate(MANIFEST_PATH ../rust/Cargo.toml)
|
||||||
|
corrosion_add_target_local_rustflags(ehviewer_rust "-Clink-args=-Wl,-z,max-page-size=16384")
|
||||||
|
|
||||||
# Build and link our app's native lib
|
# Build and link our app's native lib
|
||||||
add_library(${PROJECT_NAME} SHARED archive.c gifutils.c natsort/strnatcmp.c)
|
add_library(${PROJECT_NAME} SHARED archive.c border.c gifutils.c hash.c natsort/strnatcmp.c)
|
||||||
target_link_libraries(${PROJECT_NAME} archive_static log)
|
target_link_libraries(${PROJECT_NAME} ehviewer_rust archive_static log jnigraphics -Wl,--exclude-libs,ALL)
|
||||||
+132
-95
@@ -34,7 +34,6 @@
|
|||||||
|
|
||||||
#include "natsort/strnatcmp.h"
|
#include "natsort/strnatcmp.h"
|
||||||
#include "ehviewer.h"
|
#include "ehviewer.h"
|
||||||
#include "spinlock.h"
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int using;
|
int using;
|
||||||
@@ -49,18 +48,18 @@ typedef struct {
|
|||||||
size_t size;
|
size_t size;
|
||||||
} entry;
|
} entry;
|
||||||
|
|
||||||
_Atomic mcs_lock ctx_lock;
|
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
static archive_ctx **ctx_pool;
|
static archive_ctx **ctx_pool = NULL;
|
||||||
#define CTX_POOL_SIZE 20
|
#define CTX_POOL_SIZE 20
|
||||||
|
|
||||||
static void *mempool = NULL;
|
static void *mempool = NULL;
|
||||||
static size_t *mempoolofs = NULL;
|
static size_t *mempoolofs = NULL;
|
||||||
|
static int page_size = 0;
|
||||||
|
|
||||||
#define PAGE_ALIGN(x) ((x + ~PAGE_MASK) & PAGE_MASK)
|
#define PAGE_ALIGN(x) ((x + page_size - 1) & ~(page_size - 1))
|
||||||
|
|
||||||
#define MEMPOOL_ADDR_BY_SORTED_IDX(x) (mempool + (index ? mempoolofs[index - 1] : 0))
|
#define MEMPOOL_ADDR_BY_SORTED_IDX(x) (mempool + (index ? mempoolofs[index - 1] : 0))
|
||||||
#define MEMPOOL_SIZE (mempoolofs[entryCount - 1])
|
#define MEMPOOL_SIZE (mempoolofs[entryCount - 1])
|
||||||
#define MEMPOOL_ENTITY_SIZE(x) PAGE_ALIGN(entries[x].size)
|
|
||||||
|
|
||||||
#define PROT_RW (PROT_WRITE | PROT_READ)
|
#define PROT_RW (PROT_WRITE | PROT_READ)
|
||||||
#define MAP_ANON_POOL (MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE | MAP_UNINITIALIZED)
|
#define MAP_ANON_POOL (MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE | MAP_UNINITIALIZED)
|
||||||
@@ -86,6 +85,8 @@ const char supportExt[10][6] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static inline int filename_is_playable_file(const char *name) {
|
static inline int filename_is_playable_file(const char *name) {
|
||||||
|
if (!name)
|
||||||
|
return false;
|
||||||
const char *dotptr = strrchr(name, '.');
|
const char *dotptr = strrchr(name, '.');
|
||||||
if (!dotptr++)
|
if (!dotptr++)
|
||||||
return false;
|
return false;
|
||||||
@@ -96,26 +97,39 @@ static inline int filename_is_playable_file(const char *name) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline const char *archive_entry_pathname2(struct archive_entry *entry) {
|
||||||
|
const char *name = archive_entry_pathname_utf8(entry);
|
||||||
|
return name ? name : archive_entry_pathname(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool archive_entry_is_file(struct archive_entry *entry) {
|
||||||
|
return archive_entry_filetype(entry) == AE_IFREG;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool archive_entry_is_playable(struct archive_entry *entry) {
|
||||||
|
return archive_entry_is_file(entry) &&
|
||||||
|
filename_is_playable_file(archive_entry_pathname2(entry));
|
||||||
|
}
|
||||||
|
|
||||||
static inline int compare_entries(const void *a, const void *b) {
|
static inline int compare_entries(const void *a, const void *b) {
|
||||||
const char *fa = ((entry *) a)->filename;
|
const char *fa = ((entry *) a)->filename;
|
||||||
const char *fb = ((entry *) b)->filename;
|
const char *fb = ((entry *) b)->filename;
|
||||||
return strnatcmp(fa, fb);
|
return strnatcmp(fa, fb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static long archive_map_entries_index(archive_ctx *ctx) {
|
static long archive_map_entries_index(archive_ctx *ctx, bool sort) {
|
||||||
long count = 0;
|
long count = 0;
|
||||||
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
|
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
|
||||||
const char *name = archive_entry_pathname(ctx->entry);
|
const char *name = archive_entry_pathname2(ctx->entry);
|
||||||
if (filename_is_playable_file(name)) {
|
if (archive_entry_is_file(ctx->entry) && filename_is_playable_file(name)) {
|
||||||
entries[count].filename = strdup(name);
|
entries[count].filename = strdup(name);
|
||||||
entries[count].index = count;
|
entries[count].index = count;
|
||||||
entries[count].size = archive_entry_size(ctx->entry);
|
entries[count].size = archive_entry_size(ctx->entry);
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!count)
|
if (sort)
|
||||||
LOGE("%s", archive_error_string(ctx->arc));
|
qsort(entries, entryCount, sizeof(entry), compare_entries);
|
||||||
qsort(entries, entryCount, sizeof(entry), compare_entries);
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,39 +149,27 @@ static bool archive_prealloc_mempool() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define ADDR_IN_FILE_MAPPING(addr) (addr >= archiveAddr && addr < archiveAddr + archiveSize)
|
||||||
// Mincore does not support anon mapping yet
|
|
||||||
// Now that we use MADV_FREE, how can we detect whether pages is reclaimed?
|
|
||||||
static bool is_mempool_pages_present_and_lock(int index) {
|
|
||||||
void *addr = MEMPOOL_ADDR_BY_SORTED_IDX(index);
|
|
||||||
size_t size = MEMPOOL_ENTITY_SIZE(index);
|
|
||||||
mlock(addr, size);
|
|
||||||
#if 0
|
|
||||||
unsigned char vec[size / PAGE_SIZE];
|
|
||||||
unsigned char r = 1;
|
|
||||||
mincore(addr, size, vec);
|
|
||||||
for (int i = 0; i < size / PAGE_SIZE; ++i) r &= vec[i];
|
|
||||||
return r;
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define MEMPOOL_IN_CORE_AND_LOCK(x) is_mempool_pages_present_and_lock(x)
|
|
||||||
|
|
||||||
static void mempool_release_pages(void *addr, size_t size) {
|
static void mempool_release_pages(void *addr, size_t size) {
|
||||||
size = PAGE_ALIGN(size);
|
size = PAGE_ALIGN(size);
|
||||||
munlock(addr, size);
|
madvise_log_if_error(addr, size, MADV_DONTNEED);
|
||||||
int ret = madvise(addr, size, MADV_FREE);
|
}
|
||||||
if (ret == -EINVAL) madvise(addr, size, MADV_DONTNEED);
|
|
||||||
|
static bool kernel_can_prefault = true;
|
||||||
|
|
||||||
|
static void mempool_prefault_pages(void *addr, size_t size) {
|
||||||
|
size = PAGE_ALIGN(size);
|
||||||
|
if (kernel_can_prefault) {
|
||||||
|
if (madvise(addr, size, MADV_POPULATE_WRITE)) kernel_can_prefault = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static long archive_list_all_entries(archive_ctx *ctx) {
|
static long archive_list_all_entries(archive_ctx *ctx) {
|
||||||
long count = 0;
|
long count = 0;
|
||||||
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK)
|
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK)
|
||||||
if (filename_is_playable_file(archive_entry_pathname(ctx->entry)))
|
if (archive_entry_is_playable(ctx->entry))
|
||||||
count++;
|
count++;
|
||||||
if (!count)
|
|
||||||
LOGE("%s", archive_error_string(ctx->arc));
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +214,7 @@ static int archive_alloc_ctx(archive_ctx **ctxptr) {
|
|||||||
|
|
||||||
static int archive_skip_to_index(archive_ctx *ctx, int index) {
|
static int archive_skip_to_index(archive_ctx *ctx, int index) {
|
||||||
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
|
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
|
||||||
if (!filename_is_playable_file(archive_entry_pathname(ctx->entry)))
|
if (!archive_entry_is_playable(ctx->entry))
|
||||||
continue;
|
continue;
|
||||||
if (ctx->next_index++ == index) {
|
if (ctx->next_index++ == index) {
|
||||||
return ctx->next_index - 1;
|
return ctx->next_index - 1;
|
||||||
@@ -224,8 +226,7 @@ static int archive_skip_to_index(archive_ctx *ctx, int index) {
|
|||||||
static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
|
static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
|
||||||
int ret;
|
int ret;
|
||||||
archive_ctx *ctx = NULL;
|
archive_ctx *ctx = NULL;
|
||||||
mcs_lock_t local_lock;
|
pthread_mutex_lock(&mutex);
|
||||||
lock_mcs(&ctx_lock, &local_lock);
|
|
||||||
for (int i = 0; i < CTX_POOL_SIZE; i++) {
|
for (int i = 0; i < CTX_POOL_SIZE; i++) {
|
||||||
if (!ctx_pool[i])
|
if (!ctx_pool[i])
|
||||||
continue;
|
continue;
|
||||||
@@ -240,7 +241,7 @@ static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
|
|||||||
}
|
}
|
||||||
if (ctx)
|
if (ctx)
|
||||||
ctx->using = 1;
|
ctx->using = 1;
|
||||||
unlock_mcs(&ctx_lock, &local_lock);
|
pthread_mutex_unlock(&mutex);
|
||||||
|
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
archive_ctx *victimCtx = NULL;
|
archive_ctx *victimCtx = NULL;
|
||||||
@@ -249,7 +250,7 @@ static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
|
|||||||
ret = archive_alloc_ctx(&ctx);
|
ret = archive_alloc_ctx(&ctx);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
lock_mcs(&ctx_lock, &local_lock);
|
pthread_mutex_lock(&mutex);
|
||||||
for (int i = 0; i < CTX_POOL_SIZE; i++) {
|
for (int i = 0; i < CTX_POOL_SIZE; i++) {
|
||||||
if (!ctx_pool[i]) {
|
if (!ctx_pool[i]) {
|
||||||
ctx_pool[i] = ctx;
|
ctx_pool[i] = ctx;
|
||||||
@@ -264,7 +265,7 @@ static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (replace) ctx_pool[victimIdx] = ctx;
|
if (replace) ctx_pool[victimIdx] = ctx;
|
||||||
unlock_mcs(&ctx_lock, &local_lock);
|
pthread_mutex_unlock(&mutex);
|
||||||
if (replace) archive_release_ctx(victimCtx);
|
if (replace) archive_release_ctx(victimCtx);
|
||||||
}
|
}
|
||||||
ret = archive_skip_to_index(ctx, idx);
|
ret = archive_skip_to_index(ctx, idx);
|
||||||
@@ -279,19 +280,17 @@ static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_openArchive(JNIEnv *env, jclass thiz, jint fd,
|
Java_com_hippo_ehviewer_jni_ArchiveKt_openArchive(JNIEnv *env, jclass thiz, jint fd, jlong size, jboolean sort_entries) {
|
||||||
jlong size) {
|
|
||||||
EH_UNUSED(env);
|
EH_UNUSED(env);
|
||||||
EH_UNUSED(thiz);
|
EH_UNUSED(thiz);
|
||||||
|
page_size = getpagesize();
|
||||||
archive_ctx *ctx = NULL;
|
archive_ctx *ctx = NULL;
|
||||||
int mmap_flags = MAP_PRIVATE;
|
archiveAddr = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||||
archiveAddr = mmap64(0, size, PROT_READ, mmap_flags, fd, 0);
|
|
||||||
if (archiveAddr == MAP_FAILED) {
|
if (archiveAddr == MAP_FAILED) {
|
||||||
LOGE("%s%s", "mmap64 failed with error ", strerror(errno));
|
LOGE("%s%s", "mmap failed with error ", strerror(errno));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
archiveSize = size;
|
archiveSize = size;
|
||||||
madvise_log_if_error(archiveAddr, archiveSize, MADV_WILLNEED);
|
|
||||||
ctx_pool = calloc(CTX_POOL_SIZE, sizeof(archive_ctx **));
|
ctx_pool = calloc(CTX_POOL_SIZE, sizeof(archive_ctx **));
|
||||||
if (!ctx_pool) {
|
if (!ctx_pool) {
|
||||||
LOGE("Allocate archive ctx pool failed:ENOMEM");
|
LOGE("Allocate archive ctx pool failed:ENOMEM");
|
||||||
@@ -305,6 +304,11 @@ Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_openArchive(JNIEnv *env, jcl
|
|||||||
} else {
|
} else {
|
||||||
entryCount = archive_list_all_entries(ctx);
|
entryCount = archive_list_all_entries(ctx);
|
||||||
LOGI("%s%zu%s", "Found ", entryCount, " image entries in archive");
|
LOGI("%s%zu%s", "Found ", entryCount, " image entries in archive");
|
||||||
|
if (!entryCount) {
|
||||||
|
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
|
||||||
|
archive_release_ctx(ctx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// We must read through the file|vm then we can know whether it is encrypted
|
// We must read through the file|vm then we can know whether it is encrypted
|
||||||
int encryptRet = archive_read_has_encrypted_entries(ctx->arc);
|
int encryptRet = archive_read_has_encrypted_entries(ctx->arc);
|
||||||
@@ -340,7 +344,7 @@ Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_openArchive(JNIEnv *env, jcl
|
|||||||
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
|
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
|
||||||
} else {
|
} else {
|
||||||
entries = calloc(entryCount, sizeof(entry));
|
entries = calloc(entryCount, sizeof(entry));
|
||||||
r = archive_map_entries_index(ctx);
|
r = archive_map_entries_index(ctx, sort_entries);
|
||||||
if (!archive_prealloc_mempool()) {
|
if (!archive_prealloc_mempool()) {
|
||||||
r = 0;
|
r = 0;
|
||||||
for (int i = 0; i < entryCount; ++i) {
|
for (int i = 0; i < entryCount; ++i) {
|
||||||
@@ -354,44 +358,52 @@ Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_openArchive(JNIEnv *env, jcl
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma clang diagnostic push
|
|
||||||
#pragma ide diagnostic ignored "ConstantConditionsOC"
|
|
||||||
#pragma ide diagnostic ignored "UnreachableCode"
|
|
||||||
|
|
||||||
JNIEXPORT jobject JNICALL
|
JNIEXPORT jobject JNICALL
|
||||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_extractToByteBuffer(JNIEnv *env, jclass thiz,
|
Java_com_hippo_ehviewer_jni_ArchiveKt_extractToByteBuffer(JNIEnv *env, jclass thiz, jint index) {
|
||||||
jint index) {
|
|
||||||
EH_UNUSED(env);
|
EH_UNUSED(env);
|
||||||
EH_UNUSED(thiz);
|
EH_UNUSED(thiz);
|
||||||
void *addr = MEMPOOL_ADDR_BY_SORTED_IDX(index);
|
void *addr = MEMPOOL_ADDR_BY_SORTED_IDX(index);
|
||||||
size_t size = entries[index].size;
|
size_t size = entries[index].size;
|
||||||
if (!MEMPOOL_IN_CORE_AND_LOCK(index)) {
|
index = entries[index].index;
|
||||||
index = entries[index].index;
|
archive_ctx *ctx = NULL;
|
||||||
archive_ctx *ctx = NULL;
|
int ret = archive_get_ctx(&ctx, index);
|
||||||
int ret;
|
if (ret) return 0;
|
||||||
ret = archive_get_ctx(&ctx, index);
|
void *buffer = NULL;
|
||||||
if (ret) return 0;
|
size_t buffer_size = 0;
|
||||||
ret = archive_read_data(ctx->arc, addr, size);
|
la_int64_t output_ofs = 0;
|
||||||
|
archive_read_data_block(ctx->arc, (const void **) &buffer, &buffer_size, &output_ofs);
|
||||||
|
bool zero_copy = ADDR_IN_FILE_MAPPING(buffer) && !output_ofs && buffer_size == size;
|
||||||
|
if (zero_copy) {
|
||||||
ctx->using = 0;
|
ctx->using = 0;
|
||||||
if (ret == size)
|
return (*env)->NewDirectByteBuffer(env, buffer, buffer_size);
|
||||||
return (*env)->NewDirectByteBuffer(env, addr, size);
|
|
||||||
if (ret != size)
|
|
||||||
LOGE("%s", "No enough data read, WTF?");
|
|
||||||
if (ret < 0)
|
|
||||||
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
|
|
||||||
mempool_release_pages(addr, size);
|
|
||||||
} else {
|
} else {
|
||||||
return (*env)->NewDirectByteBuffer(env, addr, size);
|
size_t bytes = 0;
|
||||||
|
mempool_prefault_pages(addr, size);
|
||||||
|
do {
|
||||||
|
memcpy(addr + output_ofs, buffer, buffer_size);
|
||||||
|
bytes += buffer_size;
|
||||||
|
ret = archive_read_data_block(ctx->arc, (const void **) &buffer, &buffer_size, &output_ofs);
|
||||||
|
} while (bytes != size && !ret);
|
||||||
|
ctx->using = 0;
|
||||||
|
if (bytes == size) {
|
||||||
|
return (*env)->NewDirectByteBuffer(env, addr, size);
|
||||||
|
} else {
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
|
||||||
|
} else {
|
||||||
|
LOGE("%s", "No enough data read, WTF?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mempool_release_pages(addr, size);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma clang diagnostic pop
|
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_closeArchive(JNIEnv *env, jclass thiz) {
|
Java_com_hippo_ehviewer_jni_ArchiveKt_closeArchive(JNIEnv *env, jclass thiz) {
|
||||||
EH_UNUSED(env);
|
EH_UNUSED(env);
|
||||||
EH_UNUSED(thiz);
|
EH_UNUSED(thiz);
|
||||||
|
if (!ctx_pool) return;
|
||||||
for (int i = 0; i < CTX_POOL_SIZE; i++)
|
for (int i = 0; i < CTX_POOL_SIZE; i++)
|
||||||
archive_release_ctx(ctx_pool[i]);
|
archive_release_ctx(ctx_pool[i]);
|
||||||
free(passwd);
|
free(passwd);
|
||||||
@@ -408,15 +420,14 @@ Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_closeArchive(JNIEnv *env, jc
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_needPassword(JNIEnv *env, jclass thiz) {
|
Java_com_hippo_ehviewer_jni_ArchiveKt_needPassword(JNIEnv *env, jclass thiz) {
|
||||||
EH_UNUSED(env);
|
EH_UNUSED(env);
|
||||||
EH_UNUSED(thiz);
|
EH_UNUSED(thiz);
|
||||||
return need_encrypt;
|
return need_encrypt;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_providePassword(JNIEnv *env, jclass thiz,
|
Java_com_hippo_ehviewer_jni_ArchiveKt_providePassword(JNIEnv *env, jclass thiz, jstring str) {
|
||||||
jstring str) {
|
|
||||||
EH_UNUSED(thiz);
|
EH_UNUSED(thiz);
|
||||||
struct archive_entry *entry;
|
struct archive_entry *entry;
|
||||||
archive_ctx *ctx;
|
archive_ctx *ctx;
|
||||||
@@ -427,11 +438,11 @@ Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_providePassword(JNIEnv *env,
|
|||||||
LOGE("Allocate passwd buffer failed");
|
LOGE("Allocate passwd buffer failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
strcpy(passwd, (*env)->GetStringUTFChars(env, str, NULL));
|
(*env)->GetStringUTFRegion(env, str, 0, len, passwd);
|
||||||
archive_alloc_ctx(&ctx);
|
archive_alloc_ctx(&ctx);
|
||||||
void *tmpBuf = alloca(4096);
|
void *tmpBuf = alloca(4096);
|
||||||
while (archive_read_next_header(ctx->arc, &entry) == ARCHIVE_OK) {
|
while (archive_read_next_header(ctx->arc, &entry) == ARCHIVE_OK) {
|
||||||
if (!filename_is_playable_file(archive_entry_pathname(entry)))
|
if (!archive_entry_is_playable(entry))
|
||||||
continue;
|
continue;
|
||||||
if (!archive_entry_is_encrypted(entry))
|
if (!archive_entry_is_encrypted(entry))
|
||||||
continue;
|
continue;
|
||||||
@@ -446,24 +457,15 @@ Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_providePassword(JNIEnv *env,
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_getFilename(JNIEnv *env, jclass thiz,
|
Java_com_hippo_ehviewer_jni_ArchiveKt_getExtension(JNIEnv *env, jclass thiz, jint index) {
|
||||||
jint index) {
|
|
||||||
EH_UNUSED(env);
|
EH_UNUSED(env);
|
||||||
EH_UNUSED(thiz);
|
EH_UNUSED(thiz);
|
||||||
index = entries[index].index;
|
const char *ext = strrchr(entries[index].filename, '.') + 1;
|
||||||
archive_ctx *ctx = NULL;
|
return (*env)->NewStringUTF(env, ext);
|
||||||
int ret;
|
|
||||||
ret = archive_get_ctx(&ctx, index);
|
|
||||||
if (ret)
|
|
||||||
return NULL;
|
|
||||||
jstring str = (*env)->NewStringUTF(env, archive_entry_pathname(ctx->entry));
|
|
||||||
ctx->using = 0;
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_extractToFd(JNIEnv *env, jclass thiz,
|
Java_com_hippo_ehviewer_jni_ArchiveKt_extractToFd(JNIEnv *env, jclass thiz, jint index, jint fd) {
|
||||||
jint index, jint fd) {
|
|
||||||
EH_UNUSED(env);
|
EH_UNUSED(env);
|
||||||
EH_UNUSED(thiz);
|
EH_UNUSED(thiz);
|
||||||
index = entries[index].index;
|
index = entries[index].index;
|
||||||
@@ -471,16 +473,51 @@ Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_extractToFd(JNIEnv *env, jcl
|
|||||||
int ret;
|
int ret;
|
||||||
ret = archive_get_ctx(&ctx, index);
|
ret = archive_get_ctx(&ctx, index);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
archive_read_data_into_fd(ctx->arc, fd);
|
ret = archive_read_data_into_fd(ctx->arc, fd);
|
||||||
ctx->using = 0;
|
ctx->using = 0;
|
||||||
}
|
}
|
||||||
|
return ret == ARCHIVE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_hippo_ehviewer_gallery_ArchivePageLoaderKt_releaseByteBuffer(JNIEnv *env, jclass thiz,
|
Java_com_hippo_ehviewer_jni_ArchiveKt_releaseByteBuffer(JNIEnv *env, jclass thiz, jobject buffer) {
|
||||||
jobject buffer) {
|
|
||||||
EH_UNUSED(thiz);
|
EH_UNUSED(thiz);
|
||||||
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
||||||
size_t size = (*env)->GetDirectBufferCapacity(env, buffer);
|
size_t size = (*env)->GetDirectBufferCapacity(env, buffer);
|
||||||
mempool_release_pages(addr, size);
|
if (!ADDR_IN_FILE_MAPPING(addr)) mempool_release_pages(addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_com_hippo_ehviewer_jni_ArchiveKt_archiveFdBatch(JNIEnv *env, jclass clazz, jintArray fd_batch, jobjectArray names, jint arc_fd, jint size) {
|
||||||
|
EH_UNUSED(clazz);
|
||||||
|
struct archive *arc = archive_write_new();
|
||||||
|
struct stat st;
|
||||||
|
char buff[8192];
|
||||||
|
jint fdBatch[size];
|
||||||
|
(*env)->GetIntArrayRegion(env, fd_batch, 0, size, fdBatch);
|
||||||
|
archive_write_set_format_zip(arc);
|
||||||
|
archive_write_zip_set_compression_store(arc);
|
||||||
|
archive_write_open_fd(arc, arc_fd);
|
||||||
|
struct archive_entry *entry = archive_entry_new();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int fd = fdBatch[i];
|
||||||
|
jobject name = (*env)->GetObjectArrayElement(env, names, i);
|
||||||
|
const char *cname = (*env)->GetStringUTFChars(env, name, false);
|
||||||
|
archive_entry_set_pathname(entry, cname);
|
||||||
|
(*env)->ReleaseStringUTFChars(env, name, cname);
|
||||||
|
fstat(fd, &st);
|
||||||
|
archive_entry_copy_stat(entry, &st);
|
||||||
|
archive_entry_set_perm(entry, 0644);
|
||||||
|
archive_write_header(arc, entry);
|
||||||
|
int len = read(fd, buff, sizeof(buff));
|
||||||
|
while (len > 0) {
|
||||||
|
archive_write_data(arc, buff, len);
|
||||||
|
len = read(fd, buff, sizeof(buff));
|
||||||
|
}
|
||||||
|
archive_write_finish_entry(arc);
|
||||||
|
archive_entry_clear(entry);
|
||||||
|
}
|
||||||
|
archive_write_close(arc);
|
||||||
|
archive_write_free(arc);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
#include <jni.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <android/bitmap.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/** A line will be considered as having content if 0.25% of it is filled. */
|
||||||
|
const float filledRatioLimit = 0.0025;
|
||||||
|
|
||||||
|
/** When the threshold is closer to 1, less content will be cropped. **/
|
||||||
|
#define THRESHOLD 0.75
|
||||||
|
|
||||||
|
const uint8_t thresholdForBlack = (uint8_t) (255.0 * THRESHOLD);
|
||||||
|
|
||||||
|
const uint8_t thresholdForWhite = (uint8_t) (255.0 - 255.0 * THRESHOLD);
|
||||||
|
|
||||||
|
static uint8_t inline grey(uint32_t pixel) {
|
||||||
|
const uint8_t r = ((uint8_t *) &pixel)[0];
|
||||||
|
const uint8_t g = ((uint8_t *) &pixel)[1];
|
||||||
|
const uint8_t b = ((uint8_t *) &pixel)[2];
|
||||||
|
return (r * 77 + g * 151 + b * 28) >> 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool inline isBlackPixel(const uint32_t *pixels, uint32_t width, uint32_t x, uint32_t y) {
|
||||||
|
const uint32_t pixel = *((uint32_t *) pixels + (y * width + x));
|
||||||
|
return grey(pixel) < thresholdForBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool inline isWhitePixel(const uint32_t *pixels, uint32_t width, uint32_t x, uint32_t y) {
|
||||||
|
const uint32_t pixel = *((uint32_t *) pixels + (y * width + x));
|
||||||
|
return grey(pixel) > thresholdForWhite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the first x position where there is a substantial amount of fill,
|
||||||
|
* starting the search from the left. */
|
||||||
|
uint32_t findBorderLeft(uint32_t *pixels, uint32_t width, uint32_t height,
|
||||||
|
uint32_t top, uint32_t bottom) {
|
||||||
|
int x, y;
|
||||||
|
const uint32_t filledLimit = round(height * filledRatioLimit / 2);
|
||||||
|
|
||||||
|
// Scan first line to detect dominant color
|
||||||
|
uint32_t whitePixels = 0;
|
||||||
|
uint32_t blackPixels = 0;
|
||||||
|
|
||||||
|
for (y = top; y < bottom; y += 2) {
|
||||||
|
if (isBlackPixel(pixels, width, 0, y)) {
|
||||||
|
blackPixels++;
|
||||||
|
} else if (isWhitePixel(pixels, width, 0, y)) {
|
||||||
|
whitePixels++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool (*detectFunc)(const uint32_t *, uint32_t, uint32_t, uint32_t) = isBlackPixel;
|
||||||
|
if (whitePixels > filledLimit && blackPixels > filledLimit) {
|
||||||
|
// Mixed fill found... don't crop anything
|
||||||
|
return 0;
|
||||||
|
} else if (blackPixels > filledLimit) {
|
||||||
|
detectFunc = isWhitePixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan vertical lines in search of filled lines
|
||||||
|
for (x = 1; x < width; x++) {
|
||||||
|
uint32_t filledCount = 0;
|
||||||
|
|
||||||
|
for (y = top; y < bottom; y += 2) {
|
||||||
|
if (detectFunc(pixels, width, x, y)) {
|
||||||
|
filledCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filledCount > filledLimit) {
|
||||||
|
// This line contains enough fill
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No fill found... don't crop anything
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the first x position where there is a substantial amount of fill,
|
||||||
|
* starting the search from the right. */
|
||||||
|
uint32_t findBorderRight(uint32_t *pixels, uint32_t width, uint32_t height,
|
||||||
|
uint32_t top, uint32_t bottom) {
|
||||||
|
int x, y;
|
||||||
|
const uint32_t filledLimit = round(height * filledRatioLimit / 2);
|
||||||
|
|
||||||
|
// Scan first line to detect dominant color
|
||||||
|
uint32_t whitePixels = 0;
|
||||||
|
uint32_t blackPixels = 0;
|
||||||
|
|
||||||
|
uint32_t lastX = width - 1;
|
||||||
|
for (y = top; y < bottom; y += 2) {
|
||||||
|
if (isBlackPixel(pixels, width, lastX, y)) {
|
||||||
|
blackPixels++;
|
||||||
|
} else if (isWhitePixel(pixels, width, lastX, y)) {
|
||||||
|
whitePixels++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool (*detectFunc)(const uint32_t *, uint32_t, uint32_t, uint32_t) = isBlackPixel;
|
||||||
|
if (whitePixels > filledLimit && blackPixels > filledLimit) {
|
||||||
|
// Mixed fill found... don't crop anything
|
||||||
|
return width;
|
||||||
|
} else if (blackPixels > filledLimit) {
|
||||||
|
detectFunc = isWhitePixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan vertical lines in search of filled lines
|
||||||
|
for (x = width - 2; x > 0; x--) {
|
||||||
|
uint32_t filledCount = 0;
|
||||||
|
|
||||||
|
for (y = top; y < bottom; y += 2) {
|
||||||
|
if (detectFunc(pixels, width, x, y)) {
|
||||||
|
filledCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filledCount > filledLimit) {
|
||||||
|
// This line contains enough fill
|
||||||
|
return x + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No fill found... don't crop anything
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the first y position where there is a substantial amount of fill,
|
||||||
|
* starting the search from the top. */
|
||||||
|
uint32_t findBorderTop(uint32_t *pixels, uint32_t width, uint32_t height) {
|
||||||
|
int x, y;
|
||||||
|
const uint32_t filledLimit = round(width * filledRatioLimit / 2);
|
||||||
|
|
||||||
|
// Scan first line to detect dominant color
|
||||||
|
uint32_t whitePixels = 0;
|
||||||
|
uint32_t blackPixels = 0;
|
||||||
|
|
||||||
|
for (x = 0; x < width; x += 2) {
|
||||||
|
if (isBlackPixel(pixels, width, x, 0)) {
|
||||||
|
blackPixels++;
|
||||||
|
} else if (isWhitePixel(pixels, width, x, 0)) {
|
||||||
|
whitePixels++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool (*detectFunc)(const uint32_t *, uint32_t, uint32_t, uint32_t) = isBlackPixel;
|
||||||
|
if (whitePixels > filledLimit && blackPixels > filledLimit) {
|
||||||
|
// Mixed fill found... don't crop anything
|
||||||
|
return 0;
|
||||||
|
} else if (blackPixels > filledLimit) {
|
||||||
|
detectFunc = isWhitePixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan horizontal lines in search of filled lines
|
||||||
|
for (y = 1; y < height; y++) {
|
||||||
|
uint32_t filledCount = 0;
|
||||||
|
|
||||||
|
for (x = 0; x < width; x += 2) {
|
||||||
|
if (detectFunc(pixels, width, x, y)) {
|
||||||
|
filledCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filledCount > filledLimit) {
|
||||||
|
// This line contains enough fill
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No fill found... don't crop anything
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the first y position where there is a substantial amount of fill,
|
||||||
|
* starting the search from the bottom. */
|
||||||
|
uint32_t findBorderBottom(uint32_t *pixels, uint32_t width, uint32_t height) {
|
||||||
|
int x, y;
|
||||||
|
const uint32_t filledLimit = round(width * filledRatioLimit / 2);
|
||||||
|
|
||||||
|
// Scan first line to detect dominant color
|
||||||
|
uint32_t whitePixels = 0;
|
||||||
|
uint32_t blackPixels = 0;
|
||||||
|
uint32_t lastY = height - 1;
|
||||||
|
|
||||||
|
for (x = 0; x < width; x += 2) {
|
||||||
|
if (isBlackPixel(pixels, width, x, lastY)) {
|
||||||
|
blackPixels++;
|
||||||
|
} else if (isWhitePixel(pixels, width, x, lastY)) {
|
||||||
|
whitePixels++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool (*detectFunc)(const uint32_t *, uint32_t, uint32_t, uint32_t) = isBlackPixel;
|
||||||
|
if (whitePixels > filledLimit && blackPixels > filledLimit) {
|
||||||
|
// Mixed fill found... don't crop anything
|
||||||
|
return height;
|
||||||
|
} else if (blackPixels > filledLimit) {
|
||||||
|
detectFunc = isWhitePixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan horizontal lines in search of filled lines
|
||||||
|
for (y = height - 2; y > 0; y--) {
|
||||||
|
uint32_t filledCount = 0;
|
||||||
|
|
||||||
|
for (x = 0; x < width; x += 2) {
|
||||||
|
if (detectFunc(pixels, width, x, y)) {
|
||||||
|
filledCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filledCount > filledLimit) {
|
||||||
|
// This line contains enough fill
|
||||||
|
return y + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No fill found... don't crop anything
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jintArray JNICALL
|
||||||
|
Java_com_hippo_ehviewer_image_ImageKt_detectBorder(JNIEnv *env, jclass clazz, jobject bitmap) {
|
||||||
|
void *pixels = NULL;
|
||||||
|
AndroidBitmapInfo info;
|
||||||
|
memset(&info, 0, sizeof(info));
|
||||||
|
AndroidBitmap_getInfo(env, bitmap, &info);
|
||||||
|
AndroidBitmap_lockPixels(env, bitmap, &pixels);
|
||||||
|
uint32_t width = info.width;
|
||||||
|
uint32_t height = info.height;
|
||||||
|
uint32_t top = findBorderTop(pixels, width, height);
|
||||||
|
uint32_t bottom = findBorderBottom(pixels, width, height);
|
||||||
|
uint32_t left = findBorderLeft(pixels, width, height, top, bottom);
|
||||||
|
uint32_t right = findBorderRight(pixels, width, height, top, bottom);
|
||||||
|
int array[4] = {left, top, right, bottom};
|
||||||
|
AndroidBitmap_unlockPixels(env, bitmap);
|
||||||
|
jintArray ret = (*env)->NewIntArray(env, 4);
|
||||||
|
(*env)->SetIntArrayRegion(env, ret, 0, 4, array);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
TOOLCHAIN=$ANDROID_HOME/ndk/$1/toolchains/llvm/prebuilt/$(uname -s | tr '[:upper:]' '[:lower:]')-x86_64/bin
|
||||||
|
TARGET=$2
|
||||||
|
./configure --host=$TARGET --disable-public-key --disable-dependency-tracking \
|
||||||
|
AR=$TOOLCHAIN/llvm-ar \
|
||||||
|
CC="$TOOLCHAIN/clang -target $TARGET" \
|
||||||
|
RANLIB=$TOOLCHAIN/llvm-ranlib
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
@@ -11,6 +12,7 @@
|
|||||||
|
|
||||||
#define GIF_HEADER_87A "GIF87a"
|
#define GIF_HEADER_87A "GIF87a"
|
||||||
#define GIF_HEADER_89A "GIF89a"
|
#define GIF_HEADER_89A "GIF89a"
|
||||||
|
#define GIF_HEADER_LENGTH 6
|
||||||
|
|
||||||
static int FRAME_DELAY_START_MARKER = 0x0021F904;
|
static int FRAME_DELAY_START_MARKER = 0x0021F904;
|
||||||
|
|
||||||
@@ -21,7 +23,8 @@ typedef signed char byte;
|
|||||||
#define DEFAULT_FRAME_DELAY 10
|
#define DEFAULT_FRAME_DELAY 10
|
||||||
|
|
||||||
static inline bool isGif(void *addr) {
|
static inline bool isGif(void *addr) {
|
||||||
return !memcmp(addr, GIF_HEADER_87A, 6) || !memcmp(addr, GIF_HEADER_89A, 6);
|
return !memcmp(addr, GIF_HEADER_87A, GIF_HEADER_LENGTH) ||
|
||||||
|
!memcmp(addr, GIF_HEADER_89A, GIF_HEADER_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void doRewrite(byte *addr, size_t size) {
|
static void doRewrite(byte *addr, size_t size) {
|
||||||
@@ -42,20 +45,33 @@ static void doRewrite(byte *addr, size_t size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_com_hippo_ehviewer_jni_GifUtilsKt_isGif(JNIEnv *env, jclass clazz, jint fd) {
|
||||||
|
byte buffer[GIF_HEADER_LENGTH];
|
||||||
|
return read(fd, buffer, GIF_HEADER_LENGTH) == GIF_HEADER_LENGTH &&
|
||||||
|
isGif(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_hippo_ehviewer_image_ImageKt_rewriteGifSource(JNIEnv *env, jclass clazz, jobject buffer) {
|
Java_com_hippo_ehviewer_jni_GifUtilsKt_rewriteGifSource(JNIEnv *env, jclass clazz, jobject buffer) {
|
||||||
byte *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
byte *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
||||||
size_t size = (*env)->GetDirectBufferCapacity(env, buffer);
|
size_t size = (*env)->GetDirectBufferCapacity(env, buffer);
|
||||||
doRewrite(addr, size);
|
doRewrite(addr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT jobject JNICALL
|
||||||
Java_com_hippo_ehviewer_image_ImageKt_rewriteGifSource2(JNIEnv *env, jclass clazz, jint fd) {
|
Java_com_hippo_ehviewer_jni_GifUtilsKt_mmap(JNIEnv *env, jclass clazz, jint fd) {
|
||||||
struct stat64 st;
|
struct stat64 st;
|
||||||
fstat64(fd, &st);
|
fstat64(fd, &st);
|
||||||
size_t size = st.st_size;
|
size_t size = st.st_size;
|
||||||
byte *addr = mmap64(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
byte *addr = mmap64(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||||||
if (addr == MAP_FAILED) return;
|
if (addr == MAP_FAILED) return NULL;
|
||||||
doRewrite(addr, size);
|
return (*env)->NewDirectByteBuffer(env, addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_com_hippo_ehviewer_jni_GifUtilsKt_munmap(JNIEnv *env, jclass clazz, jobject buffer) {
|
||||||
|
byte *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
||||||
|
size_t size = (*env)->GetDirectBufferCapacity(env, buffer);
|
||||||
munmap(addr, size);
|
munmap(addr, size);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
#include <nettle/sha1.h>
|
||||||
|
|
||||||
|
#include "ehviewer.h"
|
||||||
|
|
||||||
|
#define BUFFER_SIZE 8192
|
||||||
|
|
||||||
|
typedef uint8_t byte;
|
||||||
|
|
||||||
|
const char hex_digits[] = "0123456789abcdef";
|
||||||
|
|
||||||
|
JNIEXPORT jstring JNICALL
|
||||||
|
Java_com_hippo_ehviewer_jni_HashKt_sha1(JNIEnv *env, jclass clazz, jint fd) {
|
||||||
|
EH_UNUSED(clazz);
|
||||||
|
struct sha1_ctx ctx;
|
||||||
|
sha1_init(&ctx);
|
||||||
|
|
||||||
|
size_t bytes_read;
|
||||||
|
byte buffer[BUFFER_SIZE];
|
||||||
|
while ((bytes_read = read(fd, buffer, BUFFER_SIZE)) > 0) {
|
||||||
|
sha1_update(&ctx, bytes_read, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte digest[SHA1_DIGEST_SIZE];
|
||||||
|
sha1_digest(&ctx, SHA1_DIGEST_SIZE, digest);
|
||||||
|
|
||||||
|
byte byte;
|
||||||
|
char hex_digest[2 * SHA1_DIGEST_SIZE + 1];
|
||||||
|
for (int i = 0; i < SHA1_DIGEST_SIZE; i++) {
|
||||||
|
byte = digest[i];
|
||||||
|
hex_digest[2 * i] = hex_digits[byte >> 4 & 0xF];
|
||||||
|
hex_digest[2 * i + 1] = hex_digits[byte & 0xF];
|
||||||
|
}
|
||||||
|
hex_digest[2 * SHA1_DIGEST_SIZE] = '\0';
|
||||||
|
|
||||||
|
return (*env)->NewStringUTF(env, hex_digest);
|
||||||
|
}
|
||||||
@@ -84,30 +84,6 @@ compare_right(nat_char const *a, nat_char const *b)
|
|||||||
} else if (!*a && !*b)
|
} else if (!*a && !*b)
|
||||||
return bias;
|
return bias;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
|
||||||
compare_left(nat_char const *a, nat_char const *b)
|
|
||||||
{
|
|
||||||
/* Compare two left-aligned numbers: the first to have a
|
|
||||||
different value wins. */
|
|
||||||
for (;; a++, b++) {
|
|
||||||
if (!nat_isdigit(*a) && !nat_isdigit(*b))
|
|
||||||
return 0;
|
|
||||||
if (!nat_isdigit(*a))
|
|
||||||
return -1;
|
|
||||||
if (!nat_isdigit(*b))
|
|
||||||
return +1;
|
|
||||||
if (*a < *b)
|
|
||||||
return -1;
|
|
||||||
if (*a > *b)
|
|
||||||
return +1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -116,30 +92,23 @@ strnatcmp0(nat_char const *a, nat_char const *b, int fold_case)
|
|||||||
{
|
{
|
||||||
int ai, bi;
|
int ai, bi;
|
||||||
nat_char ca, cb;
|
nat_char ca, cb;
|
||||||
int fractional, result;
|
int result;
|
||||||
|
|
||||||
ai = bi = 0;
|
ai = bi = 0;
|
||||||
while (1) {
|
while (1) {
|
||||||
ca = a[ai]; cb = b[bi];
|
ca = a[ai]; cb = b[bi];
|
||||||
|
|
||||||
/* skip over leading spaces or zeros */
|
/* skip over leading spaces or zeros */
|
||||||
while (nat_isspace(ca))
|
while (nat_isspace(ca) || ca == '0')
|
||||||
ca = a[++ai];
|
ca = a[++ai];
|
||||||
|
|
||||||
while (nat_isspace(cb))
|
while (nat_isspace(cb) || cb == '0')
|
||||||
cb = b[++bi];
|
cb = b[++bi];
|
||||||
|
|
||||||
/* process run of digits */
|
/* process run of digits */
|
||||||
if (nat_isdigit(ca) && nat_isdigit(cb)) {
|
if (nat_isdigit(ca) && nat_isdigit(cb)) {
|
||||||
fractional = (ca == '0' || cb == '0');
|
if ((result = compare_right(a+ai, b+bi)) != 0)
|
||||||
|
return result;
|
||||||
if (fractional) {
|
|
||||||
if ((result = compare_left(a+ai, b+bi)) != 0)
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
if ((result = compare_right(a+ai, b+bi)) != 0)
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ca && !cb) {
|
if (!ca && !cb) {
|
||||||
|
|||||||
@@ -1,239 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.4.1)
|
|
||||||
project(nettle C)
|
|
||||||
|
|
||||||
set(LIBNETTLE_DEFINITIONS
|
|
||||||
-DHAVE_CONFIG_H)
|
|
||||||
|
|
||||||
set(LIBNETTLE_SOURCES
|
|
||||||
nettle/aes-decrypt-internal.c
|
|
||||||
nettle/aes-decrypt.c
|
|
||||||
nettle/aes-decrypt-table.c
|
|
||||||
nettle/aes128-decrypt.c
|
|
||||||
nettle/aes192-decrypt.c
|
|
||||||
nettle/aes256-decrypt.c
|
|
||||||
nettle/aes-encrypt-internal.c
|
|
||||||
nettle/aes-encrypt.c
|
|
||||||
nettle/aes-encrypt-table.c
|
|
||||||
nettle/aes128-encrypt.c
|
|
||||||
nettle/aes192-encrypt.c
|
|
||||||
nettle/aes256-encrypt.c
|
|
||||||
nettle/aes-invert-internal.c
|
|
||||||
nettle/aes-set-key-internal.c
|
|
||||||
nettle/aes-set-encrypt-key.c
|
|
||||||
nettle/aes-set-decrypt-key.c
|
|
||||||
nettle/aes128-set-encrypt-key.c
|
|
||||||
nettle/aes128-set-decrypt-key.c
|
|
||||||
nettle/aes128-meta.c
|
|
||||||
nettle/aes192-set-encrypt-key.c
|
|
||||||
nettle/aes192-set-decrypt-key.c
|
|
||||||
nettle/aes192-meta.c
|
|
||||||
nettle/aes256-set-encrypt-key.c
|
|
||||||
nettle/aes256-set-decrypt-key.c
|
|
||||||
nettle/aes256-meta.c
|
|
||||||
nettle/nist-keywrap.c
|
|
||||||
nettle/arcfour.c
|
|
||||||
nettle/arcfour-crypt.c
|
|
||||||
nettle/arctwo.c
|
|
||||||
nettle/arctwo-meta.c
|
|
||||||
nettle/blowfish.c
|
|
||||||
nettle/blowfish-bcrypt.c
|
|
||||||
nettle/base16-encode.c
|
|
||||||
nettle/base16-decode.c
|
|
||||||
nettle/base16-meta.c
|
|
||||||
nettle/base64-encode.c
|
|
||||||
nettle/base64-decode.c
|
|
||||||
nettle/base64-meta.c
|
|
||||||
nettle/base64url-encode.c
|
|
||||||
nettle/base64url-decode.c
|
|
||||||
nettle/base64url-meta.c
|
|
||||||
nettle/buffer.c
|
|
||||||
nettle/buffer-init.c
|
|
||||||
nettle/camellia-crypt-internal.c
|
|
||||||
nettle/camellia-table.c
|
|
||||||
nettle/camellia-absorb.c
|
|
||||||
nettle/camellia-invert-key.c
|
|
||||||
nettle/camellia128-set-encrypt-key.c
|
|
||||||
nettle/camellia128-crypt.c
|
|
||||||
nettle/camellia128-set-decrypt-key.c
|
|
||||||
nettle/camellia128-meta.c
|
|
||||||
nettle/camellia192-meta.c
|
|
||||||
nettle/camellia256-set-encrypt-key.c
|
|
||||||
nettle/camellia256-crypt.c
|
|
||||||
nettle/camellia256-set-decrypt-key.c
|
|
||||||
nettle/camellia256-meta.c
|
|
||||||
nettle/cast128.c
|
|
||||||
nettle/cast128-meta.c
|
|
||||||
nettle/cbc.c
|
|
||||||
nettle/cbc-aes128-encrypt.c
|
|
||||||
nettle/cbc-aes192-encrypt.c
|
|
||||||
nettle/cbc-aes256-encrypt.c
|
|
||||||
nettle/ccm.c
|
|
||||||
nettle/ccm-aes128.c
|
|
||||||
nettle/ccm-aes192.c
|
|
||||||
nettle/ccm-aes256.c
|
|
||||||
nettle/cfb.c
|
|
||||||
nettle/siv-cmac.c
|
|
||||||
nettle/siv-cmac-aes128.c
|
|
||||||
nettle/siv-cmac-aes256.c
|
|
||||||
nettle/cnd-memcpy.c
|
|
||||||
nettle/chacha-crypt.c
|
|
||||||
nettle/chacha-core-internal.c
|
|
||||||
nettle/chacha-poly1305.c
|
|
||||||
nettle/chacha-poly1305-meta.c
|
|
||||||
nettle/chacha-set-key.c
|
|
||||||
nettle/chacha-set-nonce.c
|
|
||||||
nettle/ctr.c
|
|
||||||
nettle/ctr16.c
|
|
||||||
nettle/des.c
|
|
||||||
nettle/des3.c
|
|
||||||
nettle/eax.c
|
|
||||||
nettle/eax-aes128.c
|
|
||||||
nettle/eax-aes128-meta.c
|
|
||||||
nettle/ghash-set-key.c
|
|
||||||
nettle/ghash-update.c
|
|
||||||
nettle/gcm.c
|
|
||||||
nettle/gcm-aes.c
|
|
||||||
nettle/gcm-aes128.c
|
|
||||||
nettle/gcm-aes128-meta.c
|
|
||||||
nettle/gcm-aes192.c
|
|
||||||
nettle/gcm-aes192-meta.c
|
|
||||||
nettle/gcm-aes256.c
|
|
||||||
nettle/gcm-aes256-meta.c
|
|
||||||
nettle/gcm-camellia128.c
|
|
||||||
nettle/gcm-camellia128-meta.c
|
|
||||||
nettle/gcm-camellia256.c
|
|
||||||
nettle/gcm-camellia256-meta.c
|
|
||||||
nettle/cmac.c
|
|
||||||
nettle/cmac64.c
|
|
||||||
nettle/cmac-aes128.c
|
|
||||||
nettle/cmac-aes256.c
|
|
||||||
nettle/cmac-des3.c
|
|
||||||
nettle/cmac-aes128-meta.c
|
|
||||||
nettle/cmac-aes256-meta.c
|
|
||||||
nettle/cmac-des3-meta.c
|
|
||||||
nettle/gost28147.c
|
|
||||||
nettle/gosthash94.c
|
|
||||||
nettle/gosthash94-meta.c
|
|
||||||
nettle/hmac.c
|
|
||||||
nettle/hmac-gosthash94.c
|
|
||||||
nettle/hmac-md5.c
|
|
||||||
nettle/hmac-ripemd160.c
|
|
||||||
nettle/hmac-sha1.c
|
|
||||||
nettle/hmac-sha224.c
|
|
||||||
nettle/hmac-sha256.c
|
|
||||||
nettle/hmac-sha384.c
|
|
||||||
nettle/hmac-sha512.c
|
|
||||||
nettle/hmac-streebog.c
|
|
||||||
nettle/hmac-sm3.c
|
|
||||||
nettle/hmac-md5-meta.c
|
|
||||||
nettle/hmac-ripemd160-meta.c
|
|
||||||
nettle/hmac-sha1-meta.c
|
|
||||||
nettle/hmac-sha224-meta.c
|
|
||||||
nettle/hmac-sha256-meta.c
|
|
||||||
nettle/hmac-sha384-meta.c
|
|
||||||
nettle/hmac-sha512-meta.c
|
|
||||||
nettle/hmac-streebog-meta.c
|
|
||||||
nettle/hmac-sm3-meta.c
|
|
||||||
nettle/knuth-lfib.c
|
|
||||||
nettle/hkdf.c
|
|
||||||
nettle/md2.c
|
|
||||||
nettle/md2-meta.c
|
|
||||||
nettle/md4.c
|
|
||||||
nettle/md4-meta.c
|
|
||||||
nettle/md5.c
|
|
||||||
nettle/md5-compress.c
|
|
||||||
nettle/md5-compat.c
|
|
||||||
nettle/md5-meta.c
|
|
||||||
nettle/memeql-sec.c
|
|
||||||
nettle/memxor.c
|
|
||||||
nettle/memxor3.c
|
|
||||||
nettle/nettle-lookup-hash.c
|
|
||||||
nettle/nettle-meta-aeads.c
|
|
||||||
nettle/nettle-meta-armors.c
|
|
||||||
nettle/nettle-meta-ciphers.c
|
|
||||||
nettle/nettle-meta-hashes.c
|
|
||||||
nettle/nettle-meta-macs.c
|
|
||||||
nettle/pbkdf2.c
|
|
||||||
nettle/pbkdf2-hmac-gosthash94.c
|
|
||||||
nettle/pbkdf2-hmac-sha1.c
|
|
||||||
nettle/pbkdf2-hmac-sha256.c
|
|
||||||
nettle/pbkdf2-hmac-sha384.c
|
|
||||||
nettle/pbkdf2-hmac-sha512.c
|
|
||||||
nettle/poly1305-aes.c
|
|
||||||
nettle/poly1305-internal.c
|
|
||||||
nettle/realloc.c
|
|
||||||
nettle/ripemd160.c
|
|
||||||
nettle/ripemd160-compress.c
|
|
||||||
nettle/ripemd160-meta.c
|
|
||||||
nettle/salsa20-core-internal.c
|
|
||||||
nettle/salsa20-crypt-internal.c
|
|
||||||
nettle/salsa20-crypt.c
|
|
||||||
nettle/salsa20r12-crypt.c
|
|
||||||
nettle/salsa20-set-key.c
|
|
||||||
nettle/salsa20-set-nonce.c
|
|
||||||
nettle/salsa20-128-set-key.c
|
|
||||||
nettle/salsa20-256-set-key.c
|
|
||||||
nettle/sha1.c
|
|
||||||
nettle/sha1-compress.c
|
|
||||||
nettle/sha1-meta.c
|
|
||||||
nettle/sha256.c
|
|
||||||
nettle/sha256-compress.c
|
|
||||||
nettle/sha224-meta.c
|
|
||||||
nettle/sha256-meta.c
|
|
||||||
nettle/sha512.c
|
|
||||||
nettle/sha512-compress.c
|
|
||||||
nettle/sha384-meta.c
|
|
||||||
nettle/sha512-meta.c
|
|
||||||
nettle/sha512-224-meta.c
|
|
||||||
nettle/sha512-256-meta.c
|
|
||||||
nettle/sha3.c
|
|
||||||
nettle/sha3-permute.c
|
|
||||||
nettle/sha3-224.c
|
|
||||||
nettle/sha3-224-meta.c
|
|
||||||
nettle/sha3-256.c
|
|
||||||
nettle/sha3-256-meta.c
|
|
||||||
nettle/sha3-384.c
|
|
||||||
nettle/sha3-384-meta.c
|
|
||||||
nettle/sha3-512.c
|
|
||||||
nettle/sha3-512-meta.c
|
|
||||||
nettle/shake256.c
|
|
||||||
nettle/sm3.c
|
|
||||||
nettle/sm3-meta.c
|
|
||||||
nettle/serpent-set-key.c
|
|
||||||
nettle/serpent-encrypt.c
|
|
||||||
nettle/serpent-decrypt.c
|
|
||||||
nettle/serpent-meta.c
|
|
||||||
nettle/streebog.c
|
|
||||||
nettle/streebog-meta.c
|
|
||||||
nettle/twofish.c
|
|
||||||
nettle/twofish-meta.c
|
|
||||||
nettle/umac-nh.c
|
|
||||||
nettle/umac-nh-n.c
|
|
||||||
nettle/umac-l2.c
|
|
||||||
nettle/umac-l3.c
|
|
||||||
nettle/umac-poly64.c
|
|
||||||
nettle/umac-poly128.c
|
|
||||||
nettle/umac-set-key.c
|
|
||||||
nettle/umac32.c
|
|
||||||
nettle/umac64.c
|
|
||||||
nettle/umac96.c
|
|
||||||
nettle/umac128.c
|
|
||||||
nettle/version.c
|
|
||||||
nettle/write-be32.c
|
|
||||||
nettle/write-le32.c
|
|
||||||
nettle/write-le64.c
|
|
||||||
nettle/yarrow256.c
|
|
||||||
nettle/yarrow_key_event.c
|
|
||||||
nettle/xts.c
|
|
||||||
nettle/xts-aes128.c
|
|
||||||
nettle/xts-aes256.c
|
|
||||||
)
|
|
||||||
|
|
||||||
set(LIBNETTLE_INCLUDES
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/nettle
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library(nettle STATIC ${LIBNETTLE_SOURCES})
|
|
||||||
target_include_directories(nettle PUBLIC ${LIBNETTLE_INCLUDES})
|
|
||||||
target_compile_definitions(nettle PRIVATE ${LIBNETTLE_DEFINITIONS})
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
/* config.h. Generated from config.h.in by configure. */
|
|
||||||
/* config.h.in. Generated from configure.ac by autoheader. */
|
|
||||||
|
|
||||||
/* Define if building universal (internal helper macro) */
|
|
||||||
/* #undef AC_APPLE_UNIVERSAL_BUILD */
|
|
||||||
|
|
||||||
/* Define to 1 if using 'alloca.c'. */
|
|
||||||
/* #undef C_ALLOCA */
|
|
||||||
|
|
||||||
/* Define to 1 if you have 'alloca', as a function or macro. */
|
|
||||||
#define HAVE_ALLOCA 1
|
|
||||||
|
|
||||||
/* Define to 1 if <alloca.h> works. */
|
|
||||||
#define HAVE_ALLOCA_H 1
|
|
||||||
|
|
||||||
/* Define if __builtin_bswap64 is available */
|
|
||||||
#define HAVE_BUILTIN_BSWAP64 1
|
|
||||||
|
|
||||||
/* Define if clock_gettime is available */
|
|
||||||
#define HAVE_CLOCK_GETTIME 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <dlfcn.h> header file. */
|
|
||||||
#define HAVE_DLFCN_H 1
|
|
||||||
|
|
||||||
/* Define if fcntl file locking is available */
|
|
||||||
#define HAVE_FCNTL_LOCKING 1
|
|
||||||
|
|
||||||
/* Define if the compiler understands __attribute__ */
|
|
||||||
#define HAVE_GCC_ATTRIBUTE 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the `getline' function. */
|
|
||||||
#define HAVE_GETLINE 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <inttypes.h> header file. */
|
|
||||||
#define HAVE_INTTYPES_H 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have dlopen (with -ldl). */
|
|
||||||
#define HAVE_LIBDL 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the `gmp' library (-lgmp). */
|
|
||||||
/* #undef HAVE_LIBGMP */
|
|
||||||
|
|
||||||
/* Define if compiler and linker supports __attribute__ ifunc */
|
|
||||||
#define HAVE_LINK_IFUNC 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <malloc.h> header file. */
|
|
||||||
#define HAVE_MALLOC_H 1
|
|
||||||
|
|
||||||
/* Define to 1 each of the following for which a native (ie. CPU specific)
|
|
||||||
implementation of the corresponding routine exists. */
|
|
||||||
/* #undef HAVE_NATIVE_memxor3 */
|
|
||||||
/* #undef HAVE_NATIVE_aes_decrypt */
|
|
||||||
/* #undef HAVE_NATIVE_aes_encrypt */
|
|
||||||
/* #undef HAVE_NATIVE_aes128_decrypt */
|
|
||||||
/* #undef HAVE_NATIVE_aes128_encrypt */
|
|
||||||
/* #undef HAVE_NATIVE_aes128_invert_key */
|
|
||||||
/* #undef HAVE_NATIVE_aes128_set_decrypt_key */
|
|
||||||
/* #undef HAVE_NATIVE_aes128_set_encrypt_key */
|
|
||||||
/* #undef HAVE_NATIVE_aes192_decrypt */
|
|
||||||
/* #undef HAVE_NATIVE_aes192_encrypt */
|
|
||||||
/* #undef HAVE_NATIVE_aes192_invert_key */
|
|
||||||
/* #undef HAVE_NATIVE_aes192_set_decrypt_key */
|
|
||||||
/* #undef HAVE_NATIVE_aes192_set_encrypt_key */
|
|
||||||
/* #undef HAVE_NATIVE_aes256_decrypt */
|
|
||||||
/* #undef HAVE_NATIVE_aes256_encrypt */
|
|
||||||
/* #undef HAVE_NATIVE_aes256_invert_key */
|
|
||||||
/* #undef HAVE_NATIVE_aes256_set_decrypt_key */
|
|
||||||
/* #undef HAVE_NATIVE_aes256_set_encrypt_key */
|
|
||||||
/* #undef HAVE_NATIVE_cbc_aes128_encrypt */
|
|
||||||
/* #undef HAVE_NATIVE_cbc_aes192_encrypt */
|
|
||||||
/* #undef HAVE_NATIVE_cbc_aes256_encrypt */
|
|
||||||
/* #undef HAVE_NATIVE_chacha_core */
|
|
||||||
/* #undef HAVE_NATIVE_chacha_2core */
|
|
||||||
/* #undef HAVE_NATIVE_chacha_3core */
|
|
||||||
/* #undef HAVE_NATIVE_chacha_4core */
|
|
||||||
/* #undef HAVE_NATIVE_fat_chacha_2core */
|
|
||||||
/* #undef HAVE_NATIVE_fat_chacha_3core */
|
|
||||||
/* #undef HAVE_NATIVE_fat_chacha_4core */
|
|
||||||
/* #undef HAVE_NATIVE_ecc_curve25519_modp */
|
|
||||||
/* #undef HAVE_NATIVE_ecc_curve448_modp */
|
|
||||||
/* #undef HAVE_NATIVE_ecc_secp192r1_modp */
|
|
||||||
/* #undef HAVE_NATIVE_ecc_secp192r1_redc */
|
|
||||||
/* #undef HAVE_NATIVE_ecc_secp224r1_modp */
|
|
||||||
/* #undef HAVE_NATIVE_ecc_secp224r1_redc */
|
|
||||||
/* #undef HAVE_NATIVE_ecc_secp256r1_modp */
|
|
||||||
/* #undef HAVE_NATIVE_ecc_secp256r1_redc */
|
|
||||||
/* #undef HAVE_NATIVE_ecc_secp384r1_modp */
|
|
||||||
/* #undef HAVE_NATIVE_ecc_secp384r1_redc */
|
|
||||||
/* #undef HAVE_NATIVE_ecc_secp521r1_modp */
|
|
||||||
/* #undef HAVE_NATIVE_ecc_secp521r1_redc */
|
|
||||||
/* #undef HAVE_NATIVE_ghash_set_key */
|
|
||||||
/* #undef HAVE_NATIVE_ghash_update */
|
|
||||||
/* #undef HAVE_NATIVE_salsa20_core */
|
|
||||||
/* #undef HAVE_NATIVE_salsa20_2core */
|
|
||||||
/* #undef HAVE_NATIVE_fat_salsa20_2core */
|
|
||||||
/* #undef HAVE_NATIVE_sha1_compress */
|
|
||||||
/* #undef HAVE_NATIVE_sha256_compress */
|
|
||||||
/* #undef HAVE_NATIVE_sha512_compress */
|
|
||||||
/* #undef HAVE_NATIVE_sha3_permute */
|
|
||||||
/* #undef HAVE_NATIVE_umac_nh */
|
|
||||||
/* #undef HAVE_NATIVE_umac_nh_n */
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <openssl/ecdsa.h> header file. */
|
|
||||||
/* #undef HAVE_OPENSSL_ECDSA_H */
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <openssl/evp.h> header file. */
|
|
||||||
/* #undef HAVE_OPENSSL_EVP_H */
|
|
||||||
|
|
||||||
/* Define to 1 if you have the `secure_getenv' function. */
|
|
||||||
/* #undef HAVE_SECURE_GETENV */
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <stdint.h> header file. */
|
|
||||||
#define HAVE_STDINT_H 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <stdio.h> header file. */
|
|
||||||
#define HAVE_STDIO_H 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <stdlib.h> header file. */
|
|
||||||
#define HAVE_STDLIB_H 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the `strerror' function. */
|
|
||||||
#define HAVE_STRERROR 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <strings.h> header file. */
|
|
||||||
#define HAVE_STRINGS_H 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <string.h> header file. */
|
|
||||||
#define HAVE_STRING_H 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <sys/stat.h> header file. */
|
|
||||||
#define HAVE_SYS_STAT_H 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <sys/time.h> header file. */
|
|
||||||
#define HAVE_SYS_TIME_H 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <sys/types.h> header file. */
|
|
||||||
#define HAVE_SYS_TYPES_H 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <unistd.h> header file. */
|
|
||||||
#define HAVE_UNISTD_H 1
|
|
||||||
|
|
||||||
/* Define to 1 if you have the <valgrind/memcheck.h> header file. */
|
|
||||||
/* #undef HAVE_VALGRIND_MEMCHECK_H */
|
|
||||||
|
|
||||||
/* Define to the address where bug reports for this package should be sent. */
|
|
||||||
#define PACKAGE_BUGREPORT "nettle-bugs@lists.lysator.liu.se"
|
|
||||||
|
|
||||||
/* Define to the full name of this package. */
|
|
||||||
#define PACKAGE_NAME "nettle"
|
|
||||||
|
|
||||||
/* Define to the full name and version of this package. */
|
|
||||||
#define PACKAGE_STRING "nettle 3.8.1"
|
|
||||||
|
|
||||||
/* Define to the one symbol short name of this package. */
|
|
||||||
#define PACKAGE_TARNAME "nettle"
|
|
||||||
|
|
||||||
/* Define to the home page for this package. */
|
|
||||||
#define PACKAGE_URL ""
|
|
||||||
|
|
||||||
/* Define to the version of this package. */
|
|
||||||
#define PACKAGE_VERSION "3.8.1"
|
|
||||||
|
|
||||||
/* The size of `long', as computed by sizeof. */
|
|
||||||
#define SIZEOF_LONG __SIZEOF_LONG__
|
|
||||||
|
|
||||||
/* The size of `size_t', as computed by sizeof. */
|
|
||||||
#define SIZEOF_SIZE_T __SIZEOF_SIZE_T__
|
|
||||||
|
|
||||||
/* If using the C implementation of alloca, define if you know the
|
|
||||||
direction of stack growth for your system; otherwise it will be
|
|
||||||
automatically deduced at runtime.
|
|
||||||
STACK_DIRECTION > 0 => grows toward higher addresses
|
|
||||||
STACK_DIRECTION < 0 => grows toward lower addresses
|
|
||||||
STACK_DIRECTION = 0 => direction of growth unknown */
|
|
||||||
/* #undef STACK_DIRECTION */
|
|
||||||
|
|
||||||
/* Define to 1 if all of the C90 standard headers exist (not just the ones
|
|
||||||
required in a freestanding environment). This macro is provided for
|
|
||||||
backward compatibility; new code need not use it. */
|
|
||||||
#define STDC_HEADERS 1
|
|
||||||
|
|
||||||
/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. This
|
|
||||||
macro is obsolete. */
|
|
||||||
#define TIME_WITH_SYS_TIME 1
|
|
||||||
|
|
||||||
/* Defined if public key features are enabled */
|
|
||||||
/* #undef WITH_HOGWEED */
|
|
||||||
|
|
||||||
/* Define if you have openssl's libcrypto (used for benchmarking) */
|
|
||||||
/* #undef WITH_OPENSSL */
|
|
||||||
|
|
||||||
/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
|
|
||||||
significant byte first (like Motorola and SPARC, unlike Intel). */
|
|
||||||
#if defined AC_APPLE_UNIVERSAL_BUILD
|
|
||||||
# if defined __BIG_ENDIAN__
|
|
||||||
# define WORDS_BIGENDIAN 1
|
|
||||||
# endif
|
|
||||||
#else
|
|
||||||
# ifndef WORDS_BIGENDIAN
|
|
||||||
/* # undef WORDS_BIGENDIAN */
|
|
||||||
# endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Define to empty if `const' does not conform to ANSI C. */
|
|
||||||
/* #undef const */
|
|
||||||
|
|
||||||
/* Define to `int' if <sys/types.h> doesn't define. */
|
|
||||||
/* #undef gid_t */
|
|
||||||
|
|
||||||
/* Define to `__inline__' or `__inline' if that's what the C compiler
|
|
||||||
calls it, or to nothing if 'inline' is not supported under any name. */
|
|
||||||
#ifndef __cplusplus
|
|
||||||
/* #undef inline */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Define to `unsigned int' if <sys/types.h> does not define. */
|
|
||||||
/* #undef size_t */
|
|
||||||
|
|
||||||
/* Define to `int' if <sys/types.h> doesn't define. */
|
|
||||||
/* #undef uid_t */
|
|
||||||
|
|
||||||
/* AIX requires this to be the first thing in the file. */
|
|
||||||
#ifndef __GNUC__
|
|
||||||
# if HAVE_ALLOCA_H
|
|
||||||
# include <alloca.h>
|
|
||||||
# else
|
|
||||||
# ifdef _AIX
|
|
||||||
#pragma alloca
|
|
||||||
# else
|
|
||||||
# ifndef alloca /* predefined by HP cc +Olibcalls */
|
|
||||||
char *alloca ();
|
|
||||||
# endif
|
|
||||||
# endif
|
|
||||||
/* Needed for alloca on windows */
|
|
||||||
# if HAVE_MALLOC_H
|
|
||||||
# include <malloc.h>
|
|
||||||
# endif
|
|
||||||
# endif
|
|
||||||
#else /* defined __GNUC__ */
|
|
||||||
# if HAVE_ALLOCA_H
|
|
||||||
# include <alloca.h>
|
|
||||||
# else
|
|
||||||
/* Needed for alloca on windows, also with gcc */
|
|
||||||
# if HAVE_MALLOC_H
|
|
||||||
# include <malloc.h>
|
|
||||||
# endif
|
|
||||||
# endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#if HAVE_STRERROR
|
|
||||||
#define STRERROR strerror
|
|
||||||
#else
|
|
||||||
#define STRERROR(x) (sys_errlist[x])
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#if __GNUC__ && HAVE_GCC_ATTRIBUTE
|
|
||||||
# define NORETURN __attribute__ ((__noreturn__))
|
|
||||||
# define PRINTF_STYLE(f, a) __attribute__ ((__format__ (__printf__, f, a)))
|
|
||||||
# define UNUSED __attribute__ ((__unused__))
|
|
||||||
#else
|
|
||||||
# define NORETURN
|
|
||||||
# define PRINTF_STYLE(f, a)
|
|
||||||
# define UNUSED
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#if defined(__x86_64__) || defined(__arch64__)
|
|
||||||
# define HAVE_NATIVE_64_BIT 1
|
|
||||||
#else
|
|
||||||
/* Needs include of <limits.h> before use. */
|
|
||||||
# define HAVE_NATIVE_64_BIT (SIZEOF_LONG * CHAR_BIT >= 64)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
Submodule app/src/main/cpp/nettle/nettle deleted from 990abad16c.
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
/* version.h
|
|
||||||
|
|
||||||
Information about library version.
|
|
||||||
|
|
||||||
Copyright (C) 2015 Red Hat, Inc.
|
|
||||||
Copyright (C) 2015 Niels Möller
|
|
||||||
|
|
||||||
This file is part of GNU Nettle.
|
|
||||||
|
|
||||||
GNU Nettle is free software: you can redistribute it and/or
|
|
||||||
modify it under the terms of either:
|
|
||||||
|
|
||||||
* the GNU Lesser General Public License as published by the Free
|
|
||||||
Software Foundation; either version 3 of the License, or (at your
|
|
||||||
option) any later version.
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
* the GNU General Public License as published by the Free
|
|
||||||
Software Foundation; either version 2 of the License, or (at your
|
|
||||||
option) any later version.
|
|
||||||
|
|
||||||
or both in parallel, as here.
|
|
||||||
|
|
||||||
GNU Nettle is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
General Public License for more details.
|
|
||||||
|
|
||||||
You should have received copies of the GNU General Public License and
|
|
||||||
the GNU Lesser General Public License along with this program. If
|
|
||||||
not, see http://www.gnu.org/licenses/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef NETTLE_VERSION_H_INCLUDED
|
|
||||||
#define NETTLE_VERSION_H_INCLUDED
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Individual version numbers in decimal */
|
|
||||||
#define NETTLE_VERSION_MAJOR 3
|
|
||||||
#define NETTLE_VERSION_MINOR 8
|
|
||||||
|
|
||||||
#define NETTLE_USE_MINI_GMP 0
|
|
||||||
|
|
||||||
/* We need a preprocessor constant for GMP_NUMB_BITS, simply using
|
|
||||||
sizeof(mp_limb_t) * CHAR_BIT is not good enough. */
|
|
||||||
#if NETTLE_USE_MINI_GMP
|
|
||||||
# define GMP_NUMB_BITS n/a
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int
|
|
||||||
nettle_version_major (void);
|
|
||||||
|
|
||||||
int
|
|
||||||
nettle_version_minor (void);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* NETTLE_VERSION_H_INCLUDED */
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#ifndef EHVIEWER_SPINLOCK_H
|
|
||||||
#define EHVIEWER_SPINLOCK_H
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdatomic.h>
|
|
||||||
|
|
||||||
#define cmpxchg(P, O, N) __sync_val_compare_and_swap((P), (O), (N))
|
|
||||||
|
|
||||||
#define barrier() __asm__ __volatile__("":::"memory")
|
|
||||||
|
|
||||||
#if defined(__aarch64__) || defined(__arm__)
|
|
||||||
#define cpu_relax() __asm__ volatile ("wfe");
|
|
||||||
#else
|
|
||||||
#define cpu_relax() __asm__ volatile ("pause" ::: "memory");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct mcs_lock_t mcs_lock_t;
|
|
||||||
struct mcs_lock_t {
|
|
||||||
mcs_lock_t *next;
|
|
||||||
int spin;
|
|
||||||
};
|
|
||||||
typedef struct mcs_lock_t *mcs_lock;
|
|
||||||
|
|
||||||
static inline void lock_mcs(_Atomic mcs_lock *m, mcs_lock_t *me) {
|
|
||||||
mcs_lock_t *tail;
|
|
||||||
me->next = NULL;
|
|
||||||
me->spin = 0;
|
|
||||||
tail = atomic_exchange(m, me);
|
|
||||||
if (!tail) return;
|
|
||||||
tail->next = me;
|
|
||||||
barrier();
|
|
||||||
while (!me->spin) cpu_relax();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void unlock_mcs(mcs_lock *m, mcs_lock_t *me) {
|
|
||||||
if (!me->next) {
|
|
||||||
if (cmpxchg(m, me, NULL) == me) return;
|
|
||||||
while (!me->next) cpu_relax();
|
|
||||||
}
|
|
||||||
me->next->spin = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //EHVIEWER_SPINLOCK_H
|
|
||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,41 +0,0 @@
|
|||||||
package androidx.navigation.ui
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.view.MenuItem
|
|
||||||
import androidx.annotation.IdRes
|
|
||||||
import androidx.navigation.ActivityNavigator
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.NavDestination
|
|
||||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
|
||||||
import androidx.navigation.NavOptions
|
|
||||||
|
|
||||||
@SuppressLint("PrivateResource")
|
|
||||||
fun onNavDestinationSelected2(item: MenuItem, navController: NavController): Boolean {
|
|
||||||
val builder = NavOptions.Builder().setLaunchSingleTop(true)
|
|
||||||
if (
|
|
||||||
navController.currentDestination!!.parent!!.findNode(item.itemId)
|
|
||||||
is ActivityNavigator.Destination
|
|
||||||
) {
|
|
||||||
builder.setEnterAnim(R.anim.nav_default_enter_anim)
|
|
||||||
.setExitAnim(R.anim.nav_default_exit_anim)
|
|
||||||
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
|
|
||||||
.setPopExitAnim(R.anim.nav_default_pop_exit_anim)
|
|
||||||
} else {
|
|
||||||
builder.setEnterAnim(androidx.fragment.R.animator.fragment_open_enter)
|
|
||||||
.setExitAnim(androidx.fragment.R.animator.fragment_open_exit)
|
|
||||||
.setPopEnterAnim(androidx.fragment.R.animator.fragment_close_enter)
|
|
||||||
.setPopExitAnim(androidx.fragment.R.animator.fragment_close_exit)
|
|
||||||
}
|
|
||||||
val options = builder.build()
|
|
||||||
return try {
|
|
||||||
// TODO provide proper API instead of using Exceptions as Control-Flow.
|
|
||||||
navController.navigate(item.itemId, null, options)
|
|
||||||
// Return true only if the destination we've navigated to matches the MenuItem
|
|
||||||
navController.currentDestination?.matchDestination(item.itemId) == true
|
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun NavDestination.matchDestination(@IdRes destId: Int): Boolean =
|
|
||||||
hierarchy.any { it.id == destId }
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package androidx.swiperefreshlayout.widget
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.drawable.ShapeDrawable
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import androidx.core.graphics.ColorUtils
|
|
||||||
import rikka.core.res.resolveColor
|
|
||||||
|
|
||||||
@SuppressLint("PrivateResource")
|
|
||||||
class ThemedSwipeRefreshLayout @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
) : SwipeRefreshLayout(context, attrs) {
|
|
||||||
|
|
||||||
init {
|
|
||||||
val surfaceColor =
|
|
||||||
context.theme.resolveColor(com.google.android.material.R.attr.colorSurface)
|
|
||||||
val overlayColor =
|
|
||||||
context.getColor(com.google.android.material.R.color.m3_popupmenu_overlay_color)
|
|
||||||
val backgroundColor = ColorUtils.compositeColors(overlayColor, surfaceColor)
|
|
||||||
(mCircleView.background as ShapeDrawable).paint.color = backgroundColor
|
|
||||||
setColorSchemeColors(context.theme.resolveColor(com.google.android.material.R.attr.colorAccent))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
package com.davemorrissey.labs.subscaleview
|
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.Rect
|
|
||||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class used to set the source and additional attributes from a variety of sources. Supports
|
|
||||||
* use of a bitmap, asset, resource, external file or any other URI.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* When you are using a preview image, you must set the dimensions of the full size image on the
|
|
||||||
* ImageSource object for the full size image using the [.dimensions] method.
|
|
||||||
*/
|
|
||||||
class ImageSource {
|
|
||||||
val bitmap: Bitmap?
|
|
||||||
val provider: InputProvider?
|
|
||||||
|
|
||||||
private var sWidth = 0
|
|
||||||
private var sHeight = 0
|
|
||||||
|
|
||||||
var sRegion = Rect(0, 0, 0, 0)
|
|
||||||
private set
|
|
||||||
var isCached = false
|
|
||||||
private set
|
|
||||||
|
|
||||||
private constructor(bitmap: Bitmap, cached: Boolean) {
|
|
||||||
this.bitmap = bitmap
|
|
||||||
provider = null
|
|
||||||
sWidth = bitmap.width
|
|
||||||
sHeight = bitmap.height
|
|
||||||
isCached = cached
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor(provider: InputProvider) {
|
|
||||||
bitmap = null
|
|
||||||
this.provider = provider
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use a region of the source image. Region must be set independently for the full size image and the preview if
|
|
||||||
* you are using one.
|
|
||||||
*
|
|
||||||
* @param sRegion the region of the source image to be displayed.
|
|
||||||
* @return this instance for chaining.
|
|
||||||
*/
|
|
||||||
fun region(sRegion: Rect): ImageSource {
|
|
||||||
this.sRegion = sRegion
|
|
||||||
setInvariants()
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Declare the dimensions of the image. This is only required for a full size image, when you are specifying a URI
|
|
||||||
* and also a preview image. When displaying a bitmap object, or not using a preview, you do not need to declare
|
|
||||||
* the image dimensions. Note if the declared dimensions are found to be incorrect, the view will reset.
|
|
||||||
*
|
|
||||||
* @param sWidth width of the source image.
|
|
||||||
* @param sHeight height of the source image.
|
|
||||||
* @return this instance for chaining.
|
|
||||||
*/
|
|
||||||
fun dimensions(sWidth: Int, sHeight: Int): ImageSource {
|
|
||||||
if (bitmap == null) {
|
|
||||||
this.sWidth = sWidth
|
|
||||||
this.sHeight = sHeight
|
|
||||||
}
|
|
||||||
setInvariants()
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setInvariants() {
|
|
||||||
if (!sRegion.isEmpty) {
|
|
||||||
sWidth = sRegion.width()
|
|
||||||
sHeight = sRegion.height()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Create an instance from an input provider.
|
|
||||||
*
|
|
||||||
* @param provider input stream provider.
|
|
||||||
* @return an [ImageSource] instance.
|
|
||||||
*/
|
|
||||||
fun provider(provider: InputProvider): ImageSource {
|
|
||||||
return ImageSource(provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a loaded bitmap for display.
|
|
||||||
*
|
|
||||||
* @param bitmap bitmap to be displayed.
|
|
||||||
* @return an [ImageSource] instance.
|
|
||||||
*/
|
|
||||||
fun bitmap(bitmap: Bitmap): ImageSource {
|
|
||||||
return ImageSource(bitmap, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a loaded and cached bitmap for display. This bitmap will not be recycled when it is no
|
|
||||||
* longer needed. Use this method if you loaded the bitmap with an image loader such as Picasso
|
|
||||||
* or Volley.
|
|
||||||
*
|
|
||||||
* @param bitmap bitmap to be displayed.
|
|
||||||
* @return an [ImageSource] instance.
|
|
||||||
*/
|
|
||||||
fun cachedBitmap(bitmap: Bitmap): ImageSource {
|
|
||||||
return ImageSource(bitmap, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package com.davemorrissey.labs.subscaleview
|
|
||||||
|
|
||||||
import android.graphics.PointF
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps the scale, center and orientation of a displayed image for easy restoration on screen rotate.
|
|
||||||
*/
|
|
||||||
class ImageViewState(val scale: Float, center: PointF) : Serializable {
|
|
||||||
private val centerX: Float = center.x
|
|
||||||
private val centerY: Float = center.y
|
|
||||||
|
|
||||||
val center: PointF
|
|
||||||
get() = PointF(centerX, centerY)
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package com.davemorrissey.labs.subscaleview.decoder
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.Point
|
|
||||||
import android.graphics.Rect
|
|
||||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for image decoding classes.
|
|
||||||
*/
|
|
||||||
interface Decoder {
|
|
||||||
/**
|
|
||||||
* Initialise the decoder. When possible, perform initial setup work once in this method. The
|
|
||||||
* dimensions of the image must be returned.
|
|
||||||
*
|
|
||||||
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
|
||||||
* @param provider Provider of the image.
|
|
||||||
* @return Dimensions of the image.
|
|
||||||
* @throws Exception if initialisation fails.
|
|
||||||
*/
|
|
||||||
fun init(context: Context, provider: InputProvider): Point
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode a region of the image with the given sample size. This method is called off the UI
|
|
||||||
* thread so it can safely load the image on the current thread. It is called from
|
|
||||||
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
|
|
||||||
* implementations must be thread safe. Adding `synchronized` to the method signature
|
|
||||||
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
|
|
||||||
* called concurrently.
|
|
||||||
*
|
|
||||||
* @param sRect Source image rectangle to decode.
|
|
||||||
* @param sampleSize Sample size.
|
|
||||||
* @return The decoded region. It is safe to return null if decoding fails.
|
|
||||||
*/
|
|
||||||
fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Status check. Should return false before initialisation and after recycle.
|
|
||||||
*
|
|
||||||
* @return true if the decoder is ready to be used.
|
|
||||||
*/
|
|
||||||
val isReady: Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will be called when the decoder is no longer required. It should clean up any
|
|
||||||
* resources still in use.
|
|
||||||
*/
|
|
||||||
fun recycle()
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package com.davemorrissey.labs.subscaleview.decoder
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.Point
|
|
||||||
import android.graphics.Rect
|
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
|
||||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
|
||||||
|
|
||||||
class ImageDecoder(private val cropBorders: Boolean, bitmapConfig: Bitmap.Config? = null) : Decoder {
|
|
||||||
init {
|
|
||||||
val globalBitmapConfig = SubsamplingScaleImageView.preferredBitmapConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialise the decoder. When possible, perform initial setup work once in this method. The
|
|
||||||
* dimensions of the image must be returned.
|
|
||||||
*
|
|
||||||
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
|
||||||
* @param provider Provider of the image.
|
|
||||||
* @return Dimensions of the image.
|
|
||||||
* @throws Exception if initialisation fails.
|
|
||||||
*/
|
|
||||||
override fun init(context: Context, provider: InputProvider): Point {
|
|
||||||
TODO()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode a region of the image with the given sample size. This method is called off the UI
|
|
||||||
* thread so it can safely load the image on the current thread. It is called from
|
|
||||||
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
|
|
||||||
* implementations must be thread safe. Adding `synchronized` to the method signature
|
|
||||||
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
|
|
||||||
* called concurrently.
|
|
||||||
*
|
|
||||||
* @param sRect Source image rectangle to decode.
|
|
||||||
* @param sampleSize Sample size.
|
|
||||||
* @return The decoded region. It is safe to return null if decoding fails.
|
|
||||||
*/
|
|
||||||
override fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap {
|
|
||||||
TODO()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Status check. Should return false before initialisation and after recycle.
|
|
||||||
*
|
|
||||||
* @return true if the decoder is ready to be used.
|
|
||||||
*/
|
|
||||||
override val isReady: Boolean = TODO()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will be called when the decoder is no longer required. It should clean up any
|
|
||||||
* resources still in use.
|
|
||||||
*/
|
|
||||||
override fun recycle() {
|
|
||||||
TODO()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package com.davemorrissey.labs.subscaleview.provider
|
|
||||||
|
|
||||||
interface InputProvider
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.hippo.ehviewer
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.ComponentCallbacks2
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.collection.LruCache
|
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
|
||||||
import androidx.lifecycle.coroutineScope
|
|
||||||
import coil.ImageLoaderFactory
|
|
||||||
import coil.decode.ImageDecoderDecoder
|
|
||||||
import coil.util.DebugLogger
|
|
||||||
import com.hippo.ehviewer.client.EhCookieStore
|
|
||||||
import com.hippo.ehviewer.client.EhTagDatabase
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
|
||||||
import com.hippo.ehviewer.coil.MergeInterceptor
|
|
||||||
import com.hippo.ehviewer.coil.installCronetHttpUriFetcher
|
|
||||||
import com.hippo.ehviewer.dailycheck.checkDawn
|
|
||||||
import com.hippo.ehviewer.dao.SearchDatabase
|
|
||||||
import com.hippo.ehviewer.download.DownloadManager
|
|
||||||
import com.hippo.ehviewer.ktbuilder.cache
|
|
||||||
import com.hippo.ehviewer.ktbuilder.chunker
|
|
||||||
import com.hippo.ehviewer.ktbuilder.diskCache
|
|
||||||
import com.hippo.ehviewer.ktbuilder.httpClient
|
|
||||||
import com.hippo.ehviewer.ktbuilder.imageLoader
|
|
||||||
import com.hippo.ehviewer.legacy.cleanObsoleteCache
|
|
||||||
import com.hippo.ehviewer.legacy.migrateCookies
|
|
||||||
import com.hippo.ehviewer.ui.keepNoMediaFileStatus
|
|
||||||
import com.hippo.ehviewer.ui.lockObserver
|
|
||||||
import com.hippo.ehviewer.util.AppConfig
|
|
||||||
import com.hippo.ehviewer.util.Crash
|
|
||||||
import com.hippo.ehviewer.util.FavouriteStatusRouter
|
|
||||||
import com.hippo.ehviewer.util.FileUtils
|
|
||||||
import com.hippo.ehviewer.util.ReadableTime
|
|
||||||
import com.hippo.ehviewer.util.isAtLeastQ
|
|
||||||
import com.hippo.ehviewer.util.isCronetSupported
|
|
||||||
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import moe.tarsin.kt.unreachable
|
|
||||||
import okhttp3.AsyncDns
|
|
||||||
import okhttp3.android.AndroidAsyncDns
|
|
||||||
import okio.Path.Companion.toOkioPath
|
|
||||||
import splitties.arch.room.roomDb
|
|
||||||
import splitties.init.appCtx
|
|
||||||
|
|
||||||
private val lifecycle = ProcessLifecycleOwner.get().lifecycle
|
|
||||||
private val lifecycleScope = lifecycle.coroutineScope
|
|
||||||
|
|
||||||
class EhApplication : Application(), ImageLoaderFactory {
|
|
||||||
override fun onCreate() {
|
|
||||||
lifecycle.addObserver(lockObserver)
|
|
||||||
val handler = Thread.getDefaultUncaughtExceptionHandler()
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler { t, e ->
|
|
||||||
try {
|
|
||||||
if (Settings.saveCrashLog) {
|
|
||||||
Crash.saveCrashLog(e)
|
|
||||||
}
|
|
||||||
} catch (ignored: Throwable) {
|
|
||||||
}
|
|
||||||
handler?.uncaughtException(t, e)
|
|
||||||
}
|
|
||||||
super.onCreate()
|
|
||||||
System.loadLibrary("ehviewer")
|
|
||||||
System.loadLibrary("ehviewer_rust")
|
|
||||||
ReadableTime.initialize(this)
|
|
||||||
lifecycleScope.launchIO {
|
|
||||||
val theme = Settings.theme
|
|
||||||
withUIContext {
|
|
||||||
AppCompatDelegate.setDefaultNightMode(theme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lifecycleScope.launchIO {
|
|
||||||
launchIO {
|
|
||||||
migrateCookies()
|
|
||||||
}
|
|
||||||
launchIO {
|
|
||||||
EhTagDatabase.update()
|
|
||||||
}
|
|
||||||
launchIO {
|
|
||||||
EhDB
|
|
||||||
}
|
|
||||||
launchIO {
|
|
||||||
DownloadManager.isIdle
|
|
||||||
}
|
|
||||||
launchIO {
|
|
||||||
cleanupDownload()
|
|
||||||
}
|
|
||||||
if (Settings.requestNews) {
|
|
||||||
launchIO {
|
|
||||||
checkDawn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cleanObsoleteCache(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun cleanupDownload() {
|
|
||||||
runCatching {
|
|
||||||
keepNoMediaFileStatus()
|
|
||||||
}.onFailure {
|
|
||||||
it.printStackTrace()
|
|
||||||
}
|
|
||||||
runCatching {
|
|
||||||
clearTempDir()
|
|
||||||
}.onFailure {
|
|
||||||
it.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearTempDir() {
|
|
||||||
var dir = AppConfig.tempDir
|
|
||||||
if (null != dir) {
|
|
||||||
FileUtils.deleteContent(dir)
|
|
||||||
}
|
|
||||||
dir = AppConfig.externalTempDir
|
|
||||||
if (null != dir) {
|
|
||||||
FileUtils.deleteContent(dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTrimMemory(level: Int) {
|
|
||||||
super.onTrimMemory(level)
|
|
||||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
|
|
||||||
galleryDetailCache.evictAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun newImageLoader() = imageLoader {
|
|
||||||
components {
|
|
||||||
if (isCronetSupported) {
|
|
||||||
callFactory { unreachable() }
|
|
||||||
installCronetHttpUriFetcher()
|
|
||||||
} else {
|
|
||||||
callFactory(baseOkHttpClient)
|
|
||||||
}
|
|
||||||
add { result, options, _ -> ImageDecoderDecoder(result.source, options, false) }
|
|
||||||
add(MergeInterceptor)
|
|
||||||
}
|
|
||||||
diskCache(imageCache)
|
|
||||||
crossfade(300)
|
|
||||||
error(R.drawable.image_failed)
|
|
||||||
if (BuildConfig.DEBUG) logger(DebugLogger())
|
|
||||||
respectCacheHeaders(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val baseOkHttpClient by lazy {
|
|
||||||
httpClient {
|
|
||||||
cookieJar(EhCookieStore)
|
|
||||||
if (isAtLeastQ) {
|
|
||||||
dns(AsyncDns.toDns(AndroidAsyncDns.IPv4, AndroidAsyncDns.IPv6))
|
|
||||||
}
|
|
||||||
chunker { alwaysReadResponseBody(false) }
|
|
||||||
addInterceptor(CloudflareInterceptor(appCtx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val noRedirectOkHttpClient by lazy {
|
|
||||||
httpClient(baseOkHttpClient) {
|
|
||||||
followRedirects(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Never use this okhttp client to download large blobs!!!
|
|
||||||
val okHttpClient by lazy {
|
|
||||||
httpClient(baseOkHttpClient) {
|
|
||||||
cache(
|
|
||||||
appCtx.cacheDir.toOkioPath() / "http_cache",
|
|
||||||
20L * 1024L * 1024L,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val galleryDetailCache by lazy {
|
|
||||||
LruCache<Long, GalleryDetail>(25).also {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
FavouriteStatusRouter.globalFlow.collect { (gid, slot) -> it[gid]?.favoriteSlot = slot }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val imageCache by lazy {
|
|
||||||
diskCache {
|
|
||||||
directory(appCtx.cacheDir.toOkioPath() / "image_cache")
|
|
||||||
maxSizeBytes(Settings.readCacheSize.coerceIn(320, 5120).toLong() * 1024 * 1024)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val searchDatabase by lazy { roomDb<SearchDatabase>("search_database.db") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
@file:Suppress("SameParameterValue")
|
|
||||||
|
|
||||||
package com.hippo.ehviewer
|
|
||||||
|
|
||||||
import com.hippo.ehviewer.client.EhUtils
|
|
||||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.asFlow
|
|
||||||
import kotlinx.coroutines.flow.conflate
|
|
||||||
import kotlinx.coroutines.flow.debounce
|
|
||||||
import kotlinx.coroutines.flow.flatMapMerge
|
|
||||||
import splitties.preferences.DataStorePreferences
|
|
||||||
import splitties.preferences.edit
|
|
||||||
import java.util.Locale
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
object Settings : DataStorePreferences(null) {
|
|
||||||
private const val KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations"
|
|
||||||
|
|
||||||
private val _favFlow = MutableSharedFlow<Unit>()
|
|
||||||
val favChangesFlow = _favFlow.debounce(1000)
|
|
||||||
var favCat by stringArrayPref("fav_cat", 10, "Favorites").emitTo(_favFlow)
|
|
||||||
var favCount by intArrayPref("fav_count", 10).emitTo(_favFlow)
|
|
||||||
var favCloudCount by intPref("fav_cloud", 0).emitTo(_favFlow)
|
|
||||||
|
|
||||||
var downloadScheme by stringOrNullPref("image_scheme", null)
|
|
||||||
var downloadAuthority by stringOrNullPref("image_authority", null)
|
|
||||||
var downloadPath by stringOrNullPref("image_path", null)
|
|
||||||
var downloadQuery by stringOrNullPref("image_query", null)
|
|
||||||
var downloadFragment by stringOrNullPref("image_fragment", null)
|
|
||||||
var archivePasswds by stringSetOrNullPref("archive_passwds")
|
|
||||||
var downloadDelay by intFromStrPref("download_delay", 0)
|
|
||||||
var gallerySite by intFromStrPref("gallery_site", 0).observed { updateWhenGallerySiteChanges() }
|
|
||||||
var multiThreadDownload by intFromStrPref("download_thread", 3)
|
|
||||||
var preloadImage by intFromStrPref("preload_image", 5)
|
|
||||||
var theme by intFromStrPref("theme", -1).observed { updateWhenThemeChanges() }
|
|
||||||
var listMode by intFromStrPref("list_mode", 0)
|
|
||||||
var detailSize by intFromStrPref("detail_size", 0)
|
|
||||||
var thumbResolution by intFromStrPref("thumb_resolution", 0)
|
|
||||||
var readCacheSize by intFromStrPref("read_cache_size", 640)
|
|
||||||
var launchPage by intFromStrPref("launch_page", 0)
|
|
||||||
var forceEhThumb by boolPref("force_eh_thumb", false)
|
|
||||||
var showComments by boolPref("show_gallery_comments", true)
|
|
||||||
var requestNews by boolPref("request_news", false).observed { updateWhenRequestNewsChanges() }
|
|
||||||
var hideHvEvents by boolPref("hide_hv_events", false)
|
|
||||||
var showJpnTitle by boolPref("show_jpn_title", false)
|
|
||||||
var showGalleryPages by boolPref("show_gallery_pages", false)
|
|
||||||
var showTagTranslations by boolPref(KEY_SHOW_TAG_TRANSLATIONS, false).observed { updateWhenTagTranslationChanges() }
|
|
||||||
var meteredNetworkWarning by boolPref("cellular_network_warning", false)
|
|
||||||
var appLinkVerifyTip by boolPref("app_link_verify_tip", false)
|
|
||||||
var enabledSecurity by boolPref("enable_secure", false)
|
|
||||||
var useCIUpdateChannel by boolPref("ci_update_channel", false)
|
|
||||||
var mediaScan by boolPref("media_scan", false).observed { updateWhenKeepMediaStatusChanges() }
|
|
||||||
var hasDefaultDownloadLabel by boolPref("has_default_download_label", false)
|
|
||||||
var qSSaveProgress by boolPref("qs_save_progress", true)
|
|
||||||
var saveParseErrorBody by boolPref("save_parse_error_body", true)
|
|
||||||
var saveCrashLog by boolPref("save_crash_log", true)
|
|
||||||
var security by boolPref("require_unlock", false)
|
|
||||||
var removeImageFiles by boolPref("include_pic", true)
|
|
||||||
var needSignIn by boolPref("need_sign_in", true)
|
|
||||||
var blackDarkTheme by boolPref("black_dark_theme", false).observed { updateWhenAmoledModeChanges() }
|
|
||||||
var preloadThumbAggressively by boolPref("preload_thumb_aggressively", false)
|
|
||||||
var downloadOriginImage by boolPref("download_origin_image", false)
|
|
||||||
var enableQuic by boolPref("enable_quic", true)
|
|
||||||
var thumbSizeDp by intPref("thumb_size_", 120)
|
|
||||||
var recentFavCat by intPref("recent_fav_cat", FavListUrlBuilder.FAV_CAT_LOCAL)
|
|
||||||
var defaultFavSlot by intPref("default_favorite_slot", -2)
|
|
||||||
var securityDelay by intPref("require_unlock_delay", 0)
|
|
||||||
var clipboardTextHashCode by intPref("clipboard_text_hash_code", 0)
|
|
||||||
var listThumbSize by intPref("list_tile_size", 40)
|
|
||||||
var searchCategory by intPref("search_pref", EhUtils.ALL_CATEGORY)
|
|
||||||
var requestNewsTimerHour by intPref("request_news_timer_hour", -1)
|
|
||||||
var requestNewsTimerMinute by intPref("request_news_timer_minute", -1)
|
|
||||||
var updateIntervalDays by intPref("update_interval_days", 0)
|
|
||||||
var recentDownloadLabel by stringOrNullPref("recent_download_label", null)
|
|
||||||
var defaultDownloadLabel by stringOrNullPref("default_download_label", null)
|
|
||||||
var displayName by stringOrNullPref("display_name", null)
|
|
||||||
var avatar by stringOrNullPref("avatar", null)
|
|
||||||
var language by stringPref("app_language", "system").observed { updateWhenLocaleChanges() }
|
|
||||||
var lastDawnDay by longPref("last_dawn_day", 0)
|
|
||||||
var lastUpdateDay by longPref("last_update_day", 0)
|
|
||||||
|
|
||||||
init {
|
|
||||||
if ("CN" == Locale.getDefault().country) {
|
|
||||||
edit {
|
|
||||||
if (KEY_SHOW_TAG_TRANSLATIONS !in prefs) showTagTranslations = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Delegate<R> {
|
|
||||||
val flowGetter: () -> Flow<Unit>
|
|
||||||
operator fun getValue(thisRef: Any?, prop: KProperty<*>?): R
|
|
||||||
operator fun setValue(thisRef: Any?, prop: KProperty<*>?, value: R)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun intFromStrPref(key: String, defValue: Int) = object : Delegate<Int> {
|
|
||||||
override val flowGetter: () -> Flow<Unit>
|
|
||||||
private var _value by stringPref(key, defValue.toString()).also { flowGetter = { it.changesFlow() } }
|
|
||||||
override fun getValue(thisRef: Any?, prop: KProperty<*>?) = _value.toIntOrNull() ?: defValue
|
|
||||||
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: Int) { _value = value.toString() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun intArrayPref(key: String, count: Int) = object : Delegate<IntArray> {
|
|
||||||
override val flowGetter: () -> Flow<Unit> = { _value.asFlow().flatMapMerge { it.changesFlow() }.conflate() }
|
|
||||||
private var _value = (0 until count).map { intPref("${key}_$it", 0) }.toTypedArray()
|
|
||||||
override fun getValue(thisRef: Any?, prop: KProperty<*>?): IntArray = _value.map { it.value }.toIntArray()
|
|
||||||
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: IntArray) {
|
|
||||||
check(value.size == count)
|
|
||||||
edit { value.zip(_value) { v, d -> d.value = v } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stringArrayPref(key: String, count: Int, defMetaValue: String) = object : Delegate<Array<String>> {
|
|
||||||
override val flowGetter: () -> Flow<Unit> = { _value.asFlow().flatMapMerge { it.changesFlow() }.conflate() }
|
|
||||||
private var _value = (0 until count).map { stringPref("${key}_$it", "$defMetaValue $it") }.toTypedArray()
|
|
||||||
override fun getValue(thisRef: Any?, prop: KProperty<*>?): Array<String> = _value.map { it.value }.toTypedArray()
|
|
||||||
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: Array<String>) {
|
|
||||||
check(value.size == count)
|
|
||||||
edit { value.zip(_value) { v, d -> d.value = v } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.client
|
|
||||||
|
|
||||||
import android.webkit.CookieManager
|
|
||||||
import okhttp3.Cookie
|
|
||||||
import okhttp3.CookieJar
|
|
||||||
import okhttp3.HttpUrl
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
|
|
||||||
object EhCookieStore : CookieJar {
|
|
||||||
private val manager = CookieManager.getInstance()
|
|
||||||
fun signOut() = manager.removeAllCookies(null)
|
|
||||||
fun contains(url: HttpUrl, name: String) = get(url).any { it.name == name }
|
|
||||||
|
|
||||||
fun get(url: HttpUrl): List<Cookie> {
|
|
||||||
val cookies = manager.getCookie(url.toString())
|
|
||||||
|
|
||||||
return if (cookies != null && cookies.isNotEmpty()) {
|
|
||||||
cookies.split(";").mapNotNull { Cookie.parse(url, it) }.filterNot { it.name == KEY_UTMP_NAME }
|
|
||||||
} else {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hasSignedIn(): Boolean {
|
|
||||||
val url = EhUrl.HOST_E.toHttpUrl()
|
|
||||||
return contains(url, KEY_IPB_MEMBER_ID) && contains(url, KEY_IPB_PASS_HASH)
|
|
||||||
}
|
|
||||||
|
|
||||||
const val KEY_IPB_MEMBER_ID = "ipb_member_id"
|
|
||||||
const val KEY_IPB_PASS_HASH = "ipb_pass_hash"
|
|
||||||
const val KEY_IGNEOUS = "igneous"
|
|
||||||
private const val KEY_STAR = "star"
|
|
||||||
private const val KEY_CONTENT_WARNING = "nw"
|
|
||||||
private const val CONTENT_WARNING_NOT_SHOW = "1"
|
|
||||||
private const val KEY_UTMP_NAME = "__utmp"
|
|
||||||
private val sTipsCookie = Cookie.Builder().apply {
|
|
||||||
name(KEY_CONTENT_WARNING)
|
|
||||||
value(CONTENT_WARNING_NOT_SHOW)
|
|
||||||
domain(EhUrl.DOMAIN_E)
|
|
||||||
path("/")
|
|
||||||
expiresAt(Long.MAX_VALUE)
|
|
||||||
}.build()
|
|
||||||
|
|
||||||
fun copyNecessaryCookies() {
|
|
||||||
val cookie = get(EhUrl.HOST_E.toHttpUrl()).filter { it.name == KEY_STAR || it.name == KEY_IPB_MEMBER_ID || it.name == KEY_IPB_PASS_HASH || it.name == KEY_IGNEOUS }
|
|
||||||
cookie.forEach { manager.setCookie(EhUrl.HOST_EX, it.toString()) }
|
|
||||||
flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteCookie(url: HttpUrl, name: String) {
|
|
||||||
manager.setCookie(url.toString(), "$name=;Max-Age=0")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addCookie(cookie: Cookie) {
|
|
||||||
if (EhUrl.DOMAIN_E in cookie.domain) manager.setCookie(EhUrl.HOST_E, cookie.toString()) else manager.setCookie(EhUrl.DOMAIN_EX, cookie.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun flush() = manager.flush()
|
|
||||||
|
|
||||||
fun getCookieHeader(url: HttpUrl): String {
|
|
||||||
val cookies = loadForRequest(url)
|
|
||||||
val cookieHeader = StringBuilder()
|
|
||||||
var i = 0
|
|
||||||
val size = cookies.size
|
|
||||||
while (i < size) {
|
|
||||||
if (i > 0) {
|
|
||||||
cookieHeader.append("; ")
|
|
||||||
}
|
|
||||||
val cookie = cookies[i]
|
|
||||||
cookieHeader.append(cookie.name).append('=').append(cookie.value)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return cookieHeader.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://github.com/Ehviewer-Overhauled/Ehviewer/issues/873
|
|
||||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) = cookies.filterNot { it.name == KEY_UTMP_NAME }.forEach { manager.setCookie(url.toString(), it.toString()) }.also { flush() }
|
|
||||||
|
|
||||||
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
|
||||||
val checkTips = EhUrl.DOMAIN_E in url.host
|
|
||||||
return get(url).run {
|
|
||||||
if (checkTips) {
|
|
||||||
filterNot { it.name == KEY_CONTENT_WARNING }.toMutableList().apply { add(sTipsCookie) }
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,525 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.client
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import arrow.fx.coroutines.parMap
|
|
||||||
import arrow.fx.coroutines.parZip
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
import com.hippo.ehviewer.Settings
|
|
||||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
|
||||||
import com.hippo.ehviewer.client.exception.EhException
|
|
||||||
import com.hippo.ehviewer.client.exception.InsufficientFundsException
|
|
||||||
import com.hippo.ehviewer.client.exception.NotLoggedInException
|
|
||||||
import com.hippo.ehviewer.client.exception.ParseException
|
|
||||||
import com.hippo.ehviewer.client.parser.ArchiveParser
|
|
||||||
import com.hippo.ehviewer.client.parser.EventPaneParser
|
|
||||||
import com.hippo.ehviewer.client.parser.FavoritesParser
|
|
||||||
import com.hippo.ehviewer.client.parser.ForumsParser
|
|
||||||
import com.hippo.ehviewer.client.parser.GalleryApiParser
|
|
||||||
import com.hippo.ehviewer.client.parser.GalleryDetailParser
|
|
||||||
import com.hippo.ehviewer.client.parser.GalleryListParser
|
|
||||||
import com.hippo.ehviewer.client.parser.GalleryNotAvailableParser
|
|
||||||
import com.hippo.ehviewer.client.parser.GalleryPageApiParser
|
|
||||||
import com.hippo.ehviewer.client.parser.GalleryPageParser
|
|
||||||
import com.hippo.ehviewer.client.parser.GalleryTokenApiParser
|
|
||||||
import com.hippo.ehviewer.client.parser.HomeParser
|
|
||||||
import com.hippo.ehviewer.client.parser.ProfileParser
|
|
||||||
import com.hippo.ehviewer.client.parser.RateGalleryResult
|
|
||||||
import com.hippo.ehviewer.client.parser.SignInParser
|
|
||||||
import com.hippo.ehviewer.client.parser.TorrentParser
|
|
||||||
import com.hippo.ehviewer.client.parser.TorrentResult
|
|
||||||
import com.hippo.ehviewer.client.parser.VoteCommentResult
|
|
||||||
import com.hippo.ehviewer.client.parser.VoteTagParser
|
|
||||||
import com.hippo.ehviewer.cronet.awaitBodyFully
|
|
||||||
import com.hippo.ehviewer.cronet.cronetRequest
|
|
||||||
import com.hippo.ehviewer.cronet.execute
|
|
||||||
import com.hippo.ehviewer.dailycheck.showEventNotification
|
|
||||||
import com.hippo.ehviewer.dailycheck.today
|
|
||||||
import com.hippo.ehviewer.util.AppConfig
|
|
||||||
import com.hippo.ehviewer.util.StatusCodeException
|
|
||||||
import com.hippo.ehviewer.util.isCronetSupported
|
|
||||||
import io.ktor.utils.io.pool.DirectByteBufferPool
|
|
||||||
import kotlinx.coroutines.CancellationException
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.serialization.json.add
|
|
||||||
import kotlinx.serialization.json.addJsonArray
|
|
||||||
import kotlinx.serialization.json.put
|
|
||||||
import moe.tarsin.coroutines.removeAllSuspend
|
|
||||||
import moe.tarsin.coroutines.runSuspendCatching
|
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.MediaType
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
|
||||||
import okhttp3.MultipartBody
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.RequestBody.Companion.asRequestBody
|
|
||||||
import org.jsoup.Jsoup
|
|
||||||
import splitties.init.appCtx
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import kotlin.math.ceil
|
|
||||||
|
|
||||||
private const val TAG = "EhEngine"
|
|
||||||
private const val MAX_REQUEST_SIZE = 25
|
|
||||||
private const val SAD_PANDA_DISPOSITION = "inline; filename=\"sadpanda.jpg\""
|
|
||||||
private const val SAD_PANDA_TYPE = "image/gif"
|
|
||||||
private const val SAD_PANDA_LENGTH = "9615"
|
|
||||||
private const val KOKOMADE_URL = "https://exhentai.org/img/kokomade.jpg"
|
|
||||||
private const val U_CONFIG_TEXT = "Selected Profile"
|
|
||||||
private val MEDIA_TYPE_JPEG: MediaType = "image/jpeg".toMediaType()
|
|
||||||
|
|
||||||
private fun rethrowExactly(code: Int, headers: Headers, body: String, e: Throwable): Nothing {
|
|
||||||
// Don't translate coroutine cancellation
|
|
||||||
if (e is CancellationException) throw e
|
|
||||||
|
|
||||||
// Check sad panda
|
|
||||||
if (SAD_PANDA_DISPOSITION == headers["Content-Disposition"] && SAD_PANDA_TYPE == headers["Content-Type"] && SAD_PANDA_LENGTH == headers["Content-Length"]) {
|
|
||||||
throw EhException("Sad Panda")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check sad panda(without panda)
|
|
||||||
if ("text/html; charset=UTF-8" == headers["Content-Type"] && "0" == headers["Content-Length"] && EhUtils.isExHentai) {
|
|
||||||
throw EhException("Sad Panda\n(without panda)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check kokomade
|
|
||||||
if (body.contains(KOKOMADE_URL)) {
|
|
||||||
throw EhException("今回はここまで ${appCtx.getString(R.string.kokomade_tip)}".trimIndent())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check Gallery Not Available
|
|
||||||
if (body.contains("Gallery Not Available - ")) {
|
|
||||||
val error = GalleryNotAvailableParser.parse(body)
|
|
||||||
if (!error.isNullOrBlank()) {
|
|
||||||
throw EhException(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check bad response code
|
|
||||||
if (code >= 400) {
|
|
||||||
throw StatusCodeException(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e is ParseException) {
|
|
||||||
if (body.isEmpty()) {
|
|
||||||
throw EhException(appCtx.getString(R.string.error_empty_html))
|
|
||||||
} else if ("<" !in body) {
|
|
||||||
throw EhException(body)
|
|
||||||
} else {
|
|
||||||
if (Settings.saveParseErrorBody) AppConfig.saveParseErrorBody(e)
|
|
||||||
throw EhException(appCtx.getString(R.string.error_parse_error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't translate it, rethrow it anyway
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
|
|
||||||
val httpContentPool = DirectByteBufferPool(16, 16384)
|
|
||||||
|
|
||||||
suspend inline fun <T> fetchCompat(url: String, referer: String? = null, crossinline parser: (ByteBuffer) -> T): T {
|
|
||||||
return if (isCronetSupported) {
|
|
||||||
cronetRequest(url, referer).execute {
|
|
||||||
val buffer = httpContentPool.borrow()
|
|
||||||
try {
|
|
||||||
awaitBodyFully {
|
|
||||||
buffer.put(it)
|
|
||||||
}
|
|
||||||
buffer.flip()
|
|
||||||
parser(buffer)
|
|
||||||
} finally {
|
|
||||||
httpContentPool.recycle(buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ehRequest(url, referer).execute {
|
|
||||||
val buffer = httpContentPool.borrow()
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
if (body.source().read(buffer) == -1) break
|
|
||||||
}
|
|
||||||
buffer.flip()
|
|
||||||
parser(buffer)
|
|
||||||
} finally {
|
|
||||||
httpContentPool.recycle(buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
|
||||||
private suspend inline fun <T> Request.executeAndParsingWith(block: suspend String.() -> T): T {
|
|
||||||
Log.d(TAG, url.toString())
|
|
||||||
return execute {
|
|
||||||
val body = body.string()
|
|
||||||
try {
|
|
||||||
block(body)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
rethrowExactly(code, headers, body, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object EhEngine {
|
|
||||||
suspend fun getOriginalImageUrl(url: String, referer: String?): String {
|
|
||||||
Log.d(TAG, url)
|
|
||||||
return ehRequest(url, referer).executeNoRedirect {
|
|
||||||
header("Location")?.apply {
|
|
||||||
if (contains("bounce_login")) {
|
|
||||||
throw NotLoggedInException()
|
|
||||||
}
|
|
||||||
} ?: throw InsufficientFundsException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getTorrentList(url: String, gid: Long, token: String?): TorrentResult {
|
|
||||||
val referer = EhUrl.getGalleryDetailUrl(gid, token)
|
|
||||||
return fetchCompat(url, referer, TorrentParser::parse)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getArchiveList(url: String, gid: Long, token: String?) = ehRequest(url, EhUrl.getGalleryDetailUrl(gid, token))
|
|
||||||
.executeAndParsingWith(ArchiveParser::parse)
|
|
||||||
.apply { funds = funds ?: ehRequest(EhUrl.URL_FUNDS).executeAndParsingWith(HomeParser::parseFunds) }
|
|
||||||
|
|
||||||
suspend fun getImageLimits() = parZip(
|
|
||||||
{ fetchCompat(EhUrl.URL_HOME, parser = HomeParser::parse) },
|
|
||||||
{ ehRequest(EhUrl.URL_FUNDS).executeAndParsingWith(HomeParser::parseFunds) },
|
|
||||||
{ limits, funds -> HomeParser.Result(limits, funds) },
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun getNews(parse: Boolean) = ehRequest(EhUrl.URL_NEWS, EhUrl.REFERER_E)
|
|
||||||
.executeAndParsingWith { if (parse) EventPaneParser.parse(this) else null }
|
|
||||||
|
|
||||||
suspend fun getProfile(): ProfileParser.Result {
|
|
||||||
val url = ehRequest(EhUrl.URL_FORUMS).executeAndParsingWith(ForumsParser::parse)
|
|
||||||
return ehRequest(url, EhUrl.URL_FORUMS).executeAndParsingWith(ProfileParser::parse)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getUConfig(url: String = EhUrl.uConfigUrl) {
|
|
||||||
runSuspendCatching {
|
|
||||||
ehRequest(url).executeAndParsingWith { check(contains(U_CONFIG_TEXT)) { "Unable to load config from $url!" } }
|
|
||||||
}.onFailure {
|
|
||||||
// It may get redirected when accessing ex for the first time
|
|
||||||
if (url == EhUrl.URL_UCONFIG_EX) {
|
|
||||||
it.printStackTrace()
|
|
||||||
ehRequest(url).executeAndParsingWith { check(contains(U_CONFIG_TEXT)) { "Unable to load config from $url!" } }
|
|
||||||
} else {
|
|
||||||
throw it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getGalleryPage(
|
|
||||||
url: String,
|
|
||||||
gid: Long,
|
|
||||||
token: String?,
|
|
||||||
): GalleryPageParser.Result {
|
|
||||||
val referer = EhUrl.getGalleryDetailUrl(gid, token)
|
|
||||||
return ehRequest(url, referer).executeAndParsingWith(GalleryPageParser::parse)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getGalleryList(url: String) = ehRequest(url, EhUrl.referer)
|
|
||||||
.executeAndParsingWith(GalleryListParser::parse)
|
|
||||||
.apply { fillGalleryList(galleryInfoList, url, true) }
|
|
||||||
|
|
||||||
suspend fun getGalleryDetail(url: String) = ehRequest(url, EhUrl.referer).executeAndParsingWith {
|
|
||||||
EventPaneParser.parse(this)?.let {
|
|
||||||
Settings.lastDawnDay = today
|
|
||||||
showEventNotification(it)
|
|
||||||
}
|
|
||||||
GalleryDetailParser.parse(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getPreviewList(url: String) = ehRequest(url, EhUrl.referer).executeAndParsingWith {
|
|
||||||
GalleryDetailParser.parsePreviewList(this) to GalleryDetailParser.parsePreviewPages(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getFavorites(url: String) = ehRequest(url, EhUrl.referer)
|
|
||||||
.executeAndParsingWith(FavoritesParser::parse)
|
|
||||||
.apply { fillGalleryList(galleryInfoList, url, false) }
|
|
||||||
|
|
||||||
suspend fun signIn(username: String, password: String): String {
|
|
||||||
val referer = "https://forums.e-hentai.org/index.php?act=Login&CODE=00"
|
|
||||||
val url = EhUrl.API_SIGN_IN
|
|
||||||
val origin = "https://forums.e-hentai.org"
|
|
||||||
return ehRequest(url, referer, origin) {
|
|
||||||
formBody {
|
|
||||||
add("referer", referer)
|
|
||||||
add("b", "")
|
|
||||||
add("bt", "")
|
|
||||||
add("UserName", username)
|
|
||||||
add("PassWord", password)
|
|
||||||
add("CookieDate", "1")
|
|
||||||
}
|
|
||||||
}.executeAndParsingWith(SignInParser::parse)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun commentGallery(url: String, comment: String, id: String?) = ehRequest(url, url, EhUrl.origin) {
|
|
||||||
formBody {
|
|
||||||
if (id == null) {
|
|
||||||
add("commenttext_new", comment)
|
|
||||||
} else {
|
|
||||||
add("commenttext_edit", comment)
|
|
||||||
add("edit_comment", id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.executeAndParsingWith {
|
|
||||||
val document = Jsoup.parse(this)
|
|
||||||
val elements = document.select("#chd + p")
|
|
||||||
if (elements.size > 0) {
|
|
||||||
throw EhException(elements[0].text())
|
|
||||||
}
|
|
||||||
GalleryDetailParser.parseComments(document)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param dstCat -1 for delete, 0 - 9 for cloud favorite, others throw Exception
|
|
||||||
* @param note max 250 characters
|
|
||||||
*/
|
|
||||||
suspend fun modifyFavorites(
|
|
||||||
gid: Long,
|
|
||||||
token: String?,
|
|
||||||
dstCat: Int = -1,
|
|
||||||
note: String = "",
|
|
||||||
) {
|
|
||||||
val catStr: String = when (dstCat) {
|
|
||||||
-1 -> "favdel"
|
|
||||||
in 0..9 -> dstCat.toString()
|
|
||||||
else -> throw EhException("Invalid dstCat: $dstCat")
|
|
||||||
}
|
|
||||||
val url = EhUrl.getAddFavorites(gid, token)
|
|
||||||
return ehRequest(url, url, EhUrl.origin) {
|
|
||||||
formBody {
|
|
||||||
add("favcat", catStr)
|
|
||||||
add("favnote", note)
|
|
||||||
// apply=Add+to+Favorites is not necessary, just use apply=Apply+Changes all the time
|
|
||||||
add("apply", "Apply Changes")
|
|
||||||
add("update", "1")
|
|
||||||
}
|
|
||||||
}.executeAndParsingWith { }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun downloadArchive(
|
|
||||||
gid: Long,
|
|
||||||
token: String?,
|
|
||||||
or: String?,
|
|
||||||
res: String?,
|
|
||||||
isHAtH: Boolean,
|
|
||||||
): String? {
|
|
||||||
if (or.isNullOrEmpty()) {
|
|
||||||
throw EhException("Invalid form param or: $or")
|
|
||||||
}
|
|
||||||
if (res.isNullOrEmpty()) {
|
|
||||||
throw EhException("Invalid res: $res")
|
|
||||||
}
|
|
||||||
val url = EhUrl.getDownloadArchive(gid, token, or)
|
|
||||||
val referer = EhUrl.getGalleryDetailUrl(gid, token)
|
|
||||||
val request = ehRequest(url, referer, EhUrl.origin) {
|
|
||||||
formBody {
|
|
||||||
if (isHAtH) {
|
|
||||||
add("hathdl_xres", res)
|
|
||||||
} else {
|
|
||||||
add("dltype", res)
|
|
||||||
if (res == "org") {
|
|
||||||
add("dlcheck", "Download Original Archive")
|
|
||||||
} else {
|
|
||||||
add("dlcheck", "Download Resample Archive")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var result = request.executeAndParsingWith(ArchiveParser::parseArchiveUrl)
|
|
||||||
if (!isHAtH) {
|
|
||||||
if (result == null) {
|
|
||||||
// Wait for the server to prepare archives
|
|
||||||
delay(1000)
|
|
||||||
result = request.executeAndParsingWith(ArchiveParser::parseArchiveUrl)
|
|
||||||
if (result == null) {
|
|
||||||
throw EhException("Archive unavailable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun resetImageLimits() = ehRequest(EhUrl.URL_HOME) {
|
|
||||||
formBody {
|
|
||||||
add("reset_imagelimit", "Reset Limit")
|
|
||||||
}
|
|
||||||
}.executeAndParsingWith(HomeParser::parseResetLimits)
|
|
||||||
|
|
||||||
suspend fun modifyFavorites(url: String, gidArray: LongArray, dstCat: Int): FavoritesParser.Result {
|
|
||||||
val catStr: String = when (dstCat) {
|
|
||||||
-1 -> "delete"
|
|
||||||
in 0..9 -> "fav$dstCat"
|
|
||||||
else -> throw EhException("Invalid dstCat: $dstCat")
|
|
||||||
}
|
|
||||||
return ehRequest(url, url, EhUrl.origin) {
|
|
||||||
formBody {
|
|
||||||
add("ddact", catStr)
|
|
||||||
gidArray.forEach { add("modifygids[]", it.toString()) }
|
|
||||||
}
|
|
||||||
}.executeAndParsingWith(FavoritesParser::parse).apply { fillGalleryList(galleryInfoList, url, false) }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getGalleryPageApi(
|
|
||||||
gid: Long,
|
|
||||||
index: Int,
|
|
||||||
pToken: String,
|
|
||||||
showKey: String?,
|
|
||||||
previousPToken: String?,
|
|
||||||
): GalleryPageApiParser.Result {
|
|
||||||
val referer = if (index > 0 && previousPToken != null) EhUrl.getPageUrl(gid, index - 1, previousPToken) else null
|
|
||||||
return ehRequest(EhUrl.apiUrl, referer, EhUrl.origin) {
|
|
||||||
jsonBody {
|
|
||||||
put("method", "showpage")
|
|
||||||
put("gid", gid)
|
|
||||||
put("page", index + 1)
|
|
||||||
put("imgkey", pToken)
|
|
||||||
put("showkey", showKey)
|
|
||||||
}
|
|
||||||
}.executeAndParsingWith(GalleryPageApiParser::parse)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun rateGallery(
|
|
||||||
apiUid: Long,
|
|
||||||
apiKey: String?,
|
|
||||||
gid: Long,
|
|
||||||
token: String?,
|
|
||||||
rating: Float,
|
|
||||||
): RateGalleryResult = ehRequest(EhUrl.apiUrl, EhUrl.getGalleryDetailUrl(gid, token), EhUrl.origin) {
|
|
||||||
jsonBody {
|
|
||||||
put("method", "rategallery")
|
|
||||||
put("apiuid", apiUid)
|
|
||||||
put("apikey", apiKey)
|
|
||||||
put("gid", gid)
|
|
||||||
put("token", token)
|
|
||||||
put("rating", ceil((rating * 2).toDouble()).toInt())
|
|
||||||
}
|
|
||||||
}.executeAndParsingWith(String::parseAs)
|
|
||||||
|
|
||||||
suspend fun fillGalleryListByApi(galleryInfoList: List<GalleryInfo>, referer: String) = galleryInfoList.chunked(MAX_REQUEST_SIZE).parMap {
|
|
||||||
ehRequest(EhUrl.apiUrl, referer, EhUrl.origin) {
|
|
||||||
jsonBody {
|
|
||||||
put("method", "gdata")
|
|
||||||
array("gidlist") {
|
|
||||||
it.forEach {
|
|
||||||
addJsonArray {
|
|
||||||
add(it.gid)
|
|
||||||
add(it.token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
put("namespace", 1)
|
|
||||||
}
|
|
||||||
}.executeAndParsingWith { GalleryApiParser.parse(this, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun voteComment(
|
|
||||||
apiUid: Long,
|
|
||||||
apiKey: String?,
|
|
||||||
gid: Long,
|
|
||||||
token: String?,
|
|
||||||
commentId: Long,
|
|
||||||
commentVote: Int,
|
|
||||||
): VoteCommentResult = ehRequest(EhUrl.apiUrl, EhUrl.referer, EhUrl.origin) {
|
|
||||||
jsonBody {
|
|
||||||
put("method", "votecomment")
|
|
||||||
put("apiuid", apiUid)
|
|
||||||
put("apikey", apiKey)
|
|
||||||
put("gid", gid)
|
|
||||||
put("token", token)
|
|
||||||
put("comment_id", commentId)
|
|
||||||
put("comment_vote", commentVote)
|
|
||||||
}
|
|
||||||
}.executeAndParsingWith(String::parseAs)
|
|
||||||
|
|
||||||
suspend fun voteTag(
|
|
||||||
apiUid: Long,
|
|
||||||
apiKey: String?,
|
|
||||||
gid: Long,
|
|
||||||
token: String?,
|
|
||||||
tags: String?,
|
|
||||||
vote: Int,
|
|
||||||
) = ehRequest(EhUrl.apiUrl, EhUrl.referer, EhUrl.origin) {
|
|
||||||
jsonBody {
|
|
||||||
put("method", "taggallery")
|
|
||||||
put("apiuid", apiUid)
|
|
||||||
put("apikey", apiKey)
|
|
||||||
put("gid", gid)
|
|
||||||
put("token", token)
|
|
||||||
put("tags", tags)
|
|
||||||
put("vote", vote)
|
|
||||||
}
|
|
||||||
}.executeAndParsingWith(VoteTagParser::parse)
|
|
||||||
|
|
||||||
suspend fun getGalleryToken(gid: Long, gtoken: String?, page: Int) = ehRequest(EhUrl.apiUrl, EhUrl.referer, EhUrl.origin) {
|
|
||||||
jsonBody {
|
|
||||||
put("method", "gtoken")
|
|
||||||
array("pagelist") {
|
|
||||||
addJsonArray {
|
|
||||||
add(gid)
|
|
||||||
add(gtoken)
|
|
||||||
add(page + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.executeAndParsingWith(GalleryTokenApiParser::parse)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param image Must be jpeg
|
|
||||||
*/
|
|
||||||
suspend fun imageSearch(image: File, uss: Boolean, osc: Boolean) = ehRequest(EhUrl.imageSearchUrl, EhUrl.referer, EhUrl.origin) {
|
|
||||||
multipartBody {
|
|
||||||
setType(MultipartBody.FORM)
|
|
||||||
addFormDataPart("sfile", "a.jpg", image.asRequestBody(MEDIA_TYPE_JPEG))
|
|
||||||
if (uss) addFormDataPart("fs_similar", "on")
|
|
||||||
if (osc) addFormDataPart("fs_covers", "on")
|
|
||||||
addFormDataPart("f_sfile", "File Search")
|
|
||||||
}
|
|
||||||
}.executeAndParsingWith(GalleryListParser::parse).apply { fillGalleryList(galleryInfoList, EhUrl.imageSearchUrl, true) }
|
|
||||||
|
|
||||||
private suspend fun fillGalleryList(list: MutableList<BaseGalleryInfo>, url: String, filter: Boolean) {
|
|
||||||
// Filter title and uploader
|
|
||||||
if (filter) list.removeAllSuspend { EhFilter.filterTitle(it) || EhFilter.filterUploader(it) }
|
|
||||||
|
|
||||||
var hasTags = false
|
|
||||||
var hasPages = false
|
|
||||||
var hasRated = false
|
|
||||||
for (gi in list) {
|
|
||||||
if (gi.simpleTags != null) {
|
|
||||||
hasTags = true
|
|
||||||
}
|
|
||||||
if (gi.pages != 0) {
|
|
||||||
hasPages = true
|
|
||||||
}
|
|
||||||
if (gi.rated) {
|
|
||||||
hasRated = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val needApi = filter && EhFilter.needTags() && !hasTags || Settings.showGalleryPages && !hasPages || hasRated
|
|
||||||
if (needApi) fillGalleryListByApi(list, url)
|
|
||||||
|
|
||||||
// Filter tag, thumbnail mode need filter uploader again
|
|
||||||
if (filter) list.removeAllSuspend { EhFilter.filterUploader(it) || EhFilter.filterTag(it) || EhFilter.filterTagNamespace(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun modifyFavoritesRange(galleryList: List<Pair<Long, String>>, dstCat: Int) {
|
|
||||||
galleryList.forEach { (gid, token) -> modifyFavorites(gid, token, dstCat) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.client
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.hippo.ehviewer.EhApplication.Companion.baseOkHttpClient
|
|
||||||
import com.hippo.ehviewer.EhApplication.Companion.noRedirectOkHttpClient
|
|
||||||
import com.hippo.ehviewer.EhApplication.Companion.okHttpClient
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonArrayBuilder
|
|
||||||
import kotlinx.serialization.json.JsonObjectBuilder
|
|
||||||
import kotlinx.serialization.json.buildJsonArray
|
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
|
||||||
import kotlinx.serialization.json.okio.decodeFromBufferedSource
|
|
||||||
import okhttp3.Call
|
|
||||||
import okhttp3.FormBody
|
|
||||||
import okhttp3.MediaType
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
|
||||||
import okhttp3.MultipartBody
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
|
||||||
import okhttp3.Response
|
|
||||||
import okhttp3.executeAsync
|
|
||||||
import okio.BufferedSource
|
|
||||||
import kotlin.contracts.InvocationKind
|
|
||||||
import kotlin.contracts.contract
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.resumeWithException
|
|
||||||
|
|
||||||
inline fun ehRequest(url: String, referer: String? = null, origin: String? = null, builder: Request.Builder.() -> Unit = {}) = Request.Builder().url(url).apply {
|
|
||||||
addHeader("User-Agent", CHROME_USER_AGENT)
|
|
||||||
addHeader("Accept", CHROME_ACCEPT)
|
|
||||||
addHeader("Accept-Language", CHROME_ACCEPT_LANGUAGE)
|
|
||||||
referer?.let { addHeader("Referer", it) }
|
|
||||||
origin?.let { addHeader("Origin", it) }
|
|
||||||
}.apply(builder).build()
|
|
||||||
|
|
||||||
inline fun Request.Builder.formBody(builder: FormBody.Builder.() -> Unit) = post(FormBody.Builder().apply(builder).build())
|
|
||||||
|
|
||||||
inline fun Request.Builder.multipartBody(builder: MultipartBody.Builder.() -> Unit) = post(MultipartBody.Builder().apply(builder).build())
|
|
||||||
|
|
||||||
val MEDIA_TYPE_JSON: MediaType = "application/json; charset=utf-8".toMediaType()
|
|
||||||
|
|
||||||
inline fun JsonObjectBuilder.array(name: String, builder: JsonArrayBuilder.() -> Unit) = put(name, buildJsonArray(builder))
|
|
||||||
|
|
||||||
inline fun Request.Builder.jsonBody(builder: JsonObjectBuilder.() -> Unit) = post(buildJsonObject(builder).toString().toRequestBody(MEDIA_TYPE_JSON))
|
|
||||||
|
|
||||||
const val TAG_CALL = "CancellableCallReadingScope"
|
|
||||||
|
|
||||||
// This scoped response reading scope suspend function aimed at cancelling the call immediately once coroutine is cancelled.
|
|
||||||
// i.e., bind a call with a suspended coroutine, and launch another coroutine to execute reading
|
|
||||||
// Once origin coroutine is cancelled, then call cancelled, socket is closed, the reading coroutine will encounter [IOException]
|
|
||||||
// Since Okio is not suspendable/cancellable, also okhttp, [executeAsync] only make [Request -> Response] cancellable, reading a Response is still not cancellable
|
|
||||||
// Considering interrupting is not safe and performant, See https://github.com/Kotlin/kotlinx.coroutines/issues/3551#issuecomment-1353245978, we have such [usingCancellable]
|
|
||||||
// TODO: Remove logging after this function stable
|
|
||||||
suspend inline fun <R> Call.usingCancellable(crossinline block: suspend Response.() -> R): R = executeAsync().use {
|
|
||||||
val call = this
|
|
||||||
Log.d(TAG_CALL, "Got response of call$call")
|
|
||||||
coroutineScope {
|
|
||||||
suspendCancellableCoroutine<R> { cont ->
|
|
||||||
launch {
|
|
||||||
val r = runCatching {
|
|
||||||
Log.d(TAG_CALL, "Reading response of call$call")
|
|
||||||
block(it)
|
|
||||||
}
|
|
||||||
if (!isCanceled() && cont.isActive) {
|
|
||||||
r.exceptionOrNull()?.let {
|
|
||||||
Log.e(TAG_CALL, "Reading response of call$call failed!")
|
|
||||||
cont.resumeWithException(it)
|
|
||||||
}
|
|
||||||
r.getOrNull()?.let {
|
|
||||||
Log.d(TAG_CALL, "Reading response of call$call succeed!")
|
|
||||||
cont.resume(it)
|
|
||||||
}
|
|
||||||
} else if (isCanceled() && cont.isActive) {
|
|
||||||
Log.e(TAG_CALL, "call$call cancelled but coroutine is Active!")
|
|
||||||
} else if (!isCanceled() && !cont.isActive) {
|
|
||||||
Log.e(TAG_CALL, "call$call not cancelled but coroutine is Dead!")
|
|
||||||
} else {
|
|
||||||
Log.d(TAG_CALL, "Reading response of call$call cancelled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cont.invokeOnCancellation {
|
|
||||||
Log.d(TAG_CALL, "Cancelling call$call")
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.apply {
|
|
||||||
Log.d(TAG_CALL, "Reading response of call$call finished!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend inline fun <R> Request.execute(block: Response.() -> R): R {
|
|
||||||
contract {
|
|
||||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
|
||||||
}
|
|
||||||
return okHttpClient.newCall(this).executeAsync().use(block)
|
|
||||||
}
|
|
||||||
suspend inline fun <R> Request.executeNonCache(crossinline block: suspend Response.() -> R) = baseOkHttpClient.newCall(this).usingCancellable(block)
|
|
||||||
suspend inline fun <R> Request.executeNoRedirect(block: Response.() -> R) = noRedirectOkHttpClient.newCall(this).executeAsync().use(block)
|
|
||||||
|
|
||||||
suspend inline fun <reified T> Request.executeAndParseAs() = execute { parseAs<T>() }
|
|
||||||
|
|
||||||
inline fun <reified T> Response.parseAs(): T = body.source().parseAs()
|
|
||||||
inline fun <reified T> BufferedSource.parseAs(): T = json.decodeFromBufferedSource(this)
|
|
||||||
inline fun <reified T> String.parseAs(): T = json.decodeFromString(this)
|
|
||||||
|
|
||||||
val json = Json { ignoreUnknownKeys = true }
|
|
||||||
|
|
||||||
const val CHROME_USER_AGENT = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Mobile Safari/537.36"
|
|
||||||
const val CHROME_ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
|
|
||||||
const val CHROME_ACCEPT_LANGUAGE = "en-US,en;q=0.5"
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.client.data
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
class GalleryComment(
|
|
||||||
// 0 for uploader comment. can't vote
|
|
||||||
var id: Long = 0,
|
|
||||||
var score: Int = 0,
|
|
||||||
var editable: Boolean = false,
|
|
||||||
var voteUpAble: Boolean = false,
|
|
||||||
var voteUpEd: Boolean = false,
|
|
||||||
var voteDownAble: Boolean = false,
|
|
||||||
var voteDownEd: Boolean = false,
|
|
||||||
var uploader: Boolean = false,
|
|
||||||
var voteState: String? = null,
|
|
||||||
var time: Long = 0,
|
|
||||||
var user: String? = null,
|
|
||||||
var comment: String? = null,
|
|
||||||
var lastEdited: Long = 0,
|
|
||||||
) : Parcelable
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.client.data
|
|
||||||
|
|
||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
interface GalleryInfo {
|
|
||||||
var gid: Long
|
|
||||||
var token: String?
|
|
||||||
var title: String?
|
|
||||||
var titleJpn: String?
|
|
||||||
var thumbKey: String?
|
|
||||||
var category: Int
|
|
||||||
var posted: String?
|
|
||||||
var uploader: String?
|
|
||||||
var disowned: Boolean
|
|
||||||
var rating: Float
|
|
||||||
var rated: Boolean
|
|
||||||
var simpleTags: ArrayList<String>?
|
|
||||||
var pages: Int
|
|
||||||
var thumbWidth: Int
|
|
||||||
var thumbHeight: Int
|
|
||||||
var simpleLanguage: String?
|
|
||||||
var favoriteSlot: Int
|
|
||||||
var favoriteName: String?
|
|
||||||
var favoriteNote: String?
|
|
||||||
|
|
||||||
fun generateSLang() {
|
|
||||||
simpleLanguage = simpleTags?.let { generateSLangFromTags(it) }
|
|
||||||
?: title?.let { generateSLangFromTitle(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateSLangFromTags(simpleTags: List<String>): String? {
|
|
||||||
for (tag in simpleTags) {
|
|
||||||
for (i in S_LANGS.indices) {
|
|
||||||
if (S_LANG_TAGS[i] == tag) {
|
|
||||||
return S_LANGS[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateSLangFromTitle(title: String): String? {
|
|
||||||
for (i in S_LANGS.indices) {
|
|
||||||
if (S_LANG_PATTERNS[i].matcher(title).find()) {
|
|
||||||
return S_LANGS[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* ISO 639-1
|
|
||||||
*/
|
|
||||||
const val S_LANG_JA = "JA"
|
|
||||||
const val S_LANG_EN = "EN"
|
|
||||||
const val S_LANG_ZH = "ZH"
|
|
||||||
const val S_LANG_NL = "NL"
|
|
||||||
const val S_LANG_FR = "FR"
|
|
||||||
const val S_LANG_DE = "DE"
|
|
||||||
const val S_LANG_HU = "HU"
|
|
||||||
const val S_LANG_IT = "IT"
|
|
||||||
const val S_LANG_KO = "KO"
|
|
||||||
const val S_LANG_PL = "PL"
|
|
||||||
const val S_LANG_PT = "PT"
|
|
||||||
const val S_LANG_RU = "RU"
|
|
||||||
const val S_LANG_ES = "ES"
|
|
||||||
const val S_LANG_TH = "TH"
|
|
||||||
const val S_LANG_VI = "VI"
|
|
||||||
|
|
||||||
val S_LANGS = arrayOf(
|
|
||||||
S_LANG_EN,
|
|
||||||
S_LANG_ZH,
|
|
||||||
S_LANG_ES,
|
|
||||||
S_LANG_KO,
|
|
||||||
S_LANG_RU,
|
|
||||||
S_LANG_FR,
|
|
||||||
S_LANG_PT,
|
|
||||||
S_LANG_TH,
|
|
||||||
S_LANG_DE,
|
|
||||||
S_LANG_IT,
|
|
||||||
S_LANG_VI,
|
|
||||||
S_LANG_PL,
|
|
||||||
S_LANG_HU,
|
|
||||||
S_LANG_NL,
|
|
||||||
)
|
|
||||||
val S_LANG_PATTERNS = arrayOf(
|
|
||||||
Pattern.compile(
|
|
||||||
"[(\\[]eng(?:lish)?[)\\]]|英訳",
|
|
||||||
Pattern.CASE_INSENSITIVE,
|
|
||||||
), // [((\[]ch(?:inese)?[))\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳
|
|
||||||
Pattern.compile(
|
|
||||||
"[(\uFF08\\[]ch(?:inese)?[)\uFF09\\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳",
|
|
||||||
Pattern.CASE_INSENSITIVE,
|
|
||||||
),
|
|
||||||
Pattern.compile(
|
|
||||||
"[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]|スペイン翻訳",
|
|
||||||
Pattern.CASE_INSENSITIVE,
|
|
||||||
),
|
|
||||||
Pattern.compile("[(\\[]korean?[)\\]]|韓国翻訳", Pattern.CASE_INSENSITIVE),
|
|
||||||
Pattern.compile("[(\\[]rus(?:sian)?[)\\]]|ロシア翻訳", Pattern.CASE_INSENSITIVE),
|
|
||||||
Pattern.compile("[(\\[]fr(?:ench)?[)\\]]|フランス翻訳", Pattern.CASE_INSENSITIVE),
|
|
||||||
Pattern.compile("[(\\[]portuguese|ポルトガル翻訳", Pattern.CASE_INSENSITIVE),
|
|
||||||
Pattern.compile(
|
|
||||||
"[(\\[]thai(?: ภาษาไทย)?[)\\]]|แปลไทย|タイ翻訳",
|
|
||||||
Pattern.CASE_INSENSITIVE,
|
|
||||||
),
|
|
||||||
Pattern.compile("[(\\[]german[)\\]]|ドイツ翻訳", Pattern.CASE_INSENSITIVE),
|
|
||||||
Pattern.compile("[(\\[]italiano?[)\\]]|イタリア翻訳", Pattern.CASE_INSENSITIVE),
|
|
||||||
Pattern.compile(
|
|
||||||
"[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]|ベトナム翻訳",
|
|
||||||
Pattern.CASE_INSENSITIVE,
|
|
||||||
),
|
|
||||||
Pattern.compile("[(\\[]polish[)\\]]|ポーランド翻訳", Pattern.CASE_INSENSITIVE),
|
|
||||||
Pattern.compile("[(\\[]hun(?:garian)?[)\\]]|ハンガリー翻訳", Pattern.CASE_INSENSITIVE),
|
|
||||||
Pattern.compile("[(\\[]dutch[)\\]]|オランダ翻訳", Pattern.CASE_INSENSITIVE),
|
|
||||||
)
|
|
||||||
val S_LANG_TAGS = arrayOf(
|
|
||||||
"language:english",
|
|
||||||
"language:chinese",
|
|
||||||
"language:spanish",
|
|
||||||
"language:korean",
|
|
||||||
"language:russian",
|
|
||||||
"language:french",
|
|
||||||
"language:portuguese",
|
|
||||||
"language:thai",
|
|
||||||
"language:german",
|
|
||||||
"language:italian",
|
|
||||||
"language:vietnamese",
|
|
||||||
"language:polish",
|
|
||||||
"language:hungarian",
|
|
||||||
"language:dutch",
|
|
||||||
)
|
|
||||||
|
|
||||||
const val NOT_FAVORITED = -2
|
|
||||||
const val LOCAL_FAVORITED = -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.hippo.ehviewer.client.exception
|
|
||||||
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
|
|
||||||
class CloudflareBypassException : EhException(R.string.cloudflare_bypass_failed)
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package com.hippo.ehviewer.client.parser
|
|
||||||
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
import com.hippo.ehviewer.client.exception.EhException
|
|
||||||
import com.hippo.ehviewer.client.exception.ParseException
|
|
||||||
import splitties.init.appCtx
|
|
||||||
|
|
||||||
object FavoritesParser {
|
|
||||||
suspend fun parse(body: String): Result {
|
|
||||||
if (body.contains("This page requires you to log on.</p>")) {
|
|
||||||
throw EhException(appCtx.getString(R.string.need_sign_in))
|
|
||||||
}
|
|
||||||
val catArray = arrayOfNulls<String>(10)
|
|
||||||
val countArray = parseFav(body, catArray)
|
|
||||||
if (countArray.isEmpty()) throw ParseException("Parse favorites error", body)
|
|
||||||
val result = GalleryListParser.parse(body)
|
|
||||||
return Result(catArray.requireNoNulls(), countArray, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Result(
|
|
||||||
val catArray: Array<String>,
|
|
||||||
val countArray: IntArray,
|
|
||||||
galleryListResult: GalleryListResult,
|
|
||||||
) {
|
|
||||||
val prev = galleryListResult.prev
|
|
||||||
val next = galleryListResult.next
|
|
||||||
val galleryInfoList = galleryListResult.galleryInfoList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private external fun parseFav(body: String, favCat: Array<String?>): IntArray
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package com.hippo.ehviewer.client.parser
|
|
||||||
|
|
||||||
import com.hippo.ehviewer.EhDB
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.LOCAL_FAVORITED
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.NOT_FAVORITED
|
|
||||||
import com.hippo.ehviewer.client.exception.EhException
|
|
||||||
import com.hippo.ehviewer.client.exception.ParseException
|
|
||||||
|
|
||||||
object GalleryListParser {
|
|
||||||
suspend fun parse(body: String) = runCatching {
|
|
||||||
parseGalleryInfoList(body).apply {
|
|
||||||
galleryInfoList.onEach {
|
|
||||||
if (it.favoriteSlot == NOT_FAVORITED && EhDB.containLocalFavorites(it.gid)) {
|
|
||||||
it.favoriteSlot = LOCAL_FAVORITED
|
|
||||||
}
|
|
||||||
it.generateSLang()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.getOrElse {
|
|
||||||
if (body.contains("<p>You do not have any watched tags")) {
|
|
||||||
throw EhException(R.string.gallery_list_empty_hit_subscription)
|
|
||||||
}
|
|
||||||
if (body.contains("No hits found</p>")) {
|
|
||||||
throw EhException(R.string.gallery_list_empty_hit)
|
|
||||||
}
|
|
||||||
throw ParseException("Can't parse gallery list", body, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GalleryListResult(
|
|
||||||
val prev: String?,
|
|
||||||
val next: String?,
|
|
||||||
val galleryInfoList: ArrayList<BaseGalleryInfo>,
|
|
||||||
)
|
|
||||||
|
|
||||||
private external fun parseGalleryInfoList(e: String): GalleryListResult
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.client.parser
|
|
||||||
|
|
||||||
import com.hippo.ehviewer.client.exception.ParseException
|
|
||||||
import com.hippo.ehviewer.client.parseAs
|
|
||||||
import com.hippo.ehviewer.util.unescapeXml
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
object GalleryPageApiParser {
|
|
||||||
private val PATTERN_IMAGE_URL = Regex("<img[^>]*src=\"([^\"]+)\" style")
|
|
||||||
private val PATTERN_SKIP_HATH_KEY = Regex("onclick=\"return nl\\('([^)]+)'\\)")
|
|
||||||
private val PATTERN_ORIGIN_IMAGE_URL = Regex("<a href=\"([^\"]+)fullimg.php([^\"]+)\">")
|
|
||||||
|
|
||||||
fun parse(body: String): Result {
|
|
||||||
runCatching { body.parseAs<Error>() }.onSuccess { throw ParseException(it.error, body) }
|
|
||||||
val res = body.parseAs<JsonResult>()
|
|
||||||
val imageUrl = PATTERN_IMAGE_URL.find(res.imageUrl)?.run {
|
|
||||||
groupValues[1].trim().unescapeXml()
|
|
||||||
}
|
|
||||||
val skipHathKey = PATTERN_SKIP_HATH_KEY.find(res.skipHathKey)?.run {
|
|
||||||
groupValues[1].trim().unescapeXml()
|
|
||||||
}
|
|
||||||
val originImageUrl = PATTERN_ORIGIN_IMAGE_URL.find(res.originImageUrl)?.run {
|
|
||||||
groupValues[1].unescapeXml() + "fullimg.php" + groupValues[2].unescapeXml()
|
|
||||||
}
|
|
||||||
if (!imageUrl.isNullOrEmpty()) {
|
|
||||||
return Result(imageUrl, skipHathKey, originImageUrl)
|
|
||||||
} else {
|
|
||||||
throw ParseException("Parse image url and skip hath key error", body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
private data class JsonResult(
|
|
||||||
@SerialName("i3")
|
|
||||||
val imageUrl: String,
|
|
||||||
@SerialName("i6")
|
|
||||||
val skipHathKey: String,
|
|
||||||
@SerialName("i7")
|
|
||||||
val originImageUrl: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Result(val imageUrl: String, val skipHathKey: String?, val originImageUrl: String?)
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.client.parser
|
|
||||||
|
|
||||||
import com.hippo.ehviewer.client.exception.ParseException
|
|
||||||
import com.hippo.ehviewer.util.unescapeXml
|
|
||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
object GalleryPageParser {
|
|
||||||
private val PATTERN_IMAGE_URL = Pattern.compile("<img[^>]*src=\"([^\"]+)\" style")
|
|
||||||
private val PATTERN_SKIP_HATH_KEY = Pattern.compile("onclick=\"return nl\\('([^)]+)'\\)")
|
|
||||||
private val PATTERN_ORIGIN_IMAGE_URL =
|
|
||||||
Pattern.compile("<a href=\"([^\"]+)fullimg.php([^\"]+)\">")
|
|
||||||
|
|
||||||
// TODO Not sure about the size of show keys
|
|
||||||
private val PATTERN_SHOW_KEY = Pattern.compile("var showkey=\"([0-9a-z]+)\";")
|
|
||||||
|
|
||||||
fun parse(body: String): Result {
|
|
||||||
var m = PATTERN_IMAGE_URL.matcher(body)
|
|
||||||
val imageUrl = if (m.find()) {
|
|
||||||
m.group(1)!!.trim().unescapeXml()
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
m = PATTERN_SKIP_HATH_KEY.matcher(body)
|
|
||||||
val skipHathKey = if (m.find()) {
|
|
||||||
m.group(1)!!.trim().unescapeXml()
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
m = PATTERN_ORIGIN_IMAGE_URL.matcher(body)
|
|
||||||
val originImageUrl = if (m.find()) {
|
|
||||||
m.group(1)!!.unescapeXml() + "fullimg.php" + m.group(2)!!.unescapeXml()
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
m = PATTERN_SHOW_KEY.matcher(body)
|
|
||||||
val showKey = if (m.find()) {
|
|
||||||
m.group(1)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
return if (!imageUrl.isNullOrEmpty() && !showKey.isNullOrEmpty()) {
|
|
||||||
Result(imageUrl, skipHathKey, originImageUrl, showKey)
|
|
||||||
} else {
|
|
||||||
throw ParseException("Parse image url and show error", body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Result(
|
|
||||||
val imageUrl: String,
|
|
||||||
val skipHathKey: String?,
|
|
||||||
val originImageUrl: String?,
|
|
||||||
val showKey: String,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.client.parser
|
|
||||||
|
|
||||||
import com.hippo.ehviewer.util.toIntOrDefault
|
|
||||||
import com.hippo.ehviewer.util.toLongOrDefault
|
|
||||||
import com.hippo.ehviewer.util.unescapeXml
|
|
||||||
import java.time.Instant
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
object ParserUtils {
|
|
||||||
private val formatter =
|
|
||||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm", Locale.US).withZone(ZoneOffset.UTC)
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun formatDate(time: Long): String {
|
|
||||||
return formatter.format(Instant.ofEpochMilli(time))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun trim(str: String?): String {
|
|
||||||
return str?.unescapeXml()?.trim() ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
fun parseInt(str: String?, defValue: Int): Int {
|
|
||||||
return trim(str).replace(",", "").toIntOrDefault(defValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun parseLong(str: String?, defValue: Long): Long {
|
|
||||||
return trim(str).replace(",", "").toLongOrDefault(defValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package com.hippo.ehviewer.client.parser
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
object TorrentParser {
|
|
||||||
fun parse(body: ByteBuffer) = parseTorrent(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Torrent(
|
|
||||||
val posted: String,
|
|
||||||
val size: String,
|
|
||||||
val seeds: Int,
|
|
||||||
val peers: Int,
|
|
||||||
val downloads: Int,
|
|
||||||
val url: String,
|
|
||||||
val name: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Torrent.format() = "[$posted] $name [$size] [↑$seeds ↓$peers ✓$downloads]"
|
|
||||||
|
|
||||||
typealias TorrentResult = ArrayList<Torrent>
|
|
||||||
|
|
||||||
private external fun parseTorrent(body: ByteBuffer, size: Int = body.limit()): ArrayList<Torrent>
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
@file:Suppress("BlockingMethodInNonBlockingContext")
|
|
||||||
|
|
||||||
package com.hippo.ehviewer.coil
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.webkit.MimeTypeMap
|
|
||||||
import coil.ComponentRegistry
|
|
||||||
import coil.ImageLoader
|
|
||||||
import coil.decode.DataSource
|
|
||||||
import coil.decode.ImageSource
|
|
||||||
import coil.fetch.FetchResult
|
|
||||||
import coil.fetch.Fetcher
|
|
||||||
import coil.fetch.SourceResult
|
|
||||||
import coil.request.Options
|
|
||||||
import com.hippo.ehviewer.cronet.copyToChannel
|
|
||||||
import com.hippo.ehviewer.cronet.cronetRequest
|
|
||||||
import com.hippo.ehviewer.cronet.execute
|
|
||||||
import java.io.RandomAccessFile
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A HttpUriFetcher impl use Cronet for jvmheapless IO
|
|
||||||
* cacheHeader is never respected | recorded since thumb is immutable
|
|
||||||
*/
|
|
||||||
class CronetHttpUriFetcher(private val data: String, private val options: Options, private val imageLoader: ImageLoader) : Fetcher {
|
|
||||||
override suspend fun fetch(): FetchResult {
|
|
||||||
val diskCacheKey = options.diskCacheKey ?: data
|
|
||||||
val diskCache = requireNotNull(imageLoader.diskCache)
|
|
||||||
val snapshot = diskCache.openSnapshot(diskCacheKey) ?: run {
|
|
||||||
val success = cronetRequest(data) {
|
|
||||||
disableCache()
|
|
||||||
}.execute {
|
|
||||||
diskCache.suspendEdit(diskCacheKey) {
|
|
||||||
RandomAccessFile(data.toFile(), "rw").use {
|
|
||||||
copyToChannel(it.channel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check(success)
|
|
||||||
// diskcache snapshot MUST exist here
|
|
||||||
requireNotNull(diskCache.openSnapshot(diskCacheKey))
|
|
||||||
}
|
|
||||||
return SourceResult(
|
|
||||||
source = ImageSource(snapshot.data, diskCache.fileSystem, diskCacheKey, snapshot),
|
|
||||||
mimeType = getMimeType(data),
|
|
||||||
dataSource = DataSource.DISK,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ComponentRegistry.Builder.installCronetHttpUriFetcher() = add { data: Uri, options, loader ->
|
|
||||||
if (data.scheme != "http" && data.scheme != "https") return@add null
|
|
||||||
CronetHttpUriFetcher(data.toString(), options, loader)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getMimeType(url: String) = MimeTypeMap.getSingleton().getMimeTypeFromUrl(url)
|
|
||||||
|
|
||||||
private fun MimeTypeMap.getMimeTypeFromUrl(url: String?): String? {
|
|
||||||
if (url.isNullOrBlank()) return null
|
|
||||||
val extension = url
|
|
||||||
.substringBeforeLast('#') // Strip the fragment.
|
|
||||||
.substringBeforeLast('?') // Strip the query.
|
|
||||||
.substringAfterLast('/') // Get the last path segment.
|
|
||||||
.substringAfterLast('.', missingDelimiterValue = "") // Get the file extension.
|
|
||||||
return getMimeTypeFromExtension(extension)
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package com.hippo.ehviewer.coil
|
|
||||||
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.drawable.ColorDrawable
|
|
||||||
import coil.decode.DecodeResult
|
|
||||||
import coil.decode.Decoder
|
|
||||||
import coil.request.CachePolicy
|
|
||||||
import coil.request.ImageRequest
|
|
||||||
import coil.size.Size
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryPreview
|
|
||||||
import com.hippo.ehviewer.client.data.NormalGalleryPreview
|
|
||||||
import com.hippo.ehviewer.client.thumbUrl
|
|
||||||
import com.hippo.ehviewer.client.url
|
|
||||||
|
|
||||||
fun ImageRequest.Builder.ehUrl(info: GalleryInfo) = apply {
|
|
||||||
val key = info.thumbKey!!
|
|
||||||
data(info.thumbUrl)
|
|
||||||
memoryCacheKey(key)
|
|
||||||
diskCacheKey(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ImageRequest.Builder.ehPreview(preview: GalleryPreview) = apply {
|
|
||||||
data(preview.url)
|
|
||||||
memoryCacheKey(preview.imageKey)
|
|
||||||
diskCacheKey(preview.imageKey)
|
|
||||||
if (preview is NormalGalleryPreview) size(Size.ORIGINAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val stubResult = DecodeResult(ColorDrawable(Color.BLACK), false)
|
|
||||||
private val stubFactory = Decoder { stubResult }
|
|
||||||
|
|
||||||
fun ImageRequest.Builder.justDownload() = apply {
|
|
||||||
memoryCachePolicy(CachePolicy.DISABLED)
|
|
||||||
decoderFactory { _, _, _ -> stubFactory }
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
package com.hippo.ehviewer.cronet
|
|
||||||
|
|
||||||
import com.hippo.ehviewer.EhApplication
|
|
||||||
import com.hippo.ehviewer.client.CHROME_ACCEPT
|
|
||||||
import com.hippo.ehviewer.client.CHROME_ACCEPT_LANGUAGE
|
|
||||||
import com.hippo.ehviewer.client.CHROME_USER_AGENT
|
|
||||||
import com.hippo.ehviewer.client.EhCookieStore
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import io.ktor.utils.io.pool.DirectByteBufferPool
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import okio.Path.Companion.toOkioPath
|
|
||||||
import org.chromium.net.CronetEngine
|
|
||||||
import org.chromium.net.CronetException
|
|
||||||
import org.chromium.net.UrlRequest
|
|
||||||
import org.chromium.net.UrlResponseInfo
|
|
||||||
import splitties.init.appCtx
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.channels.FileChannel
|
|
||||||
import kotlin.contracts.InvocationKind
|
|
||||||
import kotlin.contracts.contract
|
|
||||||
import kotlin.coroutines.Continuation
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.resumeWithException
|
|
||||||
|
|
||||||
private const val TAG = "CronetRequest"
|
|
||||||
val pool = DirectByteBufferPool(32)
|
|
||||||
|
|
||||||
val cronetHttpClient: CronetEngine = CronetEngine.Builder(appCtx).apply {
|
|
||||||
enableBrotli(true)
|
|
||||||
val cache = (appCtx.cacheDir.toOkioPath() / "http_cache").toFile().apply { mkdirs() }
|
|
||||||
setStoragePath(cache.absolutePath)
|
|
||||||
enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 100 * 1024)
|
|
||||||
addQuicHint("e-hentai.org", 443, 443)
|
|
||||||
addQuicHint("forums.e-hentai.org", 443, 443)
|
|
||||||
addQuicHint("exhentai.org", 443, 443)
|
|
||||||
}.build()
|
|
||||||
|
|
||||||
val cronetHttpClientExecutor = EhApplication.baseOkHttpClient.dispatcher.executorService
|
|
||||||
|
|
||||||
// TODO: Rewrite this to use android.net.http.HttpEngine and make it Android 14 only when released
|
|
||||||
class CronetRequest {
|
|
||||||
lateinit var consumer: (ByteBuffer) -> Unit
|
|
||||||
lateinit var onResponse: CronetRequest.(UrlResponseInfo) -> Unit
|
|
||||||
lateinit var request: UrlRequest
|
|
||||||
lateinit var onError: (Throwable) -> Unit
|
|
||||||
lateinit var readerCont: Continuation<Unit>
|
|
||||||
val callback = object : UrlRequest.Callback() {
|
|
||||||
override fun onRedirectReceived(req: UrlRequest, info: UrlResponseInfo, url: String) {
|
|
||||||
logcat(tag = TAG) { "Redirected to $url" }
|
|
||||||
req.followRedirect()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResponseStarted(req: UrlRequest, info: UrlResponseInfo) {
|
|
||||||
onResponse(info)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onReadCompleted(req: UrlRequest, info: UrlResponseInfo, data: ByteBuffer) {
|
|
||||||
data.flip()
|
|
||||||
consumer(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSucceeded(req: UrlRequest, info: UrlResponseInfo) {
|
|
||||||
readerCont.resume(Unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailed(req: UrlRequest, info: UrlResponseInfo?, e: CronetException) {
|
|
||||||
onError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun cronetRequest(url: String, referer: String? = null, conf: UrlRequest.Builder.() -> Unit = {}) = CronetRequest().apply {
|
|
||||||
request = cronetHttpClient.newUrlRequestBuilder(url, callback, cronetHttpClientExecutor).apply {
|
|
||||||
addHeader("Cookie", EhCookieStore.getCookieHeader(url.toHttpUrl()))
|
|
||||||
addHeader("User-Agent", CHROME_USER_AGENT)
|
|
||||||
addHeader("Accept", CHROME_ACCEPT)
|
|
||||||
addHeader("Accept-Language", CHROME_ACCEPT_LANGUAGE)
|
|
||||||
referer?.let { addHeader("Referer", it) }
|
|
||||||
}.apply(conf).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend inline fun CronetRequest.awaitBodyFully(crossinline callback: (ByteBuffer) -> Unit) {
|
|
||||||
val buffer = pool.borrow()
|
|
||||||
return try {
|
|
||||||
suspendCancellableCoroutine { cont ->
|
|
||||||
consumer = {
|
|
||||||
callback(it)
|
|
||||||
buffer.clear()
|
|
||||||
request.read(buffer)
|
|
||||||
}
|
|
||||||
onError = { readerCont.resumeWithException(it) }
|
|
||||||
readerCont = cont
|
|
||||||
request.read(buffer)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
pool.recycle(buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend inline fun CronetRequest.copyToChannel(chan: FileChannel, crossinline listener: ((Int) -> Unit) = {}) = awaitBodyFully {
|
|
||||||
val bytes = chan.write(it)
|
|
||||||
listener(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend inline fun <R> CronetRequest.execute(crossinline callback: suspend CronetRequest.(UrlResponseInfo) -> R): R {
|
|
||||||
contract {
|
|
||||||
callsInPlace(callback, InvocationKind.EXACTLY_ONCE)
|
|
||||||
}
|
|
||||||
return coroutineScope {
|
|
||||||
suspendCancellableCoroutine { cont ->
|
|
||||||
onResponse = { launch { cont.resume(callback(it)) } }
|
|
||||||
cont.invokeOnCancellation { request.cancel() }
|
|
||||||
onError = { cont.resumeWithException(it) }
|
|
||||||
request.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.hippo.ehviewer.dao
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
@Entity(tableName = "OK_HTTP_3_COOKIE")
|
|
||||||
data class Cookie(
|
|
||||||
@ColumnInfo(name = "NAME")
|
|
||||||
var name: String,
|
|
||||||
@ColumnInfo(name = "VALUE")
|
|
||||||
var value: String,
|
|
||||||
@ColumnInfo(name = "EXPIRES_AT")
|
|
||||||
var expiresAt: Long,
|
|
||||||
@ColumnInfo(name = "DOMAIN")
|
|
||||||
var domain: String,
|
|
||||||
@ColumnInfo(name = "PATH")
|
|
||||||
var path: String,
|
|
||||||
@ColumnInfo(name = "SECURE")
|
|
||||||
var secure: Boolean,
|
|
||||||
@ColumnInfo(name = "HTTP_ONLY")
|
|
||||||
var httpOnly: Boolean,
|
|
||||||
@ColumnInfo(name = "PERSISTENT")
|
|
||||||
var persistent: Boolean,
|
|
||||||
@ColumnInfo(name = "HOST_ONLY")
|
|
||||||
var hostOnly: Boolean,
|
|
||||||
@PrimaryKey
|
|
||||||
@ColumnInfo(name = "_id")
|
|
||||||
var id: Long?,
|
|
||||||
)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.hippo.ehviewer.dao
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Query
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface CookiesDao {
|
|
||||||
@Query("SELECT * FROM OK_HTTP_3_COOKIE")
|
|
||||||
suspend fun list(): List<Cookie>
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.gallery
|
|
||||||
|
|
||||||
import com.hippo.unifile.UniFile
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader
|
|
||||||
|
|
||||||
abstract class PageLoader2 : PageLoader() {
|
|
||||||
open val startPage: Int
|
|
||||||
get() = 0
|
|
||||||
|
|
||||||
open fun putStartPage(page: Int) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return without extension
|
|
||||||
*/
|
|
||||||
abstract fun getImageFilename(index: Int): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return with extension
|
|
||||||
*/
|
|
||||||
abstract fun getImageFilenameWithExtension(index: Int): String
|
|
||||||
abstract fun save(index: Int, file: UniFile): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param filename with extension
|
|
||||||
*/
|
|
||||||
abstract fun save(index: Int, dir: UniFile, filename: String): UniFile?
|
|
||||||
}
|
|
||||||
|
|
||||||
val SUPPORT_IMAGE_EXTENSIONS = arrayOf(
|
|
||||||
".jpg", // Joint Photographic Experts Group
|
|
||||||
".png", // Portable Network Graphics
|
|
||||||
".gif",
|
|
||||||
)
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 Tarsin Norbin
|
|
||||||
*
|
|
||||||
* This file is part of EhViewer
|
|
||||||
*
|
|
||||||
* EhViewer is free software: you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* EhViewer is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.image
|
|
||||||
|
|
||||||
import android.graphics.ColorSpace
|
|
||||||
import android.graphics.ImageDecoder
|
|
||||||
import android.graphics.ImageDecoder.ImageInfo
|
|
||||||
import android.graphics.ImageDecoder.Source
|
|
||||||
import android.graphics.drawable.Animatable
|
|
||||||
import android.graphics.drawable.BitmapDrawable
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.os.Build
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
|
||||||
import splitties.init.appCtx
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
class Image private constructor(private val src: CloseableSource) {
|
|
||||||
var mObtainedDrawable: Drawable? =
|
|
||||||
ImageDecoder.decodeDrawable(src.source) { decoder: ImageDecoder, info: ImageInfo, _: Source ->
|
|
||||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) {
|
|
||||||
// Allocating hardware bitmap may cause a crash on framework versions prior to Android Q
|
|
||||||
decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
|
|
||||||
}
|
|
||||||
decoder.setTargetColorSpace(colorSpace)
|
|
||||||
decoder.setTargetSampleSize(
|
|
||||||
calculateSampleSize(info, 2 * screenHeight, 2 * screenWidth),
|
|
||||||
)
|
|
||||||
}.also {
|
|
||||||
if (it !is Animatable) src.close()
|
|
||||||
}
|
|
||||||
private set
|
|
||||||
|
|
||||||
val size: Int
|
|
||||||
get() = mObtainedDrawable!!.run { intrinsicHeight * intrinsicWidth * 4 * if (this is Animatable) 4 else 1 }
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun recycle() {
|
|
||||||
(mObtainedDrawable as? Animatable)?.stop()
|
|
||||||
(mObtainedDrawable as? BitmapDrawable)?.bitmap?.recycle()
|
|
||||||
mObtainedDrawable?.callback = null
|
|
||||||
if (mObtainedDrawable is Animatable) src.close()
|
|
||||||
mObtainedDrawable = null
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun calculateSampleSize(info: ImageInfo, targetHeight: Int, targetWeight: Int): Int {
|
|
||||||
return min(
|
|
||||||
info.size.width / targetWeight,
|
|
||||||
info.size.height / targetHeight,
|
|
||||||
).coerceAtLeast(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val imageSearchMaxSize = appCtx.resources.getDimensionPixelOffset(R.dimen.image_search_max_size)
|
|
||||||
|
|
||||||
val imageSearchDecoderSampleListener =
|
|
||||||
ImageDecoder.OnHeaderDecodedListener { decoder, info, _ ->
|
|
||||||
decoder.setTargetSampleSize(
|
|
||||||
calculateSampleSize(info, imageSearchMaxSize, imageSearchMaxSize),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val screenWidth = appCtx.resources.displayMetrics.widthPixels
|
|
||||||
val screenHeight = appCtx.resources.displayMetrics.heightPixels
|
|
||||||
val isWideColorGamut = appCtx.resources.configuration.isScreenWideColorGamut
|
|
||||||
var colorSpace = ColorSpace.get(
|
|
||||||
if (isWideColorGamut && ReaderPreferences.wideColorGamut().get()) {
|
|
||||||
ColorSpace.Named.DISPLAY_P3
|
|
||||||
} else {
|
|
||||||
ColorSpace.Named.SRGB
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
fun decode(src: CloseableSource): Image? {
|
|
||||||
return runCatching {
|
|
||||||
Image(src)
|
|
||||||
}.onFailure {
|
|
||||||
src.close()
|
|
||||||
it.printStackTrace()
|
|
||||||
}.getOrNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CloseableSource : AutoCloseable {
|
|
||||||
val source: Source
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
external fun rewriteGifSource(buffer: ByteBuffer)
|
|
||||||
external fun rewriteGifSource2(fd: Int)
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.hippo.ehviewer.ktbuilder
|
|
||||||
|
|
||||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
|
||||||
import okhttp3.Cache
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okio.FileSystem
|
|
||||||
import okio.Path
|
|
||||||
import splitties.init.appCtx
|
|
||||||
|
|
||||||
inline fun httpClient(builder: OkHttpClient.Builder.() -> Unit): OkHttpClient = OkHttpClient.Builder().apply(builder).build()
|
|
||||||
inline fun httpClient(client: OkHttpClient, builder: OkHttpClient.Builder.() -> Unit): OkHttpClient = client.newBuilder().apply(builder).build()
|
|
||||||
fun OkHttpClient.Builder.cache(directory: Path, maxSize: Long, fileSystem: FileSystem = FileSystem.SYSTEM) = cache(Cache(directory, maxSize, fileSystem))
|
|
||||||
inline fun OkHttpClient.Builder.chunker(builder: ChuckerInterceptor.Builder.() -> Unit) = addInterceptor(ChuckerInterceptor.Builder(appCtx).apply(builder).build())
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package com.hippo.ehviewer.legacy
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
private val OBSOLETE_CACHE_DIRS = arrayOf(
|
|
||||||
"image",
|
|
||||||
"thumb",
|
|
||||||
"gallery_image",
|
|
||||||
"spider_info",
|
|
||||||
)
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun cleanObsoleteCache(application: Application) {
|
|
||||||
launchIO {
|
|
||||||
application.deleteDatabase("hosts.db")
|
|
||||||
val dir = application.cacheDir
|
|
||||||
for (subdir in OBSOLETE_CACHE_DIRS) {
|
|
||||||
val file = File(dir, subdir)
|
|
||||||
if (file.exists()) {
|
|
||||||
file.deleteRecursively()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package com.hippo.ehviewer.legacy
|
|
||||||
|
|
||||||
import arrow.fx.coroutines.release
|
|
||||||
import arrow.fx.coroutines.resource
|
|
||||||
import com.hippo.ehviewer.client.EhCookieStore
|
|
||||||
import com.hippo.ehviewer.dao.Cookie
|
|
||||||
import com.hippo.ehviewer.dao.CookiesDatabase
|
|
||||||
import splitties.arch.room.roomDb
|
|
||||||
import splitties.init.appCtx
|
|
||||||
|
|
||||||
private const val legacyCookiesDB = "okhttp3-cookie.db"
|
|
||||||
|
|
||||||
private fun Cookie.toOkHttp3Cookie(): okhttp3.Cookie {
|
|
||||||
return okhttp3.Cookie.Builder().apply {
|
|
||||||
name(name)
|
|
||||||
value(value)
|
|
||||||
expiresAt(expiresAt)
|
|
||||||
if (hostOnly) hostOnlyDomain(domain) else domain(domain)
|
|
||||||
path(path)
|
|
||||||
if (secure) secure()
|
|
||||||
if (httpOnly) httpOnly()
|
|
||||||
}.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun migrateCookies() {
|
|
||||||
if (legacyCookiesDB !in appCtx.databaseList()) return
|
|
||||||
resource {
|
|
||||||
roomDb<CookiesDatabase>(legacyCookiesDB)
|
|
||||||
} release {
|
|
||||||
it.close()
|
|
||||||
appCtx.deleteDatabase(legacyCookiesDB)
|
|
||||||
} use { database ->
|
|
||||||
database.cookiesDao().list().forEach {
|
|
||||||
EhCookieStore.addCookie(it.toOkHttp3Cookie())
|
|
||||||
}
|
|
||||||
EhCookieStore.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,293 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.spider
|
|
||||||
|
|
||||||
import android.graphics.ImageDecoder
|
|
||||||
import android.os.ParcelFileDescriptor
|
|
||||||
import android.os.ParcelFileDescriptor.MODE_READ_WRITE
|
|
||||||
import com.hippo.ehviewer.EhDB
|
|
||||||
import com.hippo.ehviewer.client.EhUtils.getSuitableTitle
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
|
||||||
import com.hippo.ehviewer.client.ehRequest
|
|
||||||
import com.hippo.ehviewer.client.executeNonCache
|
|
||||||
import com.hippo.ehviewer.client.getImageKey
|
|
||||||
import com.hippo.ehviewer.coil.read
|
|
||||||
import com.hippo.ehviewer.coil.suspendEdit
|
|
||||||
import com.hippo.ehviewer.cronet.copyToChannel
|
|
||||||
import com.hippo.ehviewer.cronet.cronetRequest
|
|
||||||
import com.hippo.ehviewer.cronet.execute
|
|
||||||
import com.hippo.ehviewer.download.downloadLocation
|
|
||||||
import com.hippo.ehviewer.gallery.SUPPORT_IMAGE_EXTENSIONS
|
|
||||||
import com.hippo.ehviewer.image.Image.CloseableSource
|
|
||||||
import com.hippo.ehviewer.image.rewriteGifSource2
|
|
||||||
import com.hippo.ehviewer.util.FileUtils
|
|
||||||
import com.hippo.ehviewer.util.isCronetSupported
|
|
||||||
import com.hippo.ehviewer.util.sendTo
|
|
||||||
import com.hippo.unifile.UniFile
|
|
||||||
import com.hippo.unifile.openOutputStream
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
|
||||||
import okhttp3.Response
|
|
||||||
import okio.Buffer
|
|
||||||
import okio.ForwardingSink
|
|
||||||
import okio.sink
|
|
||||||
import java.util.Locale
|
|
||||||
import kotlin.io.path.readText
|
|
||||||
import com.hippo.ehviewer.EhApplication.Companion.imageCache as sCache
|
|
||||||
|
|
||||||
class SpiderDen(mGalleryInfo: GalleryInfo) {
|
|
||||||
private val mGid = mGalleryInfo.gid
|
|
||||||
var downloadDir: UniFile? = null
|
|
||||||
|
|
||||||
@Volatile
|
|
||||||
@SpiderQueen.Mode
|
|
||||||
var mode = SpiderQueen.MODE_READ
|
|
||||||
private set
|
|
||||||
|
|
||||||
suspend fun setMode(value: Int) {
|
|
||||||
mode = value
|
|
||||||
if (mode == SpiderQueen.MODE_DOWNLOAD && downloadDir == null) {
|
|
||||||
downloadDir = getGalleryDownloadDir(mGid)!!.apply { check(ensureDir()) { "Download directory $uri is not valid directory!" } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun containInCache(index: Int): Boolean {
|
|
||||||
val key = getImageKey(mGid, index)
|
|
||||||
return sCache.read(key) { true } ?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun containInDownloadDir(index: Int): Boolean {
|
|
||||||
val dir = downloadDir ?: return false
|
|
||||||
return findImageFile(dir, index) != null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun copyFromCacheToDownloadDir(index: Int): Boolean {
|
|
||||||
val dir = downloadDir ?: return false
|
|
||||||
val key = getImageKey(mGid, index)
|
|
||||||
return runCatching {
|
|
||||||
sCache.read(key) {
|
|
||||||
val extension = fixExtension("." + metadata.toFile().readText())
|
|
||||||
val file = dir.createFile(perFilename(index, extension)) ?: return false
|
|
||||||
file.openFileDescriptor("w").use { outFd ->
|
|
||||||
ParcelFileDescriptor.open(data.toFile(), MODE_READ_WRITE).use {
|
|
||||||
it sendTo outFd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} ?: false
|
|
||||||
}.onFailure {
|
|
||||||
it.printStackTrace()
|
|
||||||
}.getOrDefault(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun contains(index: Int): Boolean {
|
|
||||||
return when (mode) {
|
|
||||||
SpiderQueen.MODE_READ -> {
|
|
||||||
containInCache(index) || containInDownloadDir(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
SpiderQueen.MODE_DOWNLOAD -> {
|
|
||||||
containInDownloadDir(index) || copyFromCacheToDownloadDir(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeFromCache(index: Int): Boolean {
|
|
||||||
val key = getImageKey(mGid, index)
|
|
||||||
return sCache.remove(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeFromDownloadDir(index: Int): Boolean {
|
|
||||||
return downloadDir?.let { findImageFile(it, index)?.delete() } ?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remove(index: Int): Boolean {
|
|
||||||
return removeFromCache(index) or removeFromDownloadDir(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findDownloadFileForIndex(index: Int, extension: String): UniFile? {
|
|
||||||
val dir = downloadDir ?: return null
|
|
||||||
val ext = fixExtension(".$extension")
|
|
||||||
return dir.createFile(perFilename(index, ext))
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun makeHttpCallAndSaveImage(
|
|
||||||
index: Int,
|
|
||||||
url: String,
|
|
||||||
referer: String?,
|
|
||||||
notifyProgress: (Long, Long, Int) -> Unit,
|
|
||||||
): Boolean {
|
|
||||||
return if (isCronetSupported) {
|
|
||||||
cronetRequest(url, referer) {
|
|
||||||
disableCache()
|
|
||||||
}.execute { info ->
|
|
||||||
val headers = info.allHeaders
|
|
||||||
val type = headers["Content-Type"]?.first()?.toMediaType()?.subtype ?: "jpg"
|
|
||||||
val length = headers["Content-Length"]!!.first().toLong()
|
|
||||||
saveResponseMeta(index, type, length) { file ->
|
|
||||||
file.openOutputStream().use { os ->
|
|
||||||
val chan = os.channel
|
|
||||||
chan.truncate(0)
|
|
||||||
copyToChannel(chan) {
|
|
||||||
notifyProgress(length, info.receivedByteCount, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ehRequest(url, referer).executeNonCache {
|
|
||||||
if (code >= 400) {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
saveFromHttpResponse(index, this, notifyProgress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun saveResponseMeta(index: Int, ext: String, length: Long, fops: suspend (UniFile) -> Unit): Boolean {
|
|
||||||
suspend fun realFops(f: UniFile) = fops(f).apply {
|
|
||||||
if (ext.lowercase() == "gif") {
|
|
||||||
f.openFileDescriptor("rw").use { rewriteGifSource2(it.fd) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
findDownloadFileForIndex(index, ext)?.run {
|
|
||||||
realFops(this)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read Mode, allow save to cache
|
|
||||||
if (mode == SpiderQueen.MODE_READ) {
|
|
||||||
val key = getImageKey(mGid, index)
|
|
||||||
return sCache.suspendEdit(key) {
|
|
||||||
metadata.toFile().writeText(ext)
|
|
||||||
realFops(UniFile.fromFile(data.toFile())!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun saveFromHttpResponse(index: Int, response: Response, notifyProgress: (Long, Long, Int) -> Unit): Boolean {
|
|
||||||
val contentType = response.body.contentType()
|
|
||||||
val extension = contentType?.subtype ?: "jpg"
|
|
||||||
val length = response.body.contentLength()
|
|
||||||
return saveResponseMeta(index, extension, length) { outFile ->
|
|
||||||
object : ForwardingSink(outFile.openOutputStream().also { it.channel.truncate(0) }.sink()) {
|
|
||||||
var totalBytesRead = 0L
|
|
||||||
override fun write(source: Buffer, byteCount: Long) {
|
|
||||||
super.write(source, byteCount)
|
|
||||||
totalBytesRead += byteCount
|
|
||||||
notifyProgress(length, totalBytesRead, byteCount.toInt())
|
|
||||||
}
|
|
||||||
}.use { sink -> response.body.source().use { source -> source.readAll(sink) } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveToUniFile(index: Int, file: UniFile): Boolean {
|
|
||||||
file.openFileDescriptor("w").use { toFd ->
|
|
||||||
val key = getImageKey(mGid, index)
|
|
||||||
|
|
||||||
// Read from diskCache first
|
|
||||||
sCache.read(key) {
|
|
||||||
runCatching {
|
|
||||||
UniFile.fromFile(data.toFile())!!.openFileDescriptor("r").use {
|
|
||||||
it sendTo toFd
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}.onFailure {
|
|
||||||
it.printStackTrace()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read from download dir
|
|
||||||
downloadDir?.let { uniFile ->
|
|
||||||
runCatching {
|
|
||||||
findImageFile(uniFile, index)?.openFileDescriptor("r")?.use {
|
|
||||||
it sendTo toFd
|
|
||||||
}
|
|
||||||
}.onFailure {
|
|
||||||
it.printStackTrace()
|
|
||||||
return false
|
|
||||||
}.onSuccess {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getExtension(index: Int): String? {
|
|
||||||
val key = getImageKey(mGid, index)
|
|
||||||
return sCache.read(key) { metadata.toNioPath().readText() }
|
|
||||||
?: downloadDir?.let { findImageFile(it, index) }?.name.let { FileUtils.getExtensionFromFilename(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getImageSource(index: Int): CloseableSource? {
|
|
||||||
if (mode == SpiderQueen.MODE_READ) {
|
|
||||||
val key = getImageKey(mGid, index)
|
|
||||||
val snapshot = sCache.openSnapshot(key)
|
|
||||||
if (snapshot != null) {
|
|
||||||
val source = ImageDecoder.createSource(snapshot.data.toFile())
|
|
||||||
return object : CloseableSource, AutoCloseable by snapshot {
|
|
||||||
override val source = source
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val dir = downloadDir ?: return null
|
|
||||||
val source = findImageFile(dir, index)?.imageSource ?: return null
|
|
||||||
return object : CloseableSource {
|
|
||||||
override val source = source
|
|
||||||
|
|
||||||
override fun close() {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val COMPAT_IMAGE_EXTENSIONS = SUPPORT_IMAGE_EXTENSIONS + ".jpeg"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param extension with dot
|
|
||||||
*/
|
|
||||||
fun perFilename(index: Int, extension: String?): String {
|
|
||||||
return String.format(Locale.US, "%08d%s", index + 1, extension)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param extension with dot
|
|
||||||
*/
|
|
||||||
private fun fixExtension(extension: String): String {
|
|
||||||
return extension.takeIf { SUPPORT_IMAGE_EXTENSIONS.contains(it) } ?: SUPPORT_IMAGE_EXTENSIONS[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findImageFile(dir: UniFile, index: Int): UniFile? {
|
|
||||||
return COMPAT_IMAGE_EXTENSIONS.firstNotNullOfOrNull { dir.findFile(perFilename(index, it)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun GalleryInfo.putToDownloadDir(): String {
|
|
||||||
val title = getSuitableTitle(this)
|
|
||||||
val dirname = FileUtils.sanitizeFilename("$gid-$title")
|
|
||||||
EhDB.putDownloadDirname(gid, dirname)
|
|
||||||
return dirname
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getGalleryDownloadDir(gid: Long): UniFile? {
|
|
||||||
val dirname = EhDB.getDownloadDirname(gid) ?: return null
|
|
||||||
return downloadLocation.subFile(dirname)
|
|
||||||
}
|
|
||||||
@@ -1,382 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.ui
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Delete
|
|
||||||
import androidx.compose.material.icons.filled.Download
|
|
||||||
import androidx.compose.material.icons.filled.DriveFileMove
|
|
||||||
import androidx.compose.material.icons.filled.Favorite
|
|
||||||
import androidx.compose.material.icons.filled.FavoriteBorder
|
|
||||||
import androidx.compose.material.icons.filled.HeartBroken
|
|
||||||
import androidx.compose.material.icons.filled.MenuBook
|
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import com.hippo.ehviewer.EhDB
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
import com.hippo.ehviewer.Settings
|
|
||||||
import com.hippo.ehviewer.client.EhCookieStore
|
|
||||||
import com.hippo.ehviewer.client.EhEngine
|
|
||||||
import com.hippo.ehviewer.client.EhUtils
|
|
||||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.LOCAL_FAVORITED
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.NOT_FAVORITED
|
|
||||||
import com.hippo.ehviewer.client.exception.EhException
|
|
||||||
import com.hippo.ehviewer.dao.DownloadInfo
|
|
||||||
import com.hippo.ehviewer.download.DownloadManager
|
|
||||||
import com.hippo.ehviewer.download.DownloadService
|
|
||||||
import com.hippo.ehviewer.download.downloadLocation
|
|
||||||
import com.hippo.ehviewer.ui.legacy.ListCheckBoxDialogBuilder
|
|
||||||
import com.hippo.ehviewer.ui.scene.BaseScene
|
|
||||||
import com.hippo.ehviewer.ui.tools.DialogState
|
|
||||||
import com.hippo.ehviewer.util.FavouriteStatusRouter
|
|
||||||
import com.hippo.ehviewer.util.LongList
|
|
||||||
import com.hippo.ehviewer.util.requestPermission
|
|
||||||
import com.hippo.unifile.UniFile
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchNow
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import moe.tarsin.coroutines.runSuspendCatching
|
|
||||||
import rikka.core.util.ContextUtils.requireActivity
|
|
||||||
import splitties.init.appCtx
|
|
||||||
|
|
||||||
object CommonOperations {
|
|
||||||
fun startDownload(activity: MainActivity?, galleryInfo: BaseGalleryInfo, forceDefault: Boolean) {
|
|
||||||
startDownload(activity!!, listOf(galleryInfo), forceDefault)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun startDownload(
|
|
||||||
activity: MainActivity,
|
|
||||||
galleryInfos: List<BaseGalleryInfo>,
|
|
||||||
forceDefault: Boolean,
|
|
||||||
) {
|
|
||||||
launchNow {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
activity.requestPermission(Manifest.permission.POST_NOTIFICATIONS)
|
|
||||||
}
|
|
||||||
doStartDownload(activity, galleryInfos, forceDefault)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doStartDownload(
|
|
||||||
activity: MainActivity,
|
|
||||||
galleryInfos: List<BaseGalleryInfo>,
|
|
||||||
forceDefault: Boolean,
|
|
||||||
) {
|
|
||||||
val toStart = LongList()
|
|
||||||
val toAdd: MutableList<BaseGalleryInfo> = ArrayList()
|
|
||||||
for (gi in galleryInfos) {
|
|
||||||
if (DownloadManager.containDownloadInfo(gi.gid)) {
|
|
||||||
toStart.add(gi.gid)
|
|
||||||
} else {
|
|
||||||
toAdd.add(gi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!toStart.isEmpty()) {
|
|
||||||
val intent = Intent(activity, DownloadService::class.java)
|
|
||||||
intent.action = DownloadService.ACTION_START_RANGE
|
|
||||||
intent.putExtra(DownloadService.KEY_GID_LIST, toStart)
|
|
||||||
ContextCompat.startForegroundService(activity, intent)
|
|
||||||
}
|
|
||||||
if (toAdd.isEmpty()) {
|
|
||||||
activity.showTip(R.string.added_to_download_list, BaseScene.LENGTH_SHORT)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var justStart = forceDefault
|
|
||||||
var label: String? = null
|
|
||||||
// Get default download label
|
|
||||||
if (!justStart && Settings.hasDefaultDownloadLabel) {
|
|
||||||
label = Settings.defaultDownloadLabel
|
|
||||||
justStart = label == null || DownloadManager.containLabel(label)
|
|
||||||
}
|
|
||||||
// If there is no other label, just use null label
|
|
||||||
if (!justStart && DownloadManager.labelList.isEmpty()) {
|
|
||||||
justStart = true
|
|
||||||
label = null
|
|
||||||
}
|
|
||||||
if (justStart) {
|
|
||||||
// Got default label
|
|
||||||
for (gi in toAdd) {
|
|
||||||
val intent = Intent(activity, DownloadService::class.java)
|
|
||||||
intent.action = DownloadService.ACTION_START
|
|
||||||
intent.putExtra(DownloadService.KEY_LABEL, label)
|
|
||||||
intent.putExtra(DownloadService.KEY_GALLERY_INFO, gi)
|
|
||||||
ContextCompat.startForegroundService(activity, intent)
|
|
||||||
}
|
|
||||||
// Notify
|
|
||||||
activity.showTip(R.string.added_to_download_list, BaseScene.LENGTH_SHORT)
|
|
||||||
} else {
|
|
||||||
// Let use chose label
|
|
||||||
val list = DownloadManager.labelList
|
|
||||||
val items = mutableListOf<String>()
|
|
||||||
items.add(activity.getString(R.string.default_download_label_name))
|
|
||||||
items.addAll(list.map { it.label })
|
|
||||||
ListCheckBoxDialogBuilder(
|
|
||||||
activity,
|
|
||||||
items,
|
|
||||||
{ builder: ListCheckBoxDialogBuilder?, _: AlertDialog?, position: Int ->
|
|
||||||
var label1: String?
|
|
||||||
if (position == 0) {
|
|
||||||
label1 = null
|
|
||||||
} else {
|
|
||||||
label1 = items[position]
|
|
||||||
if (!DownloadManager.containLabel(label1)) {
|
|
||||||
label1 = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Start download
|
|
||||||
for (gi in toAdd) {
|
|
||||||
val intent = Intent(activity, DownloadService::class.java)
|
|
||||||
intent.action = DownloadService.ACTION_START
|
|
||||||
intent.putExtra(DownloadService.KEY_LABEL, label1)
|
|
||||||
intent.putExtra(DownloadService.KEY_GALLERY_INFO, gi)
|
|
||||||
ContextCompat.startForegroundService(activity, intent)
|
|
||||||
}
|
|
||||||
// Save settings
|
|
||||||
if (builder?.isChecked == true) {
|
|
||||||
Settings.hasDefaultDownloadLabel = true
|
|
||||||
Settings.defaultDownloadLabel = label1
|
|
||||||
} else {
|
|
||||||
Settings.hasDefaultDownloadLabel = false
|
|
||||||
}
|
|
||||||
// Notify
|
|
||||||
activity.showTip(R.string.added_to_download_list, BaseScene.LENGTH_SHORT)
|
|
||||||
},
|
|
||||||
activity.getString(R.string.remember_download_label),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.setTitle(R.string.download)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeNoMediaFile(downloadDir: UniFile) {
|
|
||||||
val noMedia = downloadDir.subFile(".nomedia") ?: return
|
|
||||||
noMedia.delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ensureNoMediaFile(downloadDir: UniFile) {
|
|
||||||
downloadDir.createFile(".nomedia") ?: return
|
|
||||||
}
|
|
||||||
|
|
||||||
private val lck = Mutex()
|
|
||||||
|
|
||||||
suspend fun keepNoMediaFileStatus() {
|
|
||||||
lck.withLock {
|
|
||||||
if (Settings.mediaScan) {
|
|
||||||
removeNoMediaFile(downloadLocation)
|
|
||||||
} else {
|
|
||||||
ensureNoMediaFile(downloadLocation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getFavoriteIcon(favorited: Boolean) =
|
|
||||||
if (favorited) Icons.Default.Favorite else Icons.Default.FavoriteBorder
|
|
||||||
|
|
||||||
suspend fun DialogState.modifyFavorites(galleryInfo: BaseGalleryInfo): Boolean {
|
|
||||||
val localFavorited = EhDB.containLocalFavorites(galleryInfo.gid)
|
|
||||||
if (EhCookieStore.hasSignedIn()) {
|
|
||||||
val isFavorited = galleryInfo.favoriteSlot != NOT_FAVORITED
|
|
||||||
val defaultFavSlot = Settings.defaultFavSlot
|
|
||||||
if (defaultFavSlot == -2) {
|
|
||||||
val localFav = getFavoriteIcon(localFavorited) to appCtx.getString(R.string.local_favorites)
|
|
||||||
val cloudFav = Settings.favCat.mapIndexed { index, name ->
|
|
||||||
getFavoriteIcon(galleryInfo.favoriteSlot == index) to name
|
|
||||||
}.toTypedArray()
|
|
||||||
val items = if (isFavorited) {
|
|
||||||
val remove = Icons.Default.HeartBroken to appCtx.getString(R.string.remove_from_favourites)
|
|
||||||
arrayOf(remove, localFav, *cloudFav)
|
|
||||||
} else {
|
|
||||||
arrayOf(localFav, *cloudFav)
|
|
||||||
}
|
|
||||||
val (slot, note) = showSelectItemWithIconAndTextField(
|
|
||||||
*items,
|
|
||||||
title = R.string.add_favorites_dialog_title,
|
|
||||||
hint = R.string.favorite_note,
|
|
||||||
maxChar = MAX_FAVNOTE_CHAR,
|
|
||||||
)
|
|
||||||
return doModifyFavorites(galleryInfo, if (isFavorited) slot - 2 else slot - 1, localFavorited, note)
|
|
||||||
} else {
|
|
||||||
return doModifyFavorites(galleryInfo, if (isFavorited) NOT_FAVORITED else defaultFavSlot, localFavorited)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return doModifyFavorites(galleryInfo, LOCAL_FAVORITED, localFavorited)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun doModifyFavorites(
|
|
||||||
galleryInfo: BaseGalleryInfo,
|
|
||||||
slot: Int = NOT_FAVORITED,
|
|
||||||
localFavorited: Boolean = true,
|
|
||||||
note: String = "",
|
|
||||||
): Boolean {
|
|
||||||
val add = when (slot) {
|
|
||||||
NOT_FAVORITED -> { // Remove from cloud favorites first
|
|
||||||
if (galleryInfo.favoriteSlot > LOCAL_FAVORITED) {
|
|
||||||
EhEngine.modifyFavorites(galleryInfo.gid, galleryInfo.token)
|
|
||||||
} else if (localFavorited) {
|
|
||||||
EhDB.removeLocalFavorites(galleryInfo)
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
LOCAL_FAVORITED -> {
|
|
||||||
if (localFavorited) {
|
|
||||||
EhDB.removeLocalFavorites(galleryInfo)
|
|
||||||
} else {
|
|
||||||
EhDB.putLocalFavorites(galleryInfo)
|
|
||||||
}
|
|
||||||
!localFavorited
|
|
||||||
}
|
|
||||||
|
|
||||||
in 0..9 -> {
|
|
||||||
EhEngine.modifyFavorites(galleryInfo.gid, galleryInfo.token, slot, note)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> throw EhException("Invalid favorite slot!")
|
|
||||||
}
|
|
||||||
if (add) { // Cloud favorites have priority
|
|
||||||
if (slot != LOCAL_FAVORITED || galleryInfo.favoriteSlot == NOT_FAVORITED) {
|
|
||||||
galleryInfo.favoriteSlot = slot
|
|
||||||
galleryInfo.favoriteName = Settings.favCat.getOrNull(slot)
|
|
||||||
FavouriteStatusRouter.modifyFavourites(galleryInfo.gid, slot)
|
|
||||||
}
|
|
||||||
} else if (slot != LOCAL_FAVORITED || galleryInfo.favoriteSlot == LOCAL_FAVORITED) {
|
|
||||||
val newSlot = if (galleryInfo.favoriteSlot > LOCAL_FAVORITED && localFavorited) LOCAL_FAVORITED else NOT_FAVORITED
|
|
||||||
galleryInfo.favoriteSlot = newSlot
|
|
||||||
galleryInfo.favoriteName = null
|
|
||||||
FavouriteStatusRouter.modifyFavourites(galleryInfo.gid, newSlot)
|
|
||||||
}
|
|
||||||
return add
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun removeFromFavorites(galleryInfo: BaseGalleryInfo) = doModifyFavorites(galleryInfo)
|
|
||||||
|
|
||||||
fun Context.navToReader(info: BaseGalleryInfo, page: Int = -1) {
|
|
||||||
val intent = Intent(this, ReaderActivity::class.java)
|
|
||||||
intent.action = ReaderActivity.ACTION_EH
|
|
||||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, info)
|
|
||||||
intent.putExtra(ReaderActivity.KEY_PAGE, page)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun DialogState.doGalleryInfoAction(info: BaseGalleryInfo, context: Context) {
|
|
||||||
val downloaded = DownloadManager.getDownloadState(info.gid) != DownloadInfo.STATE_INVALID
|
|
||||||
val favourite = info.favoriteSlot != NOT_FAVORITED
|
|
||||||
val selected = if (!downloaded) {
|
|
||||||
showSelectItemWithIcon(
|
|
||||||
Icons.Default.MenuBook to R.string.read,
|
|
||||||
Icons.Default.Download to R.string.download,
|
|
||||||
if (!favourite) Icons.Default.Favorite to R.string.add_to_favourites else Icons.Default.HeartBroken to R.string.remove_from_favourites,
|
|
||||||
title = EhUtils.getSuitableTitle(info),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
showSelectItemWithIcon(
|
|
||||||
Icons.Default.MenuBook to R.string.read,
|
|
||||||
Icons.Default.Delete to R.string.delete_downloads,
|
|
||||||
if (!favourite) Icons.Default.Favorite to R.string.add_to_favourites else Icons.Default.HeartBroken to R.string.remove_from_favourites,
|
|
||||||
Icons.Default.DriveFileMove to R.string.download_move_dialog_title,
|
|
||||||
title = EhUtils.getSuitableTitle(info),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
with(requireActivity<MainActivity>(context)) {
|
|
||||||
when (selected) {
|
|
||||||
0 -> navToReader(info)
|
|
||||||
1 -> withUIContext {
|
|
||||||
if (downloaded) {
|
|
||||||
confirmRemoveDownload(info)
|
|
||||||
} else {
|
|
||||||
CommonOperations.startDownload(this@with, info, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
2 -> if (favourite) {
|
|
||||||
runSuspendCatching {
|
|
||||||
removeFromFavorites(info)
|
|
||||||
showTip(R.string.remove_from_favorite_success, BaseScene.LENGTH_SHORT)
|
|
||||||
}.onFailure {
|
|
||||||
showTip(R.string.remove_from_favorite_failure, BaseScene.LENGTH_LONG)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
runSuspendCatching {
|
|
||||||
modifyFavorites(info)
|
|
||||||
showTip(R.string.add_to_favorite_success, BaseScene.LENGTH_SHORT)
|
|
||||||
}.onFailure {
|
|
||||||
showTip(R.string.add_to_favorite_failure, BaseScene.LENGTH_LONG)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
3 -> showMoveDownloadLabel(info)
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val MAX_FAVNOTE_CHAR = 200
|
|
||||||
|
|
||||||
suspend fun DialogState.confirmRemoveDownload(info: GalleryInfo, onDismiss: () -> Unit = {}) {
|
|
||||||
var checked by mutableStateOf(Settings.removeImageFiles)
|
|
||||||
awaitPermissionOrCancel(
|
|
||||||
confirmText = android.R.string.ok,
|
|
||||||
dismissText = android.R.string.cancel,
|
|
||||||
title = R.string.download_remove_dialog_title,
|
|
||||||
onDismiss = onDismiss,
|
|
||||||
text = {
|
|
||||||
Column {
|
|
||||||
Text(text = stringResource(id = R.string.download_remove_dialog_message, info.title.orEmpty()))
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Checkbox(checked = checked, onCheckedChange = { checked = it })
|
|
||||||
Text(text = stringResource(id = R.string.download_remove_dialog_check_text))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
Settings.removeImageFiles = checked
|
|
||||||
withIOContext {
|
|
||||||
DownloadManager.deleteDownload(info.gid, checked)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun DialogState.showMoveDownloadLabel(info: GalleryInfo) {
|
|
||||||
val defaultLabel = appCtx.getString(R.string.default_download_label_name)
|
|
||||||
val labels = DownloadManager.labelList.map { it.label }.toTypedArray()
|
|
||||||
val selected = showSelectItem(defaultLabel, *labels, title = R.string.download_move_dialog_title)
|
|
||||||
val downloadInfo = DownloadManager.getDownloadInfo(info.gid) ?: return
|
|
||||||
val label = if (selected == 0) null else labels[selected - 1]
|
|
||||||
withUIContext { DownloadManager.changeLabel(listOf(downloadInfo), label) }
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
package com.hippo.ehviewer.ui
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.SideEffect
|
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.compose.NavHost
|
|
||||||
import androidx.navigation.compose.composable
|
|
||||||
import androidx.navigation.compose.rememberNavController
|
|
||||||
import com.hippo.ehviewer.Settings
|
|
||||||
import com.hippo.ehviewer.client.EhCookieStore
|
|
||||||
import com.hippo.ehviewer.ui.login.CookieSignInScene
|
|
||||||
import com.hippo.ehviewer.ui.login.SelectSiteScreen
|
|
||||||
import com.hippo.ehviewer.ui.login.SignInScreen
|
|
||||||
import com.hippo.ehviewer.ui.login.WebViewSignInScreen
|
|
||||||
import com.hippo.ehviewer.ui.settings.AboutScreen
|
|
||||||
import com.hippo.ehviewer.ui.settings.AdvancedScreen
|
|
||||||
import com.hippo.ehviewer.ui.settings.BaseScreen
|
|
||||||
import com.hippo.ehviewer.ui.settings.DownloadScreen
|
|
||||||
import com.hippo.ehviewer.ui.settings.EhScreen
|
|
||||||
import com.hippo.ehviewer.ui.settings.FilterScreen
|
|
||||||
import com.hippo.ehviewer.ui.settings.LicenseScreen
|
|
||||||
import com.hippo.ehviewer.ui.settings.MyTagsScreen
|
|
||||||
import com.hippo.ehviewer.ui.settings.PrivacyScreen
|
|
||||||
import com.hippo.ehviewer.ui.settings.UConfigScreen
|
|
||||||
|
|
||||||
class ConfigureActivity : EhActivity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
||||||
setMD3Content {
|
|
||||||
val navController = rememberNavController()
|
|
||||||
val windowSizeClass = calculateWindowSizeClass(this)
|
|
||||||
CompositionLocalProvider(LocalNavController provides navController) {
|
|
||||||
NavHost(
|
|
||||||
navController = navController,
|
|
||||||
startDestination = if (Settings.needSignIn) {
|
|
||||||
if (EhCookieStore.hasSignedIn()) SELECT_SITE_ROUTE_NAME else SIGN_IN_ROUTE_NAME
|
|
||||||
} else {
|
|
||||||
BASE_SETTINGS_SCREEN
|
|
||||||
},
|
|
||||||
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Up, tween(200)) },
|
|
||||||
exitTransition = { fadeOut(tween(200)) },
|
|
||||||
popEnterTransition = { fadeIn(tween(200)) },
|
|
||||||
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Down, tween(200)) },
|
|
||||||
) {
|
|
||||||
composable(SIGN_IN_ROUTE_NAME) {
|
|
||||||
SignInScreen(windowSizeClass)
|
|
||||||
}
|
|
||||||
composable(WEBVIEW_SIGN_IN_ROUTE_NAME) {
|
|
||||||
WebViewSignInScreen()
|
|
||||||
}
|
|
||||||
composable(COOKIE_SIGN_IN_ROUTE_NAME) {
|
|
||||||
CookieSignInScene(windowSizeClass)
|
|
||||||
}
|
|
||||||
composable(SELECT_SITE_ROUTE_NAME) {
|
|
||||||
SelectSiteScreen()
|
|
||||||
}
|
|
||||||
composable(BASE_SETTINGS_SCREEN) {
|
|
||||||
BaseScreen()
|
|
||||||
}
|
|
||||||
composable(ABOUT_SETTINGS_SCREEN) {
|
|
||||||
AboutScreen()
|
|
||||||
}
|
|
||||||
composable(LICENSE_SCREEN) {
|
|
||||||
LicenseScreen()
|
|
||||||
}
|
|
||||||
composable(PRIVACY_SETTINGS_SCREEN) {
|
|
||||||
PrivacyScreen()
|
|
||||||
}
|
|
||||||
composable(ADVANCED_SETTINGS_SCREEN) {
|
|
||||||
AdvancedScreen()
|
|
||||||
}
|
|
||||||
composable(DOWNLOAD_SETTINGS_SCREEN) {
|
|
||||||
DownloadScreen()
|
|
||||||
}
|
|
||||||
composable(EH_SETTINGS_SCREEN) {
|
|
||||||
EhScreen()
|
|
||||||
}
|
|
||||||
composable(UCONFIG_SCREEN) {
|
|
||||||
UConfigScreen()
|
|
||||||
}
|
|
||||||
composable(MYTAGS_SCREEN) {
|
|
||||||
MyTagsScreen()
|
|
||||||
}
|
|
||||||
composable(FILTER_SCREEN) {
|
|
||||||
FilterScreen()
|
|
||||||
}
|
|
||||||
composable(FINISH_ROUTE_NAME) {
|
|
||||||
SideEffect {
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val LocalNavController = compositionLocalOf<NavController> { error("CompositionLocal LocalNavController not present!") }
|
|
||||||
|
|
||||||
const val BASE_SETTINGS_SCREEN = "Base"
|
|
||||||
const val EH_SETTINGS_SCREEN = "Eh"
|
|
||||||
const val DOWNLOAD_SETTINGS_SCREEN = "Download"
|
|
||||||
const val PRIVACY_SETTINGS_SCREEN = "Privacy"
|
|
||||||
const val ADVANCED_SETTINGS_SCREEN = "Advanced"
|
|
||||||
const val ABOUT_SETTINGS_SCREEN = "About"
|
|
||||||
const val LICENSE_SCREEN = "License"
|
|
||||||
const val UCONFIG_SCREEN = "UConfig"
|
|
||||||
const val MYTAGS_SCREEN = "Mytags"
|
|
||||||
const val FILTER_SCREEN = "Filter"
|
|
||||||
const val SIGN_IN_ROUTE_NAME = "SignIn"
|
|
||||||
const val WEBVIEW_SIGN_IN_ROUTE_NAME = "WebViewSignIn"
|
|
||||||
const val COOKIE_SIGN_IN_ROUTE_NAME = "CookieSignIn"
|
|
||||||
const val SELECT_SITE_ROUTE_NAME = "SelectSite"
|
|
||||||
const val FINISH_ROUTE_NAME = "Finish"
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
package com.hippo.ehviewer.ui
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.ProvideTextStyle
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.dimensionResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
import com.hippo.ehviewer.Settings
|
|
||||||
import com.hippo.ehviewer.client.EhUrl
|
|
||||||
import com.hippo.ehviewer.client.EhUtils
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
|
||||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.LOCAL_FAVORITED
|
|
||||||
import com.hippo.ehviewer.client.thumbUrl
|
|
||||||
import com.hippo.ehviewer.databinding.InfoSheetBinding
|
|
||||||
import com.hippo.ehviewer.ui.scene.navWithUrl
|
|
||||||
import com.hippo.ehviewer.util.tellClipboardWithToast
|
|
||||||
|
|
||||||
private const val INDEX_URL = 2
|
|
||||||
private const val INDEX_PARENT = 9
|
|
||||||
|
|
||||||
private fun GalleryDetail.generateContent() = arrayOf(
|
|
||||||
R.string.key_gid to gid.toString(),
|
|
||||||
R.string.key_token to token,
|
|
||||||
R.string.key_url to EhUrl.getGalleryDetailUrl(gid, token),
|
|
||||||
R.string.key_title to title,
|
|
||||||
R.string.key_title_jpn to titleJpn,
|
|
||||||
R.string.key_thumb to thumbUrl,
|
|
||||||
R.string.key_category to EhUtils.getCategory(category),
|
|
||||||
R.string.key_uploader to uploader,
|
|
||||||
R.string.key_posted to posted,
|
|
||||||
R.string.key_parent to parent,
|
|
||||||
R.string.key_visible to visible,
|
|
||||||
R.string.key_language to language,
|
|
||||||
R.string.key_pages to pages.toString(),
|
|
||||||
R.string.key_size to size,
|
|
||||||
R.string.key_favorite_count to favoriteCount.toString(),
|
|
||||||
R.string.key_favorited to (favoriteSlot > LOCAL_FAVORITED).toString(),
|
|
||||||
R.string.key_rating_count to ratingCount.toString(),
|
|
||||||
R.string.key_rating to rating.toString(),
|
|
||||||
R.string.key_torrents to torrentCount.toString(),
|
|
||||||
R.string.key_torrent_url to torrentUrl,
|
|
||||||
R.string.favorite_name to favoriteName,
|
|
||||||
)
|
|
||||||
|
|
||||||
class GalleryInfoBottomSheet(private val detail: GalleryDetail) : BottomSheetDialogFragment() {
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = InfoSheetBinding.inflate(layoutInflater, container, false).apply {
|
|
||||||
content.setMD3Content {
|
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val navController = remember { findNavController() }
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.gallery_info),
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
)
|
|
||||||
val data = remember(detail) { detail.generateContent() }
|
|
||||||
ProvideTextStyle(MaterialTheme.typography.labelLarge) {
|
|
||||||
LazyColumn {
|
|
||||||
itemsIndexed(data) { index, (key, content) ->
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.keyline_margin)).clickable {
|
|
||||||
if (index == INDEX_PARENT) {
|
|
||||||
if (content != null) {
|
|
||||||
navController.navWithUrl(content)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
context tellClipboardWithToast content
|
|
||||||
if (index == INDEX_URL) {
|
|
||||||
// Save it to avoid detect the gallery
|
|
||||||
Settings.clipboardTextHashCode = data[index].hashCode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Text(stringResource(id = key), modifier = Modifier.width(90.dp).padding(8.dp))
|
|
||||||
Text(content.orEmpty(), modifier = Modifier.padding(8.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.root
|
|
||||||
}
|
|
||||||
@@ -1,447 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.ui
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.assist.AssistContent
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.content.pm.verify.domain.DomainVerificationManager
|
|
||||||
import android.content.pm.verify.domain.DomainVerificationUserState
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.ImageDecoder
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.OnBackPressedCallback
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.customview.widget.Openable
|
|
||||||
import androidx.drawerlayout.widget.DrawerLayout
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
|
||||||
import androidx.navigation.ui.NavigationUI
|
|
||||||
import androidx.navigation.ui.onNavDestinationSelected2
|
|
||||||
import androidx.navigation.ui.setupWithNavController
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
import com.hippo.ehviewer.Settings
|
|
||||||
import com.hippo.ehviewer.Settings.launchPage
|
|
||||||
import com.hippo.ehviewer.client.EhUtils
|
|
||||||
import com.hippo.ehviewer.client.data.ListUrlBuilder
|
|
||||||
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser
|
|
||||||
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser
|
|
||||||
import com.hippo.ehviewer.databinding.ActivityMainBinding
|
|
||||||
import com.hippo.ehviewer.download.DownloadService
|
|
||||||
import com.hippo.ehviewer.download.downloadLocation
|
|
||||||
import com.hippo.ehviewer.image.Image
|
|
||||||
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
|
||||||
import com.hippo.ehviewer.ui.legacy.EditTextDialogBuilder
|
|
||||||
import com.hippo.ehviewer.ui.scene.BaseScene
|
|
||||||
import com.hippo.ehviewer.ui.scene.GalleryDetailScene
|
|
||||||
import com.hippo.ehviewer.ui.scene.GalleryListScene
|
|
||||||
import com.hippo.ehviewer.ui.scene.GalleryListScene.Companion.toStartArgs
|
|
||||||
import com.hippo.ehviewer.ui.scene.ProgressScene
|
|
||||||
import com.hippo.ehviewer.ui.scene.navAnimated
|
|
||||||
import com.hippo.ehviewer.ui.scene.navWithUrl
|
|
||||||
import com.hippo.ehviewer.util.AppConfig
|
|
||||||
import com.hippo.ehviewer.util.addTextToClipboard
|
|
||||||
import com.hippo.ehviewer.util.getParcelableExtraCompat
|
|
||||||
import com.hippo.ehviewer.util.getUrlFromClipboard
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import splitties.systemservices.clipboardManager
|
|
||||||
import splitties.systemservices.connectivityManager
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
|
|
||||||
class MainActivity : EhActivity() {
|
|
||||||
private lateinit var binding: ActivityMainBinding
|
|
||||||
private lateinit var navController: NavController
|
|
||||||
|
|
||||||
private fun saveImageToTempFile(uri: Uri): File? {
|
|
||||||
val src = ImageDecoder.createSource(contentResolver, uri)
|
|
||||||
val bitmap = runCatching {
|
|
||||||
ImageDecoder.decodeBitmap(src, Image.imageSearchDecoderSampleListener)
|
|
||||||
}.getOrNull() ?: return null
|
|
||||||
val temp = AppConfig.createTempFile() ?: return null
|
|
||||||
return runCatching {
|
|
||||||
FileOutputStream(temp).use { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, it) }
|
|
||||||
temp
|
|
||||||
}.getOrElse {
|
|
||||||
it.printStackTrace()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent?) {
|
|
||||||
super.onNewIntent(intent)
|
|
||||||
lifecycleScope.launchUI {
|
|
||||||
if (!handleIntent(intent)) {
|
|
||||||
if (intent != null && Intent.ACTION_VIEW == intent.action) {
|
|
||||||
if (intent.data != null) {
|
|
||||||
val url = intent.data.toString()
|
|
||||||
EditTextDialogBuilder(this@MainActivity, url, "")
|
|
||||||
.setTitle(R.string.error_cannot_parse_the_url)
|
|
||||||
.setPositiveButton(android.R.string.copy) { _: DialogInterface?, _: Int ->
|
|
||||||
this@MainActivity.addTextToClipboard(
|
|
||||||
url,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleIntent(intent: Intent?): Boolean {
|
|
||||||
when (intent?.action) {
|
|
||||||
Intent.ACTION_VIEW -> {
|
|
||||||
val uri = intent.data ?: return false
|
|
||||||
return navController.navWithUrl(uri.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent.ACTION_SEND -> {
|
|
||||||
val type = intent.type
|
|
||||||
if ("text/plain" == type) {
|
|
||||||
val builder = ListUrlBuilder()
|
|
||||||
builder.keyword = intent.getStringExtra(Intent.EXTRA_TEXT)
|
|
||||||
navController.navAnimated(
|
|
||||||
R.id.galleryListScene,
|
|
||||||
builder.toStartArgs(),
|
|
||||||
)
|
|
||||||
return true
|
|
||||||
} else if (type != null && type.startsWith("image/")) {
|
|
||||||
val uri = intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM)
|
|
||||||
if (null != uri) {
|
|
||||||
val temp = saveImageToTempFile(uri)
|
|
||||||
if (null != temp) {
|
|
||||||
val builder = ListUrlBuilder()
|
|
||||||
builder.mode = ListUrlBuilder.MODE_IMAGE_SEARCH
|
|
||||||
builder.imagePath = temp.path
|
|
||||||
builder.isUseSimilarityScan = true
|
|
||||||
navController.navAnimated(
|
|
||||||
R.id.galleryListScene,
|
|
||||||
builder.toStartArgs(),
|
|
||||||
)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DownloadService.ACTION_START_DOWNLOADSCENE -> {
|
|
||||||
val args = intent.getBundleExtra(DownloadService.ACTION_START_DOWNLOADSCENE_ARGS)
|
|
||||||
navController.navAnimated(R.id.nav_downloads, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setNavGraph() {
|
|
||||||
navController.apply {
|
|
||||||
graph = navInflater.inflate(R.navigation.nav_graph).apply {
|
|
||||||
when (
|
|
||||||
when (val value = launchPage) {
|
|
||||||
0 -> GalleryListScene.ACTION_HOMEPAGE
|
|
||||||
1 -> GalleryListScene.ACTION_SUBSCRIPTION
|
|
||||||
2 -> GalleryListScene.ACTION_WHATS_HOT
|
|
||||||
3 -> GalleryListScene.ACTION_TOP_LIST
|
|
||||||
else -> throw IllegalStateException("Unexpected value: $value")
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
GalleryListScene.ACTION_HOMEPAGE -> setStartDestination(R.id.nav_homepage)
|
|
||||||
GalleryListScene.ACTION_SUBSCRIPTION -> setStartDestination(R.id.nav_subscription)
|
|
||||||
GalleryListScene.ACTION_WHATS_HOT -> setStartDestination(R.id.nav_whats_hot)
|
|
||||||
GalleryListScene.ACTION_TOP_LIST -> setStartDestination(R.id.nav_toplist)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
|
||||||
setContentView(binding.root)
|
|
||||||
val navHostFragment =
|
|
||||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
|
||||||
navController = navHostFragment.navController
|
|
||||||
if (!EhUtils.needSignedIn()) setNavGraph()
|
|
||||||
binding.drawView.addDrawerListener(mDrawerOnBackPressedCallback)
|
|
||||||
binding.navView.setupWithNavController(navController)
|
|
||||||
|
|
||||||
// Trick: Tweak NavigationUI to disable multiple backstack
|
|
||||||
binding.navView.setNavigationItemSelectedListener {
|
|
||||||
val navigationView = binding.navView
|
|
||||||
val handled = navigationView.checkedItem?.itemId == it.itemId || onNavDestinationSelected2(it, navController)
|
|
||||||
if (handled) {
|
|
||||||
val parent = navigationView.parent
|
|
||||||
if (parent is Openable) {
|
|
||||||
parent.close()
|
|
||||||
} else {
|
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
val bottomSheetBehavior = NavigationUI.findBottomSheetBehavior(navigationView)
|
|
||||||
if (bottomSheetBehavior != null) {
|
|
||||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handled
|
|
||||||
}
|
|
||||||
// Trick End
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
if (intent.action != Intent.ACTION_MAIN) {
|
|
||||||
onNewIntent(intent)
|
|
||||||
}
|
|
||||||
checkDownloadLocation()
|
|
||||||
if (Settings.meteredNetworkWarning) {
|
|
||||||
checkMeteredNetwork()
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
if (!Settings.appLinkVerifyTip) {
|
|
||||||
try {
|
|
||||||
checkAppLinkVerify()
|
|
||||||
} catch (ignored: PackageManager.NameNotFoundException) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.S)
|
|
||||||
private fun checkAppLinkVerify() {
|
|
||||||
val manager = getSystemService(DomainVerificationManager::class.java)
|
|
||||||
val userState = manager.getDomainVerificationUserState(packageName) ?: return
|
|
||||||
var hasUnverified = false
|
|
||||||
val hostToStateMap = userState.hostToStateMap
|
|
||||||
for (key in hostToStateMap.keys) {
|
|
||||||
val stateValue = hostToStateMap[key]
|
|
||||||
if (stateValue == null || stateValue == DomainVerificationUserState.DOMAIN_STATE_VERIFIED || stateValue == DomainVerificationUserState.DOMAIN_STATE_SELECTED) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hasUnverified = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (hasUnverified) {
|
|
||||||
BaseDialogBuilder(this)
|
|
||||||
.setTitle(R.string.app_link_not_verified_title)
|
|
||||||
.setMessage(R.string.app_link_not_verified_message)
|
|
||||||
.setPositiveButton(R.string.open_settings) { _: DialogInterface?, _: Int ->
|
|
||||||
try {
|
|
||||||
val intent = Intent(
|
|
||||||
android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
|
|
||||||
Uri.parse("package:$packageName"),
|
|
||||||
)
|
|
||||||
startActivity(intent)
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
val intent = Intent(
|
|
||||||
android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
|
||||||
Uri.parse("package:$packageName"),
|
|
||||||
)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.setNeutralButton(R.string.dont_show_again) { _: DialogInterface?, _: Int ->
|
|
||||||
Settings.appLinkVerifyTip = true
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkDownloadLocation() {
|
|
||||||
val uniFile = downloadLocation
|
|
||||||
// null == uniFile for first start
|
|
||||||
if (uniFile.ensureDir()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
BaseDialogBuilder(this)
|
|
||||||
.setTitle(R.string.waring)
|
|
||||||
.setMessage(R.string.invalid_download_location)
|
|
||||||
.setPositiveButton(R.string.get_it, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkMeteredNetwork() {
|
|
||||||
if (connectivityManager.isActiveNetworkMetered) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
Snackbar.make(
|
|
||||||
binding.drawView,
|
|
||||||
R.string.metered_network_warning,
|
|
||||||
Snackbar.LENGTH_LONG,
|
|
||||||
)
|
|
||||||
.setAction(R.string.settings) {
|
|
||||||
val panelIntent =
|
|
||||||
Intent(android.provider.Settings.Panel.ACTION_INTERNET_CONNECTIVITY)
|
|
||||||
startActivity(panelIntent)
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
} else {
|
|
||||||
showTip(R.string.metered_network_warning, BaseScene.LENGTH_LONG)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val loginLauncher =
|
|
||||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
|
||||||
setNavGraph()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
if (EhUtils.needSignedIn()) {
|
|
||||||
loginLauncher.launch(Intent(this, ConfigureActivity::class.java))
|
|
||||||
}
|
|
||||||
super.onResume()
|
|
||||||
lifecycleScope.launch {
|
|
||||||
delay(300)
|
|
||||||
mDrawerOnBackPressedCallback.remove()
|
|
||||||
onBackPressedDispatcher.addCallback(mDrawerOnBackPressedCallback)
|
|
||||||
checkClipboardUrl()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun checkClipboardUrl() {
|
|
||||||
val text = clipboardManager.getUrlFromClipboard(this)
|
|
||||||
val hashCode = text?.hashCode() ?: 0
|
|
||||||
if (text != null && hashCode != 0 && Settings.clipboardTextHashCode != hashCode) {
|
|
||||||
val result1 = GalleryDetailUrlParser.parse(text, false)
|
|
||||||
var launch: (() -> Unit)? = null
|
|
||||||
if (result1 != null) {
|
|
||||||
val args = Bundle()
|
|
||||||
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GID_TOKEN)
|
|
||||||
args.putLong(GalleryDetailScene.KEY_GID, result1.gid)
|
|
||||||
args.putString(GalleryDetailScene.KEY_TOKEN, result1.token)
|
|
||||||
launch = { navController.navAnimated(R.id.galleryDetailScene, args) }
|
|
||||||
}
|
|
||||||
val result2 = GalleryPageUrlParser.parse(text, false)
|
|
||||||
if (result2 != null) {
|
|
||||||
val args = Bundle()
|
|
||||||
args.putString(ProgressScene.KEY_ACTION, ProgressScene.ACTION_GALLERY_TOKEN)
|
|
||||||
args.putLong(ProgressScene.KEY_GID, result2.gid)
|
|
||||||
args.putString(ProgressScene.KEY_PTOKEN, result2.pToken)
|
|
||||||
args.putInt(ProgressScene.KEY_PAGE, result2.page)
|
|
||||||
launch = { navController.navAnimated(R.id.progressScene, args) }
|
|
||||||
}
|
|
||||||
launch?.let {
|
|
||||||
withUIContext {
|
|
||||||
val snackbar = Snackbar.make(
|
|
||||||
binding.drawView,
|
|
||||||
R.string.clipboard_gallery_url_snack_message,
|
|
||||||
Snackbar.LENGTH_SHORT,
|
|
||||||
)
|
|
||||||
snackbar.setAction(R.string.clipboard_gallery_url_snack_action) {
|
|
||||||
it()
|
|
||||||
}
|
|
||||||
snackbar.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Settings.clipboardTextHashCode = hashCode
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addAboveSnackView(view: View) {
|
|
||||||
binding.absnacker.addAboveSnackView(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeAboveSnackView(view: View) {
|
|
||||||
binding.absnacker.removeAboveSnackView(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setDrawerLockMode(lockMode: Int, edgeGravity: Int) {
|
|
||||||
binding.drawView.setDrawerLockMode(lockMode, edgeGravity)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getDrawerLockMode(edgeGravity: Int): Int {
|
|
||||||
return binding.drawView.getDrawerLockMode(edgeGravity)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isDrawerOpen(drawerGravity: Int) = binding.drawView.isDrawerOpen(drawerGravity)
|
|
||||||
|
|
||||||
fun openDrawer(drawerGravity: Int) {
|
|
||||||
binding.drawView.openDrawer(drawerGravity)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun closeDrawer(drawerGravity: Int) {
|
|
||||||
binding.drawView.closeDrawer(drawerGravity)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toggleDrawer(drawerGravity: Int) {
|
|
||||||
if (isDrawerOpen(drawerGravity)) {
|
|
||||||
closeDrawer(drawerGravity)
|
|
||||||
} else {
|
|
||||||
openDrawer(drawerGravity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearNavCheckedItem() {
|
|
||||||
binding.navView.setCheckedItem(R.id.nav_stub)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun showTip(@StringRes id: Int, length: Int, useToast: Boolean = false) {
|
|
||||||
showTip(getString(id), length, useToast)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If activity is running, show snack bar, otherwise show toast
|
|
||||||
*/
|
|
||||||
fun showTip(message: CharSequence, length: Int, useToast: Boolean = false) {
|
|
||||||
findViewById<View>(R.id.snackbar)?.takeUnless { useToast }?.apply {
|
|
||||||
Snackbar.make(
|
|
||||||
this,
|
|
||||||
message,
|
|
||||||
if (length == BaseScene.LENGTH_LONG) Snackbar.LENGTH_LONG else Snackbar.LENGTH_SHORT,
|
|
||||||
).show()
|
|
||||||
} ?: Toast.makeText(
|
|
||||||
this,
|
|
||||||
message,
|
|
||||||
if (length == BaseScene.LENGTH_LONG) Toast.LENGTH_LONG else Toast.LENGTH_SHORT,
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
var mShareUrl: String? = null
|
|
||||||
override fun onProvideAssistContent(outContent: AssistContent?) {
|
|
||||||
super.onProvideAssistContent(outContent)
|
|
||||||
mShareUrl?.let { outContent?.webUri = Uri.parse(mShareUrl) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val mDrawerOnBackPressedCallback =
|
|
||||||
object : OnBackPressedCallback(false), DrawerLayout.DrawerListener {
|
|
||||||
val slideThreshold = 0.05
|
|
||||||
override fun handleOnBackPressed() {
|
|
||||||
binding.drawView.closeDrawers()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
|
|
||||||
isEnabled = slideOffset > slideThreshold
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDrawerOpened(drawerView: View) {}
|
|
||||||
override fun onDrawerClosed(drawerView: View) {}
|
|
||||||
override fun onDrawerStateChanged(newState: Int) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.hippo.ehviewer.ui
|
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.platform.ComposeView
|
|
||||||
import com.google.accompanist.themeadapter.material3.Mdc3Theme
|
|
||||||
|
|
||||||
// To make sure compose theme and legacy view theme are in sync, we only use Mdc3Theme
|
|
||||||
fun ComposeView.setMD3Content(content: @Composable () -> Unit) = setContent { Mdc3Theme(content = content) }
|
|
||||||
fun ComponentActivity.setMD3Content(content: @Composable () -> Unit) = setContent { Mdc3Theme(content = content) }
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package com.hippo.ehviewer.ui
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import com.hippo.ehviewer.ui.webview.WebViewScreen
|
|
||||||
|
|
||||||
class WebViewActivity : EhActivity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
val url = intent.extras?.getString(KEY_URL) ?: return
|
|
||||||
setMD3Content {
|
|
||||||
WebViewScreen(url = url) { finish() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val KEY_URL = "url"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
package com.hippo.ehviewer.ui.legacy
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout.AttachedBehavior
|
|
||||||
import androidx.core.util.ObjectsCompat
|
|
||||||
import androidx.core.view.GravityCompat
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.drawerlayout.widget.DrawerLayout
|
|
||||||
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
|
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
|
||||||
import com.drakeet.drawer.FullDraggableHelper
|
|
||||||
import com.google.android.material.snackbar.Snackbar.SnackbarLayout
|
|
||||||
import com.hippo.ehviewer.util.dp2px
|
|
||||||
|
|
||||||
class AboveSnackerLayout : FrameLayout, AttachedBehavior, FullDraggableHelper.Callback {
|
|
||||||
private val helper: FullDraggableHelper
|
|
||||||
private var drawerLayout: DrawerLayout? = null
|
|
||||||
private var mAboveSnackViewList: MutableList<View>? = null
|
|
||||||
|
|
||||||
constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) {
|
|
||||||
helper = FullDraggableHelper(context, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) :
|
|
||||||
super(context!!, attrs, defStyle) {
|
|
||||||
helper = FullDraggableHelper(context, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttachedToWindow() {
|
|
||||||
super.onAttachedToWindow()
|
|
||||||
ensureDrawerLayout()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ensureDrawerLayout() {
|
|
||||||
val parent = parent.parent
|
|
||||||
check(parent is DrawerLayout) { "This $this must be added to a DrawerLayout" }
|
|
||||||
drawerLayout = parent
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
|
||||||
return helper.onInterceptTouchEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
||||||
return helper.onTouchEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDrawerMainContainer(): View {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isDrawerOpen(gravity: Int): Boolean {
|
|
||||||
return drawerLayout!!.isDrawerOpen(gravity)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hasEnabledDrawer(gravity: Int): Boolean {
|
|
||||||
return drawerLayout!!.getDrawerLockMode(gravity) == DrawerLayout.LOCK_MODE_UNLOCKED &&
|
|
||||||
findDrawerWithGravity(gravity) != null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun offsetDrawer(gravity: Int, offset: Float) {
|
|
||||||
setDrawerToOffset(gravity, offset)
|
|
||||||
drawerLayout!!.invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun smoothOpenDrawer(gravity: Int) {
|
|
||||||
drawerLayout!!.openDrawer(gravity, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun smoothCloseDrawer(gravity: Int) {
|
|
||||||
drawerLayout!!.closeDrawer(gravity, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDrawerDragging() {
|
|
||||||
val drawerListeners = drawerListeners
|
|
||||||
if (drawerListeners != null) {
|
|
||||||
val listenerCount = drawerListeners.size
|
|
||||||
for (i in listenerCount - 1 downTo 0) {
|
|
||||||
drawerListeners[i].onDrawerStateChanged(DrawerLayout.STATE_DRAGGING)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val drawerListeners: List<DrawerListener>?
|
|
||||||
get() = try {
|
|
||||||
val listeners = DrawerLayout::class.java.getDeclaredField("mListeners")
|
|
||||||
listeners.isAccessible = true
|
|
||||||
listeners[drawerLayout] as List<DrawerListener>
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// throw to let developer know the api is changed
|
|
||||||
throw RuntimeException(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setDrawerToOffset(gravity: Int, offset: Float) {
|
|
||||||
val drawerView = findDrawerWithGravity(gravity)
|
|
||||||
val slideOffsetPercent =
|
|
||||||
(offset / ObjectsCompat.requireNonNull(drawerView).width).coerceIn(0f, 1f)
|
|
||||||
try {
|
|
||||||
val method = DrawerLayout::class.java.getDeclaredMethod(
|
|
||||||
"moveDrawerToOffset",
|
|
||||||
View::class.java,
|
|
||||||
Float::class.javaPrimitiveType,
|
|
||||||
)
|
|
||||||
method.isAccessible = true
|
|
||||||
method.invoke(drawerLayout, drawerView, slideOffsetPercent)
|
|
||||||
drawerView!!.visibility = VISIBLE
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// throw to let developer know the api is changed
|
|
||||||
throw RuntimeException(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from DrawerLayout
|
|
||||||
private fun findDrawerWithGravity(gravity: Int): View? {
|
|
||||||
val absHorizontalGravity = GravityCompat.getAbsoluteGravity(
|
|
||||||
gravity,
|
|
||||||
ViewCompat.getLayoutDirection(drawerLayout!!),
|
|
||||||
) and Gravity.HORIZONTAL_GRAVITY_MASK
|
|
||||||
val childCount = drawerLayout!!.childCount
|
|
||||||
for (i in 0 until childCount) {
|
|
||||||
val child = drawerLayout!!.getChildAt(i)
|
|
||||||
val childAbsGravity = getDrawerViewAbsoluteGravity(child)
|
|
||||||
if (childAbsGravity and Gravity.HORIZONTAL_GRAVITY_MASK == absHorizontalGravity) {
|
|
||||||
return child
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from DrawerLayout
|
|
||||||
private fun getDrawerViewAbsoluteGravity(drawerView: View): Int {
|
|
||||||
val gravity = (drawerView.layoutParams as DrawerLayout.LayoutParams).gravity
|
|
||||||
return GravityCompat.getAbsoluteGravity(
|
|
||||||
gravity,
|
|
||||||
ViewCompat.getLayoutDirection(drawerLayout!!),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addAboveSnackView(view: View) {
|
|
||||||
if (null == mAboveSnackViewList) {
|
|
||||||
mAboveSnackViewList = ArrayList()
|
|
||||||
}
|
|
||||||
mAboveSnackViewList!!.add(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeAboveSnackView(view: View) {
|
|
||||||
if (null == mAboveSnackViewList) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mAboveSnackViewList!!.remove(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
val aboveSnackViewCount: Int
|
|
||||||
get() = if (null == mAboveSnackViewList) 0 else mAboveSnackViewList!!.size
|
|
||||||
|
|
||||||
fun getAboveSnackViewAt(index: Int): View? {
|
|
||||||
return if (null == mAboveSnackViewList || index < 0 || index >= mAboveSnackViewList!!.size) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
mAboveSnackViewList!![index]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getBehavior(): Behavior {
|
|
||||||
return Behavior()
|
|
||||||
}
|
|
||||||
|
|
||||||
class Behavior : CoordinatorLayout.Behavior<AboveSnackerLayout>() {
|
|
||||||
override fun layoutDependsOn(
|
|
||||||
parent: CoordinatorLayout,
|
|
||||||
child: AboveSnackerLayout,
|
|
||||||
dependency: View,
|
|
||||||
): Boolean {
|
|
||||||
return dependency is SnackbarLayout
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDependentViewChanged(
|
|
||||||
parent: CoordinatorLayout,
|
|
||||||
child: AboveSnackerLayout,
|
|
||||||
dependency: View,
|
|
||||||
): Boolean {
|
|
||||||
for (i in 0 until child.aboveSnackViewCount) {
|
|
||||||
val view = child.getAboveSnackViewAt(i)
|
|
||||||
if (view != null) {
|
|
||||||
val translationY =
|
|
||||||
(dependency.translationY - dependency.height - dp2px(view.context, 8f))
|
|
||||||
.coerceAtMost(0f)
|
|
||||||
ViewCompat.animate(view).setInterpolator(FastOutSlowInInterpolator())
|
|
||||||
.translationY(translationY).setDuration(150).start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDependentViewRemoved(
|
|
||||||
parent: CoordinatorLayout,
|
|
||||||
child: AboveSnackerLayout,
|
|
||||||
dependency: View,
|
|
||||||
) {
|
|
||||||
for (i in 0 until child.aboveSnackViewCount) {
|
|
||||||
val view = child.getAboveSnackViewAt(i)
|
|
||||||
if (view != null) {
|
|
||||||
ViewCompat.animate(view).setInterpolator(FastOutSlowInInterpolator())
|
|
||||||
.translationY(0f).setDuration(75).start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.ui.legacy
|
|
||||||
|
|
||||||
import android.animation.ObjectAnimator
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.ColorFilter
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.Path
|
|
||||||
import android.graphics.PixelFormat
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import androidx.annotation.Keep
|
|
||||||
import com.google.android.material.math.MathUtils
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context used to get the configuration for the drawable from
|
|
||||||
*/
|
|
||||||
class AddDeleteDrawable(context: Context, color: Int) : Drawable() {
|
|
||||||
private val mPaint = Paint()
|
|
||||||
private val mPath = Path()
|
|
||||||
private val mSize: Int
|
|
||||||
private var mProgress = 0f
|
|
||||||
private var mAutoUpdateMirror = false
|
|
||||||
private var mVerticalMirror = false
|
|
||||||
|
|
||||||
init {
|
|
||||||
val resources = context.resources
|
|
||||||
mSize = resources.getDimensionPixelSize(R.dimen.add_size)
|
|
||||||
val barThickness = resources.getDimension(R.dimen.add_thickness).roundToInt().toFloat()
|
|
||||||
mPaint.color = color
|
|
||||||
mPaint.style = Paint.Style.STROKE
|
|
||||||
mPaint.strokeJoin = Paint.Join.MITER
|
|
||||||
mPaint.strokeCap = Paint.Cap.BUTT
|
|
||||||
mPaint.strokeWidth = barThickness
|
|
||||||
val halfSize = (mSize / 2).toFloat()
|
|
||||||
mPath.moveTo(0f, -halfSize)
|
|
||||||
mPath.lineTo(0f, halfSize)
|
|
||||||
mPath.moveTo(-halfSize, 0f)
|
|
||||||
mPath.lineTo(halfSize, 0f)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
|
||||||
val bounds = bounds
|
|
||||||
val canvasRotate = if (mVerticalMirror) {
|
|
||||||
MathUtils.lerp(270f, 135f, mProgress)
|
|
||||||
} else {
|
|
||||||
MathUtils.lerp(0f, 135f, mProgress)
|
|
||||||
}
|
|
||||||
canvas.save()
|
|
||||||
canvas.translate(bounds.centerX().toFloat(), bounds.centerY().toFloat())
|
|
||||||
canvas.rotate(canvasRotate)
|
|
||||||
canvas.drawPath(mPath, mPaint)
|
|
||||||
canvas.restore()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setColor(color: Int) {
|
|
||||||
mPaint.color = color
|
|
||||||
invalidateSelf()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAlpha(alpha: Int) {
|
|
||||||
mPaint.alpha = alpha
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setColorFilter(cf: ColorFilter?) {
|
|
||||||
mPaint.colorFilter = cf
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getIntrinsicHeight(): Int {
|
|
||||||
return mSize * 6 / 5
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getIntrinsicWidth(): Int {
|
|
||||||
return mSize * 6 / 5
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If set, canvas is flipped when progress reached to end and going back to start.
|
|
||||||
*/
|
|
||||||
private fun setVerticalMirror(verticalMirror: Boolean) {
|
|
||||||
mVerticalMirror = verticalMirror
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setAutoUpdateMirror(autoUpdateMirror: Boolean) {
|
|
||||||
mAutoUpdateMirror = autoUpdateMirror
|
|
||||||
}
|
|
||||||
|
|
||||||
@get:Keep
|
|
||||||
@set:Keep
|
|
||||||
var progress: Float
|
|
||||||
get() = mProgress
|
|
||||||
set(progress) {
|
|
||||||
if (mAutoUpdateMirror) {
|
|
||||||
if (progress == 1f) {
|
|
||||||
setVerticalMirror(true)
|
|
||||||
} else if (progress == 0f) {
|
|
||||||
setVerticalMirror(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mProgress = progress
|
|
||||||
invalidateSelf()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setAdd(duration: Long) {
|
|
||||||
setShape(false, duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setDelete(duration: Long) {
|
|
||||||
setShape(true, duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setShape(delete: Boolean, duration: Long) {
|
|
||||||
if (!(!delete && mProgress == 0f || delete && mProgress == 1f)) {
|
|
||||||
val endProgress = if (delete) 1f else 0f
|
|
||||||
if (duration <= 0) {
|
|
||||||
progress = endProgress
|
|
||||||
} else {
|
|
||||||
val oa = ObjectAnimator.ofFloat(this, "progress", endProgress)
|
|
||||||
oa.duration = duration
|
|
||||||
oa.setAutoCancel(true)
|
|
||||||
oa.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated(
|
|
||||||
"Deprecated in Java",
|
|
||||||
ReplaceWith("PixelFormat.TRANSLUCENT", "android.graphics.PixelFormat"),
|
|
||||||
)
|
|
||||||
override fun getOpacity(): Int {
|
|
||||||
return PixelFormat.TRANSLUCENT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.ui.legacy
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
|
||||||
import com.hippo.ehviewer.Settings
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
class AutoStaggeredGridLayoutManager(columnSize: Int, orientation: Int) :
|
|
||||||
StaggeredGridLayoutManager(1, orientation) {
|
|
||||||
private var mColumnSize = columnSize
|
|
||||||
private var mColumnSizeChanged = true
|
|
||||||
private var mStrategy = 0
|
|
||||||
var supportsPredictiveItemAnimations = true
|
|
||||||
|
|
||||||
fun setColumnSize(columnSize: Int) {
|
|
||||||
if (columnSize == mColumnSize) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mColumnSize = columnSize
|
|
||||||
mColumnSizeChanged = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setStrategy(strategy: Int) {
|
|
||||||
if (strategy == mStrategy) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mStrategy = strategy
|
|
||||||
mColumnSizeChanged = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun supportsPredictiveItemAnimations(): Boolean {
|
|
||||||
return supportsPredictiveItemAnimations && super.supportsPredictiveItemAnimations()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMeasure(
|
|
||||||
recycler: RecyclerView.Recycler,
|
|
||||||
state: RecyclerView.State,
|
|
||||||
widthSpec: Int,
|
|
||||||
heightSpec: Int,
|
|
||||||
) {
|
|
||||||
if (mColumnSizeChanged && mColumnSize > 0) {
|
|
||||||
val totalSpace = if (orientation == VERTICAL) {
|
|
||||||
check(
|
|
||||||
View.MeasureSpec.EXACTLY == View.MeasureSpec.getMode(
|
|
||||||
widthSpec,
|
|
||||||
),
|
|
||||||
) { "RecyclerView need a fixed width for AutoStaggeredGridLayoutManager" }
|
|
||||||
View.MeasureSpec.getSize(widthSpec) - paddingRight - paddingLeft
|
|
||||||
} else {
|
|
||||||
check(
|
|
||||||
View.MeasureSpec.EXACTLY == View.MeasureSpec.getMode(
|
|
||||||
heightSpec,
|
|
||||||
),
|
|
||||||
) { "RecyclerView need a fixed height for AutoStaggeredGridLayoutManager" }
|
|
||||||
View.MeasureSpec.getSize(heightSpec) - paddingTop - paddingBottom
|
|
||||||
}
|
|
||||||
val spanCount = when (mStrategy) {
|
|
||||||
STRATEGY_MIN_SIZE -> getSpanCountForMinSize(
|
|
||||||
totalSpace,
|
|
||||||
mColumnSize,
|
|
||||||
)
|
|
||||||
|
|
||||||
STRATEGY_SUITABLE_SIZE -> getSpanCountForSuitableSize(
|
|
||||||
totalSpace,
|
|
||||||
mColumnSize,
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> getSpanCountForMinSize(
|
|
||||||
totalSpace,
|
|
||||||
mColumnSize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
setSpanCount(spanCount)
|
|
||||||
mColumnSizeChanged = false
|
|
||||||
}
|
|
||||||
super.onMeasure(recycler, state, widthSpec, heightSpec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const val STRATEGY_MIN_SIZE = 0
|
|
||||||
const val STRATEGY_SUITABLE_SIZE = 1
|
|
||||||
fun getSpanCountForSuitableSize(total: Int, single: Int): Int {
|
|
||||||
return (total / single.toFloat()).roundToInt().coerceAtLeast(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun calculateSuitableSpanCount(): Int {
|
|
||||||
val totalSpace = LocalConfiguration.current.screenWidthDp
|
|
||||||
return getSpanCountForSuitableSize(totalSpace, Settings.thumbSizeDp)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSpanCountForMinSize(total: Int, single: Int): Int {
|
|
||||||
return (total / single).coerceAtLeast(1)
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.hippo.ehviewer.ui.legacy
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
|
|
||||||
open class BaseDialogBuilder(
|
|
||||||
context: Context,
|
|
||||||
) : MaterialAlertDialogBuilder(context)
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.ui.legacy
|
|
||||||
|
|
||||||
import android.animation.Animator
|
|
||||||
import android.animation.AnimatorListenerAdapter
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
|
|
||||||
class BringOutTransition(contentLayout: FrameLayout, mSearchLayout: SearchLayout) :
|
|
||||||
ViewTransition(contentLayout, mSearchLayout) {
|
|
||||||
override fun startAnimations(hiddenView: View, shownView: View) {
|
|
||||||
mAnimator1 = hiddenView.animate().alpha(0f).scaleY(0.7f).scaleX(0.7f).apply {
|
|
||||||
setDuration(ANIMATE_TIME).setListener(object : AnimatorListenerAdapter() {
|
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
|
||||||
hiddenView.visibility = View.GONE
|
|
||||||
mAnimator1 = null
|
|
||||||
}
|
|
||||||
}).start()
|
|
||||||
}
|
|
||||||
shownView.alpha = 0f
|
|
||||||
shownView.scaleX = 0.7f
|
|
||||||
shownView.scaleY = 0.7f
|
|
||||||
shownView.visibility = View.VISIBLE
|
|
||||||
mAnimator2 = shownView.animate().alpha(1f).scaleX(1f).scaleY(1f).apply {
|
|
||||||
setDuration(ANIMATE_TIME).setListener(object : AnimatorListenerAdapter() {
|
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
|
||||||
mAnimator2 = null
|
|
||||||
}
|
|
||||||
}).start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.ui.legacy
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.widget.CheckBox
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
|
|
||||||
class CheckBoxDialogBuilder(
|
|
||||||
context: Context,
|
|
||||||
message: String?,
|
|
||||||
checkText: String?,
|
|
||||||
checked: Boolean,
|
|
||||||
) : BaseDialogBuilder(
|
|
||||||
context,
|
|
||||||
) {
|
|
||||||
private val mCheckBox: CheckBox
|
|
||||||
val isChecked: Boolean
|
|
||||||
get() = mCheckBox.isChecked
|
|
||||||
|
|
||||||
init {
|
|
||||||
val view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_checkbox_builder, null)
|
|
||||||
setView(view)
|
|
||||||
setMessage(message)
|
|
||||||
mCheckBox = view.findViewById(R.id.checkbox)
|
|
||||||
mCheckBox.text = checkText
|
|
||||||
mCheckBox.isChecked = checked
|
|
||||||
view.setOnClickListener { mCheckBox.toggle() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.ui.legacy
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
|
|
||||||
class DrawerView @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
) : FrameLayout(context, attrs) {
|
|
||||||
private var mMaxWidth = 0
|
|
||||||
|
|
||||||
init {
|
|
||||||
val a = context.obtainStyledAttributes(attrs, SIZE_ATTRS)
|
|
||||||
mMaxWidth = a.getDimensionPixelOffset(0, 0)
|
|
||||||
a.recycle()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
|
|
||||||
var widthSpec = widthSpec
|
|
||||||
when (MeasureSpec.getMode(widthSpec)) {
|
|
||||||
MeasureSpec.EXACTLY -> {}
|
|
||||||
MeasureSpec.AT_MOST -> widthSpec = MeasureSpec.makeMeasureSpec(
|
|
||||||
MeasureSpec.getSize(widthSpec).coerceAtMost(mMaxWidth),
|
|
||||||
MeasureSpec.EXACTLY,
|
|
||||||
)
|
|
||||||
|
|
||||||
MeasureSpec.UNSPECIFIED ->
|
|
||||||
widthSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, MeasureSpec.EXACTLY)
|
|
||||||
}
|
|
||||||
// Let super sort out the height
|
|
||||||
super.onMeasure(widthSpec, heightSpec)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val SIZE_ATTRS = intArrayOf(android.R.attr.maxWidth)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.ui.legacy
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.TextView.OnEditorActionListener
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
|
|
||||||
class EditTextDialogBuilder(
|
|
||||||
context: Context,
|
|
||||||
text: String?,
|
|
||||||
hint: String?,
|
|
||||||
) : BaseDialogBuilder(
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
OnEditorActionListener {
|
|
||||||
private val mTextInputLayout: TextInputLayout
|
|
||||||
val editText: EditText
|
|
||||||
private var mDialog: AlertDialog? = null
|
|
||||||
val text: String
|
|
||||||
get() = editText.text.toString()
|
|
||||||
|
|
||||||
fun setError(error: CharSequence?) {
|
|
||||||
mTextInputLayout.error = error
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun create(): AlertDialog {
|
|
||||||
mDialog = super.create()
|
|
||||||
return mDialog as AlertDialog
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onEditorAction(v: TextView?, p1: Int, event: KeyEvent?): Boolean {
|
|
||||||
return if (mDialog != null) {
|
|
||||||
val button = mDialog!!.getButton(DialogInterface.BUTTON_POSITIVE)
|
|
||||||
button?.performClick()
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
val view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_edittext_builder, null)
|
|
||||||
setView(view)
|
|
||||||
mTextInputLayout = view as TextInputLayout
|
|
||||||
editText = view.findViewById(R.id.edit_text)
|
|
||||||
editText.setText(text)
|
|
||||||
editText.setSelection(editText.text.length)
|
|
||||||
editText.setOnEditorActionListener(this)
|
|
||||||
mTextInputLayout.hint = hint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,432 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.ui.legacy
|
|
||||||
|
|
||||||
import android.animation.Animator
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.Parcelable
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.animation.Interpolator
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.OnBackPressedCallback
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
import com.hippo.ehviewer.util.AnimationUtils
|
|
||||||
import com.hippo.ehviewer.util.SimpleAnimatorListener
|
|
||||||
import com.hippo.ehviewer.util.getParcelableCompat
|
|
||||||
|
|
||||||
class FabLayout @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
defStyleAttr: Int = 0,
|
|
||||||
) : ViewGroup(context, attrs, defStyleAttr), View.OnClickListener {
|
|
||||||
private var mFabSize = 0
|
|
||||||
private var mFabMiniSize = 0
|
|
||||||
private var mIntervalPrimary = 0
|
|
||||||
private var mIntervalSecondary = 0
|
|
||||||
private var mExpanded = true
|
|
||||||
private var mAutoCancel = true
|
|
||||||
private var mHidePrimaryFab = false
|
|
||||||
private var mMainFabCenterY = -1f
|
|
||||||
private var mOnExpandListeners = arrayListOf<OnExpandListener>()
|
|
||||||
private var mOnClickFabListener: OnClickFabListener? = null
|
|
||||||
|
|
||||||
private val mOnBackPressedCallback = object : OnBackPressedCallback(false), OnExpandListener {
|
|
||||||
override fun handleOnBackPressed() {
|
|
||||||
isExpanded = false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onExpand(expanded: Boolean) {
|
|
||||||
isEnabled = expanded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttachedToWindow() {
|
|
||||||
super.onAttachedToWindow()
|
|
||||||
(context as ComponentActivity).onBackPressedDispatcher.addCallback(mOnBackPressedCallback)
|
|
||||||
mOnExpandListeners.add(mOnBackPressedCallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromWindow() {
|
|
||||||
super.onDetachedFromWindow()
|
|
||||||
mOnBackPressedCallback.remove()
|
|
||||||
mOnExpandListeners.remove(mOnBackPressedCallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
isSoundEffectsEnabled = false
|
|
||||||
clipToPadding = false
|
|
||||||
mFabSize = context.resources.getDimensionPixelOffset(R.dimen.fab_size)
|
|
||||||
mFabMiniSize = context.resources.getDimensionPixelOffset(R.dimen.fab_min_size)
|
|
||||||
mIntervalPrimary =
|
|
||||||
context.resources.getDimensionPixelOffset(R.dimen.fab_layout_primary_margin)
|
|
||||||
mIntervalSecondary =
|
|
||||||
context.resources.getDimensionPixelOffset(R.dimen.fab_layout_secondary_margin)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addView(child: View, index: Int, params: LayoutParams) {
|
|
||||||
check(child is FloatingActionButton) { "FloatingActionBarLayout should only " + "contain FloatingActionButton, but try to add " + child.javaClass.name }
|
|
||||||
super.addView(child, index, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
val primaryFab: FloatingActionButton?
|
|
||||||
get() = getChildAt(childCount - 1) as? FloatingActionButton
|
|
||||||
|
|
||||||
private val secondaryFabCount: Int
|
|
||||||
get() = 0.coerceAtLeast(childCount - 1)
|
|
||||||
|
|
||||||
private fun getSecondaryFabAt(index: Int): FloatingActionButton? {
|
|
||||||
return if (index < 0 || index >= secondaryFabCount) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
getChildAt(index) as FloatingActionButton
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSecondaryFabVisibilityAt(index: Int, visible: Boolean) {
|
|
||||||
getSecondaryFabAt(index)?.run {
|
|
||||||
if (visible && visibility == GONE) {
|
|
||||||
animate().cancel()
|
|
||||||
visibility = if (mExpanded) VISIBLE else INVISIBLE
|
|
||||||
} else if (!visible && visibility != GONE) {
|
|
||||||
animate().cancel()
|
|
||||||
visibility = GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getChildMeasureSpec(parentMeasureSpec: Int): Int {
|
|
||||||
val parentMode = MeasureSpec.getMode(parentMeasureSpec)
|
|
||||||
val parentSize = MeasureSpec.getSize(parentMeasureSpec)
|
|
||||||
val childMode: Int
|
|
||||||
val childSize: Int
|
|
||||||
when (parentMode) {
|
|
||||||
MeasureSpec.AT_MOST, MeasureSpec.EXACTLY -> {
|
|
||||||
childMode = MeasureSpec.AT_MOST
|
|
||||||
childSize = parentSize
|
|
||||||
}
|
|
||||||
|
|
||||||
MeasureSpec.UNSPECIFIED -> {
|
|
||||||
childMode = MeasureSpec.UNSPECIFIED
|
|
||||||
childSize = parentSize
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
childMode = MeasureSpec.AT_MOST
|
|
||||||
childSize = parentSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MeasureSpec.makeMeasureSpec(childSize, childMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
||||||
val childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec)
|
|
||||||
val childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec)
|
|
||||||
measureChildren(childWidthMeasureSpec, childHeightMeasureSpec)
|
|
||||||
var maxWidth = 0
|
|
||||||
var maxHeight = 0
|
|
||||||
val count = childCount
|
|
||||||
for (i in 0 until count) {
|
|
||||||
val child = getChildAt(i)
|
|
||||||
if (child.visibility == GONE) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
maxWidth = maxWidth.coerceAtLeast(child.measuredWidth)
|
|
||||||
maxHeight += child.measuredHeight
|
|
||||||
}
|
|
||||||
maxWidth += paddingLeft + paddingRight
|
|
||||||
maxHeight += paddingTop + paddingBottom
|
|
||||||
maxHeight = maxHeight.coerceAtLeast(suggestedMinimumHeight)
|
|
||||||
maxWidth = maxWidth.coerceAtLeast(suggestedMinimumWidth)
|
|
||||||
setMeasuredDimension(
|
|
||||||
resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
|
|
||||||
resolveSizeAndState(maxHeight, heightMeasureSpec, 0),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
|
||||||
var centerX = 0
|
|
||||||
var bottom = measuredHeight - paddingBottom
|
|
||||||
val count = childCount
|
|
||||||
var i = count
|
|
||||||
while (--i >= 0) {
|
|
||||||
val child = getChildAt(i)
|
|
||||||
if (child.visibility == GONE) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
val childWidth = child.measuredWidth
|
|
||||||
val childHeight = child.measuredHeight
|
|
||||||
var layoutBottom: Int
|
|
||||||
var layoutRight: Int
|
|
||||||
if (i == count - 1) {
|
|
||||||
layoutBottom = bottom + (childHeight - mFabSize) / 2
|
|
||||||
layoutRight = measuredWidth - paddingRight + (childWidth - mFabSize) / 2
|
|
||||||
bottom -= mFabSize + mIntervalPrimary
|
|
||||||
centerX = layoutRight - childWidth / 2
|
|
||||||
mMainFabCenterY = layoutBottom - childHeight / 2f
|
|
||||||
} else {
|
|
||||||
layoutBottom = bottom + (childHeight - mFabMiniSize) / 2
|
|
||||||
layoutRight = centerX + childWidth / 2
|
|
||||||
bottom -= mFabMiniSize + mIntervalSecondary
|
|
||||||
}
|
|
||||||
child.layout(
|
|
||||||
layoutRight - childWidth,
|
|
||||||
layoutBottom - childHeight,
|
|
||||||
layoutRight,
|
|
||||||
layoutBottom,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addOnExpandListener(listener: OnExpandListener) {
|
|
||||||
mOnExpandListeners.add(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setOnClickFabListener(listener: OnClickFabListener?) {
|
|
||||||
mOnClickFabListener = listener
|
|
||||||
if (listener != null) {
|
|
||||||
var i = 0
|
|
||||||
val n = childCount
|
|
||||||
while (i < n) {
|
|
||||||
getChildAt(i).setOnClickListener(this)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var i = 0
|
|
||||||
val n = childCount
|
|
||||||
while (i < n) {
|
|
||||||
getChildAt(i).isClickable = false
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setHidePrimaryFab(hidePrimaryFab: Boolean) {
|
|
||||||
if (mHidePrimaryFab != hidePrimaryFab) {
|
|
||||||
mHidePrimaryFab = hidePrimaryFab
|
|
||||||
val expanded = mExpanded
|
|
||||||
val count = childCount
|
|
||||||
if (!expanded && count > 0) {
|
|
||||||
getChildAt(count - 1).visibility = if (hidePrimaryFab) INVISIBLE else VISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setAutoCancel(autoCancel: Boolean) {
|
|
||||||
if (mAutoCancel != autoCancel) {
|
|
||||||
mAutoCancel = autoCancel
|
|
||||||
if (mExpanded) {
|
|
||||||
if (autoCancel) {
|
|
||||||
setOnClickListener(this)
|
|
||||||
} else {
|
|
||||||
isClickable = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toggle() {
|
|
||||||
isExpanded = !mExpanded
|
|
||||||
}
|
|
||||||
|
|
||||||
var isExpanded: Boolean
|
|
||||||
get() = mExpanded
|
|
||||||
set(expanded) {
|
|
||||||
setExpanded(expanded, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setExpanded(expanded: Boolean, animation: Boolean) {
|
|
||||||
if (mExpanded != expanded) {
|
|
||||||
mExpanded = expanded
|
|
||||||
if (mAutoCancel) {
|
|
||||||
if (expanded) {
|
|
||||||
setOnClickListener(this)
|
|
||||||
} else {
|
|
||||||
isClickable = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val count = childCount
|
|
||||||
if (count > 0) {
|
|
||||||
if (mMainFabCenterY == -1f || !animation) {
|
|
||||||
// It is before first onLayout
|
|
||||||
val checkCount = if (mHidePrimaryFab) count else count - 1
|
|
||||||
for (i in 0 until checkCount) {
|
|
||||||
val child = getChildAt(i)
|
|
||||||
if (child.visibility == GONE) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
child.visibility = if (expanded) VISIBLE else INVISIBLE
|
|
||||||
if (expanded) {
|
|
||||||
child.alpha = 1f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (mHidePrimaryFab) {
|
|
||||||
setPrimaryFabAnimation(getChildAt(count - 1), expanded, !expanded)
|
|
||||||
}
|
|
||||||
for (i in 0 until count - 1) {
|
|
||||||
val child = getChildAt(i)
|
|
||||||
if (child.visibility == GONE) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
setSecondaryFabAnimation(child, expanded, expanded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mOnExpandListeners.forEach { it.onExpand(expanded) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setPrimaryFabAnimation(child: View, expanded: Boolean, delay: Boolean) {
|
|
||||||
val startRotation: Float
|
|
||||||
val endRotation: Float
|
|
||||||
val startScale: Float
|
|
||||||
val endScale: Float
|
|
||||||
val interpolator: Interpolator
|
|
||||||
if (expanded) {
|
|
||||||
startRotation = -45.0f
|
|
||||||
endRotation = 0.0f
|
|
||||||
startScale = 0.0f
|
|
||||||
endScale = 1.0f
|
|
||||||
interpolator = AnimationUtils.FAST_SLOW_INTERPOLATOR
|
|
||||||
} else {
|
|
||||||
startRotation = 0.0f
|
|
||||||
endRotation = 0.0f
|
|
||||||
startScale = 1.0f
|
|
||||||
endScale = 0.0f
|
|
||||||
interpolator = AnimationUtils.SLOW_FAST_INTERPOLATOR
|
|
||||||
}
|
|
||||||
child.scaleX = startScale
|
|
||||||
child.scaleY = startScale
|
|
||||||
child.rotation = startRotation
|
|
||||||
child.animate()
|
|
||||||
.scaleX(endScale)
|
|
||||||
.scaleY(endScale)
|
|
||||||
.rotation(endRotation)
|
|
||||||
.setStartDelay(if (delay) ANIMATE_TIME else 0L)
|
|
||||||
.setDuration(ANIMATE_TIME)
|
|
||||||
.setInterpolator(interpolator)
|
|
||||||
.setListener(object : SimpleAnimatorListener() {
|
|
||||||
override fun onAnimationStart(animation: Animator) {
|
|
||||||
if (expanded) {
|
|
||||||
child.visibility = VISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
|
||||||
if (!expanded) {
|
|
||||||
child.visibility = INVISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setSecondaryFabAnimation(child: View, expanded: Boolean, delay: Boolean) {
|
|
||||||
val startTranslationY: Float
|
|
||||||
val endTranslationY: Float
|
|
||||||
val startAlpha: Float
|
|
||||||
val endAlpha: Float
|
|
||||||
val interpolator: Interpolator
|
|
||||||
if (expanded) {
|
|
||||||
startTranslationY = mMainFabCenterY - child.top - child.height / 2
|
|
||||||
endTranslationY = 0f
|
|
||||||
startAlpha = 0f
|
|
||||||
endAlpha = 1f
|
|
||||||
interpolator = AnimationUtils.FAST_SLOW_INTERPOLATOR
|
|
||||||
} else {
|
|
||||||
startTranslationY = 0f
|
|
||||||
endTranslationY = mMainFabCenterY - child.top - child.height / 2
|
|
||||||
startAlpha = 1f
|
|
||||||
endAlpha = 0f
|
|
||||||
interpolator = AnimationUtils.SLOW_FAST_INTERPOLATOR
|
|
||||||
}
|
|
||||||
child.alpha = startAlpha
|
|
||||||
child.translationY = startTranslationY
|
|
||||||
child.animate()
|
|
||||||
.alpha(endAlpha)
|
|
||||||
.translationY(endTranslationY)
|
|
||||||
.setStartDelay(if (delay) ANIMATE_TIME else 0L)
|
|
||||||
.setDuration(ANIMATE_TIME)
|
|
||||||
.setInterpolator(interpolator)
|
|
||||||
.setListener(object : SimpleAnimatorListener() {
|
|
||||||
override fun onAnimationStart(animation: Animator) {
|
|
||||||
if (expanded) {
|
|
||||||
child.visibility = VISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
|
||||||
if (!expanded) {
|
|
||||||
child.visibility = INVISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
|
||||||
if (this === v) {
|
|
||||||
isExpanded = false
|
|
||||||
} else {
|
|
||||||
mOnClickFabListener?.let {
|
|
||||||
val position = indexOfChild(v)
|
|
||||||
if (position == childCount - 1) {
|
|
||||||
it.onClickPrimaryFab(this, v as FloatingActionButton)
|
|
||||||
} else if (position >= 0 && mExpanded) {
|
|
||||||
it.onClickSecondaryFab(this, v as FloatingActionButton, position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispatchSetPressed(pressed: Boolean) {
|
|
||||||
// Don't dispatch it to children
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(): Parcelable {
|
|
||||||
val state = Bundle()
|
|
||||||
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState())
|
|
||||||
state.putBoolean(STATE_KEY_AUTO_CANCEL, mAutoCancel)
|
|
||||||
state.putBoolean(STATE_KEY_EXPANDED, mExpanded)
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRestoreInstanceState(state: Parcelable) {
|
|
||||||
if (state is Bundle) {
|
|
||||||
super.onRestoreInstanceState(state.getParcelableCompat(STATE_KEY_SUPER))
|
|
||||||
setAutoCancel(state.getBoolean(STATE_KEY_AUTO_CANCEL))
|
|
||||||
setExpanded(state.getBoolean(STATE_KEY_EXPANDED), false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun interface OnExpandListener {
|
|
||||||
fun onExpand(expanded: Boolean)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OnClickFabListener {
|
|
||||||
fun onClickPrimaryFab(view: FabLayout, fab: FloatingActionButton)
|
|
||||||
fun onClickSecondaryFab(view: FabLayout, fab: FloatingActionButton, position: Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val ANIMATE_TIME = 300L
|
|
||||||
private const val STATE_KEY_SUPER = "super"
|
|
||||||
private const val STATE_KEY_AUTO_CANCEL = "auto_cancel"
|
|
||||||
private const val STATE_KEY_EXPANDED = "expanded"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,336 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.ui.legacy
|
|
||||||
|
|
||||||
import android.animation.Animator
|
|
||||||
import android.animation.ObjectAnimator
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewConfiguration
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
|
|
||||||
import com.google.android.material.math.MathUtils.dist
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
import com.hippo.ehviewer.util.AnimationUtils
|
|
||||||
import com.hippo.ehviewer.util.SimpleAnimatorListener
|
|
||||||
import com.hippo.ehviewer.util.SimpleHandler
|
|
||||||
import com.hippo.ehviewer.util.dp2px
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
class FastScroller : View {
|
|
||||||
private var mDraggable = false
|
|
||||||
private var mMinHandlerHeight = 0
|
|
||||||
private var mRecyclerView: RecyclerView? = null
|
|
||||||
private var mOnScrollChangeListener: RecyclerView.OnScrollListener? = null
|
|
||||||
private var mAdapter: RecyclerView.Adapter<*>? = null
|
|
||||||
private var mAdapterDataObserver: AdapterDataObserver? = null
|
|
||||||
private var mHandler: Drawable? = null
|
|
||||||
private var mHandlerOffset = INVALID
|
|
||||||
private var mHandlerHeight = INVALID
|
|
||||||
private var mDownX = INVALID.toFloat()
|
|
||||||
private var mDownY = INVALID.toFloat()
|
|
||||||
private var mLastMotionY = INVALID.toFloat()
|
|
||||||
private var mDragged = false
|
|
||||||
private var mCantDrag = false
|
|
||||||
private var mTouchSlop = 0
|
|
||||||
private var mListener: OnDragHandlerListener? = null
|
|
||||||
private var mShowAnimator: ObjectAnimator? = null
|
|
||||||
private var mHideAnimator: ObjectAnimator? = null
|
|
||||||
private val mHideRunnable = Runnable { mHideAnimator!!.start() }
|
|
||||||
|
|
||||||
constructor(context: Context) : super(context) {
|
|
||||||
init(context, null, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
|
|
||||||
init(context, attrs, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
|
||||||
context,
|
|
||||||
attrs,
|
|
||||||
defStyleAttr,
|
|
||||||
) {
|
|
||||||
init(context, attrs, defStyleAttr)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun init(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
|
|
||||||
val a = context.obtainStyledAttributes(attrs, R.styleable.FastScroller, defStyleAttr, 0)
|
|
||||||
mHandler = a.getDrawable(R.styleable.FastScroller_handler)
|
|
||||||
mDraggable = a.getBoolean(R.styleable.FastScroller_draggable, true)
|
|
||||||
a.recycle()
|
|
||||||
alpha = 0.0f
|
|
||||||
visibility = INVISIBLE
|
|
||||||
mMinHandlerHeight = dp2px(context, MIN_HANDLER_HEIGHT_DP.toFloat())
|
|
||||||
mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
|
||||||
mShowAnimator = ObjectAnimator.ofFloat(this, "alpha", 1.0f).apply {
|
|
||||||
interpolator = AnimationUtils.FAST_SLOW_INTERPOLATOR
|
|
||||||
duration = SCROLL_BAR_FADE_DURATION.toLong()
|
|
||||||
}
|
|
||||||
mHideAnimator = ObjectAnimator.ofFloat(this, "alpha", 0.0f).apply {
|
|
||||||
interpolator = AnimationUtils.SLOW_FAST_INTERPOLATOR
|
|
||||||
duration = SCROLL_BAR_FADE_DURATION.toLong()
|
|
||||||
addListener(object : SimpleAnimatorListener() {
|
|
||||||
private var mCancel = false
|
|
||||||
override fun onAnimationCancel(animation: Animator) {
|
|
||||||
mCancel = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
|
||||||
if (mCancel) {
|
|
||||||
mCancel = false
|
|
||||||
} else {
|
|
||||||
visibility = INVISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setOnDragHandlerListener(listener: OnDragHandlerListener?) {
|
|
||||||
mListener = listener
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updatePosition(show: Boolean) {
|
|
||||||
if (mRecyclerView == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val paddingTop = paddingTop
|
|
||||||
val paddingBottom = paddingBottom
|
|
||||||
val height = height - paddingTop - paddingBottom
|
|
||||||
val offset = mRecyclerView!!.computeVerticalScrollOffset()
|
|
||||||
val extent = mRecyclerView!!.computeVerticalScrollExtent()
|
|
||||||
val range = mRecyclerView!!.computeVerticalScrollRange()
|
|
||||||
if (height <= 0 || extent >= range || extent <= 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var endOffset = height * offset / range
|
|
||||||
var endHeight = height * extent / range
|
|
||||||
endHeight = endHeight.coerceAtLeast(mMinHandlerHeight)
|
|
||||||
endOffset = endOffset.coerceAtMost(height - endHeight)
|
|
||||||
mHandlerOffset = endOffset + paddingTop
|
|
||||||
mHandlerHeight = endHeight
|
|
||||||
if (show) {
|
|
||||||
if (mHideAnimator!!.isRunning) {
|
|
||||||
mHideAnimator!!.cancel()
|
|
||||||
mShowAnimator!!.start()
|
|
||||||
} else if (visibility != VISIBLE && !mShowAnimator!!.isRunning) {
|
|
||||||
visibility = VISIBLE
|
|
||||||
mShowAnimator!!.start()
|
|
||||||
}
|
|
||||||
SimpleHandler.removeCallbacks(mHideRunnable)
|
|
||||||
if (!mDragged) {
|
|
||||||
SimpleHandler.postDelayed(mHideRunnable, SCROLL_BAR_DELAY.toLong())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setHandlerDrawable(drawable: Drawable?) {
|
|
||||||
mHandler = drawable
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
var isDraggable: Boolean
|
|
||||||
get() = mDraggable
|
|
||||||
set(draggable) {
|
|
||||||
mDraggable = draggable
|
|
||||||
if (mDragged) {
|
|
||||||
mDragged = false
|
|
||||||
}
|
|
||||||
SimpleHandler.removeCallbacks(mHideRunnable)
|
|
||||||
mHideRunnable.run()
|
|
||||||
}
|
|
||||||
val isAttached: Boolean
|
|
||||||
get() = mRecyclerView != null
|
|
||||||
|
|
||||||
fun attachToRecyclerView(recyclerView: RecyclerView?) {
|
|
||||||
if (recyclerView == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
check(mRecyclerView == null) {
|
|
||||||
"The FastScroller is already attached to a RecyclerView, " +
|
|
||||||
"call detachedFromRecyclerView first"
|
|
||||||
}
|
|
||||||
mRecyclerView = recyclerView
|
|
||||||
mOnScrollChangeListener = object : RecyclerView.OnScrollListener() {
|
|
||||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {}
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
||||||
updatePosition(true)
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
recyclerView.addOnScrollListener(mOnScrollChangeListener!!)
|
|
||||||
mAdapter = recyclerView.adapter
|
|
||||||
if (mAdapter != null) {
|
|
||||||
mAdapterDataObserver = object : AdapterDataObserver() {
|
|
||||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
|
|
||||||
super.onItemRangeChanged(positionStart, itemCount)
|
|
||||||
updatePosition(false)
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
|
|
||||||
super.onItemRangeChanged(positionStart, itemCount, payload)
|
|
||||||
updatePosition(false)
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
|
||||||
super.onItemRangeInserted(positionStart, itemCount)
|
|
||||||
updatePosition(false)
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
|
||||||
super.onItemRangeRemoved(positionStart, itemCount)
|
|
||||||
updatePosition(false)
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
|
|
||||||
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
|
|
||||||
updatePosition(false)
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mAdapter!!.registerAdapterDataObserver(mAdapterDataObserver!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun detachedFromRecyclerView() {
|
|
||||||
if (mRecyclerView != null && mOnScrollChangeListener != null) {
|
|
||||||
mRecyclerView!!.removeOnScrollListener(mOnScrollChangeListener!!)
|
|
||||||
}
|
|
||||||
mRecyclerView = null
|
|
||||||
mOnScrollChangeListener = null
|
|
||||||
if (mAdapter != null && mAdapterDataObserver != null) {
|
|
||||||
mAdapter!!.unregisterAdapterDataObserver(mAdapterDataObserver!!)
|
|
||||||
}
|
|
||||||
mAdapter = null
|
|
||||||
mAdapterDataObserver = null
|
|
||||||
alpha = 0.0f
|
|
||||||
visibility = INVISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
|
||||||
if (mRecyclerView == null || mHandler == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (mHandlerHeight == INVALID) {
|
|
||||||
updatePosition(false)
|
|
||||||
}
|
|
||||||
if (mHandlerHeight == INVALID) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val paddingLeft = paddingLeft
|
|
||||||
val saved = canvas.save()
|
|
||||||
canvas.translate(paddingLeft.toFloat(), mHandlerOffset.toFloat())
|
|
||||||
mHandler!!.setBounds(0, 0, width - paddingLeft - paddingRight, mHandlerHeight)
|
|
||||||
mHandler!!.draw(canvas)
|
|
||||||
canvas.restoreToCount(saved)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
||||||
if (!mDraggable || visibility != VISIBLE || mRecyclerView == null || mHandlerHeight == INVALID) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val action = event.action
|
|
||||||
if (action == MotionEvent.ACTION_DOWN) {
|
|
||||||
mCantDrag = false
|
|
||||||
}
|
|
||||||
if (mCantDrag) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
when (action) {
|
|
||||||
MotionEvent.ACTION_DOWN -> {
|
|
||||||
mDragged = false
|
|
||||||
mDownX = event.x
|
|
||||||
mDownY = event.y
|
|
||||||
if (mDownY < mHandlerOffset || mDownY > mHandlerOffset + mHandlerHeight) {
|
|
||||||
mCantDrag = true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
|
||||||
if (!mDragged) {
|
|
||||||
val x = event.x
|
|
||||||
val y = event.y
|
|
||||||
// Check touch slop
|
|
||||||
if (dist(x, y, mDownX, mDownY) < mTouchSlop) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (abs(x - mDownX) > abs(y - mDownY) || y < mHandlerOffset || y > mHandlerOffset + mHandlerHeight) {
|
|
||||||
mCantDrag = true
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
mDragged = true
|
|
||||||
SimpleHandler.removeCallbacks(mHideRunnable)
|
|
||||||
// Update mLastMotionY
|
|
||||||
mLastMotionY =
|
|
||||||
if (mDownY < mHandlerOffset || mDownY >= mHandlerOffset + mHandlerHeight) {
|
|
||||||
// the point out of handler, make the point in handler center
|
|
||||||
(mHandlerOffset + mHandlerHeight / 2).toFloat()
|
|
||||||
} else {
|
|
||||||
mDownY
|
|
||||||
}
|
|
||||||
// Notify
|
|
||||||
if (mListener != null) {
|
|
||||||
mListener!!.onStartDragHandler()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val range = mRecyclerView!!.computeVerticalScrollRange()
|
|
||||||
if (range <= 0) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
val y = event.y
|
|
||||||
val scroll =
|
|
||||||
(range * (y - mLastMotionY) / (height - paddingTop - paddingBottom)).toInt()
|
|
||||||
mRecyclerView!!.scrollBy(0, scroll)
|
|
||||||
mLastMotionY = y
|
|
||||||
}
|
|
||||||
|
|
||||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
|
||||||
// Notify
|
|
||||||
if (mDragged && mListener != null) {
|
|
||||||
mListener!!.onEndDragHandler()
|
|
||||||
}
|
|
||||||
mDragged = false
|
|
||||||
SimpleHandler.postDelayed(mHideRunnable, SCROLL_BAR_DELAY.toLong())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OnDragHandlerListener {
|
|
||||||
fun onStartDragHandler()
|
|
||||||
fun onEndDragHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val INVALID = -1
|
|
||||||
private const val SCROLL_BAR_FADE_DURATION = 500
|
|
||||||
private const val SCROLL_BAR_DELAY = 1000
|
|
||||||
private const val MIN_HANDLER_HEIGHT_DP = 32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.ui.legacy
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.widget.RatingBar
|
|
||||||
import android.widget.RatingBar.OnRatingBarChangeListener
|
|
||||||
import androidx.appcompat.widget.AppCompatRatingBar
|
|
||||||
|
|
||||||
class GalleryRatingBar @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
) : AppCompatRatingBar(context, attrs), OnRatingBarChangeListener {
|
|
||||||
private var mListener: OnUserRateListener? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
onRatingBarChangeListener = this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
|
||||||
super.onDraw(canvas)
|
|
||||||
if (mListener != null) {
|
|
||||||
mListener!!.onUserRate(rating)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setOnUserRateListener(l: OnUserRateListener?) {
|
|
||||||
mListener = l
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRatingChanged(ratingBar: RatingBar, rating: Float, fromUser: Boolean) {
|
|
||||||
if (rating <= 0.0f) {
|
|
||||||
setRating(0.5f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OnUserRateListener {
|
|
||||||
fun onUserRate(rating: Float)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.ui.legacy
|
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.ColorFilter
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.PixelFormat
|
|
||||||
import android.graphics.RectF
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
|
|
||||||
class HandlerDrawable : Drawable() {
|
|
||||||
private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
||||||
private val mTemp = RectF()
|
|
||||||
private var mColor = Color.BLACK
|
|
||||||
|
|
||||||
init {
|
|
||||||
mPaint.color = mColor
|
|
||||||
mPaint.style = Paint.Style.FILL
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
|
||||||
val width = bounds.width()
|
|
||||||
val height = bounds.height()
|
|
||||||
if (width > height) {
|
|
||||||
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), mPaint)
|
|
||||||
} else {
|
|
||||||
mTemp[0f, 0f, width.toFloat()] = width.toFloat()
|
|
||||||
canvas.drawArc(mTemp, -180f, 180f, true, mPaint)
|
|
||||||
mTemp[0f, (height - width).toFloat(), width.toFloat()] = height.toFloat()
|
|
||||||
canvas.drawArc(mTemp, 0f, 180f, true, mPaint)
|
|
||||||
val halfWidth = width.toFloat() / 2.0f
|
|
||||||
canvas.drawRect(0f, halfWidth, width.toFloat(), height - halfWidth, mPaint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setColor(color: Int) {
|
|
||||||
if (mColor != color) {
|
|
||||||
mColor = color
|
|
||||||
mPaint.color = color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAlpha(alpha: Int) {
|
|
||||||
mPaint.alpha = alpha
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
|
||||||
mPaint.colorFilter = colorFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
|
||||||
override fun getOpacity(): Int {
|
|
||||||
return when (Color.alpha(mColor)) {
|
|
||||||
0xff -> {
|
|
||||||
PixelFormat.OPAQUE
|
|
||||||
}
|
|
||||||
|
|
||||||
0x00 -> {
|
|
||||||
PixelFormat.TRANSPARENT
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
PixelFormat.TRANSLUCENT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Hippo Seven
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.hippo.ehviewer.ui.legacy
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.widget.ListView
|
|
||||||
import com.hippo.ehviewer.R
|
|
||||||
|
|
||||||
@SuppressLint("CustomViewStyleable")
|
|
||||||
class IndicatingListView @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
) : ListView(context, attrs) {
|
|
||||||
private val mPaint = Paint()
|
|
||||||
private val mTemp = Rect()
|
|
||||||
private var mIndicatorHeight = 0
|
|
||||||
|
|
||||||
init {
|
|
||||||
val a = context.obtainStyledAttributes(attrs, R.styleable.Indicating)
|
|
||||||
mIndicatorHeight = a.getDimensionPixelOffset(R.styleable.Indicating_indicatorHeight, 1)
|
|
||||||
mPaint.color = a.getColor(
|
|
||||||
R.styleable.Indicating_indicatorColor,
|
|
||||||
Color.BLACK,
|
|
||||||
)
|
|
||||||
mPaint.style = Paint.Style.FILL
|
|
||||||
a.recycle()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fillTopIndicatorDrawRect() {
|
|
||||||
mTemp[0, 0, width] = mIndicatorHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fillBottomIndicatorDrawRect() {
|
|
||||||
mTemp[0, height - mIndicatorHeight, width] = height
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun needShowTopIndicator(): Boolean {
|
|
||||||
return canScrollVertically(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun needShowBottomIndicator(): Boolean {
|
|
||||||
return canScrollVertically(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
|
||||||
super.draw(canvas)
|
|
||||||
val restoreCount = canvas.save()
|
|
||||||
canvas.translate(scrollX.toFloat(), scrollY.toFloat())
|
|
||||||
|
|
||||||
// Draw top indicator
|
|
||||||
if (needShowTopIndicator()) {
|
|
||||||
fillTopIndicatorDrawRect()
|
|
||||||
canvas.drawRect(mTemp, mPaint)
|
|
||||||
}
|
|
||||||
// Draw bottom indicator
|
|
||||||
if (needShowBottomIndicator()) {
|
|
||||||
fillBottomIndicatorDrawRect()
|
|
||||||
canvas.drawRect(mTemp, mPaint)
|
|
||||||
}
|
|
||||||
canvas.restoreToCount(restoreCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loaded 100 of 751 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user