mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-27 11:37:51 +02:00
Compare commits
732 Commits
Author | SHA1 | Date | |
---|---|---|---|
c1064b1ba7 | |||
3a9f59b7a5 | |||
ccbe240846 | |||
2c0d96917f | |||
0316cf47ed | |||
e4c8de1145 | |||
9c978c608d | |||
b4a88926ed | |||
de593f458f | |||
f3c8b37928 | |||
91f22c03c0 | |||
81ee1ce39a | |||
2ed54eed73 | |||
a9ef4bef8e | |||
c9b988aca6 | |||
b803dbe3af | |||
35ef07d720 | |||
4f596d68b9 | |||
26776f8430 | |||
471eb36a92 | |||
8b8b377c29 | |||
d70d2cdff5 | |||
b0704063f2 | |||
dde7254dcf | |||
4edb9ed398 | |||
9d764781c3 | |||
f7d991ab54 | |||
59b3b7d79d | |||
354bf362c0 | |||
69304466a7 | |||
6e4d4739a6 | |||
e8947b3107 | |||
153641a249 | |||
60706e8b8b | |||
19dc859ef2 | |||
06ff4444c5 | |||
156cfb0c74 | |||
e49a25b604 | |||
b34a137c07 | |||
1fb6b3775d | |||
b1c9a204c1 | |||
8435be1b1f | |||
7affb9ab63 | |||
5627ad0801 | |||
08017a0cd1 | |||
b6603c3425 | |||
f63323feab | |||
eea5f0ba6a | |||
9f51dfad33 | |||
c31789c112 | |||
86043fbb31 | |||
1136ec2ad4 | |||
7910f6e420 | |||
a087b0bf4b | |||
d0929c1dc5 | |||
46500dcb32 | |||
f85c34b807 | |||
dfc3a5df42 | |||
2b78925c68 | |||
eb2fedd669 | |||
b8b5e70151 | |||
8c00905c99 | |||
598af5ebd5 | |||
8adedee973 | |||
4dc2143160 | |||
aded163e0a | |||
11a553cbf4 | |||
a0048e9397 | |||
a8a22b5803 | |||
4122af491c | |||
0de42b6654 | |||
d7aa1144cf | |||
855f768e29 | |||
87fbda0b29 | |||
99dd9a0750 | |||
c8befdd5ea | |||
54cf97170d | |||
a5aa89512a | |||
b02813f30a | |||
51c8430e9c | |||
a846f7da47 | |||
b4e5443e56 | |||
3fdfd91fff | |||
e4b52c036a | |||
43098aa61b | |||
e4d8fea138 | |||
6da22ea8b0 | |||
5e618e0134 | |||
fd09f64fe6 | |||
ef2241e0cb | |||
bcfe5af135 | |||
a598afec43 | |||
3c9ec48da3 | |||
7ce15caded | |||
7e65e0de0e | |||
07a70d8c92 | |||
038574b107 | |||
24067e1e97 | |||
68b13c6425 | |||
a9de6a123e | |||
b0251b8177 | |||
c069d75ede | |||
5886cb7406 | |||
1713dd4ea0 | |||
f5f7971cb5 | |||
5271abbd1f | |||
7d2ded5944 | |||
d2b0319d63 | |||
c15f4c7fd0 | |||
3aee05bf26 | |||
9d148a70c8 | |||
4a64bb250d | |||
e6f5ea172a | |||
6a0ab3526a | |||
fa29b914cc | |||
3a79f8fb50 | |||
321eb38f2d | |||
46dccf8a2d | |||
7f02a533fa | |||
38d329a601 | |||
1c0f08fc5b | |||
a076deeb5f | |||
a006bc7d93 | |||
2b215524b6 | |||
f497e605b4 | |||
b09f1fff51 | |||
92af538b0a | |||
f0b821b122 | |||
4c5e99142f | |||
b0f39a7d0a | |||
38b469755f | |||
3bfda338ef | |||
6e1da22353 | |||
60a0303d7f | |||
6135b4daae | |||
2fe38192b4 | |||
ef3f4c2e17 | |||
e4e069ccca | |||
505d2e4274 | |||
4cdf2f468c | |||
8c7a2c4262 | |||
156ce14dd8 | |||
27dc6443ec | |||
0a5773211d | |||
b3b0c39163 | |||
bc6b4d4be6 | |||
a14e5a99d9 | |||
d643b947a9 | |||
2033e82ff0 | |||
c2c872d000 | |||
ae9377f8be | |||
93a84cad34 | |||
f9ceb92495 | |||
bfdd5c9ec3 | |||
d800528550 | |||
6ec656b094 | |||
29c5da1494 | |||
0a21a9e5f0 | |||
d3aaa3656b | |||
69a8223d06 | |||
6959370087 | |||
1d0937397f | |||
aeeafe8556 | |||
38c5163ab9 | |||
4646e4b0d9 | |||
e9f105c52a | |||
3b225405cb | |||
b96a870c4e | |||
b70d70e82d | |||
f4d1c27cde | |||
4195b00e48 | |||
0a37dabf4b | |||
2d5ac20c46 | |||
700fd61f34 | |||
a0462fb480 | |||
d9d969406e | |||
f19685787f | |||
60caba86a5 | |||
80fa7ebe6c | |||
9096edfc92 | |||
f30ad78a4c | |||
ce0d99b78f | |||
3ffb80c6f1 | |||
4e3c407583 | |||
aaf9fc5d92 | |||
5043f840a2 | |||
fac3bd1edd | |||
976cd41a87 | |||
4aeaee65c3 | |||
f880a0f1a3 | |||
3a886e39fb | |||
005d43a3ec | |||
1d760f4728 | |||
02e3148efe | |||
e9ada7e5fb | |||
2ce01a1c86 | |||
1af8bc95a8 | |||
0046ed879f | |||
4f8c6455bc | |||
c80b8c8ce9 | |||
5435c2fb53 | |||
88963758b1 | |||
e4d40c7693 | |||
2f928ac034 | |||
509d7b20b4 | |||
35c4d0bf4e | |||
5e5f2b0f1a | |||
58135c965c | |||
5a9b84fe00 | |||
c9a10d9033 | |||
730c316e89 | |||
29a07429df | |||
e44616fb3c | |||
cb6ca49607 | |||
ff8b58ee62 | |||
b1350928ee | |||
52641494d3 | |||
9bfe4ed829 | |||
d6f0c0837b | |||
074a1bbca4 | |||
178cd2b52d | |||
e35f807218 | |||
63ca5cc66b | |||
6b66d4a34c | |||
49580a7630 | |||
0300c1057b | |||
f5b8d3120d | |||
fd88db37f7 | |||
39e36f957d | |||
e84c39bf7b | |||
5641b33bdd | |||
72681bef83 | |||
6de16125f7 | |||
46478546a8 | |||
d4659ffaae | |||
e192806950 | |||
1170334fde | |||
d368064549 | |||
ada0703d17 | |||
06d3a753ac | |||
567c6ea8e7 | |||
bfc0868721 | |||
f5b7a8db1a | |||
bef0a44447 | |||
e9ff202851 | |||
4a3323f7d0 | |||
0014a0ca6c | |||
7e99a9f789 | |||
03e5c5ca10 | |||
e4ff988f4d | |||
dde2320afc | |||
988d508025 | |||
24850450cb | |||
e97d1ac257 | |||
bc44eee5b7 | |||
7a010e3446 | |||
cb4fbb19ed | |||
ddf0357c5c | |||
800c01ea42 | |||
68bfba486e | |||
c09f6cb20c | |||
959bad0247 | |||
efb8555d76 | |||
a393772083 | |||
b4ade8c15d | |||
03502f6533 | |||
42c7669bca | |||
1916751f4f | |||
e46dd808e7 | |||
5e92664761 | |||
aa5fd22db0 | |||
56b1c07f58 | |||
2ecac08bcc | |||
95002a6e87 | |||
cea2b42b41 | |||
8bc5a7d746 | |||
85723b975b | |||
a2e05cf80b | |||
87363e47b0 | |||
aefa7a1a4a | |||
18f90587f2 | |||
f47e9b6e14 | |||
a7e4c1295d | |||
50097eef9f | |||
949cdccbae | |||
3d0dc64de1 | |||
2e0102d689 | |||
2557111607 | |||
46a626d2c5 | |||
fb92b1a0b4 | |||
8e6ed194a6 | |||
01175e687c | |||
00d2d4f969 | |||
43715f9835 | |||
b99229b033 | |||
62df1263b1 | |||
a6f0e7f9b9 | |||
f1472d4f8b | |||
52ad282492 | |||
e7b39f29f2 | |||
c62d3abbc5 | |||
fb3ce226b5 | |||
de5f43713f | |||
84aba68b96 | |||
1be0171398 | |||
82c49a42c3 | |||
599a4c7b17 | |||
895dbbdacb | |||
77026d4eeb | |||
256b1881f0 | |||
2a299485cc | |||
a6909a76ed | |||
1ecba5bb7e | |||
c381b737d3 | |||
70e7974396 | |||
2104b60b81 | |||
09dcb8029c | |||
f2ed26479f | |||
d03d9b344a | |||
9b5fbda9a2 | |||
647fbd1f4b | |||
fa6ed901df | |||
23ac3d18e5 | |||
f18891a07e | |||
c85825f3c7 | |||
319571edf3 | |||
1f20d96191 | |||
c2e50305eb | |||
d00e1284d6 | |||
ef4430216d | |||
643f666178 | |||
989b968e5b | |||
92484e26e5 | |||
429056f2ca | |||
fa2b44eeb3 | |||
52e742049b | |||
97a86269e6 | |||
654d98b5c4 | |||
0509db1935 | |||
acf879d28c | |||
cefe45e8cc | |||
03b44fff22 | |||
71c373926f | |||
1af11f076f | |||
41c99c33a6 | |||
58cce53746 | |||
df6cafd6d1 | |||
cc6c1b5641 | |||
d61adc0259 | |||
9ed7ee65c3 | |||
5f94e230f9 | |||
4d8f44ddae | |||
b20c028566 | |||
9b883b1a09 | |||
53402459f2 | |||
bc6a1a1da6 | |||
690d2fb15b | |||
7ac188709b | |||
93c8773e91 | |||
eefe95c2ee | |||
af9825d369 | |||
59f38cb343 | |||
810d2d4776 | |||
027cb10d0f | |||
24a6eba94e | |||
c1c43bb6fb | |||
0804550539 | |||
3156482334 | |||
052317b38a | |||
6ff684f638 | |||
6857c8c1fe | |||
44d563e689 | |||
6ce2809450 | |||
0759036536 | |||
6f6490b7ac | |||
da12a4b17f | |||
1302bc84dd | |||
1c4a8046d0 | |||
141edac99b | |||
b64ecfb836 | |||
d5f4db5eb7 | |||
7a3f6e9f63 | |||
b3ecc91be9 | |||
2a27eacf5e | |||
e747686ad8 | |||
282af20146 | |||
f25cd9fdbf | |||
2369547d59 | |||
d86b8cc78c | |||
b4c1e6a44c | |||
45fc2f2e0e | |||
9cfcacf45e | |||
801fd83649 | |||
16f1dcd922 | |||
8f36d698cf | |||
a89a36fd4c | |||
8a7f8c4068 | |||
9c70e69300 | |||
a0b490b10f | |||
d69dc375a3 | |||
ac6dbbcd89 | |||
14879c4466 | |||
dce08d4922 | |||
5195cb8eda | |||
68de7b516e | |||
10095205d8 | |||
c4c988f7a4 | |||
333dfbc642 | |||
e915fd28cb | |||
46636b537c | |||
cd5545284e | |||
3dac6366ff | |||
054a26cf65 | |||
024c76d480 | |||
9abdfeac77 | |||
0f12ae0cf7 | |||
71182e664a | |||
7fc6d53cae | |||
4f2985469c | |||
9cc24a3be3 | |||
7ec18861bf | |||
3d1c02136a | |||
aca34155b9 | |||
d04ffb7ea9 | |||
5b3e72db54 | |||
f811cc5c87 | |||
e7abe27bb6 | |||
126d875547 | |||
a2b7228e95 | |||
10d6b3a6ca | |||
4c9be5557d | |||
37e0ac0895 | |||
8934d251d9 | |||
b5263a6968 | |||
15bd8e964d | |||
d931027067 | |||
39ffd3c3bc | |||
9ff8235de4 | |||
54075733b7 | |||
2ea0538825 | |||
275e20eabd | |||
7fe742e6ed | |||
9a6bb69df8 | |||
4c93ca6914 | |||
51939ea652 | |||
256a4a2e7f | |||
c217cab5db | |||
c93b6deaf8 | |||
55196b86b3 | |||
c5ab79f618 | |||
59fcfbe9d2 | |||
6ce70296a6 | |||
fdef687f0c | |||
cdd7f42532 | |||
65955fd7dc | |||
b785f68154 | |||
895191814e | |||
b06f253f83 | |||
0814439886 | |||
a978cb90e8 | |||
39e0d434ad | |||
6ada3cbf59 | |||
4aff768b8e | |||
e08e569135 | |||
8cac0fca67 | |||
f447b06eff | |||
78b2045b14 | |||
a427401d66 | |||
6da6ca710e | |||
f46c42f522 | |||
c4df7b496b | |||
6c4332f5c2 | |||
86076d890a | |||
76a041f504 | |||
c9bdc72d5d | |||
f6a8b23498 | |||
2a60c828d7 | |||
ea7ff432b2 | |||
5c2fbec80a | |||
f0109aa796 | |||
66b92f3eb7 | |||
ec4af65c36 | |||
852c1a423d | |||
1904be277d | |||
ebb1022100 | |||
8d85ec3cd9 | |||
1d36c3269e | |||
a218f4a48b | |||
b14c3cc49f | |||
cbc4a0f3ab | |||
70a024d3de | |||
28d43bbecc | |||
55a3b2f3a1 | |||
2545b22ab1 | |||
77c07d13c0 | |||
98ac8a69c2 | |||
0e22af7ba0 | |||
6951ce34c7 | |||
24e10d6037 | |||
f1b08bf56e | |||
3cc3799ebb | |||
aed20a790e | |||
81b20a23bf | |||
bd27cb74a7 | |||
07fd3575c5 | |||
6da504c999 | |||
23c1827838 | |||
49833fcc48 | |||
0e4c0fba5c | |||
bfe67f1cdf | |||
863e349711 | |||
5e13df9bd8 | |||
65abb8fa6c | |||
fe5c0295c3 | |||
4d15ac2fa3 | |||
5e4b9fb15b | |||
8a70dffae8 | |||
8119eb4b34 | |||
1bae7ba8f5 | |||
d4f1014df6 | |||
dce685e711 | |||
c4e6668c22 | |||
9b5608c86d | |||
2485a00d34 | |||
8abe2d76b0 | |||
8e9087226f | |||
8ccc3c8b0c | |||
9b3579acf6 | |||
6f2ff6a77e | |||
f772501159 | |||
cadd389658 | |||
6f36331818 | |||
d684eb5147 | |||
349546cf87 | |||
352593ebc6 | |||
c6b28cbcaf | |||
c5e69d2080 | |||
cd4964d086 | |||
ac3967e997 | |||
5df18f2558 | |||
0b054126bc | |||
d4740c57be | |||
739fc9f95d | |||
96b1340aec | |||
57d83e3d1b | |||
4e2c9dc083 | |||
fa6f60d454 | |||
71fe2bda97 | |||
f24c5e944e | |||
603fd84753 | |||
5fbe1a8614 | |||
42169783e1 | |||
90574937df | |||
ec0fe2210d | |||
450fcccaa6 | |||
72504ca53f | |||
6bfddb3ece | |||
b62fcbc2ec | |||
411dda0e75 | |||
d9d71c8745 | |||
35239af039 | |||
840e571917 | |||
2fd4204db8 | |||
1fd105ac56 | |||
6ae90e0a7d | |||
43dc12a0f3 | |||
28d05b629f | |||
30c12fc9de | |||
cdb62f502c | |||
5cfdbbce7a | |||
e2af1af656 | |||
a96c25c37c | |||
c1abec3b4c | |||
c5a488ca3f | |||
dd4a18a0b2 | |||
34df0759f3 | |||
b6a3f6ebd2 | |||
ad2819a3bc | |||
0f2be86d5a | |||
4342584c32 | |||
71c10df270 | |||
db90b5d2cb | |||
5cc38c1369 | |||
e7c48a98df | |||
d03c49db58 | |||
b69af710ad | |||
4d3b469c48 | |||
8cc6c0171b | |||
8be5be4720 | |||
995a1155e4 | |||
234c3bb72a | |||
aafe863774 | |||
fb1db914aa | |||
7aa8abdd98 | |||
4bd965a795 | |||
df2a4779bf | |||
d2dc063c8e | |||
45f4c63941 | |||
5e968e5651 | |||
3e3c0a1f14 | |||
c48bebe0b2 | |||
0dd9e9e015 | |||
d2a2e17e91 | |||
09dbd723e4 | |||
eb965542cc | |||
615fa05a75 | |||
87a2ac7887 | |||
07ce90ab8c | |||
a71ae29c98 | |||
117214c671 | |||
c54d26d6ba | |||
5447bd098b | |||
2b7c0e8e80 | |||
47966d89f2 | |||
b28a2c3bd4 | |||
8f51abfc97 | |||
e9bea8ed47 | |||
c2c1b6d2e8 | |||
ded22f1717 | |||
126da3979c | |||
d892f2f7f4 | |||
f18b32626a | |||
8c8f2585aa | |||
1a811d0917 | |||
9af552c15a | |||
263cc1d97c | |||
dec4471871 | |||
c8122ebd68 | |||
b766ddea54 | |||
5cb219d83e | |||
908128b55d | |||
b240960a8a | |||
b91d8e4c19 | |||
4853ff7eee | |||
e1582bd73a | |||
ac17335dbe | |||
7053777997 | |||
1107b95ebd | |||
36003fd622 | |||
84121ff901 | |||
bcc2ec1668 | |||
08dffda2a1 | |||
9f4540a4f1 | |||
f19ef9aa01 | |||
f10a06341b | |||
67ac95f15f | |||
cd291f0a27 | |||
bb6b88a703 | |||
239b36c31a | |||
dcb6ae44dd | |||
5ed365cf0d | |||
b20b3d6f5c | |||
32d02f9329 | |||
3da7c47bf5 | |||
029f159aea | |||
0a300822a4 | |||
8d48e0289a | |||
9dbb59f337 | |||
a243aeafba | |||
5c671de29e | |||
e161e5d30d | |||
2fd64d0ca7 | |||
d981c75600 | |||
492adb7035 | |||
811bca18d9 | |||
3222e0646d | |||
fa6790856d | |||
38a15d4158 | |||
77cd459c51 | |||
fa115ed9a0 | |||
30a2b572ab | |||
b1ab77188d | |||
e212e4581d | |||
1b8daa7e09 | |||
99d126fd3a | |||
9fee7c6b19 | |||
1b5a83563f | |||
c8a8eb0a4d | |||
0a7812bb2c | |||
061b434687 | |||
f3be1a6d09 | |||
1e797bfe8a | |||
97435dbe0f | |||
3fdbb95b15 | |||
961a6c0b4f | |||
c42f011a05 | |||
957c50088d | |||
3b129176d6 | |||
f36327ecc9 | |||
5f48bb8e7d | |||
aba8d01818 | |||
03cb7062f2 | |||
d4b35736c0 | |||
c30aa710f3 | |||
0adac4217e | |||
7e102edd0a | |||
adefe87129 | |||
8a05fc8966 | |||
8fa798cf63 | |||
01b20b2acb | |||
a8d72dd6f8 | |||
84042c61e0 | |||
407ba6c628 | |||
16a3c676ef | |||
9b3a99432b | |||
036f757086 | |||
a391529b32 | |||
d10b782308 | |||
a58f780452 | |||
606de03c7c | |||
2b988c51ad | |||
cb73e55db1 | |||
8fb4093dc6 | |||
f5c4535cb0 | |||
e4f2bffbc2 | |||
5c942460f9 | |||
cce1f2c20d | |||
516a3bd017 | |||
63d58c7a4f | |||
c49b865ee7 | |||
097a83e3ba | |||
02c70d868d | |||
c836f52460 | |||
caa1e1ef09 | |||
5c63531066 | |||
15b2bbc6d1 | |||
3cafbd9141 | |||
dc0405a373 | |||
f84832716a | |||
200aa4042f | |||
f44c5adcc0 | |||
3c43bebe64 |
4
.github/CONTRIBUTING.md
vendored
Normal file → Executable file
4
.github/CONTRIBUTING.md
vendored
Normal file → Executable file
@ -14,9 +14,9 @@
|
||||
* Catalogue requests should be created at https://github.com/inorichi/tachiyomi-extensions#readme, not here
|
||||
|
||||
# Bugs
|
||||
* Include version (More > About > Version)
|
||||
* Include version (Setting > About > Version)
|
||||
* If not latest, try updating, it may have already been solved
|
||||
* Preview version is equal to the number of commits as seen in the main page
|
||||
* Dev version is equal to the number of commits as seen in the main page
|
||||
* Include steps to reproduce (if not obvious from description)
|
||||
* Include screenshot (if needed)
|
||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||
|
4
.github/ISSUE_TEMPLATE.md
vendored
Normal file → Executable file
4
.github/ISSUE_TEMPLATE.md
vendored
Normal file → Executable file
@ -2,7 +2,7 @@
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v0.10.4)
|
||||
- I have updated to the latest version of the app (stable is v0.9.2)
|
||||
- I have updated all extensions
|
||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
||||
|
||||
@ -10,7 +10,7 @@ I acknowledge that:
|
||||
|
||||
---
|
||||
|
||||
## Device information
|
||||
### Device information
|
||||
* Tachiyomi version: ?
|
||||
* Android version: ?
|
||||
* Device: ?
|
||||
|
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: "🐞 Bug report"
|
||||
about: Report a bug
|
||||
title: "[Bug] <Write short description here>"
|
||||
title: "[Bug] Write short description here"
|
||||
labels: "bug"
|
||||
---
|
||||
|
||||
@ -9,7 +9,7 @@ labels: "bug"
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v0.10.4)
|
||||
- I have updated to the latest version of the app (stable is v0.9.2)
|
||||
- I have updated all extensions
|
||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
||||
|
||||
@ -17,7 +17,7 @@ I acknowledge that:
|
||||
|
||||
---
|
||||
|
||||
## Device information
|
||||
### Device information
|
||||
* Tachiyomi version: ?
|
||||
* Android version: ?
|
||||
* Device: ?
|
||||
@ -32,5 +32,5 @@ This should happen.
|
||||
### Actual behavior
|
||||
This happened instead.
|
||||
|
||||
## Other details
|
||||
### Other details
|
||||
Additional details and attachments.
|
||||
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
8
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Tachiyomi help website
|
||||
url: https://tachiyomi.org/help/
|
||||
about: Common questions are answered here.
|
||||
- name: Tachiyomi extensions GitHub repository
|
||||
url: https://github.com/inorichi/tachiyomi-extensions
|
||||
about: Issues about an extension/source/catalogue should be opened here instead.
|
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: "🌟 Feature request"
|
||||
about: Suggest a feature to improve Tachiyomi
|
||||
title: "[Feature Request] <Write short description here>"
|
||||
title: "[Feature Request] Write short description here"
|
||||
labels: "feature"
|
||||
---
|
||||
|
||||
@ -9,7 +9,7 @@ labels: "feature"
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v0.10.4)
|
||||
- I have updated to the latest version of the app (stable is v0.9.2)
|
||||
- I have updated all extensions
|
||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
||||
|
||||
@ -17,8 +17,8 @@ I acknowledge that:
|
||||
|
||||
---
|
||||
|
||||
## Why/User Benefit/User Problem
|
||||
### Why/User Benefit/User Problem
|
||||
(explain why this feature should be added)
|
||||
|
||||
## What/Requirements
|
||||
### What/Requirements
|
||||
(explain how this feature would behave)
|
||||
|
4
.github/ISSUE_TEMPLATE/source_issue.md
vendored
4
.github/ISSUE_TEMPLATE/source_issue.md
vendored
@ -2,7 +2,7 @@
|
||||
name: "Extension/source/catalogue issue"
|
||||
about: "Do not open an issue here. See https://github.com/inorichi/tachiyomi-extensions"
|
||||
title: "THIS ISSUE IS IN THE WRONG REPO; SEE https://github.com/inorichi/tachiyomi-extensions"
|
||||
labels: "catalog, invalid"
|
||||
labels: "catalog"
|
||||
---
|
||||
|
||||
DO NOT OPEN AN ISSUE IN THIS REPO. SEE https://github.com/inorichi/tachiyomi-extensions
|
||||
DO NOT OPEN AN ISSUE IN THIS REPO. SEE https://github.com/inorichi/tachiyomi-extensions
|
BIN
.github/readme-images/screens.png
vendored
BIN
.github/readme-images/screens.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.0 MiB |
25
.github/workflows/PullRequestTester.yml
vendored
Normal file
25
.github/workflows/PullRequestTester.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: Pull Request Checker
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
apk:
|
||||
name: Generate APK
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Get NDK
|
||||
run: sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;20.0.5594570"
|
||||
- name: Build Release APK
|
||||
run: bash ./gradlew assembleDebug --stacktrace
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: TachiyomiSY-${{ github.sha }}.apk
|
||||
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
|
31
.github/workflows/TachiyomiSY-Preview-Builder.yml
vendored
Normal file
31
.github/workflows/TachiyomiSY-Preview-Builder.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
name: Remote Dispatch Action Initiator
|
||||
|
||||
on:
|
||||
push:
|
||||
repository_dispatch:
|
||||
|
||||
jobs:
|
||||
ping-pong:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: '0'
|
||||
- name: TAG - Bump version and push tag
|
||||
uses: anothrNick/github-tag-action@1.17.2
|
||||
if: github.event.action != 'pong'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
WITH_V: true
|
||||
RELEASE_BRANCHES: master
|
||||
- name: PING - Dispatch initiating repository event
|
||||
if: github.event.action != 'pong'
|
||||
run: |
|
||||
curl -X POST https://api.github.com/repos/jobobby04/TachiyomiSYPreview/dispatches \
|
||||
-H 'Accept: application/vnd.github.everest-preview+json' \
|
||||
-u ${{ secrets.ACCESS_TOKEN }} \
|
||||
--data '{"event_type": "ping", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
|
||||
- name: ACK - Acknowledge pong from remote repository
|
||||
if: github.event.action == 'pong'
|
||||
run: |
|
||||
echo "PONG received from '${{ github.event.client_payload.repository }}'"
|
31
.github/workflows/issue_closer.yml
vendored
31
.github/workflows/issue_closer.yml
vendored
@ -4,31 +4,10 @@ jobs:
|
||||
autoclose:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Autoclose when created in wrong repo
|
||||
uses: arkon/issue-closer-action@v1.1
|
||||
- name: Autoclose issue
|
||||
uses: arkon/issue-closer-action@v1.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
type: title
|
||||
regex: ".*THIS ISSUE IS IN THE WRONG REPO.*"
|
||||
message: "@${issue.user.login} this issue was automatically closed because it was not opened in the correct repo, as the template mentioned."
|
||||
- name: Autoclose when no short description provided
|
||||
uses: arkon/issue-closer-action@v1.1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
type: title
|
||||
regex: ".*<Write short description here>*"
|
||||
message: "@${issue.user.login} this issue was automatically closed because you did not fill out the description in the title."
|
||||
- name: Autoclose when body acknowledgement section not removed
|
||||
uses: arkon/issue-closer-action@v1.1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
type: body
|
||||
regex: ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*"
|
||||
message: "@${issue.user.login} this issue was automatically closed because the acknowledgment section was not removed."
|
||||
- name: Autoclose when body requested information not filled out
|
||||
uses: arkon/issue-closer-action@v1.1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
type: body
|
||||
regex: ".*\\* (Tachiyomi version|Android version|Device): \\?.*"
|
||||
message: "@${issue.user.login} this issue was automatically closed because the requested information was not filled out."
|
||||
issue-close-message: "@${issue.user.login} this issue was automatically closed because it was not filled in correctly or the acknowledgment section was not removed."
|
||||
issue-title-pattern: ".*THIS ISSUE IS IN THE WRONG REPO.*"
|
||||
issue-body-pattern: ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*"
|
||||
|
7
.gitignore
vendored
Normal file → Executable file
7
.gitignore
vendored
Normal file → Executable file
@ -5,6 +5,8 @@
|
||||
.idea/
|
||||
*iml
|
||||
*.iml
|
||||
/mainframer
|
||||
/.mainframer
|
||||
|
||||
# Built files
|
||||
*/build
|
||||
@ -14,3 +16,8 @@ app/**/output.json
|
||||
|
||||
# Hebrew assets are copied on build
|
||||
app/src/main/res/values-iw/
|
||||
|
||||
TODO.md
|
||||
CHANGELOG.md
|
||||
/captures
|
||||
build.sh
|
||||
|
@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
git fetch --unshallow #required for commit count
|
||||
|
||||
if [ -z "$TRAVIS_TAG" ]; then
|
||||
./gradlew clean assembleStandardDebug
|
||||
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
export ARTIFACT="tachiyomi-r${COMMIT_COUNT}.apk"
|
||||
|
||||
mv app/build/outputs/apk/standard/debug/app-standard-debug.apk $ARTIFACT
|
||||
else
|
||||
./gradlew clean assembleStandardRelease
|
||||
|
||||
TOOLS="$(ls -d ${ANDROID_HOME}/build-tools/* | tail -1)"
|
||||
export ARTIFACT="tachiyomi-${TRAVIS_TAG}.apk"
|
||||
|
||||
${TOOLS}/zipalign -v -p 4 app/build/outputs/apk/standard/release/app-standard-release-unsigned.apk app-aligned.apk
|
||||
${TOOLS}/apksigner sign --ks $STORE_PATH --ks-key-alias $STORE_ALIAS --ks-pass env:STORE_PASS --key-pass env:KEY_PASS --out $ARTIFACT app-aligned.apk
|
||||
fi
|
@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
pattern="tachiyomi-r*"
|
||||
files=( $pattern )
|
||||
export ARTIFACT="${files[0]}"
|
||||
|
||||
if [ -z "$ARTIFACT" ]; then
|
||||
echo "Artifact not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export SSHOPTIONS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ${DEPLOY_KEY}"
|
||||
|
||||
scp $SSHOPTIONS $ARTIFACT $DEPLOY_USER@$DEPLOY_HOST:builds/
|
||||
ssh $SSHOPTIONS $DEPLOY_USER@$DEPLOY_HOST ln -sf $ARTIFACT builds/latest
|
Binary file not shown.
@ -1,28 +0,0 @@
|
||||
### r1810
|
||||
- Background jobs were migrated to a new system. You may need to toggle the settings to ensure they
|
||||
run properly. This includes app updates, library updates, and automatic backups.
|
||||
|
||||
### r1340
|
||||
- A new screen for managing extensions was added. If you previously installed extensions from FDroid,
|
||||
you will have to uninstall all of them first (tap on the extension then uninstall), otherwise you won't be able
|
||||
to update them due to signature mismatch. You won't lose anything in this process as the extensions themselves
|
||||
don't store anything.
|
||||
|
||||
### r959
|
||||
- The download manager has been rewritten and it's possible some of your downloads
|
||||
aren't recognized anymore. You may have to check your downloads folder and manually delete those.
|
||||
- You can now download to any folder in your SD card.
|
||||
- The download directory setting has been reset.
|
||||
|
||||
### r857
|
||||
- **Important!** Delete after read has been updated.
|
||||
This means the value has been reset set to disabled.
|
||||
This can be changed in Settings > Downloads
|
||||
|
||||
### r736
|
||||
- **Important!** Now chapters follow the order of the sources. **It's required that you update your entire library
|
||||
before reading in order for them to be synced.** Old behavior can be restored for a manga in the overflow menu of the chapters tab.
|
||||
|
||||
### r724
|
||||
- Kissmanga covers may not load anymore. The only workaround is to update the details of the manga
|
||||
from the info tab, or clearing the database (the latter won't fix covers from library manga).
|
88
README.md
88
README.md
@ -1,87 +1,3 @@
|
||||
| Build | Stable | Weekly Preview | Contribute | Support Server |
|
||||
|-------|----------|---------|------------|---------|
|
||||
| [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) | [](http://tachiyomi.kanade.eu/latest) | [](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [](https://discord.gg/tachiyomi) |
|
||||
I haven't started a readme
|
||||
|
||||
|
||||
# Tachiyomi
|
||||
Tachiyomi is a free and open source manga reader for Android 5.0 and above.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
Features include:
|
||||
* Online reading from sources such as MangaDex, MangaSee, Mangakakalot, [and more](https://github.com/inorichi/tachiyomi-extensions)
|
||||
* Local reading of downloaded manga
|
||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
||||
* [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
|
||||
* Categories to organize your library
|
||||
* Light and dark themes
|
||||
* Schedule updating your library for new chapters
|
||||
* Create backups locally to read offline or to your desired cloud service
|
||||
|
||||
## Download
|
||||
Get the app from our [releases page](https://github.com/inorichi/tachiyomi/releases).
|
||||
|
||||
If you want to try new features before they get to the stable release, you can download the preview version [here](http://tachiyomi.kanade.eu/latest).
|
||||
|
||||
## Issues, Feature Requests and Contributing
|
||||
|
||||
Please make sure to read the full guidelines. Your issue may be closed without warning if you do not.
|
||||
|
||||
<details><summary>Issues</summary>
|
||||
|
||||
1. **Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened [issues](https://github.com/inorichi/tachiyomi/issues).**
|
||||
2. If you are unsure, ask here: [](https://discord.gg/tachiyomi)
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Bugs</summary>
|
||||
|
||||
* Include version (More > About > Version)
|
||||
* If not latest, try updating, it may have already been solved
|
||||
* Preview version is equal to the number of commits as seen in the main page
|
||||
* Include steps to reproduce (if not obvious from description)
|
||||
* Include screenshot (if needed)
|
||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||
* For large logs use http://pastebin.com/ (or similar)
|
||||
* Don't group unrelated requests into one issue
|
||||
|
||||
DO: https://github.com/inorichi/tachiyomi/issues/24 https://github.com/inorichi/tachiyomi/issues/71
|
||||
|
||||
DON'T: https://github.com/inorichi/tachiyomi/issues/75
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Feature Requests</summary>
|
||||
|
||||
* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does"
|
||||
* Include screenshot (if needed)
|
||||
|
||||
Catalogue requests should be created at https://github.com/inorichi/tachiyomi-extensions, they do not belong in this repository.
|
||||
</details>
|
||||
|
||||
## FAQ
|
||||
|
||||
[See our website.](https://tachiyomi.org/)
|
||||
You can also reach out to us on [Discord](https://discord.gg/tachiyomi).
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2015 Javier Tomás
|
||||
|
||||
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.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
The developer of this application does not have any affiliation with the content providers available.
|
||||
Automated Preview Builds(with updater): https://github.com/jobobby04/TachiyomiSYPreview/releases
|
||||
|
2
app/.gitignore
vendored
Normal file → Executable file
2
app/.gitignore
vendored
Normal file → Executable file
@ -2,3 +2,5 @@
|
||||
*iml
|
||||
*.iml
|
||||
custom.gradle
|
||||
google-services.json
|
||||
output.json
|
215
app/build.gradle
Normal file → Executable file
215
app/build.gradle
Normal file → Executable file
@ -1,13 +1,15 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
|
||||
|
||||
//noinspection GradleDependency
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
|
||||
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'com.github.zellius.shortcut-helper'
|
||||
// Realm (EH)
|
||||
apply plugin: 'realm-android'
|
||||
|
||||
shortcutHelper.filePath = './shortcuts.xml'
|
||||
|
||||
@ -32,21 +34,22 @@ ext {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion AndroidConfig.compileSdk
|
||||
buildToolsVersion AndroidConfig.buildTools
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '29.0.3'
|
||||
publishNonDefault true
|
||||
|
||||
defaultConfig {
|
||||
applicationId "eu.kanade.tachiyomi"
|
||||
minSdkVersion AndroidConfig.minSdk
|
||||
targetSdkVersion AndroidConfig.targetSdk
|
||||
applicationId "eu.kanade.tachiyomi.sy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
versionCode 50
|
||||
versionName "0.10.4"
|
||||
versionCode 1
|
||||
versionName "0.9.2.7"
|
||||
|
||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
||||
buildConfigField "boolean", "INCLUDE_UPDATER", "false"
|
||||
buildConfigField "boolean", "INCLUDE_UPDATER", "true"
|
||||
|
||||
multiDexEnabled true
|
||||
|
||||
@ -55,23 +58,28 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
viewBinding {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
versionNameSuffix "-${getCommitCount()}"
|
||||
applicationIdSuffix ".debug"
|
||||
ext.enableCrashlytics = false
|
||||
}
|
||||
releaseTest {
|
||||
applicationIdSuffix ".rt"
|
||||
// minifyEnabled true
|
||||
// shrinkResources true
|
||||
zipAlignEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
release {
|
||||
postprocessing {
|
||||
obfuscate false
|
||||
optimizeCode true
|
||||
removeUnusedCode false
|
||||
removeUnusedResources true
|
||||
proguardFiles 'proguard-rules.pro'
|
||||
}
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
zipAlignEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,42 +90,41 @@ android {
|
||||
buildConfigField "boolean", "INCLUDE_UPDATER", "true"
|
||||
dimension "default"
|
||||
}
|
||||
fdroid {
|
||||
dimension "default"
|
||||
}
|
||||
dev {
|
||||
resConfigs "en", "xxhdpi"
|
||||
dimension "default"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility 1.8
|
||||
targetCompatibility 1.8
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/DEPENDENCIES'
|
||||
exclude 'LICENSE.txt'
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
exclude 'META-INF/NOTICE'
|
||||
}
|
||||
exclude 'META-INF/*.kotlin_module'
|
||||
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
// Compatibility for two RxJava versions (EXH)
|
||||
exclude 'META-INF/rxjava.properties'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
disable 'ExtraTranslation'
|
||||
|
||||
abortOnError false
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,34 +134,30 @@ androidExtensions {
|
||||
|
||||
dependencies {
|
||||
|
||||
// AndroidX libraries
|
||||
implementation 'androidx.annotation:annotation:1.1.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0-alpha01'
|
||||
implementation 'androidx.biometric:biometric:1.0.1'
|
||||
implementation 'androidx.browser:browser:1.2.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.4.0-alpha01'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha05'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
||||
// Modified dependencies
|
||||
implementation 'com.github.inorichi:subsampling-scale-image-view:ac0dae7'
|
||||
implementation 'com.github.inorichi:junrar-android:634c1f5'
|
||||
|
||||
final lifecycle_version = '2.3.0-alpha06'
|
||||
// Android support library
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.annotation:annotation:1.1.0'
|
||||
implementation 'androidx.browser:browser:1.2.0'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'androidx.biometric:biometric:1.0.1'
|
||||
|
||||
final lifecycle_version = '2.2.0'
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||
|
||||
// Job scheduling
|
||||
final work_version = '2.4.0'
|
||||
implementation "androidx.work:work-runtime:$work_version"
|
||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||
|
||||
// UI library
|
||||
implementation 'com.google.android.material:material:1.3.0-alpha02'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
|
||||
standardImplementation 'com.google.firebase:firebase-core:17.4.4'
|
||||
standardImplementation 'com.google.firebase:firebase-core:17.4.1'
|
||||
|
||||
// ReactiveX
|
||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||
@ -163,14 +166,10 @@ dependencies {
|
||||
implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
|
||||
|
||||
// Network client
|
||||
final okhttp_version = '4.8.1'
|
||||
final okhttp_version = '4.7.2'
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
|
||||
implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttp_version"
|
||||
implementation 'com.squareup.okio:okio:2.7.0'
|
||||
|
||||
// TLS 1.3 support for Android < 10
|
||||
implementation 'org.conscrypt:conscrypt-android:2.4.0'
|
||||
implementation 'com.squareup.okio:okio:2.6.0'
|
||||
|
||||
// REST
|
||||
final retrofit_version = '2.9.0'
|
||||
@ -183,24 +182,35 @@ dependencies {
|
||||
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
||||
|
||||
// JavaScript engine
|
||||
implementation 'com.squareup.duktape:duktape-android:1.3.0'
|
||||
implementation 'com.squareup.duktape:duktape-android:1.2.0' // Stuck on 1.2.0 to fix MangaPlus extension
|
||||
|
||||
// Disk
|
||||
implementation 'com.jakewharton:disklrucache:2.0.2'
|
||||
implementation 'com.github.inorichi:unifile:e9ee588'
|
||||
implementation 'com.github.inorichi:junrar-android:634c1f5'
|
||||
|
||||
// HTML parser
|
||||
implementation 'org.jsoup:jsoup:1.13.1'
|
||||
|
||||
// Job scheduling
|
||||
final work_version = '2.3.4'
|
||||
implementation "androidx.work:work-runtime:$work_version"
|
||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||
|
||||
// [EXH] Android 7 SSL Workaround
|
||||
implementation 'com.google.android.gms:play-services-safetynet:17.0.0'
|
||||
|
||||
// Changelog
|
||||
implementation 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
|
||||
|
||||
// Database
|
||||
implementation 'androidx.sqlite:sqlite:2.1.0'
|
||||
implementation 'com.github.inorichi.storio:storio-common:8be19de@aar'
|
||||
implementation 'com.github.inorichi.storio:storio-sqlite:8be19de@aar'
|
||||
implementation 'io.requery:sqlite-android:3.32.2'
|
||||
implementation 'io.requery:sqlite-android:3.31.0'
|
||||
|
||||
// Preferences
|
||||
implementation 'com.github.tfcporciuncula:flow-preferences:1.3.0'
|
||||
implementation 'com.f2prateek.rx.preferences:rx-preferences:1.0.2'
|
||||
implementation 'com.github.tfcporciuncula:flow-preferences:1.1.1'
|
||||
|
||||
// Model View Presenter
|
||||
final nucleus_version = '3.0.0'
|
||||
@ -211,18 +221,17 @@ dependencies {
|
||||
implementation "com.github.inorichi.injekt:injekt-core:65b0440"
|
||||
|
||||
// Image library
|
||||
final glide_version = '4.11.0'
|
||||
final glide_version = '4.10.0'
|
||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
|
||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
||||
|
||||
implementation 'com.github.tachiyomiorg:subsampling-scale-image-view:bff2806'
|
||||
|
||||
// Logging
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
|
||||
// Crash reports
|
||||
implementation 'ch.acra:acra-http:5.7.0'
|
||||
final acra_version = '5.5.0'
|
||||
implementation "ch.acra:acra-http:$acra_version"
|
||||
|
||||
// Sort
|
||||
implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1'
|
||||
@ -249,21 +258,16 @@ dependencies {
|
||||
implementation("com.bluelinelabs:conductor-support:2.1.5") {
|
||||
exclude group: "com.android.support"
|
||||
}
|
||||
implementation 'com.github.tachiyomiorg:conductor-support-preference:1.1.1'
|
||||
implementation 'com.github.inorichi:conductor-support-preference:a32c357'
|
||||
|
||||
// FlowBinding
|
||||
final flowbinding_version = '0.12.0'
|
||||
final flowbinding_version = '0.11.1'
|
||||
implementation "io.github.reactivecircus.flowbinding:flowbinding-android:$flowbinding_version"
|
||||
implementation "io.github.reactivecircus.flowbinding:flowbinding-appcompat:$flowbinding_version"
|
||||
implementation "io.github.reactivecircus.flowbinding:flowbinding-recyclerview:$flowbinding_version"
|
||||
implementation "io.github.reactivecircus.flowbinding:flowbinding-swiperefreshlayout:$flowbinding_version"
|
||||
implementation "io.github.reactivecircus.flowbinding:flowbinding-viewpager:$flowbinding_version"
|
||||
|
||||
// Licenses
|
||||
final aboutlibraries_version = '8.3.0'
|
||||
implementation "com.mikepenz:aboutlibraries-core:$aboutlibraries_version"
|
||||
implementation "com.mikepenz:aboutlibraries:$aboutlibraries_version"
|
||||
|
||||
// Tests
|
||||
testImplementation 'junit:junit:4.13'
|
||||
testImplementation 'org.assertj:assertj-core:3.12.2'
|
||||
@ -275,17 +279,64 @@ dependencies {
|
||||
testImplementation "org.robolectric:shadows-play-services:$robolectric_version"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
|
||||
final coroutines_version = '1.3.8'
|
||||
final coroutines_version = '1.3.7'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$coroutines_version"
|
||||
|
||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
|
||||
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
|
||||
|
||||
// Debug tool; see https://fbflipper.com/
|
||||
// debugImplementation 'com.facebook.flipper:flipper:0.50.0'
|
||||
// debugImplementation 'com.facebook.soloader:soloader:0.9.0'
|
||||
// Text distance (EH)
|
||||
implementation 'info.debatty:java-string-similarity:1.2.1'
|
||||
|
||||
// Pin lock view (EH)
|
||||
implementation 'com.github.jawnnypoo:pinlockview:2.2.0'
|
||||
|
||||
// Reprint (EH)
|
||||
implementation 'com.github.ajalt.reprint:core:3.2.1@aar'
|
||||
implementation 'com.github.ajalt.reprint:rxjava:3.2.1@aar' // optional: the RxJava 1 interface
|
||||
|
||||
// Swirl (EH)
|
||||
implementation 'com.mattprecious.swirl:swirl:1.2.0'
|
||||
|
||||
// RxJava 2 interop for Realm (EH)
|
||||
implementation 'com.github.akarnokd:rxjava2-interop:0.13.7'
|
||||
|
||||
// Firebase (EH)
|
||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
|
||||
|
||||
// Better logging (EH)
|
||||
implementation 'com.elvishew:xlog:1.6.1'
|
||||
|
||||
// Time utils (EH)
|
||||
def typed_time_version = '1.0.2'
|
||||
implementation "com.github.kizitonwose.time:time:$typed_time_version"
|
||||
implementation "com.github.kizitonwose.time:time-android:$typed_time_version"
|
||||
|
||||
// Debug utils (EH)
|
||||
debugImplementation 'com.ms-square:debugoverlay:1.1.3'
|
||||
releaseTestImplementation 'com.ms-square:debugoverlay:1.1.3'
|
||||
releaseImplementation 'com.ms-square:debugoverlay-no-op:1.1.3'
|
||||
testImplementation 'com.ms-square:debugoverlay-no-op:1.1.3'
|
||||
|
||||
// Humanize (EH)
|
||||
implementation 'com.github.mfornos:humanize-slim:1.2.2'
|
||||
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
|
||||
final def markwon_version = '4.1.0'
|
||||
|
||||
implementation "io.noties.markwon:core:$markwon_version"
|
||||
implementation "io.noties.markwon:ext-strikethrough:$markwon_version"
|
||||
implementation "io.noties.markwon:ext-tables:$markwon_version"
|
||||
implementation "io.noties.markwon:html:$markwon_version"
|
||||
implementation "io.noties.markwon:image:$markwon_version"
|
||||
implementation "io.noties.markwon:linkify:$markwon_version"
|
||||
|
||||
implementation 'com.google.guava:guava:27.0.1-android'
|
||||
}
|
||||
|
||||
buildscript {
|
||||
@ -304,7 +355,7 @@ repositories {
|
||||
|
||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api-markers
|
||||
tasks.withType(AbstractKotlinCompile).all {
|
||||
kotlinOptions.freeCompilerArgs += ["-Xopt-in=kotlin.Experimental"]
|
||||
kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental"]
|
||||
}
|
||||
|
||||
// Duplicating Hebrew string assets due to some locale code issues on different devices
|
||||
@ -318,4 +369,6 @@ preBuild.dependsOn(ktlintFormat, copyResources)
|
||||
|
||||
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
// Firebase (EH)
|
||||
apply plugin: 'io.fabric'
|
||||
}
|
||||
|
195
app/proguard-rules.pro
vendored
195
app/proguard-rules.pro
vendored
@ -11,6 +11,13 @@
|
||||
-keep class com.github.salomonbrys.kotson.** { *; }
|
||||
-keep class com.squareup.duktape.** { *; }
|
||||
|
||||
# === Keep EH classes
|
||||
-keep class exh.** { *; }
|
||||
-keep class xyz.nulldev.** { *; }
|
||||
|
||||
# === Keep RxAndroid, https://github.com/ReactiveX/RxAndroid/issues/350
|
||||
-keep class rx.android.** { *; }
|
||||
|
||||
# Design library
|
||||
-dontwarn com.google.android.material.**
|
||||
-keep class com.google.android.material.** { *; }
|
||||
@ -23,20 +30,6 @@
|
||||
<init>();
|
||||
}
|
||||
|
||||
# OkHttp
|
||||
-dontwarn okhttp3.**
|
||||
-dontwarn okio.**
|
||||
-dontwarn javax.annotation.**
|
||||
-dontwarn retrofit2.Platform$Java8
|
||||
|
||||
# Glide specific rules #
|
||||
# https://github.com/bumptech/glide
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
|
||||
# RxJava 1.1.0
|
||||
-dontwarn sun.misc.**
|
||||
@ -54,20 +47,186 @@
|
||||
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
||||
}
|
||||
|
||||
# ReactiveNetwork
|
||||
-dontwarn com.github.pwittchen.reactivenetwork.**
|
||||
-dontnote rx.internal.util.PlatformDependent
|
||||
|
||||
## GSON ##
|
||||
# === Reactive network: https://github.com/pwittchen/ReactiveNetwork/tree/v0.12.4#proguard-configuration
|
||||
-dontwarn com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
|
||||
-dontwarn io.reactivex.functions.Function
|
||||
-dontwarn rx.internal.util.**
|
||||
-dontwarn sun.misc.Unsafe
|
||||
|
||||
# === Okhttp: https://github.com/square/okhttp/blob/3637fc56f70f87da696847defd311dbfb28e87b5/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro
|
||||
# JSR 305 annotations are for embedding nullability information.
|
||||
-dontwarn javax.annotation.**
|
||||
# A resource is loaded with a relative path so the package of this class must be preserved.
|
||||
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
|
||||
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.*
|
||||
# OkHttp platform used only on JVM and when Conscrypt dependency is available.
|
||||
-dontwarn okhttp3.internal.platform.ConscryptPlatform
|
||||
|
||||
# === Okio: https://github.com/square/okio/tree/9b8545e7fa267c9d89753283990f24a35cd69cd6#proguard
|
||||
-dontwarn okio.**
|
||||
|
||||
# === GSON: https://raw.githubusercontent.com/google/gson/master/examples/android-proguard-example/proguard.cfg
|
||||
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
||||
# removes such information by default, so configure it to keep all of it.
|
||||
-keepattributes Signature
|
||||
|
||||
# For using GSON @Expose annotation
|
||||
-keepattributes *Annotation*
|
||||
|
||||
# Gson specific classes
|
||||
-keep class sun.misc.Unsafe { *; }
|
||||
-dontwarn sun.misc.**
|
||||
#-keep class com.google.gson.stream.** { *; }
|
||||
|
||||
# Application classes that will be serialized/deserialized over Gson
|
||||
-keep class com.google.gson.examples.android.model.** { <fields>; }
|
||||
|
||||
# Prevent proguard from stripping interface information from TypeAdapterFactory,
|
||||
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
||||
-keep class * implements com.google.gson.TypeAdapterFactory
|
||||
-keep class * implements com.google.gson.JsonSerializer
|
||||
-keep class * implements com.google.gson.JsonDeserializer
|
||||
|
||||
# Prevent R8 from leaving Data object members always null
|
||||
-keepclassmembers,allowobfuscation class * {
|
||||
@com.google.gson.annotations.SerializedName <fields>;
|
||||
}
|
||||
|
||||
# == Nucleus
|
||||
-keepclassmembers class * extends nucleus.presenter.Presenter {
|
||||
<init>();
|
||||
}
|
||||
|
||||
# TODO Changeloglib? Does it need proguard?
|
||||
|
||||
# === Injekt
|
||||
## From original config: "Attempt to fix: java.lang.NoClassDefFoundError: uy.kohesive.injekt.registry.default.DefaultRegistrar$NOKEY$1"
|
||||
-keep class uy.kohesive.injekt.** { *; }
|
||||
|
||||
|
||||
# === Glide
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
|
||||
-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder
|
||||
|
||||
# === Glide-transformations: https://github.com/wasabeef/glide-transformations/blob/3aa8e53c6a51b8351d312f802ba1354c5b115168/transformations/proguard-rules.txt
|
||||
-dontwarn jp.co.cyberagent.android.gpuimage.**
|
||||
|
||||
# === Conductor
|
||||
# This isn't in the consumer proguard rules yet: https://github.com/bluelinelabs/Conductor/pull/550/files
|
||||
-keepclassmembers public class * extends com.bluelinelabs.conductor.ControllerChangeHandler {
|
||||
public <init>();
|
||||
}
|
||||
|
||||
# === RxBinding
|
||||
-dontwarn com.google.auto.value.AutoValue
|
||||
|
||||
# === Crashlytics
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-keep class com.crashlytics.** { *; }
|
||||
-dontwarn com.crashlytics.**
|
||||
|
||||
# === Humanize + Guava: https://github.com/google/guava/wiki/UsingProGuardWithGuava
|
||||
-dontwarn javax.lang.model.element.Modifier
|
||||
-keep class org.ocpsoft.prettytime.i18n.**
|
||||
|
||||
# Note: We intentionally don't add the flags we'd need to make Enums work.
|
||||
# That's because the Proguard configuration required to make it work on
|
||||
# optimized code would preclude lots of optimization, like converting enums
|
||||
# into ints.
|
||||
|
||||
# Throwables uses internal APIs for lazy stack trace resolution
|
||||
-dontnote sun.misc.SharedSecrets
|
||||
-keep class sun.misc.SharedSecrets {
|
||||
*** getJavaLangAccess(...);
|
||||
}
|
||||
-dontnote sun.misc.JavaLangAccess
|
||||
-keep class sun.misc.JavaLangAccess {
|
||||
*** getStackTraceElement(...);
|
||||
*** getStackTraceDepth(...);
|
||||
}
|
||||
|
||||
# FinalizableReferenceQueue calls this reflectively
|
||||
# Proguard is intelligent enough to spot the use of reflection onto this, so we
|
||||
# only need to keep the names, and allow it to be stripped out if
|
||||
# FinalizableReferenceQueue is unused.
|
||||
-keepnames class com.google.common.base.internal.Finalizer {
|
||||
*** startFinalizer(...);
|
||||
}
|
||||
# However, it cannot "spot" that this method needs to be kept IF the class is.
|
||||
-keepclassmembers class com.google.common.base.internal.Finalizer {
|
||||
*** startFinalizer(...);
|
||||
}
|
||||
-keepnames class com.google.common.base.FinalizableReference {
|
||||
void finalizeReferent();
|
||||
}
|
||||
-keepclassmembers class com.google.common.base.FinalizableReference {
|
||||
void finalizeReferent();
|
||||
}
|
||||
|
||||
# Striped64, LittleEndianByteArray, UnsignedBytes, AbstractFuture
|
||||
-dontwarn sun.misc.Unsafe
|
||||
|
||||
# Striped64 appears to make some assumptions about object layout that
|
||||
# really might not be safe. This should be investigated.
|
||||
-keepclassmembers class com.google.common.cache.Striped64 {
|
||||
*** base;
|
||||
*** busy;
|
||||
}
|
||||
-keepclassmembers class com.google.common.cache.Striped64$Cell {
|
||||
<fields>;
|
||||
}
|
||||
|
||||
-dontwarn java.lang.SafeVarargs
|
||||
|
||||
-keep class java.lang.Throwable {
|
||||
*** addSuppressed(...);
|
||||
}
|
||||
|
||||
# Futures.getChecked, in both of its variants, is incompatible with proguard.
|
||||
|
||||
# Used by AtomicReferenceFieldUpdater and sun.misc.Unsafe
|
||||
-keepclassmembers class com.google.common.util.concurrent.AbstractFuture** {
|
||||
*** waiters;
|
||||
*** value;
|
||||
*** listeners;
|
||||
*** thread;
|
||||
*** next;
|
||||
}
|
||||
-keepclassmembers class com.google.common.util.concurrent.AtomicDouble {
|
||||
*** value;
|
||||
}
|
||||
-keepclassmembers class com.google.common.util.concurrent.AggregateFutureState {
|
||||
*** remaining;
|
||||
*** seenExceptions;
|
||||
}
|
||||
|
||||
# Since Unsafe is using the field offsets of these inner classes, we don't want
|
||||
# to have class merging or similar tricks applied to these classes and their
|
||||
# fields. It's safe to allow obfuscation, since the by-name references are
|
||||
# already preserved in the -keep statement above.
|
||||
-keep,allowshrinking,allowobfuscation class com.google.common.util.concurrent.AbstractFuture** {
|
||||
<fields>;
|
||||
}
|
||||
|
||||
# Futures.getChecked (which often won't work with Proguard anyway) uses this. It
|
||||
# has a fallback, but again, don't use Futures.getChecked on Android regardless.
|
||||
-dontwarn java.lang.ClassValue
|
||||
|
||||
# MoreExecutors references AppEngine
|
||||
-dontnote com.google.appengine.api.ThreadManager
|
||||
-keep class com.google.appengine.api.ThreadManager {
|
||||
static *** currentRequestThreadFactory(...);
|
||||
}
|
||||
-dontnote com.google.apphosting.api.ApiProxy
|
||||
-keep class com.google.apphosting.api.ApiProxy {
|
||||
static *** getCurrentEnvironment (...);
|
||||
}
|
||||
|
@ -16,19 +16,82 @@
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#607D8B"/>
|
||||
<path
|
||||
android:pathData="M54,54.5m-25.5,0a25.5,25.5 0,1 1,51 0a25.5,25.5 0,1 1,-51 0"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#000"/>
|
||||
android:name="path_3"
|
||||
android:pathData="M 54 54.5 M 28.5 54.5 C 28.5 47.74 31.188 41.249 35.969 36.469 C 40.749 31.688 47.24 29 54 29 C 60.76 29 67.251 31.688 72.031 36.469 C 76.812 41.249 79.5 47.74 79.5 54.5 C 79.5 61.26 76.812 67.751 72.031 72.531 C 67.251 77.312 60.76 80 54 80 C 47.24 80 40.749 77.312 35.969 72.531 C 31.188 67.751 28.5 61.26 28.5 54.5"
|
||||
android:fillColor="#000"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M54,54.5m-25.5,0a25.5,25.5 0,1 1,51 0a25.5,25.5 0,1 1,-51 0"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#CE2828"/>
|
||||
android:name="path_4"
|
||||
android:pathData="M 54 54.5 M 28.5 54.5 C 28.5 47.74 31.188 41.249 35.969 36.469 C 40.749 31.688 47.24 29 54 29 C 60.76 29 67.251 31.688 72.031 36.469 C 76.812 41.249 79.5 47.74 79.5 54.5 C 79.5 61.26 76.812 67.751 72.031 72.531 C 67.251 77.312 60.76 80 54 80 C 47.24 80 40.749 77.312 35.969 72.531 C 31.188 67.751 28.5 61.26 28.5 54.5"
|
||||
android:fillColor="#CE2828"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M54,54.5m-19.94,0a19.94,19.94 0,1 1,39.87 0a19.94,19.94 0,1 1,-39.87 0"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#FFF"/>
|
||||
android:name="path_5"
|
||||
android:pathData="M 54 54.5 M 34.06 54.5 C 33.964 50.23 35.243 46.04 37.707 42.551 C 40.171 39.062 43.692 36.455 47.748 35.117 C 51.805 33.779 56.185 33.779 60.242 35.117 C 64.298 36.455 67.819 39.062 70.283 42.551 C 72.747 46.04 74.026 50.23 73.93 54.5 C 74.026 58.77 72.747 62.96 70.283 66.449 C 67.819 69.938 64.298 72.545 60.242 73.883 C 56.185 75.221 51.805 75.221 47.748 73.883 C 43.692 72.545 40.171 69.938 37.707 66.449 C 35.243 62.96 33.964 58.77 34.06 54.5"
|
||||
android:fillColor="#FFF"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M52.04,46.3L47.42,46.3C46.14,46.3 44.93,46.23 44.2,46.14L44.2,49.76C45,49.65 46.16,49.6 47.42,49.6L60.58,49.6C61.86,49.6 63.02,49.65 63.82,49.76L63.82,46.14C63.09,46.23 61.86,46.3 60.58,46.3L55.69,46.3L55.69,45.07C55.69,44.43 55.73,43.95 55.82,43.45L51.9,43.45C51.99,44 52.04,44.43 52.04,45.07L52.04,46.3ZM46.78,60.68C45.46,60.68 44.29,60.63 43.45,60.52L43.45,64.14C44.34,64.03 45.46,63.98 46.78,63.98L61.29,63.98C62.57,63.98 63.71,64.03 64.57,64.14L64.57,60.52C63.73,60.63 62.57,60.68 61.29,60.68L58.24,60.68C59.33,58.06 59.99,56.23 60.7,53.91C61.34,51.81 61.34,51.81 61.56,51.13L57.58,50.06C57.51,50.93 57.37,51.52 56.89,53.41C56.19,56.14 55.32,58.74 54.5,60.68L46.78,60.68ZM46.48,51.36C47.55,54.02 48.28,56.53 49.03,60.15L52.66,58.9C51.65,54.98 50.92,52.66 49.94,50.11L46.48,51.36Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#000"/>
|
||||
android:name="path_6"
|
||||
android:pathData="M 54.174 36.266 C 64.147 36.266 72.234 44.397 72.234 54.426 C 72.234 64.459 64.147 72.593 54.174 72.593 C 44.197 72.593 36.113 64.459 36.113 54.426 C 36.113 44.397 44.197 36.266 54.174 36.266 Z"
|
||||
android:fillColor="#ffcc4d"
|
||||
android:strokeColor="#ffcc4d"
|
||||
android:strokeWidth="4.628571428571428"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"/>
|
||||
<path
|
||||
android:name="path_7"
|
||||
android:pathData="M 48.774 45.158 C 49.988 45.158 50.973 46.452 50.973 48.05 C 50.973 49.65 49.988 50.946 48.774 50.946 C 47.559 50.946 46.576 49.65 46.576 48.05 C 46.576 46.452 47.559 45.158 48.774 45.158 Z"
|
||||
android:fillColor="#674600"
|
||||
android:strokeColor="#674600"
|
||||
android:strokeWidth="0.1"
|
||||
android:strokeLineJoin="round"/>
|
||||
<path
|
||||
android:name="path_8"
|
||||
android:pathData="M 62.02 45.158 C 63.235 45.158 64.219 46.452 64.219 48.05 C 64.219 49.65 63.235 50.946 62.02 50.946 C 60.805 50.946 59.821 49.65 59.821 48.05 C 59.821 46.452 60.805 45.158 62.02 45.158 Z"
|
||||
android:fillColor="#674600"
|
||||
android:strokeColor="#674600"
|
||||
android:strokeWidth="0.1"
|
||||
android:strokeLineJoin="round"/>
|
||||
<path
|
||||
android:name="path_9"
|
||||
android:pathData="M 44.687 42.102 C 44.687 42.102 48.404 40.049 53.119 42.102"
|
||||
android:strokeColor="#674600"
|
||||
android:strokeWidth="2"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:name="path_10"
|
||||
android:pathData="M 58.035 42.102 C 58.035 42.102 61.751 40.049 66.465 42.102"
|
||||
android:strokeColor="#674600"
|
||||
android:strokeWidth="2"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:name="path_11"
|
||||
android:pathData="M 49.191 56.685 C 49.191 56.685 51.703 59.685 56.992 59.685"
|
||||
android:strokeColor="#674600"
|
||||
android:strokeWidth="2"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:name="path_12"
|
||||
android:pathData="M 34.072 67.511 C 34.072 67.511 34.524 66.248 37.476 63.634 C 37.476 63.634 39.143 61.991 37.876 58.114 C 37.876 58.114 37.534 56.06 39.681 57.255 C 39.681 57.255 44.129 60.46 40.871 65 C 40.871 65 40.241 66.117 41.652 65.78 L 53.939 63.103 C 53.939 63.103 55.275 62.752 55.535 64.163 C 55.535 64.163 55.696 65.372 54.641 65.672 L 47.742 67.378 C 47.742 67.378 50.72 69.597 47.742 70.825 C 47.742 70.825 50.294 72.779 47.336 74.268 C 47.336 74.268 49.276 76.015 46.407 77.019 C 46.407 77.019 42.525 78.474 39.209 77.831 C 35.509 77.103 31.947 72.114 34.072 67.511 Z"
|
||||
android:fillColor="#f4900c"
|
||||
android:strokeColor="#f4900c"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.1"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:name="path_13"
|
||||
android:pathData="M 56.182 67.511 C 56.182 67.511 56.633 66.248 59.585 63.634 C 59.585 63.634 61.253 61.991 59.985 58.114 C 59.985 58.114 59.642 56.06 61.789 57.255 C 61.789 57.255 66.239 60.46 62.98 65 C 62.98 65 62.351 66.117 63.761 65.78 L 76.066 63.103 C 76.066 63.103 77.386 62.752 77.641 64.163 C 77.641 64.163 77.812 65.372 76.746 65.672 L 69.85 67.378 C 69.85 67.378 72.829 69.597 69.85 70.825 C 69.85 70.825 72.404 72.779 69.446 74.268 C 69.446 74.268 71.387 76.015 68.516 77.019 C 68.516 77.019 64.633 78.474 61.317 77.831 C 57.618 77.103 54.057 72.114 56.182 67.511 Z"
|
||||
android:fillColor="#f4900c"
|
||||
android:strokeColor="#f4900c"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.1"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
|
6
app/src/debug/res/drawable/ic_migrate_direction.xml
Normal file
6
app/src/debug/res/drawable/ic_migrate_direction.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<vector android:height="100dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="100dp" xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 -5,5z"/>
|
||||
</vector>
|
155
app/src/main/AndroidManifest.xml
Normal file → Executable file
155
app/src/main/AndroidManifest.xml
Normal file → Executable file
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="eu.kanade.tachiyomi">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
@ -12,6 +13,14 @@
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.GET_TASKS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.PACKAGE_USAGE_STATS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<!-- Lock vibrate -->
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
@ -24,6 +33,7 @@
|
||||
android:largeHeap="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@style/Theme.Tachiyomi.Light"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity
|
||||
@ -39,6 +49,11 @@
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.main.ForceCloseActivity"
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
<activity
|
||||
android:name=".ui.main.DeepLinkActivity"
|
||||
android:launchMode="singleTask"
|
||||
@ -118,6 +133,13 @@
|
||||
android:name=".extension.util.ExtensionInstallActivity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||
|
||||
<activity
|
||||
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
|
||||
android:theme="@style/Theme.MaterialComponents" />
|
||||
<activity
|
||||
android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
|
||||
android:theme="@style/Theme.MaterialComponents" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
@ -152,6 +174,139 @@
|
||||
android:name=".data.backup.BackupRestoreService"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- EH -->
|
||||
<service
|
||||
android:name="exh.eh.EHentaiUpdateWorker"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name="exh.ui.intercept.InterceptActivity"
|
||||
android:label="TachiyomiEH"
|
||||
android:theme="@style/Theme.EHActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<!-- EH -->
|
||||
<data
|
||||
android:host="g.e-hentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="g.e-hentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="e-hentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="e-hentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- EXH -->
|
||||
<data
|
||||
android:host="exhentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="exhentai.org"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- nhentai -->
|
||||
<data
|
||||
android:host="nhentai.net"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="nhentai.net"
|
||||
android:pathPrefix="/g/"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- Perv Eden -->
|
||||
<data
|
||||
android:host="www.perveden.com"
|
||||
android:pathPattern="/.*/.*-manga/.*"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="www.perveden.com"
|
||||
android:pathPattern="/.*/.*-manga/.*"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- Hentai Cafe -->
|
||||
<data
|
||||
android:host="hentai.cafe"
|
||||
android:pathPattern="/.*/.*"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="hentai.cafe"
|
||||
android:pathPattern="/.*/.*"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- Tsumino -->
|
||||
<data
|
||||
android:host="www.tsumino.com"
|
||||
android:pathPrefix="/Book/Info/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="www.tsumino.com"
|
||||
android:pathPrefix="/Book/Info/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.tsumino.com"
|
||||
android:pathPrefix="/Read/View/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="www.tsumino.com"
|
||||
android:pathPrefix="/Read/View/"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- Hitomi.la -->
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/galleries/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/reader/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/galleries/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/reader/"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- Pururin.io -->
|
||||
<data
|
||||
android:host="pururin.io"
|
||||
android:pathPrefix="/gallery/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="pururin.io"
|
||||
android:pathPrefix="/gallery/"
|
||||
android:scheme="https" />
|
||||
|
||||
<!-- HBrowse -->
|
||||
<data
|
||||
android:host="www.hbrowse.com"
|
||||
android:pathPrefix="/"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="www.hbrowse.com"
|
||||
android:pathPrefix="/"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="exh.ui.captcha.BrowserActionActivity"
|
||||
android:theme="@style/Theme.EHActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
0
app/src/main/ic_launcher-web.png
Normal file → Executable file
0
app/src/main/ic_launcher-web.png
Normal file → Executable file
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
183
app/src/main/java/eu/kanade/tachiyomi/App.kt
Normal file → Executable file
183
app/src/main/java/eu/kanade/tachiyomi/App.kt
Normal file → Executable file
@ -3,61 +3,79 @@ package eu.kanade.tachiyomi
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.multidex.MultiDex
|
||||
import com.elvishew.xlog.LogConfiguration
|
||||
import com.elvishew.xlog.LogLevel
|
||||
import com.elvishew.xlog.XLog
|
||||
import com.elvishew.xlog.printer.AndroidPrinter
|
||||
import com.elvishew.xlog.printer.Printer
|
||||
import com.elvishew.xlog.printer.file.FilePrinter
|
||||
import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy
|
||||
import com.elvishew.xlog.printer.file.clean.FileLastModifiedCleanStrategy
|
||||
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
|
||||
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
|
||||
import com.google.android.gms.common.GooglePlayServicesRepairableException
|
||||
import com.google.android.gms.security.ProviderInstaller
|
||||
import com.kizitonwose.time.days
|
||||
import com.ms_square.debugoverlay.DebugOverlay
|
||||
import com.ms_square.debugoverlay.modules.FpsModule
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.main.ForceCloseActivity
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import java.security.Security
|
||||
import org.acra.ACRA
|
||||
import org.acra.annotation.AcraCore
|
||||
import org.acra.annotation.AcraHttpSender
|
||||
import org.acra.sender.HttpSender
|
||||
import org.conscrypt.Conscrypt
|
||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import exh.debug.DebugToggles
|
||||
import exh.log.CrashlyticsPrinter
|
||||
import exh.log.EHDebugModeOverlay
|
||||
import exh.log.EHLogLevel
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import java.io.File
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import javax.net.ssl.SSLContext
|
||||
import kotlin.concurrent.thread
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.InjektScope
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
||||
|
||||
@AcraCore(
|
||||
buildConfigClass = BuildConfig::class,
|
||||
excludeMatchingSharedPreferencesKeys = [".*username.*", ".*password.*", ".*token.*"]
|
||||
)
|
||||
@AcraHttpSender(
|
||||
uri = "https://tachiyomi.kanade.eu/crash_report",
|
||||
httpMethod = HttpSender.Method.PUT
|
||||
)
|
||||
open class App : Application(), LifecycleObserver {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
||||
setupExhLogging() // EXH logging
|
||||
|
||||
// Debug tool; see https://fbflipper.com/
|
||||
// SoLoader.init(this, false)
|
||||
// if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
|
||||
// val client = AndroidFlipperClient.getInstance(this)
|
||||
// client.addPlugin(InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()))
|
||||
// client.addPlugin(DatabasesFlipperPlugin(this))
|
||||
// client.start()
|
||||
// }
|
||||
workaroundAndroid7BrokenSSL()
|
||||
|
||||
// TLS 1.3 support for Android < 10
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
Security.insertProviderAt(Conscrypt.newProvider(), 1)
|
||||
// Enforce WebView availability
|
||||
if (!WebViewUtil.supportsWebView(this)) {
|
||||
toast(R.string.information_webview_required, Toast.LENGTH_LONG)
|
||||
ForceCloseActivity.closeApp(this)
|
||||
}
|
||||
|
||||
Injekt = InjektScope(DefaultRegistrar())
|
||||
Injekt.importModule(AppModule(this))
|
||||
|
||||
setupAcra()
|
||||
setupNotificationChannels()
|
||||
GlobalScope.launch { deleteOldMetadataRealm() } // Delete old metadata DB (EH)
|
||||
// Reprint.initialize(this) //Setup fingerprint (EH)
|
||||
if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) {
|
||||
setupDebugOverlay()
|
||||
}
|
||||
|
||||
LocaleHelper.updateConfiguration(this, resources.configuration)
|
||||
|
||||
@ -74,6 +92,26 @@ open class App : Application(), LifecycleObserver {
|
||||
LocaleHelper.updateConfiguration(this, newConfig, true)
|
||||
}
|
||||
|
||||
private fun workaroundAndroid7BrokenSSL() {
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N ||
|
||||
Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1
|
||||
) {
|
||||
try {
|
||||
SSLContext.getInstance("TLSv1.2")
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
XLog.e("Could not install Android 7 broken SSL workaround!", e)
|
||||
}
|
||||
|
||||
try {
|
||||
ProviderInstaller.installIfNeeded(applicationContext)
|
||||
} catch (e: GooglePlayServicesRepairableException) {
|
||||
XLog.e("Could not install Android 7 broken SSL workaround!", e)
|
||||
} catch (e: GooglePlayServicesNotAvailableException) {
|
||||
XLog.e("Could not install Android 7 broken SSL workaround!", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||
@Suppress("unused")
|
||||
fun onAppBackgrounded() {
|
||||
@ -83,11 +121,96 @@ open class App : Application(), LifecycleObserver {
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun setupAcra() {
|
||||
ACRA.init(this)
|
||||
}
|
||||
|
||||
protected open fun setupNotificationChannels() {
|
||||
Notifications.createChannels(this)
|
||||
}
|
||||
|
||||
// EXH
|
||||
private fun deleteOldMetadataRealm() {
|
||||
Realm.init(this)
|
||||
val config = RealmConfiguration.Builder()
|
||||
.name("gallery-metadata.realm")
|
||||
.schemaVersion(3)
|
||||
.deleteRealmIfMigrationNeeded()
|
||||
.build()
|
||||
Realm.deleteRealm(config)
|
||||
|
||||
// Delete old paper db files
|
||||
listOf(
|
||||
File(filesDir, "gallery-ex"),
|
||||
File(filesDir, "gallery-perveden"),
|
||||
File(filesDir, "gallery-nhentai")
|
||||
).forEach {
|
||||
if (it.exists()) {
|
||||
thread {
|
||||
it.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EXH
|
||||
private fun setupExhLogging() {
|
||||
EHLogLevel.init(this)
|
||||
|
||||
val logLevel = if (EHLogLevel.shouldLog(EHLogLevel.EXTRA)) {
|
||||
LogLevel.ALL
|
||||
} else {
|
||||
LogLevel.WARN
|
||||
}
|
||||
|
||||
val logConfig = LogConfiguration.Builder()
|
||||
.logLevel(logLevel)
|
||||
.t()
|
||||
.st(2)
|
||||
.nb()
|
||||
.build()
|
||||
|
||||
val printers = mutableListOf<Printer>(AndroidPrinter())
|
||||
|
||||
val logFolder = File(
|
||||
Environment.getExternalStorageDirectory().absolutePath + File.separator +
|
||||
getString(R.string.app_name),
|
||||
"logs"
|
||||
)
|
||||
|
||||
printers += FilePrinter
|
||||
.Builder(logFolder.absolutePath)
|
||||
.fileNameGenerator(object : DateFileNameGenerator() {
|
||||
override fun generateFileName(logLevel: Int, timestamp: Long): String {
|
||||
return super.generateFileName(logLevel, timestamp) + "-${BuildConfig.BUILD_TYPE}"
|
||||
}
|
||||
})
|
||||
.cleanStrategy(FileLastModifiedCleanStrategy(7.days.inMilliseconds.longValue))
|
||||
.backupStrategy(NeverBackupStrategy())
|
||||
.build()
|
||||
|
||||
// Install Crashlytics in prod
|
||||
if (!BuildConfig.DEBUG) {
|
||||
printers += CrashlyticsPrinter(LogLevel.ERROR)
|
||||
}
|
||||
|
||||
XLog.init(
|
||||
logConfig,
|
||||
*printers.toTypedArray()
|
||||
)
|
||||
|
||||
XLog.d("Application booting...")
|
||||
}
|
||||
|
||||
// EXH
|
||||
private fun setupDebugOverlay() {
|
||||
try {
|
||||
DebugOverlay.Builder(this)
|
||||
.modules(FpsModule(), EHDebugModeOverlay(this))
|
||||
.bgColor(Color.parseColor("#7F000000"))
|
||||
.notification(false)
|
||||
.allowSystemLayer(false)
|
||||
.build()
|
||||
.install()
|
||||
} catch (e: IllegalStateException) {
|
||||
// Crashes if app is in background
|
||||
XLog.e("Failed to initialize debug overlay, app in background?", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
6
app/src/main/java/eu/kanade/tachiyomi/AppModule.kt
Normal file → Executable file
6
app/src/main/java/eu/kanade/tachiyomi/AppModule.kt
Normal file → Executable file
@ -11,6 +11,8 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import exh.eh.EHentaiUpdateHelper
|
||||
import io.noties.markwon.Markwon
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import uy.kohesive.injekt.api.InjektModule
|
||||
@ -44,6 +46,10 @@ class AppModule(val app: Application) : InjektModule {
|
||||
|
||||
addSingletonFactory { Gson() }
|
||||
|
||||
addSingletonFactory { EHentaiUpdateHelper(app) }
|
||||
|
||||
addSingletonFactory { Markwon.create(app) }
|
||||
|
||||
// Asynchronously init expensive components for a faster cold start
|
||||
|
||||
GlobalScope.launch { get<PreferencesHelper>() }
|
||||
|
@ -10,6 +10,8 @@ import java.io.File
|
||||
|
||||
object Migrations {
|
||||
|
||||
// TODO NATIVE TACHIYOMI MIGRATIONS ARE FUCKED UP DUE TO DIFFERING VERSION NUMBERS
|
||||
|
||||
/**
|
||||
* Performs a migration when the application is updated.
|
||||
*
|
||||
|
@ -1,5 +0,0 @@
|
||||
package eu.kanade.tachiyomi.annoations
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class Nsfw
|
@ -7,7 +7,6 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import androidx.core.net.toUri
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||
@ -107,7 +106,7 @@ class BackupCreateService : Service() {
|
||||
val backupFlags = intent.getIntExtra(BackupConst.EXTRA_FLAGS, 0)
|
||||
backupManager = BackupManager(this)
|
||||
|
||||
val backupFileUri = backupManager.createBackup(uri, backupFlags, false)?.toUri()
|
||||
val backupFileUri = Uri.parse(backupManager.createBackup(uri, backupFlags, false))
|
||||
val unifile = UniFile.fromUri(this, backupFileUri)
|
||||
notifier.showBackupComplete(unifile)
|
||||
} catch (e: Exception) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package eu.kanade.tachiyomi.data.backup
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toUri
|
||||
import android.net.Uri
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
@ -18,7 +18,7 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
|
||||
override fun doWork(): Result {
|
||||
val preferences = Injekt.get<PreferencesHelper>()
|
||||
val backupManager = BackupManager(context)
|
||||
val uri = preferences.backupsDirectory().get().toUri()
|
||||
val uri = Uri.parse(preferences.backupsDirectory().get())
|
||||
val flags = BackupCreateService.BACKUP_ALL
|
||||
return try {
|
||||
backupManager.createBackup(uri, flags, true)
|
||||
|
38
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
Normal file → Executable file
38
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
Normal file → Executable file
@ -49,7 +49,9 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||
import exh.eh.EHentaiThrottleManager
|
||||
import kotlin.math.max
|
||||
import rx.Observable
|
||||
import timber.log.Timber
|
||||
@ -297,9 +299,23 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
* @param manga manga that needs updating
|
||||
* @return [Observable] that contains manga
|
||||
*/
|
||||
fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||
return source.fetchChapterList(manga)
|
||||
.map { syncChaptersWithSource(databaseHelper, it, manga, source) }
|
||||
fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>, throttleManager: EHentaiThrottleManager): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||
return (
|
||||
if (source is EHentai) {
|
||||
source.fetchChapterList(manga, throttleManager::throttle)
|
||||
} else {
|
||||
source.fetchChapterList(manga)
|
||||
}
|
||||
).map {
|
||||
if (it.last().chapter_number == -99F) {
|
||||
chapters.forEach { chapter ->
|
||||
chapter.name = "Chapter ${chapter.chapter_number} restored by dummy source"
|
||||
}
|
||||
syncChaptersWithSource(databaseHelper, chapters, manga, source)
|
||||
} else {
|
||||
syncChaptersWithSource(databaseHelper, it, manga, source)
|
||||
}
|
||||
}
|
||||
.doOnNext { pair ->
|
||||
if (pair.first.isNotEmpty()) {
|
||||
chapters.forEach { it.manga_id = manga.id }
|
||||
@ -325,7 +341,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
for (dbCategory in dbCategories) {
|
||||
// If the category is already in the db, assign the id to the file's category
|
||||
// and do nothing
|
||||
if (category.name == dbCategory.name) {
|
||||
if (category.nameLower == dbCategory.nameLower) {
|
||||
category.id = dbCategory.id
|
||||
found = true
|
||||
break
|
||||
@ -350,10 +366,10 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
*/
|
||||
internal fun restoreCategoriesForManga(manga: Manga, categories: List<String>) {
|
||||
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
|
||||
val mangaCategoriesToUpdate = mutableListOf<MangaCategory>()
|
||||
val mangaCategoriesToUpdate = ArrayList<MangaCategory>()
|
||||
for (backupCategoryStr in categories) {
|
||||
for (dbCategory in dbCategories) {
|
||||
if (backupCategoryStr == dbCategory.name) {
|
||||
if (backupCategoryStr.toLowerCase() == dbCategory.nameLower) {
|
||||
mangaCategoriesToUpdate.add(MangaCategory.create(manga, dbCategory))
|
||||
break
|
||||
}
|
||||
@ -362,7 +378,9 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
|
||||
// Update database
|
||||
if (mangaCategoriesToUpdate.isNotEmpty()) {
|
||||
databaseHelper.deleteOldMangasCategories(listOf(manga)).executeAsBlocking()
|
||||
val mangaAsList = ArrayList<Manga>()
|
||||
mangaAsList.add(manga)
|
||||
databaseHelper.deleteOldMangasCategories(mangaAsList).executeAsBlocking()
|
||||
databaseHelper.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
@ -374,7 +392,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
*/
|
||||
internal fun restoreHistoryForManga(history: List<DHistory>) {
|
||||
// List containing history to be updated
|
||||
val historyToBeUpdated = mutableListOf<History>()
|
||||
val historyToBeUpdated = ArrayList<History>()
|
||||
for ((url, lastRead) in history) {
|
||||
val dbHistory = databaseHelper.getHistoryByChapterUrl(url).executeAsBlocking()
|
||||
// Check if history already in database and update
|
||||
@ -408,9 +426,9 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
|
||||
// Get tracks from database
|
||||
val dbTracks = databaseHelper.getTracks(manga).executeAsBlocking()
|
||||
val trackToUpdate = mutableListOf<Track>()
|
||||
val trackToUpdate = ArrayList<Track>()
|
||||
|
||||
tracks.forEach { track ->
|
||||
for (track in tracks) {
|
||||
val service = trackManager.getService(track.sync_id)
|
||||
if (service != null && service.isLogged) {
|
||||
var isInDatabase = false
|
||||
|
@ -34,6 +34,8 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||
import exh.EXHMigrations
|
||||
import exh.eh.EHentaiThrottleManager
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
@ -100,6 +102,8 @@ class BackupRestoreService : Service() {
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
private val throttleManager = EHentaiThrottleManager()
|
||||
|
||||
/**
|
||||
* The progress of a backup restore
|
||||
*/
|
||||
@ -110,6 +114,10 @@ class BackupRestoreService : Service() {
|
||||
*/
|
||||
private var restoreAmount = 0
|
||||
|
||||
private var skippedAmount = 0
|
||||
|
||||
private var totalAmount = 0
|
||||
|
||||
/**
|
||||
* Mapping of source ID to source name from backup data
|
||||
*/
|
||||
@ -169,6 +177,8 @@ class BackupRestoreService : Service() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
|
||||
|
||||
throttleManager.resetThrottle()
|
||||
|
||||
// Cancel any previous job if needed.
|
||||
job?.cancel()
|
||||
val handler = CoroutineExceptionHandler { _, exception ->
|
||||
@ -210,12 +220,25 @@ class BackupRestoreService : Service() {
|
||||
|
||||
val mangasJson = json.get(MANGAS).asJsonArray
|
||||
|
||||
restoreAmount = mangasJson.size() + 1 // +1 for categories
|
||||
val validManga = mangasJson.filter {
|
||||
var manga = backupManager.parser.fromJson<MangaImpl>(it.asJsonObject.get(MANGA))
|
||||
// EXH -->
|
||||
manga = EXHMigrations.migrateBackupEntry(manga)
|
||||
val sourced = backupManager.sourceManager.get(manga.source) != null
|
||||
if (!sourced) {
|
||||
restoreAmount -= 1
|
||||
}
|
||||
sourced
|
||||
}
|
||||
|
||||
totalAmount = mangasJson.size()
|
||||
restoreAmount = validManga.count() + 1 // +1 for categories
|
||||
skippedAmount = mangasJson.size() - validManga.count()
|
||||
restoreProgress = 0
|
||||
errors.clear()
|
||||
|
||||
// Restore categories
|
||||
json.get(CATEGORIES)?.let { restoreCategories(it) }
|
||||
restoreCategories(json.get(CATEGORIES))
|
||||
|
||||
// Store source mapping for error messages
|
||||
sourceMapping = BackupRestoreValidator.getSourceMapping(json)
|
||||
@ -248,7 +271,7 @@ class BackupRestoreService : Service() {
|
||||
}
|
||||
|
||||
private fun restoreManga(mangaJson: JsonObject) {
|
||||
val manga = backupManager.parser.fromJson<MangaImpl>(mangaJson.get(MANGA))
|
||||
var manga = backupManager.parser.fromJson<MangaImpl>(mangaJson.get(MANGA))
|
||||
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(
|
||||
mangaJson.get(CHAPTERS)
|
||||
?: JsonArray()
|
||||
@ -266,13 +289,22 @@ class BackupRestoreService : Service() {
|
||||
?: JsonArray()
|
||||
)
|
||||
|
||||
// EXH -->
|
||||
manga = EXHMigrations.migrateBackupEntry(manga)
|
||||
// <-- EXH
|
||||
|
||||
try {
|
||||
val source = backupManager.sourceManager.get(manga.source)
|
||||
if (source != null) {
|
||||
restoreMangaData(manga, source, chapters, categories, history, tracks)
|
||||
} else {
|
||||
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
|
||||
errors.add(Date() to "${manga.title} - ${getString(R.string.source_not_found_name, sourceName)}")
|
||||
val message = if (manga.source in sourceMapping) {
|
||||
getString(R.string.source_not_found_name, sourceMapping[manga.source])
|
||||
} else {
|
||||
getString(R.string.source_not_found)
|
||||
}
|
||||
|
||||
errors.add(Date() to "${manga.title} - $message")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
errors.add(Date() to "${manga.title} - ${e.message}")
|
||||
@ -395,7 +427,7 @@ class BackupRestoreService : Service() {
|
||||
* @return [Observable] that contains manga
|
||||
*/
|
||||
private fun chapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||
return backupManager.restoreChapterFetchObservable(source, manga, chapters)
|
||||
return backupManager.restoreChapterFetchObservable(source, manga, chapters, throttleManager)
|
||||
// If there's any error, return empty update and continue.
|
||||
.onErrorReturn {
|
||||
errors.add(Date() to "${manga.title} - ${it.message}")
|
||||
|
@ -7,22 +7,16 @@ import com.google.gson.JsonParser
|
||||
import com.google.gson.stream.JsonReader
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
object BackupRestoreValidator {
|
||||
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
private val trackManager: TrackManager by injectLazy()
|
||||
|
||||
/**
|
||||
* Checks for critical backup file data.
|
||||
*
|
||||
* @throws Exception if version or manga cannot be found.
|
||||
* @return List of missing sources or missing trackers.
|
||||
* @return List of required sources.
|
||||
*/
|
||||
fun validate(context: Context, uri: Uri): Results {
|
||||
fun validate(context: Context, uri: Uri): Map<Long, String> {
|
||||
val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader())
|
||||
val json = JsonParser.parseReader(reader).asJsonObject
|
||||
|
||||
@ -32,29 +26,11 @@ object BackupRestoreValidator {
|
||||
throw Exception(context.getString(R.string.invalid_backup_file_missing_data))
|
||||
}
|
||||
|
||||
val mangas = mangasJson.asJsonArray
|
||||
if (mangas.size() == 0) {
|
||||
if (mangasJson.asJsonArray.size() == 0) {
|
||||
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
|
||||
}
|
||||
|
||||
val sources = getSourceMapping(json)
|
||||
val missingSources = sources
|
||||
.filter { sourceManager.get(it.key) == null }
|
||||
.values
|
||||
.sorted()
|
||||
|
||||
val trackers = mangas
|
||||
.filter { it.asJsonObject.has("track") }
|
||||
.flatMap { it.asJsonObject["track"].asJsonArray }
|
||||
.map { it.asJsonObject["s"].asInt }
|
||||
.distinct()
|
||||
val missingTrackers = trackers
|
||||
.mapNotNull { trackManager.getService(it) }
|
||||
.filter { !it.isLogged }
|
||||
.map { it.name }
|
||||
.sorted()
|
||||
|
||||
return Results(missingSources, missingTrackers)
|
||||
return getSourceMapping(json)
|
||||
}
|
||||
|
||||
fun getSourceMapping(json: JsonObject): Map<Long, String> {
|
||||
@ -67,6 +43,4 @@ object BackupRestoreValidator {
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
|
||||
}
|
||||
|
47
app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt
vendored
Normal file → Executable file
47
app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt
vendored
Normal file → Executable file
@ -6,11 +6,17 @@ import com.github.salomonbrys.kotson.fromJson
|
||||
import com.google.gson.Gson
|
||||
import com.jakewharton.disklrucache.DiskLruCache
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import okhttp3.Response
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
@ -37,21 +43,32 @@ class ChapterCache(private val context: Context) {
|
||||
|
||||
/** The number of values per cache entry. Must be positive. */
|
||||
const val PARAMETER_VALUE_COUNT = 1
|
||||
|
||||
/** The maximum number of bytes this cache should use to store. */
|
||||
const val PARAMETER_CACHE_SIZE = 100L * 1024 * 1024
|
||||
}
|
||||
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||
|
||||
/** Google Json class used for parsing JSON files. */
|
||||
private val gson: Gson by injectLazy()
|
||||
|
||||
// --> EH
|
||||
private val prefs: PreferencesHelper by injectLazy()
|
||||
// <-- EH
|
||||
|
||||
/** Cache class used for cache management. */
|
||||
private val diskCache = DiskLruCache.open(
|
||||
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
||||
PARAMETER_APP_VERSION,
|
||||
PARAMETER_VALUE_COUNT,
|
||||
PARAMETER_CACHE_SIZE
|
||||
)
|
||||
// --> EH
|
||||
private var diskCache = setupDiskCache(prefs.eh_cacheSize().get().toLong())
|
||||
|
||||
init {
|
||||
prefs.eh_cacheSize().asFlow()
|
||||
.onEach {
|
||||
// Save old cache for destruction later
|
||||
val oldCache = diskCache
|
||||
diskCache = setupDiskCache(it.toLong())
|
||||
oldCache.close()
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
// <-- EH
|
||||
|
||||
/**
|
||||
* Returns directory of cache.
|
||||
@ -71,6 +88,18 @@ class ChapterCache(private val context: Context) {
|
||||
val readableSize: String
|
||||
get() = Formatter.formatFileSize(context, realSize)
|
||||
|
||||
// --> EH
|
||||
// Cache size is in MB
|
||||
private fun setupDiskCache(cacheSize: Long): DiskLruCache {
|
||||
return DiskLruCache.open(
|
||||
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
||||
PARAMETER_APP_VERSION,
|
||||
PARAMETER_VALUE_COUNT,
|
||||
cacheSize * 1024 * 1024
|
||||
)
|
||||
}
|
||||
// <-- EH
|
||||
|
||||
/**
|
||||
* Remove file from cache.
|
||||
*
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt
vendored
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt
vendored
Normal file → Executable file
18
app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt
Normal file → Executable file
18
app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt
Normal file → Executable file
@ -21,13 +21,22 @@ import eu.kanade.tachiyomi.data.database.queries.HistoryQueries
|
||||
import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries
|
||||
import eu.kanade.tachiyomi.data.database.queries.MangaQueries
|
||||
import eu.kanade.tachiyomi.data.database.queries.TrackQueries
|
||||
import exh.metadata.sql.mappers.SearchMetadataTypeMapping
|
||||
import exh.metadata.sql.mappers.SearchTagTypeMapping
|
||||
import exh.metadata.sql.mappers.SearchTitleTypeMapping
|
||||
import exh.metadata.sql.models.SearchMetadata
|
||||
import exh.metadata.sql.models.SearchTag
|
||||
import exh.metadata.sql.models.SearchTitle
|
||||
import exh.metadata.sql.queries.SearchMetadataQueries
|
||||
import exh.metadata.sql.queries.SearchTagQueries
|
||||
import exh.metadata.sql.queries.SearchTitleQueries
|
||||
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
||||
|
||||
/**
|
||||
* This class provides operations to manage the database through its interfaces.
|
||||
*/
|
||||
open class DatabaseHelper(context: Context) :
|
||||
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries {
|
||||
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries, /* EXH --> */ SearchMetadataQueries, SearchTagQueries, SearchTitleQueries /* EXH <-- */ {
|
||||
|
||||
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
||||
.name(DbOpenCallback.DATABASE_NAME)
|
||||
@ -42,7 +51,14 @@ open class DatabaseHelper(context: Context) :
|
||||
.addTypeMapping(Category::class.java, CategoryTypeMapping())
|
||||
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
|
||||
.addTypeMapping(History::class.java, HistoryTypeMapping())
|
||||
// EXH -->
|
||||
.addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping())
|
||||
.addTypeMapping(SearchTag::class.java, SearchTagTypeMapping())
|
||||
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
|
||||
// EXH <--
|
||||
.build()
|
||||
|
||||
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
|
||||
|
||||
fun lowLevel() = db.lowLevel()
|
||||
}
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/DbExtensions.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/DbExtensions.kt
Normal file → Executable file
64
app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt
Normal file → Executable file
64
app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt
Normal file → Executable file
@ -7,7 +7,11 @@ import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MergedTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.TrackTable
|
||||
import exh.metadata.sql.tables.SearchMetadataTable
|
||||
import exh.metadata.sql.tables.SearchTagTable
|
||||
import exh.metadata.sql.tables.SearchTitleTable
|
||||
|
||||
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
|
||||
@ -20,7 +24,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
/**
|
||||
* Version of the database.
|
||||
*/
|
||||
const val DATABASE_VERSION = 11
|
||||
const val DATABASE_VERSION = 2 // [SY]
|
||||
}
|
||||
|
||||
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
||||
@ -30,6 +34,14 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
execSQL(CategoryTable.createTableQuery)
|
||||
execSQL(MangaCategoryTable.createTableQuery)
|
||||
execSQL(HistoryTable.createTableQuery)
|
||||
// EXH -->
|
||||
execSQL(SearchMetadataTable.createTableQuery)
|
||||
execSQL(SearchTagTable.createTableQuery)
|
||||
execSQL(SearchTitleTable.createTableQuery)
|
||||
// EXH <--
|
||||
// AZ -->
|
||||
execSQL(MergedTable.createTableQuery)
|
||||
// AZ <--
|
||||
|
||||
// DB indexes
|
||||
execSQL(MangaTable.createUrlIndexQuery)
|
||||
@ -37,51 +49,23 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
execSQL(ChapterTable.createMangaIdIndexQuery)
|
||||
execSQL(ChapterTable.createUnreadChaptersIndexQuery)
|
||||
execSQL(HistoryTable.createChapterIdIndexQuery)
|
||||
// EXH -->
|
||||
db.execSQL(SearchMetadataTable.createUploaderIndexQuery)
|
||||
db.execSQL(SearchMetadataTable.createIndexedExtraIndexQuery)
|
||||
db.execSQL(SearchTagTable.createMangaIdIndexQuery)
|
||||
db.execSQL(SearchTagTable.createNamespaceNameIndexQuery)
|
||||
db.execSQL(SearchTitleTable.createMangaIdIndexQuery)
|
||||
db.execSQL(SearchTitleTable.createTitleIndexQuery)
|
||||
// EXH <--
|
||||
// AZ -->
|
||||
execSQL(MergedTable.createIndexQuery)
|
||||
// AZ <--
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
if (oldVersion < 2) {
|
||||
db.execSQL(ChapterTable.sourceOrderUpdateQuery)
|
||||
|
||||
// Fix kissmanga covers after supporting cloudflare
|
||||
db.execSQL(
|
||||
"""UPDATE mangas SET thumbnail_url =
|
||||
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4"""
|
||||
)
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
// Initialize history tables
|
||||
db.execSQL(HistoryTable.createTableQuery)
|
||||
db.execSQL(HistoryTable.createChapterIdIndexQuery)
|
||||
}
|
||||
if (oldVersion < 4) {
|
||||
db.execSQL(ChapterTable.bookmarkUpdateQuery)
|
||||
}
|
||||
if (oldVersion < 5) {
|
||||
db.execSQL(ChapterTable.addScanlator)
|
||||
}
|
||||
if (oldVersion < 6) {
|
||||
db.execSQL(TrackTable.addTrackingUrl)
|
||||
}
|
||||
if (oldVersion < 7) {
|
||||
db.execSQL(TrackTable.addLibraryId)
|
||||
}
|
||||
if (oldVersion < 8) {
|
||||
db.execSQL("DROP INDEX IF EXISTS mangas_favorite_index")
|
||||
db.execSQL(MangaTable.createLibraryIndexQuery)
|
||||
db.execSQL(ChapterTable.createUnreadChaptersIndexQuery)
|
||||
}
|
||||
if (oldVersion < 9) {
|
||||
db.execSQL(TrackTable.addStartDate)
|
||||
db.execSQL(TrackTable.addFinishDate)
|
||||
}
|
||||
if (oldVersion < 10) {
|
||||
db.execSQL(MangaTable.addCoverLastModified)
|
||||
}
|
||||
if (oldVersion < 11) {
|
||||
db.execSQL(MangaTable.addDateAdded)
|
||||
db.execSQL(MangaTable.backfillDateAdded)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigure(db: SupportSQLiteDatabase) {
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/DbProvider.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/DbProvider.kt
Normal file → Executable file
6
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt
Normal file → Executable file
6
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt
Normal file → Executable file
@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_FLAGS
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ID
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_MANGA_ORDER
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_NAME
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER
|
||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE
|
||||
@ -40,6 +41,8 @@ class CategoryPutResolver : DefaultPutResolver<Category>() {
|
||||
put(COL_NAME, obj.name)
|
||||
put(COL_ORDER, obj.order)
|
||||
put(COL_FLAGS, obj.flags)
|
||||
val orderString = obj.mangaOrder.joinToString("/")
|
||||
put(COL_MANGA_ORDER, orderString)
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +53,9 @@ class CategoryGetResolver : DefaultGetResolver<Category>() {
|
||||
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
|
||||
order = cursor.getInt(cursor.getColumnIndex(COL_ORDER))
|
||||
flags = cursor.getInt(cursor.getColumnIndex(COL_FLAGS))
|
||||
|
||||
val orderString = cursor.getString(cursor.getColumnIndex(COL_MANGA_ORDER))
|
||||
mangaOrder = orderString?.split("/")?.mapNotNull { it.toLongOrNull() } ?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt
Normal file → Executable file
5
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt
Normal file → Executable file
5
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt
Normal file → Executable file
@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ARTIST
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_AUTHOR
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_CHAPTER_FLAGS
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_COVER_LAST_MODIFIED
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DATE_ADDED
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
|
||||
@ -48,7 +47,7 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
|
||||
.whereArgs(obj.id)
|
||||
.build()
|
||||
|
||||
override fun mapToContentValues(obj: Manga) = ContentValues(17).apply {
|
||||
override fun mapToContentValues(obj: Manga) = ContentValues(15).apply {
|
||||
put(COL_ID, obj.id)
|
||||
put(COL_SOURCE, obj.source)
|
||||
put(COL_URL, obj.url)
|
||||
@ -65,7 +64,6 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
|
||||
put(COL_VIEWER, obj.viewer)
|
||||
put(COL_CHAPTER_FLAGS, obj.chapter_flags)
|
||||
put(COL_COVER_LAST_MODIFIED, obj.cover_last_modified)
|
||||
put(COL_DATE_ADDED, obj.date_added)
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +85,6 @@ interface BaseMangaGetResolver {
|
||||
viewer = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
|
||||
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
|
||||
cover_last_modified = cursor.getLong(cursor.getColumnIndex(COL_COVER_LAST_MODIFIED))
|
||||
date_added = cursor.getLong(cursor.getColumnIndex(COL_DATE_ADDED))
|
||||
}
|
||||
}
|
||||
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/TrackTypeMapping.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/TrackTypeMapping.kt
Normal file → Executable file
5
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
Normal file → Executable file
5
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
Normal file → Executable file
@ -12,6 +12,11 @@ interface Category : Serializable {
|
||||
|
||||
var flags: Int
|
||||
|
||||
var mangaOrder: List<Long>
|
||||
|
||||
val nameLower: String
|
||||
get() = name.toLowerCase()
|
||||
|
||||
companion object {
|
||||
|
||||
fun create(name: String): Category = CategoryImpl().apply {
|
||||
|
3
app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
Normal file → Executable file
3
app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
Normal file → Executable file
@ -10,11 +10,14 @@ class CategoryImpl : Category {
|
||||
|
||||
override var flags: Int = 0
|
||||
|
||||
override var mangaOrder: List<Long> = emptyList()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || javaClass != other.javaClass) return false
|
||||
|
||||
val category = other as Category
|
||||
|
||||
return name == category.name
|
||||
}
|
||||
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt
Normal file → Executable file
5
app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt
Normal file → Executable file
5
app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt
Normal file → Executable file
@ -31,11 +31,10 @@ class ChapterImpl : Chapter {
|
||||
if (other == null || javaClass != other.javaClass) return false
|
||||
|
||||
val chapter = other as Chapter
|
||||
if (url != chapter.url) return false
|
||||
return id == chapter.id
|
||||
return url == chapter.url
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return url.hashCode() + id.hashCode()
|
||||
return url.hashCode()
|
||||
}
|
||||
}
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt
Normal file → Executable file
10
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt
Normal file → Executable file
10
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt
Normal file → Executable file
@ -12,8 +12,6 @@ interface Manga : SManga {
|
||||
|
||||
var last_update: Long
|
||||
|
||||
var date_added: Long
|
||||
|
||||
var viewer: Int
|
||||
|
||||
var chapter_flags: Int
|
||||
@ -24,6 +22,10 @@ interface Manga : SManga {
|
||||
setFlags(order, SORT_MASK)
|
||||
}
|
||||
|
||||
private fun setFlags(flag: Int, mask: Int) {
|
||||
chapter_flags = chapter_flags and mask.inv() or (flag and mask)
|
||||
}
|
||||
|
||||
fun sortDescending(): Boolean {
|
||||
return chapter_flags and SORT_MASK == SORT_DESC
|
||||
}
|
||||
@ -32,10 +34,6 @@ interface Manga : SManga {
|
||||
return genre?.split(", ")?.map { it.trim() }
|
||||
}
|
||||
|
||||
private fun setFlags(flag: Int, mask: Int) {
|
||||
chapter_flags = chapter_flags and mask.inv() or (flag and mask)
|
||||
}
|
||||
|
||||
// Used to display the chapter's title one way or another
|
||||
var displayMode: Int
|
||||
get() = chapter_flags and DISPLAY_MASK
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt
Normal file → Executable file
10
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt
Normal file → Executable file
10
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt
Normal file → Executable file
@ -8,7 +8,7 @@ open class MangaImpl : Manga {
|
||||
|
||||
override lateinit var url: String
|
||||
|
||||
override lateinit var title: String
|
||||
override var title: String = ""
|
||||
|
||||
override var artist: String? = null
|
||||
|
||||
@ -26,8 +26,6 @@ open class MangaImpl : Manga {
|
||||
|
||||
override var last_update: Long = 0
|
||||
|
||||
override var date_added: Long = 0
|
||||
|
||||
override var initialized: Boolean = false
|
||||
|
||||
override var viewer: Int = 0
|
||||
@ -41,11 +39,11 @@ open class MangaImpl : Manga {
|
||||
if (other == null || javaClass != other.javaClass) return false
|
||||
|
||||
val manga = other as Manga
|
||||
if (url != manga.url) return false
|
||||
return id == manga.id
|
||||
|
||||
return url == manga.url
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return url.hashCode() + id.hashCode()
|
||||
return url.hashCode()
|
||||
}
|
||||
}
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt
Normal file → Executable file
26
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt
Normal file → Executable file
26
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt
Normal file → Executable file
@ -15,13 +15,24 @@ import java.util.Date
|
||||
|
||||
interface ChapterQueries : DbProvider {
|
||||
|
||||
fun getChapters(manga: Manga) = db.get()
|
||||
fun getChapters(manga: Manga) = getChaptersByMangaId(manga.id)
|
||||
|
||||
fun getChaptersByMangaId(mangaId: Long?) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.whereArgs(mangaId)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getChaptersByMergedMangaId(mangaId: Long) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getMergedChaptersQuery(mangaId))
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
@ -71,6 +82,17 @@ interface ChapterQueries : DbProvider {
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getChapters(url: String) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ?")
|
||||
.whereArgs(url)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
||||
|
||||
fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).prepare()
|
||||
|
21
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt
Normal file → Executable file
21
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt
Normal file → Executable file
@ -22,11 +22,28 @@ interface HistoryQueries : DbProvider {
|
||||
* Returns history of recent manga containing last read chapter
|
||||
* @param date recent date range
|
||||
*/
|
||||
fun getRecentManga(date: Date) = db.get()
|
||||
fun getRecentManga(date: Date, offset: Int = 0, search: String = "") = db.get()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentMangasQuery())
|
||||
.query(getRecentMangasQuery(offset, search))
|
||||
.args(date.time)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build()
|
||||
)
|
||||
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||
.prepare()
|
||||
|
||||
/**
|
||||
* Returns history of recent manga containing last read chapter in 25s
|
||||
* @param date recent date range
|
||||
* @offset offset the db by
|
||||
*/
|
||||
fun getRecentMangaLimit(date: Date, limit: Int = 0, search: String = "") = db.get()
|
||||
.listOfObjects(MangaChapterHistory::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getRecentMangasLimitQuery(limit, search))
|
||||
.args(date.time)
|
||||
.observesTables(HistoryTable.TABLE)
|
||||
.build()
|
||||
|
8
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaCategoryQueries.kt
Normal file → Executable file
8
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaCategoryQueries.kt
Normal file → Executable file
@ -26,8 +26,12 @@ interface MangaCategoryQueries : DbProvider {
|
||||
|
||||
fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) {
|
||||
db.inTransaction {
|
||||
deleteOldMangasCategories(mangas).executeAsBlocking()
|
||||
insertMangasCategories(mangasCategories).executeAsBlocking()
|
||||
mangas.chunked(100) { chunk ->
|
||||
deleteOldMangasCategories(chunk).executeAsBlocking()
|
||||
}
|
||||
mangasCategories.chunked(100) { chunk ->
|
||||
insertMangasCategories(chunk).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
60
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt
Normal file → Executable file
60
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt
Normal file → Executable file
@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||
import exh.metadata.sql.tables.SearchMetadataTable
|
||||
|
||||
interface MangaQueries : DbProvider {
|
||||
|
||||
@ -74,6 +75,15 @@ interface MangaQueries : DbProvider {
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getMergedMangas(id: Long) = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getMergedMangaQuery(id))
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
|
||||
|
||||
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
|
||||
@ -159,4 +169,54 @@ interface MangaQueries : DbProvider {
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getMangaWithMetadata() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(
|
||||
"""
|
||||
SELECT ${MangaTable.TABLE}.* FROM ${MangaTable.TABLE}
|
||||
INNER JOIN ${SearchMetadataTable.TABLE}
|
||||
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
||||
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
||||
""".trimIndent()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getFavoriteMangaWithMetadata() = db.get()
|
||||
.listOfObjects(Manga::class.java)
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(
|
||||
"""
|
||||
SELECT ${MangaTable.TABLE}.* FROM ${MangaTable.TABLE}
|
||||
INNER JOIN ${SearchMetadataTable.TABLE}
|
||||
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
||||
WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1
|
||||
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
||||
""".trimIndent()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getIdsOfFavoriteMangaWithMetadata() = db.get()
|
||||
.cursor()
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(
|
||||
"""
|
||||
SELECT ${MangaTable.TABLE}.${MangaTable.COL_ID} FROM ${MangaTable.TABLE}
|
||||
INNER JOIN ${SearchMetadataTable.TABLE}
|
||||
ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID}
|
||||
WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1
|
||||
ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID}
|
||||
""".trimIndent()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
}
|
||||
|
63
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt
Normal file → Executable file
63
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt
Normal file → Executable file
@ -5,6 +5,33 @@ import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
|
||||
import eu.kanade.tachiyomi.data.database.tables.HistoryTable as History
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
|
||||
import eu.kanade.tachiyomi.data.database.tables.MergedTable as Merged
|
||||
|
||||
/**
|
||||
* Query to get the manga merged into a merged manga
|
||||
*/
|
||||
fun getMergedMangaQuery(id: Long) =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*
|
||||
FROM (
|
||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
||||
) AS M
|
||||
JOIN ${Manga.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID}
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the chapters of all manga in a merged manga
|
||||
*/
|
||||
fun getMergedChaptersQuery(id: Long) =
|
||||
"""
|
||||
SELECT ${Chapter.TABLE}.*
|
||||
FROM (
|
||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
||||
) AS M
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = M.${Merged.COL_MANGA_ID}
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the manga from the library, with their categories and unread count.
|
||||
@ -38,9 +65,7 @@ fun getRecentsQuery() =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${Manga.COL_FAVORITE} = 1
|
||||
AND ${Chapter.COL_DATE_UPLOAD} > ?
|
||||
AND ${Chapter.COL_DATE_FETCH} > ${Manga.COL_DATE_ADDED}
|
||||
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Chapter.COL_DATE_UPLOAD} > ?
|
||||
ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC
|
||||
"""
|
||||
|
||||
@ -51,7 +76,7 @@ fun getRecentsQuery() =
|
||||
* and are read after the given time period
|
||||
* @return return limit is 25
|
||||
*/
|
||||
fun getRecentMangasQuery() =
|
||||
fun getRecentMangasQuery(offset: Int = 0, search: String = "") =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
||||
FROM ${Manga.TABLE}
|
||||
@ -66,8 +91,36 @@ fun getRecentMangasQuery() =
|
||||
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ? AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
|
||||
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
|
||||
LIMIT 25
|
||||
LIMIT 25 OFFSET $offset
|
||||
"""
|
||||
|
||||
/**
|
||||
* Query to get the recently read chapters of manga from the library up to a date.
|
||||
* The max_last_read table contains the most recent chapters grouped by manga
|
||||
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read
|
||||
* and are read after the given time period
|
||||
*/
|
||||
fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
||||
FROM ${Manga.TABLE}
|
||||
JOIN ${Chapter.TABLE}
|
||||
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||
JOIN ${History.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
JOIN (
|
||||
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID}, MAX(${History.TABLE}.${History.COL_LAST_READ}) as ${History.COL_LAST_READ}
|
||||
FROM ${Chapter.TABLE} JOIN ${History.TABLE}
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
|
||||
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
|
||||
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ?
|
||||
AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
|
||||
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
|
||||
LIMIT $limit
|
||||
"""
|
||||
|
||||
fun getHistoryByMangaId() =
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterProgressPutResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterProgressPutResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterSourceOrderPutResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterSourceOrderPutResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/LibraryMangaGetResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/LibraryMangaGetResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterHistoryGetResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaLastUpdatedPutResolver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaLastUpdatedPutResolver.kt
Normal file → Executable file
@ -0,0 +1,32 @@
|
||||
package eu.kanade.tachiyomi.data.database.resolvers
|
||||
|
||||
import android.content.ContentValues
|
||||
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
||||
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
||||
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||
|
||||
// [EXH]
|
||||
class MangaUrlPutResolver : PutResolver<Manga>() {
|
||||
|
||||
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
|
||||
val updateQuery = mapToUpdateQuery(manga)
|
||||
val contentValues = mapToContentValues(manga)
|
||||
|
||||
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
||||
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||
}
|
||||
|
||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
|
||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||
put(MangaTable.COL_URL, manga.url)
|
||||
}
|
||||
}
|
8
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt
Normal file → Executable file
8
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt
Normal file → Executable file
@ -12,12 +12,18 @@ object CategoryTable {
|
||||
|
||||
const val COL_FLAGS = "flags"
|
||||
|
||||
const val COL_MANGA_ORDER = "manga_order"
|
||||
|
||||
val createTableQuery: String
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||
$COL_NAME TEXT NOT NULL,
|
||||
$COL_ORDER INTEGER NOT NULL,
|
||||
$COL_FLAGS INTEGER NOT NULL
|
||||
$COL_FLAGS INTEGER NOT NULL,
|
||||
$COL_MANGA_ORDER TEXT NOT NULL
|
||||
)"""
|
||||
|
||||
val addMangaOrder: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_MANGA_ORDER TEXT"
|
||||
}
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt
Normal file → Executable file
18
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt
Normal file → Executable file
18
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt
Normal file → Executable file
@ -28,8 +28,6 @@ object MangaTable {
|
||||
|
||||
const val COL_LAST_UPDATE = "last_update"
|
||||
|
||||
const val COL_DATE_ADDED = "date_added"
|
||||
|
||||
const val COL_INITIALIZED = "initialized"
|
||||
|
||||
const val COL_VIEWER = "viewer"
|
||||
@ -60,8 +58,7 @@ object MangaTable {
|
||||
$COL_INITIALIZED BOOLEAN NOT NULL,
|
||||
$COL_VIEWER INTEGER NOT NULL,
|
||||
$COL_CHAPTER_FLAGS INTEGER NOT NULL,
|
||||
$COL_COVER_LAST_MODIFIED LONG NOT NULL,
|
||||
$COL_DATE_ADDED LONG NOT NULL
|
||||
$COL_COVER_LAST_MODIFIED LONG NOT NULL
|
||||
)"""
|
||||
|
||||
val createUrlIndexQuery: String
|
||||
@ -73,17 +70,4 @@ object MangaTable {
|
||||
|
||||
val addCoverLastModified: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_COVER_LAST_MODIFIED LONG NOT NULL DEFAULT 0"
|
||||
|
||||
val addDateAdded: String
|
||||
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_DATE_ADDED LONG NOT NULL DEFAULT 0"
|
||||
|
||||
/**
|
||||
* Used with addDateAdded to populate it with the oldest chapter fetch date.
|
||||
*/
|
||||
val backfillDateAdded: String
|
||||
get() = "UPDATE $TABLE SET $COL_DATE_ADDED = " +
|
||||
"(SELECT MIN(${ChapterTable.COL_DATE_FETCH}) " +
|
||||
"FROM $TABLE INNER JOIN ${ChapterTable.TABLE} " +
|
||||
"ON $TABLE.$COL_ID = ${ChapterTable.TABLE}.${ChapterTable.COL_MANGA_ID} " +
|
||||
"GROUP BY $TABLE.$COL_ID)"
|
||||
}
|
||||
|
20
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MergedTable.kt
Executable file
20
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MergedTable.kt
Executable file
@ -0,0 +1,20 @@
|
||||
package eu.kanade.tachiyomi.data.database.tables
|
||||
|
||||
object MergedTable {
|
||||
|
||||
const val TABLE = "merged"
|
||||
|
||||
const val COL_MERGE_ID = "mergeID"
|
||||
|
||||
const val COL_MANGA_ID = "mangaID"
|
||||
|
||||
val createTableQuery: String
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_MERGE_ID INTEGER NOT NULL,
|
||||
$COL_MANGA_ID INTEGER NOT NULL
|
||||
)"""
|
||||
|
||||
val createIndexQuery: String
|
||||
get() = "CREATE INDEX ${TABLE}_${COL_MERGE_ID}_index ON $TABLE($COL_MERGE_ID)"
|
||||
}
|
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
package eu.kanade.tachiyomi.data.download
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toUri
|
||||
import android.net.Uri
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
@ -59,7 +59,7 @@ class DownloadCache(
|
||||
*/
|
||||
private fun getDirectoryFromPreference(): UniFile {
|
||||
val dir = preferences.downloadsDirectory().get()
|
||||
return UniFile.fromUri(context, dir.toUri())
|
||||
return UniFile.fromUri(context, Uri.parse(dir))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,7 +81,7 @@ class DownloadCache(
|
||||
if (sourceDir != null) {
|
||||
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)]
|
||||
if (mangaDir != null) {
|
||||
return provider.getValidChapterDirNames(chapter).any { it in mangaDir.files }
|
||||
return provider.getChapterDirName(chapter) in mangaDir.files
|
||||
}
|
||||
}
|
||||
return false
|
||||
@ -128,7 +128,7 @@ class DownloadCache(
|
||||
.orEmpty()
|
||||
.associate { it.name to SourceDirectory(it) }
|
||||
.mapNotNullKeys { entry ->
|
||||
onlineSources.find { provider.getSourceDirName(it).toLowerCase() == entry.key?.toLowerCase() }?.id
|
||||
onlineSources.find { provider.getSourceDirName(it) == entry.key }?.id
|
||||
}
|
||||
|
||||
rootDir.files = sourceDirs
|
||||
@ -191,10 +191,9 @@ class DownloadCache(
|
||||
fun removeChapter(chapter: Chapter, manga: Manga) {
|
||||
val sourceDir = rootDir.files[manga.source] ?: return
|
||||
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] ?: return
|
||||
provider.getValidChapterDirNames(chapter).forEach {
|
||||
if (it in mangaDir.files) {
|
||||
mangaDir.files -= it
|
||||
}
|
||||
val chapterDirName = provider.getChapterDirName(chapter)
|
||||
if (chapterDirName in mangaDir.files) {
|
||||
mangaDir.files -= chapterDirName
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,11 +207,10 @@ class DownloadCache(
|
||||
fun removeChapters(chapters: List<Chapter>, manga: Manga) {
|
||||
val sourceDir = rootDir.files[manga.source] ?: return
|
||||
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] ?: return
|
||||
chapters.forEach { chapter ->
|
||||
provider.getValidChapterDirNames(chapter).forEach {
|
||||
if (it in mangaDir.files) {
|
||||
mangaDir.files -= it
|
||||
}
|
||||
for (chapter in chapters) {
|
||||
val chapterDirName = provider.getChapterDirName(chapter)
|
||||
if (chapterDirName in mangaDir.files) {
|
||||
mangaDir.files -= chapterDirName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
Normal file → Executable file
31
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
Normal file → Executable file
@ -3,12 +3,10 @@ package eu.kanade.tachiyomi.data.download
|
||||
import android.content.Context
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.jakewharton.rxrelay.BehaviorRelay
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
@ -25,8 +23,10 @@ import uy.kohesive.injekt.injectLazy
|
||||
*/
|
||||
class DownloadManager(private val context: Context) {
|
||||
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
/**
|
||||
* The sources manager.
|
||||
*/
|
||||
private val sourceManager by injectLazy<SourceManager>()
|
||||
|
||||
/**
|
||||
* Downloads provider, used to retrieve the folders where the chapters are or should be stored.
|
||||
@ -152,7 +152,7 @@ class DownloadManager(private val context: Context) {
|
||||
.filter { "image" in it.type.orEmpty() }
|
||||
|
||||
if (files.isEmpty()) {
|
||||
throw Exception(context.getString(R.string.page_list_empty_error))
|
||||
throw Exception("Page list is empty")
|
||||
}
|
||||
|
||||
files.sortedBy { it.name }
|
||||
@ -200,16 +200,9 @@ class DownloadManager(private val context: Context) {
|
||||
*/
|
||||
fun deleteChapters(chapters: List<Chapter>, manga: Manga, source: Source) {
|
||||
queue.remove(chapters)
|
||||
|
||||
val filteredChapters = if (!preferences.removeBookmarkedChapters()) {
|
||||
chapters.filterNot { it.bookmark }
|
||||
} else {
|
||||
chapters
|
||||
}
|
||||
|
||||
val chapterDirs = provider.findChapterDirs(filteredChapters, manga, source)
|
||||
val chapterDirs = provider.findChapterDirs(chapters, manga, source)
|
||||
chapterDirs.forEach { it.delete() }
|
||||
cache.removeChapters(filteredChapters, manga)
|
||||
cache.removeChapters(chapters, manga)
|
||||
if (cache.getDownloadCount(manga) == 0) { // Delete manga directory if empty
|
||||
chapterDirs.firstOrNull()?.parentFile?.delete()
|
||||
}
|
||||
@ -257,20 +250,16 @@ class DownloadManager(private val context: Context) {
|
||||
* @param newChapter the target chapter with the new name.
|
||||
*/
|
||||
fun renameChapter(source: Source, manga: Manga, oldChapter: Chapter, newChapter: Chapter) {
|
||||
val oldNames = provider.getValidChapterDirNames(oldChapter)
|
||||
val oldName = provider.getChapterDirName(oldChapter)
|
||||
val newName = provider.getChapterDirName(newChapter)
|
||||
val mangaDir = provider.getMangaDir(manga, source)
|
||||
|
||||
// Assume there's only 1 version of the chapter name formats present
|
||||
val oldFolder = oldNames.asSequence()
|
||||
.mapNotNull { mangaDir.findFile(it) }
|
||||
.firstOrNull()
|
||||
|
||||
val oldFolder = mangaDir.findFile(oldName)
|
||||
if (oldFolder?.renameTo(newName) == true) {
|
||||
cache.removeChapter(oldChapter, manga)
|
||||
cache.addChapter(newName, mangaDir, manga)
|
||||
} else {
|
||||
Timber.e("Could not rename downloaded chapter: %s.", oldNames.joinToString())
|
||||
Timber.e("Could not rename downloaded chapter: %s.", oldName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
82
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt
Normal file → Executable file
82
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt
Normal file → Executable file
@ -13,7 +13,8 @@ import eu.kanade.tachiyomi.util.lang.chop
|
||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import java.util.regex.Pattern
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* DownloadNotifier is used to show notifications when downloading one or multiple chapters.
|
||||
@ -22,25 +23,11 @@ import uy.kohesive.injekt.injectLazy
|
||||
*/
|
||||
internal class DownloadNotifier(private val context: Context) {
|
||||
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
|
||||
private val progressNotificationBuilder by lazy {
|
||||
context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
|
||||
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
||||
}
|
||||
private val notificationBuilder = context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER) {
|
||||
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
||||
}
|
||||
|
||||
private val completeNotificationBuilder by lazy {
|
||||
context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_COMPLETE) {
|
||||
setAutoCancel(false)
|
||||
}
|
||||
}
|
||||
|
||||
private val errorNotificationBuilder by lazy {
|
||||
context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_ERROR) {
|
||||
setAutoCancel(false)
|
||||
}
|
||||
}
|
||||
private val preferences by lazy { Injekt.get<PreferencesHelper>() }
|
||||
|
||||
/**
|
||||
* Status of download. Used for correct notification icon.
|
||||
@ -62,14 +49,14 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
*
|
||||
* @param id the id of the notification.
|
||||
*/
|
||||
private fun NotificationCompat.Builder.show(id: Int) {
|
||||
private fun NotificationCompat.Builder.show(id: Int = Notifications.ID_DOWNLOAD_CHAPTER) {
|
||||
context.notificationManager.notify(id, build())
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear old actions if they exist.
|
||||
*/
|
||||
private fun NotificationCompat.Builder.clearActions() {
|
||||
private fun clearActions() = with(notificationBuilder) {
|
||||
if (mActions.isNotEmpty()) {
|
||||
mActions.clear()
|
||||
}
|
||||
@ -79,8 +66,8 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
* Dismiss the downloader's notification. Downloader error notifications use a different id, so
|
||||
* those can only be dismissed by the user.
|
||||
*/
|
||||
fun dismissProgress() {
|
||||
context.notificationManager.cancel(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS)
|
||||
fun dismiss() {
|
||||
context.notificationManager.cancel(Notifications.ID_DOWNLOAD_CHAPTER)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,7 +76,8 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
* @param download download object containing download information.
|
||||
*/
|
||||
fun onProgressChange(download: Download) {
|
||||
with(progressNotificationBuilder) {
|
||||
// Create notification
|
||||
with(notificationBuilder) {
|
||||
// Check if first call.
|
||||
if (!isDownloading) {
|
||||
setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
@ -121,16 +109,17 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
}
|
||||
|
||||
setProgress(download.pages!!.size, download.downloadedImages, false)
|
||||
|
||||
show(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS)
|
||||
}
|
||||
|
||||
// Displays the progress bar on notification
|
||||
notificationBuilder.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notification when download is paused.
|
||||
*/
|
||||
fun onPaused() {
|
||||
with(progressNotificationBuilder) {
|
||||
fun onDownloadPaused() {
|
||||
with(notificationBuilder) {
|
||||
setContentTitle(context.getString(R.string.chapter_paused))
|
||||
setContentText(context.getString(R.string.download_notifier_download_paused))
|
||||
setSmallIcon(R.drawable.ic_pause_24dp)
|
||||
@ -151,45 +140,22 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
context.getString(R.string.action_cancel_all),
|
||||
NotificationReceiver.clearDownloadsPendingBroadcast(context)
|
||||
)
|
||||
|
||||
show(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS)
|
||||
}
|
||||
|
||||
// Show notification.
|
||||
notificationBuilder.show()
|
||||
|
||||
// Reset initial values
|
||||
isDownloading = false
|
||||
}
|
||||
|
||||
/**
|
||||
* This function shows a notification to inform download tasks are done.
|
||||
*/
|
||||
fun onComplete() {
|
||||
if (!errorThrown) {
|
||||
// Create notification
|
||||
with(completeNotificationBuilder) {
|
||||
setContentTitle(context.getString(R.string.download_notifier_downloader_title))
|
||||
setContentText(context.getString(R.string.download_notifier_download_finish))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
clearActions()
|
||||
setAutoCancel(true)
|
||||
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
||||
setProgress(0, 0, false)
|
||||
|
||||
show(Notifications.ID_DOWNLOAD_CHAPTER_COMPLETE)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset states to default
|
||||
errorThrown = false
|
||||
isDownloading = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the downloader receives a warning.
|
||||
*
|
||||
* @param reason the text to show.
|
||||
*/
|
||||
fun onWarning(reason: String) {
|
||||
with(errorNotificationBuilder) {
|
||||
with(notificationBuilder) {
|
||||
setContentTitle(context.getString(R.string.download_notifier_downloader_title))
|
||||
setContentText(reason)
|
||||
setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||
@ -197,9 +163,8 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
clearActions()
|
||||
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
||||
setProgress(0, 0, false)
|
||||
|
||||
show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR)
|
||||
}
|
||||
notificationBuilder.show()
|
||||
|
||||
// Reset download information
|
||||
isDownloading = false
|
||||
@ -214,7 +179,7 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
*/
|
||||
fun onError(error: String? = null, chapter: String? = null) {
|
||||
// Create notification
|
||||
with(errorNotificationBuilder) {
|
||||
with(notificationBuilder) {
|
||||
setContentTitle(
|
||||
chapter
|
||||
?: context.getString(R.string.download_notifier_downloader_title)
|
||||
@ -225,9 +190,8 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
setAutoCancel(false)
|
||||
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
||||
setProgress(0, 0, false)
|
||||
|
||||
show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR)
|
||||
}
|
||||
notificationBuilder.show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR)
|
||||
|
||||
// Reset download information
|
||||
errorThrown = true
|
||||
|
@ -1,7 +1,6 @@
|
||||
package eu.kanade.tachiyomi.data.download
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import com.github.salomonbrys.kotson.fromJson
|
||||
import com.google.gson.Gson
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
@ -23,7 +22,7 @@ class DownloadPendingDeleter(context: Context) {
|
||||
/**
|
||||
* Preferences used to store the list of chapters to delete.
|
||||
*/
|
||||
private val preferences = context.getSharedPreferences("chapters_to_delete", Context.MODE_PRIVATE)
|
||||
private val prefs = context.getSharedPreferences("chapters_to_delete", Context.MODE_PRIVATE)
|
||||
|
||||
/**
|
||||
* Last added chapter, used to avoid decoding from the preference too often.
|
||||
@ -50,7 +49,7 @@ class DownloadPendingDeleter(context: Context) {
|
||||
// Last entry matches the manga, reuse it to avoid decoding json from preferences
|
||||
lastEntry.copy(chapters = newChapters)
|
||||
} else {
|
||||
val existingEntry = preferences.getString(manga.id!!.toString(), null)
|
||||
val existingEntry = prefs.getString(manga.id!!.toString(), null)
|
||||
if (existingEntry != null) {
|
||||
// Existing entry found on preferences, decode json and add the new chapter
|
||||
val savedEntry = gson.fromJson<Entry>(existingEntry)
|
||||
@ -70,9 +69,7 @@ class DownloadPendingDeleter(context: Context) {
|
||||
|
||||
// Save current state
|
||||
val json = gson.toJson(newEntry)
|
||||
preferences.edit {
|
||||
putString(newEntry.manga.id.toString(), json)
|
||||
}
|
||||
prefs.edit().putString(newEntry.manga.id.toString(), json).apply()
|
||||
lastAddedEntry = newEntry
|
||||
}
|
||||
|
||||
@ -85,9 +82,7 @@ class DownloadPendingDeleter(context: Context) {
|
||||
@Synchronized
|
||||
fun getPendingChapters(): Map<Manga, List<Chapter>> {
|
||||
val entries = decodeAll()
|
||||
preferences.edit {
|
||||
clear()
|
||||
}
|
||||
prefs.edit().clear().apply()
|
||||
lastAddedEntry = null
|
||||
|
||||
return entries.associate { entry ->
|
||||
@ -99,7 +94,7 @@ class DownloadPendingDeleter(context: Context) {
|
||||
* Decodes all the chapters from preferences.
|
||||
*/
|
||||
private fun decodeAll(): List<Entry> {
|
||||
return preferences.all.values.mapNotNull { rawEntry ->
|
||||
return prefs.all.values.mapNotNull { rawEntry ->
|
||||
try {
|
||||
(rawEntry as? String)?.let { gson.fromJson<Entry>(it) }
|
||||
} catch (e: Exception) {
|
||||
@ -135,8 +130,7 @@ class DownloadPendingDeleter(context: Context) {
|
||||
private data class ChapterEntry(
|
||||
val id: Long,
|
||||
val url: String,
|
||||
val name: String,
|
||||
val scanlator: String?
|
||||
val name: String
|
||||
)
|
||||
|
||||
/**
|
||||
@ -160,7 +154,7 @@ class DownloadPendingDeleter(context: Context) {
|
||||
* Returns a chapter entry from a chapter model.
|
||||
*/
|
||||
private fun Chapter.toEntry(): ChapterEntry {
|
||||
return ChapterEntry(id!!, url, name, scanlator)
|
||||
return ChapterEntry(id!!, url, name)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -180,7 +174,6 @@ class DownloadPendingDeleter(context: Context) {
|
||||
it.id = id
|
||||
it.url = url
|
||||
it.name = name
|
||||
it.scanlator = scanlator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
37
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt
Normal file → Executable file
37
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
package eu.kanade.tachiyomi.data.download
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toUri
|
||||
import android.net.Uri
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
@ -32,14 +32,14 @@ class DownloadProvider(private val context: Context) {
|
||||
* The root directory for downloads.
|
||||
*/
|
||||
private var downloadsDir = preferences.downloadsDirectory().get().let {
|
||||
val dir = UniFile.fromUri(context, it.toUri())
|
||||
val dir = UniFile.fromUri(context, Uri.parse(it))
|
||||
DiskUtil.createNoMediaFile(dir, context)
|
||||
dir
|
||||
}
|
||||
|
||||
init {
|
||||
preferences.downloadsDirectory().asFlow()
|
||||
.onEach { downloadsDir = UniFile.fromUri(context, it.toUri()) }
|
||||
.onEach { downloadsDir = UniFile.fromUri(context, Uri.parse(it)) }
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
||||
@ -88,9 +88,7 @@ class DownloadProvider(private val context: Context) {
|
||||
*/
|
||||
fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? {
|
||||
val mangaDir = findMangaDir(manga, source)
|
||||
return getValidChapterDirNames(chapter).asSequence()
|
||||
.mapNotNull { mangaDir?.findFile(it) }
|
||||
.firstOrNull()
|
||||
return mangaDir?.findFile(getChapterDirName(chapter))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,11 +100,7 @@ class DownloadProvider(private val context: Context) {
|
||||
*/
|
||||
fun findChapterDirs(chapters: List<Chapter>, manga: Manga, source: Source): List<UniFile> {
|
||||
val mangaDir = findMangaDir(manga, source) ?: return emptyList()
|
||||
return chapters.mapNotNull { chapter ->
|
||||
getValidChapterDirNames(chapter).asSequence()
|
||||
.mapNotNull { mangaDir.findFile(it) }
|
||||
.firstOrNull()
|
||||
}
|
||||
return chapters.mapNotNull { mangaDir.findFile(getChapterDirName(it)) }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,25 +127,6 @@ class DownloadProvider(private val context: Context) {
|
||||
* @param chapter the chapter to query.
|
||||
*/
|
||||
fun getChapterDirName(chapter: Chapter): String {
|
||||
return DiskUtil.buildValidFilename(
|
||||
when {
|
||||
chapter.scanlator != null -> "${chapter.scanlator}_${chapter.name}"
|
||||
else -> chapter.name
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns valid downloaded chapter directory names.
|
||||
*
|
||||
* @param chapter the chapter to query.
|
||||
*/
|
||||
fun getValidChapterDirNames(chapter: Chapter): List<String> {
|
||||
return listOf(
|
||||
getChapterDirName(chapter),
|
||||
|
||||
// Legacy chapter directory name used in v0.9.2 and before
|
||||
DiskUtil.buildValidFilename(chapter.name)
|
||||
)
|
||||
return DiskUtil.buildValidFilename(chapter.name)
|
||||
}
|
||||
}
|
||||
|
11
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt
Normal file → Executable file
11
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt
Normal file → Executable file
@ -4,7 +4,6 @@ import android.app.Notification
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkInfo.State.CONNECTED
|
||||
import android.net.NetworkInfo.State.DISCONNECTED
|
||||
import android.os.Build
|
||||
@ -83,7 +82,7 @@ class DownloadService : Service() {
|
||||
*/
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
startForeground(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS, getPlaceholderNotification())
|
||||
startForeground(Notifications.ID_DOWNLOAD_CHAPTER, getPlaceholderNotification())
|
||||
wakeLock = acquireWakeLock(javaClass.name)
|
||||
runningRelay.call(true)
|
||||
subscriptions = CompositeSubscription()
|
||||
@ -144,7 +143,7 @@ class DownloadService : Service() {
|
||||
private fun onNetworkStateChanged(connectivity: Connectivity) {
|
||||
when (connectivity.state) {
|
||||
CONNECTED -> {
|
||||
if (preferences.downloadOnlyOverWifi() && connectivityManager.activeNetworkInfo?.type != ConnectivityManager.TYPE_WIFI) {
|
||||
if (preferences.downloadOnlyOverWifi() && connectivityManager.isActiveNetworkMetered) {
|
||||
downloadManager.stopDownloads(getString(R.string.download_notifier_text_only_wifi))
|
||||
} else {
|
||||
val started = downloadManager.startDownloads()
|
||||
@ -176,19 +175,19 @@ class DownloadService : Service() {
|
||||
/**
|
||||
* Releases the wake lock if it's held.
|
||||
*/
|
||||
private fun PowerManager.WakeLock.releaseIfNeeded() {
|
||||
fun PowerManager.WakeLock.releaseIfNeeded() {
|
||||
if (isHeld) release()
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires the wake lock if it's not held.
|
||||
*/
|
||||
private fun PowerManager.WakeLock.acquireIfNeeded() {
|
||||
fun PowerManager.WakeLock.acquireIfNeeded() {
|
||||
if (!isHeld) acquire()
|
||||
}
|
||||
|
||||
private fun getPlaceholderNotification(): Notification {
|
||||
return notification(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
|
||||
return notification(Notifications.CHANNEL_DOWNLOADER) {
|
||||
setContentTitle(getString(R.string.download_notifier_downloader_title))
|
||||
}
|
||||
}
|
||||
|
15
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt
Normal file → Executable file
15
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt
Normal file → Executable file
@ -1,7 +1,6 @@
|
||||
package eu.kanade.tachiyomi.data.download
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import com.google.gson.Gson
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
@ -43,9 +42,9 @@ class DownloadStore(
|
||||
* @param downloads the list of downloads to add.
|
||||
*/
|
||||
fun addAll(downloads: List<Download>) {
|
||||
preferences.edit {
|
||||
downloads.forEach { putString(getKey(it), serialize(it)) }
|
||||
}
|
||||
val editor = preferences.edit()
|
||||
downloads.forEach { editor.putString(getKey(it), serialize(it)) }
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,18 +53,14 @@ class DownloadStore(
|
||||
* @param download the download to remove.
|
||||
*/
|
||||
fun remove(download: Download) {
|
||||
preferences.edit {
|
||||
remove(getKey(download))
|
||||
}
|
||||
preferences.edit().remove(getKey(download)).apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the downloads from the store.
|
||||
*/
|
||||
fun clear() {
|
||||
preferences.edit {
|
||||
clear()
|
||||
}
|
||||
preferences.edit().clear().apply()
|
||||
}
|
||||
|
||||
/**
|
||||
|
34
app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
Normal file → Executable file
34
app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
Normal file → Executable file
@ -5,7 +5,6 @@ import android.webkit.MimeTypeMap
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.jakewharton.rxrelay.BehaviorRelay
|
||||
import com.jakewharton.rxrelay.PublishRelay
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
@ -117,8 +116,6 @@ class Downloader(
|
||||
val pending = queue.filter { it.status != Download.DOWNLOADED }
|
||||
pending.forEach { if (it.status != Download.QUEUE) it.status = Download.QUEUE }
|
||||
|
||||
notifier.paused = false
|
||||
|
||||
downloadsRelay.call(pending)
|
||||
return pending.isNotEmpty()
|
||||
}
|
||||
@ -137,10 +134,9 @@ class Downloader(
|
||||
} else {
|
||||
if (notifier.paused) {
|
||||
notifier.paused = false
|
||||
notifier.onPaused()
|
||||
notifier.onDownloadPaused()
|
||||
} else {
|
||||
notifier.dismissProgress()
|
||||
notifier.onComplete()
|
||||
notifier.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -171,7 +167,7 @@ class Downloader(
|
||||
.forEach { it.status = Download.NOT_DOWNLOADED }
|
||||
}
|
||||
queue.clear()
|
||||
notifier.dismissProgress()
|
||||
notifier.dismiss()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -195,7 +191,7 @@ class Downloader(
|
||||
},
|
||||
5
|
||||
)
|
||||
.onBackpressureLatest()
|
||||
.onBackpressureBuffer()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
@ -232,9 +228,13 @@ class Downloader(
|
||||
val wasEmpty = queue.isEmpty()
|
||||
// Called in background thread, the operation can be slow with SAF.
|
||||
val chaptersWithoutDir = async {
|
||||
val mangaDir = provider.findMangaDir(manga, source)
|
||||
|
||||
chapters
|
||||
// Avoid downloading chapters with the same name.
|
||||
.distinctBy { it.name }
|
||||
// Filter out those already downloaded.
|
||||
.filter { provider.findChapterDir(it, manga, source) == null }
|
||||
.filter { mangaDir?.findFile(provider.getChapterDirName(it)) == null }
|
||||
// Add chapters to queue from the start.
|
||||
.sortedByDescending { it.source_order }
|
||||
}
|
||||
@ -267,16 +267,8 @@ class Downloader(
|
||||
* @param download the chapter to be downloaded.
|
||||
*/
|
||||
private fun downloadChapter(download: Download): Observable<Download> = Observable.defer {
|
||||
val mangaDir = provider.getMangaDir(download.manga, download.source)
|
||||
|
||||
val availSpace = DiskUtil.getAvailableStorageSpace(mangaDir)
|
||||
if (availSpace != -1L && availSpace < MIN_DISK_SPACE) {
|
||||
download.status = Download.ERROR
|
||||
notifier.onError(context.getString(R.string.download_insufficient_space), download.chapter.name)
|
||||
return@defer Observable.just(download)
|
||||
}
|
||||
|
||||
val chapterDirname = provider.getChapterDirName(download.chapter)
|
||||
val mangaDir = provider.getMangaDir(download.manga, download.source)
|
||||
val tmpDir = mangaDir.createDirectory(chapterDirname + TMP_DIR_SUFFIX)
|
||||
|
||||
val pageListObservable = if (download.pages == null) {
|
||||
@ -284,7 +276,7 @@ class Downloader(
|
||||
download.source.fetchPageList(download.chapter)
|
||||
.doOnNext { pages ->
|
||||
if (pages.isEmpty()) {
|
||||
throw Exception(context.getString(R.string.page_list_empty_error))
|
||||
throw Exception("Page list is empty")
|
||||
}
|
||||
download.pages = pages
|
||||
}
|
||||
@ -308,7 +300,6 @@ class Downloader(
|
||||
// Start downloading images, consider we can have downloaded images already
|
||||
// Concurrently do 5 pages at a time
|
||||
.flatMap({ page -> getOrDownloadImage(page, download, tmpDir) }, 5)
|
||||
.onBackpressureLatest()
|
||||
// Do when page is downloaded.
|
||||
.doOnNext { notifier.onProgressChange(download) }
|
||||
.toList()
|
||||
@ -494,8 +485,5 @@ class Downloader(
|
||||
|
||||
companion object {
|
||||
const val TMP_DIR_SUFFIX = "_tmp"
|
||||
|
||||
// Arbitrary minimum required space to start a download: 50 MB
|
||||
const val MIN_DISK_SPACE = 50 * 1024 * 1024
|
||||
}
|
||||
}
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
Normal file → Executable file
@ -104,14 +104,10 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
)
|
||||
)
|
||||
setSmallIcon(R.drawable.ic_tachi)
|
||||
|
||||
val errorLogIntent = NotificationReceiver.openErrorLogPendingActivity(context, uri)
|
||||
|
||||
setContentIntent(errorLogIntent)
|
||||
addAction(
|
||||
R.drawable.nnf_ic_file_folder,
|
||||
context.getString(R.string.action_open_log),
|
||||
errorLogIntent
|
||||
NotificationReceiver.openErrorLogPendingActivity(context, uri)
|
||||
)
|
||||
}
|
||||
.build()
|
||||
|
42
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
Normal file → Executable file
42
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
Normal file → Executable file
@ -27,7 +27,9 @@ import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||
import exh.LIBRARY_UPDATE_EXCLUDED_SOURCES
|
||||
import java.io.File
|
||||
import java.util.ArrayList
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
@ -253,9 +255,9 @@ class LibraryUpdateService(
|
||||
// Initialize the variables holding the progress of the updates.
|
||||
val count = AtomicInteger(0)
|
||||
// List containing new updates
|
||||
val newUpdates = mutableListOf<Pair<LibraryManga, Array<Chapter>>>()
|
||||
val newUpdates = ArrayList<Pair<LibraryManga, Array<Chapter>>>()
|
||||
// List containing failed updates
|
||||
val failedUpdates = mutableListOf<Pair<Manga, String?>>()
|
||||
val failedUpdates = ArrayList<Pair<Manga, String?>>()
|
||||
// Boolean to determine if DownloadManager has downloads
|
||||
var hasDownloads = false
|
||||
|
||||
@ -265,20 +267,25 @@ class LibraryUpdateService(
|
||||
.doOnNext { notifier.showProgressNotification(it, count.andIncrement, mangaToUpdate.size) }
|
||||
// Update the chapters of the manga
|
||||
.concatMap { manga ->
|
||||
updateManga(manga)
|
||||
// If there's any error, return empty update and continue.
|
||||
.onErrorReturn {
|
||||
failedUpdates.add(Pair(manga, it.message))
|
||||
Pair(emptyList(), emptyList())
|
||||
}
|
||||
// Filter out mangas without new chapters (or failed).
|
||||
.filter { pair -> pair.first.isNotEmpty() }
|
||||
.doOnNext {
|
||||
if (manga.shouldDownloadNewChapters(db, preferences)) {
|
||||
downloadChapters(manga, it.first)
|
||||
hasDownloads = true
|
||||
if (manga.source in LIBRARY_UPDATE_EXCLUDED_SOURCES) {
|
||||
// Ignore EXH manga, updating chapters for every manga will get you banned
|
||||
Observable.empty()
|
||||
} else {
|
||||
updateManga(manga)
|
||||
// If there's any error, return empty update and continue.
|
||||
.onErrorReturn {
|
||||
failedUpdates.add(Pair(manga, it.message))
|
||||
Pair(emptyList(), emptyList())
|
||||
}
|
||||
}
|
||||
// Filter out mangas without new chapters (or failed).
|
||||
.filter { pair -> pair.first.isNotEmpty() }
|
||||
.doOnNext {
|
||||
if (manga.shouldDownloadNewChapters(db, preferences)) {
|
||||
downloadChapters(manga, it.first)
|
||||
hasDownloads = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// Convert to the manga that contains new chapters.
|
||||
.map {
|
||||
Pair(
|
||||
@ -330,7 +337,7 @@ class LibraryUpdateService(
|
||||
* @return a pair of the inserted and removed chapters.
|
||||
*/
|
||||
fun updateManga(manga: Manga): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
val source = sourceManager.get(manga.source) ?: return Observable.empty()
|
||||
|
||||
// Update manga details metadata in the background
|
||||
if (preferences.autoUpdateMetadata()) {
|
||||
@ -429,8 +436,7 @@ class LibraryUpdateService(
|
||||
|
||||
destFile.bufferedWriter().use { out ->
|
||||
errors.forEach { (manga, error) ->
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
out.write("${manga.title} ($source): $error\n")
|
||||
out.write("${manga.title}: $error\n")
|
||||
}
|
||||
}
|
||||
return destFile
|
||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt
Normal file → Executable file
2
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
Normal file → Executable file
2
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
Normal file → Executable file
@ -155,7 +155,7 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
* @param mangaId id of manga
|
||||
* @param chapterId id of chapter
|
||||
*/
|
||||
private fun openChapter(context: Context, mangaId: Long, chapterId: Long) {
|
||||
internal fun openChapter(context: Context, mangaId: Long, chapterId: Long) {
|
||||
val db = DatabaseHelper(context)
|
||||
val manga = db.getManga(mangaId).executeAsBlocking()
|
||||
val chapter = db.getChapter(chapterId).executeAsBlocking()
|
||||
|
@ -30,12 +30,8 @@ object Notifications {
|
||||
/**
|
||||
* Notification channel and ids used by the downloader.
|
||||
*/
|
||||
private const val GROUP_DOWNLOADER = "group_downloader"
|
||||
const val CHANNEL_DOWNLOADER_PROGRESS = "downloader_progress_channel"
|
||||
const val ID_DOWNLOAD_CHAPTER_PROGRESS = -201
|
||||
const val CHANNEL_DOWNLOADER_COMPLETE = "downloader_complete_channel"
|
||||
const val ID_DOWNLOAD_CHAPTER_COMPLETE = -203
|
||||
const val CHANNEL_DOWNLOADER_ERROR = "downloader_error_channel"
|
||||
const val CHANNEL_DOWNLOADER = "downloader_channel"
|
||||
const val ID_DOWNLOAD_CHAPTER = -201
|
||||
const val ID_DOWNLOAD_CHAPTER_ERROR = -202
|
||||
|
||||
/**
|
||||
@ -54,7 +50,7 @@ object Notifications {
|
||||
/**
|
||||
* Notification channel and ids used by the backup/restore system.
|
||||
*/
|
||||
private const val GROUP_BACKUP_RESTORE = "group_backup_restore"
|
||||
private const val GROUP_BACK_RESTORE = "group_backup_restore"
|
||||
const val CHANNEL_BACKUP_RESTORE_PROGRESS = "backup_restore_progress_channel"
|
||||
const val ID_BACKUP_PROGRESS = -501
|
||||
const val ID_RESTORE_PROGRESS = -503
|
||||
@ -63,7 +59,6 @@ object Notifications {
|
||||
const val ID_RESTORE_COMPLETE = -504
|
||||
|
||||
private val deprecatedChannels = listOf(
|
||||
"downloader_channel",
|
||||
"backup_restore_complete_channel"
|
||||
)
|
||||
|
||||
@ -75,12 +70,10 @@ object Notifications {
|
||||
fun createChannels(context: Context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||
|
||||
listOf(
|
||||
NotificationChannelGroup(GROUP_BACKUP_RESTORE, context.getString(R.string.group_backup_restore)),
|
||||
NotificationChannelGroup(GROUP_DOWNLOADER, context.getString(R.string.group_downloader))
|
||||
).forEach(context.notificationManager::createNotificationChannelGroup)
|
||||
val backupRestoreGroup = NotificationChannelGroup(GROUP_BACK_RESTORE, context.getString(R.string.channel_backup_restore))
|
||||
context.notificationManager.createNotificationChannelGroup(backupRestoreGroup)
|
||||
|
||||
listOf(
|
||||
val channels = listOf(
|
||||
NotificationChannel(
|
||||
CHANNEL_COMMON, context.getString(R.string.channel_common),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
@ -92,24 +85,9 @@ object Notifications {
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_DOWNLOADER_PROGRESS, context.getString(R.string.channel_progress),
|
||||
CHANNEL_DOWNLOADER, context.getString(R.string.channel_downloader),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_DOWNLOADER
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_DOWNLOADER_COMPLETE, context.getString(R.string.channel_complete),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_DOWNLOADER
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_DOWNLOADER_ERROR, context.getString(R.string.channel_errors),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_DOWNLOADER
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
@ -121,23 +99,26 @@ object Notifications {
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
),
|
||||
NotificationChannel(
|
||||
CHANNEL_BACKUP_RESTORE_PROGRESS, context.getString(R.string.channel_progress),
|
||||
CHANNEL_BACKUP_RESTORE_PROGRESS, context.getString(R.string.channel_backup_restore_progress),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_BACKUP_RESTORE
|
||||
group = GROUP_BACK_RESTORE
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_BACKUP_RESTORE_COMPLETE, context.getString(R.string.channel_complete),
|
||||
CHANNEL_BACKUP_RESTORE_COMPLETE, context.getString(R.string.channel_backup_restore_complete),
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply {
|
||||
group = GROUP_BACKUP_RESTORE
|
||||
group = GROUP_BACK_RESTORE
|
||||
setShowBadge(false)
|
||||
setSound(null, null)
|
||||
}
|
||||
).forEach(context.notificationManager::createNotificationChannel)
|
||||
)
|
||||
context.notificationManager.createNotificationChannels(channels)
|
||||
|
||||
// Delete old notification channels
|
||||
deprecatedChannels.forEach(context.notificationManager::deleteNotificationChannel)
|
||||
deprecatedChannels.forEach {
|
||||
context.notificationManager.deleteNotificationChannel(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
104
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
Normal file → Executable file
104
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
Normal file → Executable file
@ -21,8 +21,6 @@ object PreferenceKeys {
|
||||
|
||||
const val showPageNumber = "pref_show_page_number_key"
|
||||
|
||||
const val showReadingMode = "pref_show_reading_mode"
|
||||
|
||||
const val trueColor = "pref_true_color_key"
|
||||
|
||||
const val fullscreen = "fullscreen"
|
||||
@ -55,8 +53,6 @@ object PreferenceKeys {
|
||||
|
||||
const val readWithTapping = "reader_tap"
|
||||
|
||||
const val readWithTappingInverted = "reader_tapping_inverted"
|
||||
|
||||
const val readWithLongTap = "reader_long_tap"
|
||||
|
||||
const val readWithVolumeKeys = "reader_volume_keys"
|
||||
@ -69,20 +65,20 @@ object PreferenceKeys {
|
||||
|
||||
const val landscapeColumns = "pref_library_columns_landscape_key"
|
||||
|
||||
const val jumpToChapters = "jump_to_chapters"
|
||||
|
||||
const val updateOnlyNonCompleted = "pref_update_only_non_completed_key"
|
||||
|
||||
const val autoUpdateTrack = "pref_auto_update_manga_sync_key"
|
||||
|
||||
const val lastUsedSource = "last_catalogue_source"
|
||||
const val lastUsedCatalogueSource = "last_catalogue_source"
|
||||
|
||||
const val lastUsedCategory = "last_used_category"
|
||||
|
||||
const val sourceDisplayMode = "pref_display_mode_catalogue"
|
||||
const val catalogueViewSetting = "pref_display_catalogue_view_setting"
|
||||
|
||||
const val enabledLanguages = "source_languages"
|
||||
|
||||
const val sourcesSort = "sources_sort"
|
||||
|
||||
const val backupDirectory = "backup_directory"
|
||||
|
||||
const val downloadsDirectory = "download_directory"
|
||||
@ -97,8 +93,6 @@ object PreferenceKeys {
|
||||
|
||||
const val removeAfterMarkedAsRead = "pref_remove_after_marked_as_read_key"
|
||||
|
||||
const val removeBookmarkedChapters = "pref_remove_bookmarked"
|
||||
|
||||
const val libraryUpdateInterval = "pref_library_update_interval_key"
|
||||
|
||||
const val libraryUpdateRestriction = "library_update_restriction"
|
||||
@ -115,12 +109,14 @@ object PreferenceKeys {
|
||||
|
||||
const val filterCompleted = "pref_filter_completed_key"
|
||||
|
||||
const val filterTracked = "pref_filter_tracked_key"
|
||||
|
||||
const val filterLewd = "pref_filter_lewd_key"
|
||||
|
||||
const val librarySortingMode = "library_sorting_mode"
|
||||
|
||||
const val automaticExtUpdates = "automatic_ext_updates"
|
||||
|
||||
const val allowNsfwSource = "allow_nsfw_source"
|
||||
|
||||
const val startScreen = "start_screen"
|
||||
|
||||
const val useBiometricLock = "use_biometric_lock"
|
||||
@ -141,7 +137,7 @@ object PreferenceKeys {
|
||||
|
||||
const val downloadNewCategories = "download_new_categories"
|
||||
|
||||
const val libraryDisplayMode = "pref_display_mode_library"
|
||||
const val libraryViewSetting = "pref_display_library_view_setting"
|
||||
|
||||
const val lang = "app_language"
|
||||
|
||||
@ -157,17 +153,93 @@ object PreferenceKeys {
|
||||
|
||||
const val unreadBadge = "display_unread_badge"
|
||||
|
||||
const val categoryTabs = "display_category_tabs"
|
||||
const val skipPreMigration = "skip_pre_migration"
|
||||
|
||||
const val alwaysShowChapterTransition = "always_show_chapter_transition"
|
||||
|
||||
const val searchPinnedSourcesOnly = "search_pinned_sources_only"
|
||||
|
||||
const val enableDoh = "enable_doh"
|
||||
|
||||
fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"
|
||||
|
||||
fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"
|
||||
|
||||
fun trackToken(syncId: Int) = "track_token_$syncId"
|
||||
|
||||
const val eh_lock_hash = "lock_hash"
|
||||
|
||||
const val eh_lock_salt = "lock_salt"
|
||||
|
||||
const val eh_lock_length = "lock_length"
|
||||
|
||||
const val eh_lock_finger = "lock_finger"
|
||||
|
||||
const val eh_lock_manually = "eh_lock_manually"
|
||||
|
||||
const val eh_nh_useHighQualityThumbs = "eh_nh_hq_thumbs"
|
||||
|
||||
const val eh_showSyncIntro = "eh_show_sync_intro"
|
||||
|
||||
const val eh_readOnlySync = "eh_sync_read_only"
|
||||
|
||||
const val eh_lenientSync = "eh_lenient_sync"
|
||||
|
||||
const val eh_useOrigImages = "eh_useOrigImages"
|
||||
|
||||
const val eh_ehSettingsProfile = "eh_ehSettingsProfile"
|
||||
|
||||
const val eh_exhSettingsProfile = "eh_exhSettingsProfile"
|
||||
|
||||
const val eh_settingsKey = "eh_settingsKey"
|
||||
|
||||
const val eh_sessionCookie = "eh_sessionCookie"
|
||||
|
||||
const val eh_hathPerksCookie = "eh_hathPerksCookie"
|
||||
|
||||
const val eh_enableExHentai = "enable_exhentai"
|
||||
|
||||
const val eh_ts_aspNetCookie = "eh_ts_aspNetCookie"
|
||||
|
||||
const val eh_showSettingsUploadWarning = "eh_showSettingsUploadWarning2"
|
||||
|
||||
const val eh_expandFilters = "eh_expand_filters"
|
||||
|
||||
const val eh_readerThreads = "eh_reader_threads"
|
||||
|
||||
const val eh_readerInstantRetry = "eh_reader_instant_retry"
|
||||
|
||||
const val eh_utilAutoscrollInterval = "eh_util_autoscroll_interval"
|
||||
|
||||
const val eh_cacheSize = "eh_cache_size"
|
||||
|
||||
const val eh_preserveReadingPosition = "eh_preserve_reading_position"
|
||||
|
||||
const val eh_autoSolveCaptchas = "eh_autosolve_captchas"
|
||||
|
||||
const val eh_delegateSources = "eh_delegate_sources"
|
||||
|
||||
const val eh_logLevel = "eh_log_level"
|
||||
|
||||
const val eh_enableSourceBlacklist = "eh_enable_source_blacklist"
|
||||
|
||||
const val eh_autoUpdateFrequency = "eh_auto_update_frequency"
|
||||
|
||||
const val eh_autoUpdateRestrictions = "eh_auto_update_restrictions"
|
||||
|
||||
const val eh_autoUpdateStats = "eh_auto_update_stats"
|
||||
|
||||
const val eh_aggressivePageLoading = "eh_aggressive_page_loading"
|
||||
|
||||
const val eh_hl_useHighQualityThumbs = "eh_hl_hq_thumbs"
|
||||
|
||||
const val eh_preload_size = "eh_preload_size"
|
||||
|
||||
const val eh_tag_filtering_value = "eh_tag_filtering_value"
|
||||
|
||||
const val eh_tag_watching_value = "eh_tag_watching_value"
|
||||
|
||||
const val eh_is_hentai_enabled = "eh_is_hentai_enabled"
|
||||
|
||||
const val eh_use_new_manga_interface = "eh_use_new_manga_interface"
|
||||
|
||||
const val eh_use_auto_webtoon = "eh_use_auto_webtoon"
|
||||
}
|
||||
|
@ -5,42 +5,14 @@ package eu.kanade.tachiyomi.data.preference
|
||||
*/
|
||||
object PreferenceValues {
|
||||
|
||||
// Keys are lowercase to match legacy string values
|
||||
enum class ThemeMode {
|
||||
light,
|
||||
dark,
|
||||
system,
|
||||
}
|
||||
const val THEME_MODE_LIGHT = "light"
|
||||
const val THEME_MODE_DARK = "dark"
|
||||
const val THEME_MODE_SYSTEM = "system"
|
||||
|
||||
// Keys are lowercase to match legacy string values
|
||||
enum class LightThemeVariant {
|
||||
default,
|
||||
blue,
|
||||
}
|
||||
const val THEME_LIGHT_DEFAULT = "default"
|
||||
const val THEME_LIGHT_BLUE = "blue"
|
||||
|
||||
// Keys are lowercase to match legacy string values
|
||||
enum class DarkThemeVariant {
|
||||
default,
|
||||
blue,
|
||||
amoled,
|
||||
}
|
||||
|
||||
enum class DisplayMode {
|
||||
COMPACT_GRID,
|
||||
COMFORTABLE_GRID,
|
||||
LIST,
|
||||
}
|
||||
|
||||
enum class TappingInvertMode {
|
||||
NONE,
|
||||
HORIZONTAL,
|
||||
VERTICAL,
|
||||
BOTH
|
||||
}
|
||||
|
||||
enum class NsfwAllowance {
|
||||
ALLOWED,
|
||||
PARTIAL,
|
||||
BLOCKED
|
||||
}
|
||||
const val THEME_DARK_DEFAULT = "default"
|
||||
const val THEME_DARK_BLUE = "blue"
|
||||
const val THEME_DARK_AMOLED = "amoled"
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user