mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-26 19:17: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 |
0
.github/CONTRIBUTING.md
vendored
Normal file → Executable file
0
.github/CONTRIBUTING.md
vendored
Normal file → Executable file
0
.github/ISSUE_TEMPLATE.md
vendored
Normal file → Executable file
0
.github/ISSUE_TEMPLATE.md
vendored
Normal file → Executable file
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 }}'"
|
7
.gitignore
vendored
Normal file → Executable file
7
.gitignore
vendored
Normal file → Executable file
@ -5,6 +5,8 @@
|
|||||||
.idea/
|
.idea/
|
||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
|
/mainframer
|
||||||
|
/.mainframer
|
||||||
|
|
||||||
# Built files
|
# Built files
|
||||||
*/build
|
*/build
|
||||||
@ -14,3 +16,8 @@ app/**/output.json
|
|||||||
|
|
||||||
# Hebrew assets are copied on build
|
# Hebrew assets are copied on build
|
||||||
app/src/main/res/values-iw/
|
app/src/main/res/values-iw/
|
||||||
|
|
||||||
|
TODO.md
|
||||||
|
CHANGELOG.md
|
||||||
|
/captures
|
||||||
|
build.sh
|
||||||
|
@ -62,6 +62,13 @@ deploy:
|
|||||||
branch: master
|
branch: master
|
||||||
condition: "-z $TRAVIS_TAG"
|
condition: "-z $TRAVIS_TAG"
|
||||||
repo: inorichi/tachiyomi
|
repo: inorichi/tachiyomi
|
||||||
|
- provider: script
|
||||||
|
script: ".travis/deploy.sh"
|
||||||
|
skip_cleanup: true
|
||||||
|
on:
|
||||||
|
branch: dev
|
||||||
|
condition: "-z $TRAVIS_TAG"
|
||||||
|
repo: inorichi/tachiyomi
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
|
@ -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.
88
README.md
88
README.md
@ -1,87 +1,3 @@
|
|||||||
| Build | Stable | Weekly Preview | Contribute | Support Server |
|
I haven't started a readme
|
||||||
|-------|----------|---------|------------|---------|
|
|
||||||
| [](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) |
|
|
||||||
|
|
||||||
|
Automated Preview Builds(with updater): https://github.com/jobobby04/TachiyomiSYPreview/releases
|
||||||
# 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 (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
|
|
||||||
* 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.
|
|
||||||
|
2
app/.gitignore
vendored
Normal file → Executable file
2
app/.gitignore
vendored
Normal file → Executable file
@ -2,3 +2,5 @@
|
|||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
custom.gradle
|
custom.gradle
|
||||||
|
google-services.json
|
||||||
|
output.json
|
116
app/build.gradle
Normal file → Executable file
116
app/build.gradle
Normal file → Executable file
@ -1,3 +1,5 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
|
||||||
|
//noinspection GradleDependency
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
@ -6,6 +8,8 @@ apply plugin: 'kotlin-android'
|
|||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'com.github.zellius.shortcut-helper'
|
apply plugin: 'com.github.zellius.shortcut-helper'
|
||||||
|
// Realm (EH)
|
||||||
|
apply plugin: 'realm-android'
|
||||||
|
|
||||||
shortcutHelper.filePath = './shortcuts.xml'
|
shortcutHelper.filePath = './shortcuts.xml'
|
||||||
|
|
||||||
@ -32,19 +36,20 @@ ext {
|
|||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 29
|
||||||
buildToolsVersion '29.0.3'
|
buildToolsVersion '29.0.3'
|
||||||
|
publishNonDefault true
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "eu.kanade.tachiyomi"
|
applicationId "eu.kanade.tachiyomi.sy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
versionCode 45
|
versionCode 1
|
||||||
versionName "0.9.2"
|
versionName "0.9.2.7"
|
||||||
|
|
||||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||||
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
||||||
buildConfigField "boolean", "INCLUDE_UPDATER", "false"
|
buildConfigField "boolean", "INCLUDE_UPDATER", "true"
|
||||||
|
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
@ -61,12 +66,21 @@ android {
|
|||||||
debug {
|
debug {
|
||||||
versionNameSuffix "-${getCommitCount()}"
|
versionNameSuffix "-${getCommitCount()}"
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
|
ext.enableCrashlytics = false
|
||||||
}
|
}
|
||||||
// release {
|
releaseTest {
|
||||||
|
applicationIdSuffix ".rt"
|
||||||
// minifyEnabled true
|
// minifyEnabled true
|
||||||
// shrinkResources true
|
// shrinkResources true
|
||||||
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
zipAlignEnabled true
|
||||||
// }
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
|
zipAlignEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "default"
|
flavorDimensions "default"
|
||||||
@ -76,21 +90,27 @@ android {
|
|||||||
buildConfigField "boolean", "INCLUDE_UPDATER", "true"
|
buildConfigField "boolean", "INCLUDE_UPDATER", "true"
|
||||||
dimension "default"
|
dimension "default"
|
||||||
}
|
}
|
||||||
fdroid {
|
|
||||||
dimension "default"
|
|
||||||
}
|
|
||||||
dev {
|
dev {
|
||||||
resConfigs "en", "xxhdpi"
|
resConfigs "en", "xxhdpi"
|
||||||
dimension "default"
|
dimension "default"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility 1.8
|
||||||
|
targetCompatibility 1.8
|
||||||
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
exclude 'META-INF/DEPENDENCIES'
|
exclude 'META-INF/DEPENDENCIES'
|
||||||
exclude 'LICENSE.txt'
|
exclude 'LICENSE.txt'
|
||||||
exclude 'META-INF/LICENSE'
|
exclude 'META-INF/LICENSE'
|
||||||
exclude 'META-INF/LICENSE.txt'
|
exclude 'META-INF/LICENSE.txt'
|
||||||
exclude 'META-INF/NOTICE'
|
exclude 'META-INF/NOTICE'
|
||||||
|
exclude 'META-INF/*.kotlin_module'
|
||||||
|
|
||||||
|
// Compatibility for two RxJava versions (EXH)
|
||||||
|
exclude 'META-INF/rxjava.properties'
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
@ -137,7 +157,7 @@ dependencies {
|
|||||||
// UI library
|
// UI library
|
||||||
implementation 'com.google.android.material:material:1.1.0'
|
implementation 'com.google.android.material:material:1.1.0'
|
||||||
|
|
||||||
standardImplementation 'com.google.firebase:firebase-core:17.4.0'
|
standardImplementation 'com.google.firebase:firebase-core:17.4.1'
|
||||||
|
|
||||||
// ReactiveX
|
// ReactiveX
|
||||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||||
@ -146,13 +166,13 @@ dependencies {
|
|||||||
implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
|
implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
|
||||||
|
|
||||||
// Network client
|
// Network client
|
||||||
final okhttp_version = '4.5.0'
|
final okhttp_version = '4.7.2'
|
||||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
|
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
|
||||||
implementation 'com.squareup.okio:okio:2.6.0'
|
implementation 'com.squareup.okio:okio:2.6.0'
|
||||||
|
|
||||||
// REST
|
// REST
|
||||||
final retrofit_version = '2.8.1'
|
final retrofit_version = '2.9.0'
|
||||||
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
||||||
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
|
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
|
||||||
implementation "com.squareup.retrofit2:adapter-rxjava:$retrofit_version"
|
implementation "com.squareup.retrofit2:adapter-rxjava:$retrofit_version"
|
||||||
@ -162,7 +182,7 @@ dependencies {
|
|||||||
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
||||||
|
|
||||||
// JavaScript engine
|
// 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
|
// Disk
|
||||||
implementation 'com.jakewharton:disklrucache:2.0.2'
|
implementation 'com.jakewharton:disklrucache:2.0.2'
|
||||||
@ -176,6 +196,9 @@ dependencies {
|
|||||||
implementation "androidx.work:work-runtime:$work_version"
|
implementation "androidx.work:work-runtime:$work_version"
|
||||||
implementation "androidx.work:work-runtime-ktx:$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
|
// Changelog
|
||||||
implementation 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
|
implementation 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
|
||||||
|
|
||||||
@ -190,7 +213,7 @@ dependencies {
|
|||||||
implementation 'com.github.tfcporciuncula:flow-preferences:1.1.1'
|
implementation 'com.github.tfcporciuncula:flow-preferences:1.1.1'
|
||||||
|
|
||||||
// Model View Presenter
|
// Model View Presenter
|
||||||
final nucleus_version = '6.0.0'
|
final nucleus_version = '3.0.0'
|
||||||
implementation "info.android15.nucleus:nucleus:$nucleus_version"
|
implementation "info.android15.nucleus:nucleus:$nucleus_version"
|
||||||
implementation "info.android15.nucleus:nucleus-support-v7:$nucleus_version"
|
implementation "info.android15.nucleus:nucleus-support-v7:$nucleus_version"
|
||||||
|
|
||||||
@ -198,7 +221,7 @@ dependencies {
|
|||||||
implementation "com.github.inorichi.injekt:injekt-core:65b0440"
|
implementation "com.github.inorichi.injekt:injekt-core:65b0440"
|
||||||
|
|
||||||
// Image library
|
// 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:glide:$glide_version"
|
||||||
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
|
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
|
||||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
||||||
@ -256,15 +279,64 @@ dependencies {
|
|||||||
testImplementation "org.robolectric:shadows-play-services:$robolectric_version"
|
testImplementation "org.robolectric:shadows-play-services:$robolectric_version"
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
|
|
||||||
final coroutines_version = '1.3.5'
|
final coroutines_version = '1.3.7'
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$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"
|
||||||
|
|
||||||
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
|
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
|
||||||
|
|
||||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
// Text distance (EH)
|
||||||
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
|
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 {
|
buildscript {
|
||||||
@ -282,8 +354,8 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api-markers
|
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api-markers
|
||||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
|
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
|
// Duplicating Hebrew string assets due to some locale code issues on different devices
|
||||||
@ -297,4 +369,6 @@ preBuild.dependsOn(ktlintFormat, copyResources)
|
|||||||
|
|
||||||
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) {
|
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) {
|
||||||
apply plugin: 'com.google.gms.google-services'
|
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.github.salomonbrys.kotson.** { *; }
|
||||||
-keep class com.squareup.duktape.** { *; }
|
-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
|
# Design library
|
||||||
-dontwarn com.google.android.material.**
|
-dontwarn com.google.android.material.**
|
||||||
-keep class com.google.android.material.** { *; }
|
-keep class com.google.android.material.** { *; }
|
||||||
@ -23,20 +30,6 @@
|
|||||||
<init>();
|
<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
|
# RxJava 1.1.0
|
||||||
-dontwarn sun.misc.**
|
-dontwarn sun.misc.**
|
||||||
@ -54,20 +47,186 @@
|
|||||||
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
# ReactiveNetwork
|
-dontnote rx.internal.util.PlatformDependent
|
||||||
-dontwarn com.github.pwittchen.reactivenetwork.**
|
|
||||||
|
|
||||||
## 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
|
# 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.
|
# removes such information by default, so configure it to keep all of it.
|
||||||
-keepattributes Signature
|
-keepattributes Signature
|
||||||
|
|
||||||
|
# For using GSON @Expose annotation
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
|
||||||
# Gson specific classes
|
# 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,
|
# Prevent proguard from stripping interface information from TypeAdapterFactory,
|
||||||
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
||||||
-keep class * implements com.google.gson.TypeAdapterFactory
|
-keep class * implements com.google.gson.TypeAdapterFactory
|
||||||
-keep class * implements com.google.gson.JsonSerializer
|
-keep class * implements com.google.gson.JsonSerializer
|
||||||
-keep class * implements com.google.gson.JsonDeserializer
|
-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:fillType="evenOdd"
|
||||||
android:fillColor="#607D8B"/>
|
android:fillColor="#607D8B"/>
|
||||||
<path
|
<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:name="path_3"
|
||||||
android:fillType="evenOdd"
|
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:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
<path
|
<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:name="path_4"
|
||||||
android:fillType="evenOdd"
|
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:fillColor="#CE2828"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
<path
|
<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:name="path_5"
|
||||||
android:fillType="evenOdd"
|
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:fillColor="#FFF"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
<path
|
<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:name="path_6"
|
||||||
android:fillType="evenOdd"
|
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="#000"/>
|
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>
|
</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>
|
147
app/src/main/AndroidManifest.xml
Normal file → Executable file
147
app/src/main/AndroidManifest.xml
Normal file → Executable file
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="eu.kanade.tachiyomi">
|
package="eu.kanade.tachiyomi">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
@ -13,6 +14,13 @@
|
|||||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
<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
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
@ -25,6 +33,7 @@
|
|||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:theme="@style/Theme.Tachiyomi.Light"
|
android:theme="@style/Theme.Tachiyomi.Light"
|
||||||
android:usesCleartextTraffic="true">
|
android:usesCleartextTraffic="true">
|
||||||
<activity
|
<activity
|
||||||
@ -40,6 +49,11 @@
|
|||||||
android:name="android.app.shortcuts"
|
android:name="android.app.shortcuts"
|
||||||
android:resource="@xml/shortcuts" />
|
android:resource="@xml/shortcuts" />
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.main.ForceCloseActivity"
|
||||||
|
android:clearTaskOnLaunch="true"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.DeepLinkActivity"
|
android:name=".ui.main.DeepLinkActivity"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
@ -160,6 +174,139 @@
|
|||||||
android:name=".data.backup.BackupRestoreService"
|
android:name=".data.backup.BackupRestoreService"
|
||||||
android:exported="false" />
|
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>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</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 |
174
app/src/main/java/eu/kanade/tachiyomi/App.kt
Normal file → Executable file
174
app/src/main/java/eu/kanade/tachiyomi/App.kt
Normal file → Executable file
@ -3,44 +3,79 @@ package eu.kanade.tachiyomi
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
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.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleObserver
|
import androidx.lifecycle.LifecycleObserver
|
||||||
import androidx.lifecycle.OnLifecycleEvent
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.multidex.MultiDex
|
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.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
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.ui.security.SecureActivityDelegate
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import org.acra.ACRA
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import org.acra.annotation.AcraCore
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import org.acra.annotation.AcraHttpSender
|
import exh.debug.DebugToggles
|
||||||
import org.acra.sender.HttpSender
|
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 timber.log.Timber
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.InjektScope
|
import uy.kohesive.injekt.api.InjektScope
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
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 {
|
open class App : Application(), LifecycleObserver {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
||||||
|
setupExhLogging() // EXH logging
|
||||||
|
|
||||||
|
workaroundAndroid7BrokenSSL()
|
||||||
|
|
||||||
|
// Enforce WebView availability
|
||||||
|
if (!WebViewUtil.supportsWebView(this)) {
|
||||||
|
toast(R.string.information_webview_required, Toast.LENGTH_LONG)
|
||||||
|
ForceCloseActivity.closeApp(this)
|
||||||
|
}
|
||||||
|
|
||||||
Injekt = InjektScope(DefaultRegistrar())
|
Injekt = InjektScope(DefaultRegistrar())
|
||||||
Injekt.importModule(AppModule(this))
|
Injekt.importModule(AppModule(this))
|
||||||
|
|
||||||
setupAcra()
|
|
||||||
setupNotificationChannels()
|
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)
|
LocaleHelper.updateConfiguration(this, resources.configuration)
|
||||||
|
|
||||||
@ -57,6 +92,26 @@ open class App : Application(), LifecycleObserver {
|
|||||||
LocaleHelper.updateConfiguration(this, newConfig, true)
|
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)
|
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun onAppBackgrounded() {
|
fun onAppBackgrounded() {
|
||||||
@ -66,11 +121,96 @@ open class App : Application(), LifecycleObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun setupAcra() {
|
|
||||||
ACRA.init(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun setupNotificationChannels() {
|
protected open fun setupNotificationChannels() {
|
||||||
Notifications.createChannels(this)
|
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.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
import exh.eh.EHentaiUpdateHelper
|
||||||
|
import io.noties.markwon.Markwon
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.api.InjektModule
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
@ -44,6 +46,10 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
|
|
||||||
addSingletonFactory { Gson() }
|
addSingletonFactory { Gson() }
|
||||||
|
|
||||||
|
addSingletonFactory { EHentaiUpdateHelper(app) }
|
||||||
|
|
||||||
|
addSingletonFactory { Markwon.create(app) }
|
||||||
|
|
||||||
// Asynchronously init expensive components for a faster cold start
|
// Asynchronously init expensive components for a faster cold start
|
||||||
|
|
||||||
GlobalScope.launch { get<PreferencesHelper>() }
|
GlobalScope.launch { get<PreferencesHelper>() }
|
||||||
|
@ -10,6 +10,8 @@ import java.io.File
|
|||||||
|
|
||||||
object Migrations {
|
object Migrations {
|
||||||
|
|
||||||
|
// TODO NATIVE TACHIYOMI MIGRATIONS ARE FUCKED UP DUE TO DIFFERING VERSION NUMBERS
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a migration when the application is updated.
|
* Performs a migration when the application is updated.
|
||||||
*
|
*
|
||||||
|
@ -9,6 +9,7 @@ import android.os.IBinder
|
|||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,14 +70,11 @@ class BackupCreateService : Service() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
notifier = BackupNotifier(this)
|
notifier = BackupNotifier(this)
|
||||||
|
wakeLock = acquireWakeLock(javaClass.name)
|
||||||
|
|
||||||
startForeground(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
|
startForeground(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
|
||||||
|
|
||||||
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
|
||||||
PowerManager.PARTIAL_WAKE_LOCK, "${javaClass.name}:WakeLock"
|
|
||||||
)
|
|
||||||
wakeLock.acquire()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stopService(name: Intent?): Boolean {
|
override fun stopService(name: Intent?): Boolean {
|
||||||
|
29
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
Normal file → Executable file
29
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
Normal file → Executable file
@ -46,9 +46,12 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
|||||||
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||||
|
import exh.eh.EHentaiThrottleManager
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -131,8 +134,10 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
mangaEntries.add(backupMangaObject(manga, flags))
|
mangaEntries.add(backupMangaObject(manga, flags))
|
||||||
|
|
||||||
// Maintain set of extensions/sources used (excludes local source)
|
// Maintain set of extensions/sources used (excludes local source)
|
||||||
if (manga.source != 0L && sourceManager.get(manga.source) != null) {
|
if (manga.source != LocalSource.ID) {
|
||||||
extensions.add("${manga.source}:${sourceManager.get(manga.source)!!.name}")
|
sourceManager.get(manga.source)?.let {
|
||||||
|
extensions.add("${manga.source}:${it.name}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,9 +299,23 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
* @param manga manga that needs updating
|
* @param manga manga that needs updating
|
||||||
* @return [Observable] that contains manga
|
* @return [Observable] that contains manga
|
||||||
*/
|
*/
|
||||||
fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>, throttleManager: EHentaiThrottleManager): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||||
return source.fetchChapterList(manga)
|
return (
|
||||||
.map { syncChaptersWithSource(databaseHelper, it, manga, source) }
|
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 ->
|
.doOnNext { pair ->
|
||||||
if (pair.first.isNotEmpty()) {
|
if (pair.first.isNotEmpty()) {
|
||||||
chapters.forEach { it.manga_id = manga.id }
|
chapters.forEach { it.manga_id = manga.id }
|
||||||
|
@ -41,6 +41,7 @@ internal class BackupNotifier(private val context: Context) {
|
|||||||
setContentTitle(context.getString(R.string.creating_backup))
|
setContentTitle(context.getString(R.string.creating_backup))
|
||||||
|
|
||||||
setProgress(0, 0, true)
|
setProgress(0, 0, true)
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.show(Notifications.ID_BACKUP_PROGRESS)
|
builder.show(Notifications.ID_BACKUP_PROGRESS)
|
||||||
@ -93,6 +94,7 @@ internal class BackupNotifier(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setProgress(maxAmount, progress, false)
|
setProgress(maxAmount, progress, false)
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
|
|
||||||
// Clear old actions if they exist
|
// Clear old actions if they exist
|
||||||
if (mActions.isNotEmpty()) {
|
if (mActions.isNotEmpty()) {
|
||||||
@ -135,7 +137,7 @@ internal class BackupNotifier(private val context: Context) {
|
|||||||
|
|
||||||
with(completeNotificationBuilder) {
|
with(completeNotificationBuilder) {
|
||||||
setContentTitle(context.getString(R.string.restore_completed))
|
setContentTitle(context.getString(R.string.restore_completed))
|
||||||
setContentText(context.getString(R.string.restore_completed_content, timeString, errorCount))
|
setContentText(context.resources.getQuantityString(R.plurals.restore_completed_message, errorCount, timeString, errorCount))
|
||||||
|
|
||||||
// Clear old actions if they exist
|
// Clear old actions if they exist
|
||||||
if (mActions.isNotEmpty()) {
|
if (mActions.isNotEmpty()) {
|
||||||
|
@ -32,7 +32,10 @@ import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
|||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
|
import exh.EXHMigrations
|
||||||
|
import exh.eh.EHentaiThrottleManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@ -99,6 +102,8 @@ class BackupRestoreService : Service() {
|
|||||||
|
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
|
|
||||||
|
private val throttleManager = EHentaiThrottleManager()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The progress of a backup restore
|
* The progress of a backup restore
|
||||||
*/
|
*/
|
||||||
@ -109,6 +114,15 @@ class BackupRestoreService : Service() {
|
|||||||
*/
|
*/
|
||||||
private var restoreAmount = 0
|
private var restoreAmount = 0
|
||||||
|
|
||||||
|
private var skippedAmount = 0
|
||||||
|
|
||||||
|
private var totalAmount = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping of source ID to source name from backup data
|
||||||
|
*/
|
||||||
|
private var sourceMapping: Map<Long, String> = emptyMap()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List containing errors
|
* List containing errors
|
||||||
*/
|
*/
|
||||||
@ -123,14 +137,11 @@ class BackupRestoreService : Service() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
notifier = BackupNotifier(this)
|
notifier = BackupNotifier(this)
|
||||||
|
wakeLock = acquireWakeLock(javaClass.name)
|
||||||
|
|
||||||
startForeground(Notifications.ID_RESTORE_PROGRESS, notifier.showRestoreProgress().build())
|
startForeground(Notifications.ID_RESTORE_PROGRESS, notifier.showRestoreProgress().build())
|
||||||
|
|
||||||
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
|
||||||
PowerManager.PARTIAL_WAKE_LOCK, "${javaClass.name}:WakeLock"
|
|
||||||
)
|
|
||||||
wakeLock.acquire()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stopService(name: Intent?): Boolean {
|
override fun stopService(name: Intent?): Boolean {
|
||||||
@ -166,6 +177,8 @@ class BackupRestoreService : Service() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
|
val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
|
||||||
|
|
||||||
|
throttleManager.resetThrottle()
|
||||||
|
|
||||||
// Cancel any previous job if needed.
|
// Cancel any previous job if needed.
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
val handler = CoroutineExceptionHandler { _, exception ->
|
val handler = CoroutineExceptionHandler { _, exception ->
|
||||||
@ -177,7 +190,9 @@ class BackupRestoreService : Service() {
|
|||||||
stopSelf(startId)
|
stopSelf(startId)
|
||||||
}
|
}
|
||||||
job = GlobalScope.launch(handler) {
|
job = GlobalScope.launch(handler) {
|
||||||
restoreBackup(uri)
|
if (!restoreBackup(uri)) {
|
||||||
|
notifier.showRestoreError(getString(R.string.restoring_backup_canceled))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
job?.invokeOnCompletion {
|
job?.invokeOnCompletion {
|
||||||
stopSelf(startId)
|
stopSelf(startId)
|
||||||
@ -191,7 +206,7 @@ class BackupRestoreService : Service() {
|
|||||||
*
|
*
|
||||||
* @param uri backup file to restore
|
* @param uri backup file to restore
|
||||||
*/
|
*/
|
||||||
private fun restoreBackup(uri: Uri) {
|
private fun restoreBackup(uri: Uri): Boolean {
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
|
|
||||||
val reader = JsonReader(contentResolver.openInputStream(uri)!!.bufferedReader())
|
val reader = JsonReader(contentResolver.openInputStream(uri)!!.bufferedReader())
|
||||||
@ -205,15 +220,35 @@ class BackupRestoreService : Service() {
|
|||||||
|
|
||||||
val mangasJson = json.get(MANGAS).asJsonArray
|
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
|
restoreProgress = 0
|
||||||
errors.clear()
|
errors.clear()
|
||||||
|
|
||||||
// Restore categories
|
// Restore categories
|
||||||
restoreCategories(json.get(CATEGORIES))
|
restoreCategories(json.get(CATEGORIES))
|
||||||
|
|
||||||
|
// Store source mapping for error messages
|
||||||
|
sourceMapping = BackupRestoreValidator.getSourceMapping(json)
|
||||||
|
|
||||||
// Restore individual manga
|
// Restore individual manga
|
||||||
mangasJson.forEach {
|
mangasJson.forEach {
|
||||||
|
if (job?.isActive != true) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
restoreManga(it.asJsonObject)
|
restoreManga(it.asJsonObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,56 +258,67 @@ class BackupRestoreService : Service() {
|
|||||||
val logFile = writeErrorLog()
|
val logFile = writeErrorLog()
|
||||||
|
|
||||||
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
|
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restoreCategories(categoriesJson: JsonElement) {
|
private fun restoreCategories(categoriesJson: JsonElement) {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
backupManager.restoreCategories(categoriesJson.asJsonArray)
|
backupManager.restoreCategories(categoriesJson.asJsonArray)
|
||||||
|
|
||||||
restoreProgress += 1
|
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, getString(R.string.categories))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restoreProgress += 1
|
||||||
|
showRestoreProgress(restoreProgress, restoreAmount, getString(R.string.categories))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restoreManga(mangaJson: JsonObject) {
|
private fun restoreManga(mangaJson: JsonObject) {
|
||||||
db.inTransaction {
|
var manga = backupManager.parser.fromJson<MangaImpl>(mangaJson.get(MANGA))
|
||||||
val manga = backupManager.parser.fromJson<MangaImpl>(mangaJson.get(MANGA))
|
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(
|
||||||
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(
|
mangaJson.get(CHAPTERS)
|
||||||
mangaJson.get(CHAPTERS)
|
?: JsonArray()
|
||||||
?: JsonArray()
|
)
|
||||||
)
|
val categories = backupManager.parser.fromJson<List<String>>(
|
||||||
val categories = backupManager.parser.fromJson<List<String>>(
|
mangaJson.get(CATEGORIES)
|
||||||
mangaJson.get(CATEGORIES)
|
?: JsonArray()
|
||||||
?: JsonArray()
|
)
|
||||||
)
|
val history = backupManager.parser.fromJson<List<DHistory>>(
|
||||||
val history = backupManager.parser.fromJson<List<DHistory>>(
|
mangaJson.get(HISTORY)
|
||||||
mangaJson.get(HISTORY)
|
?: JsonArray()
|
||||||
?: JsonArray()
|
)
|
||||||
)
|
val tracks = backupManager.parser.fromJson<List<TrackImpl>>(
|
||||||
val tracks = backupManager.parser.fromJson<List<TrackImpl>>(
|
mangaJson.get(TRACK)
|
||||||
mangaJson.get(TRACK)
|
?: JsonArray()
|
||||||
?: JsonArray()
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if (job?.isActive != true) {
|
// EXH -->
|
||||||
throw Exception(getString(R.string.restoring_backup_canceled))
|
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 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) {
|
||||||
try {
|
errors.add(Date() to "${manga.title} - ${e.message}")
|
||||||
restoreMangaData(manga, chapters, categories, history, tracks)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
errors.add(Date() to "${manga.title} - ${getString(R.string.source_not_found)}")
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreProgress += 1
|
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, manga.title)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restoreProgress += 1
|
||||||
|
showRestoreProgress(restoreProgress, restoreAmount, manga.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a manga restore observable
|
* Returns a manga restore observable
|
||||||
*
|
*
|
||||||
* @param manga manga data from json
|
* @param manga manga data from json
|
||||||
|
* @param source source to get manga data from
|
||||||
* @param chapters chapters data from json
|
* @param chapters chapters data from json
|
||||||
* @param categories categories data from json
|
* @param categories categories data from json
|
||||||
* @param history history data from json
|
* @param history history data from json
|
||||||
@ -280,23 +326,24 @@ class BackupRestoreService : Service() {
|
|||||||
*/
|
*/
|
||||||
private fun restoreMangaData(
|
private fun restoreMangaData(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
|
source: Source,
|
||||||
chapters: List<Chapter>,
|
chapters: List<Chapter>,
|
||||||
categories: List<String>,
|
categories: List<String>,
|
||||||
history: List<DHistory>,
|
history: List<DHistory>,
|
||||||
tracks: List<Track>
|
tracks: List<Track>
|
||||||
) {
|
) {
|
||||||
// Get source
|
|
||||||
val source = backupManager.sourceManager.getOrStub(manga.source)
|
|
||||||
val dbManga = backupManager.getMangaFromDatabase(manga)
|
val dbManga = backupManager.getMangaFromDatabase(manga)
|
||||||
|
|
||||||
if (dbManga == null) {
|
db.inTransaction {
|
||||||
// Manga not in database
|
if (dbManga == null) {
|
||||||
restoreMangaFetch(source, manga, chapters, categories, history, tracks)
|
// Manga not in database
|
||||||
} else { // Manga in database
|
restoreMangaFetch(source, manga, chapters, categories, history, tracks)
|
||||||
// Copy information from manga already in database
|
} else { // Manga in database
|
||||||
backupManager.restoreMangaNoFetch(manga, dbManga)
|
// Copy information from manga already in database
|
||||||
// Fetch rest of manga information
|
backupManager.restoreMangaNoFetch(manga, dbManga)
|
||||||
restoreMangaNoFetch(source, manga, chapters, categories, history, tracks)
|
// Fetch rest of manga information
|
||||||
|
restoreMangaNoFetch(source, manga, chapters, categories, history, tracks)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,7 +427,7 @@ class BackupRestoreService : Service() {
|
|||||||
* @return [Observable] that contains manga
|
* @return [Observable] that contains manga
|
||||||
*/
|
*/
|
||||||
private fun chapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
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.
|
// If there's any error, return empty update and continue.
|
||||||
.onErrorReturn {
|
.onErrorReturn {
|
||||||
errors.add(Date() to "${manga.title} - ${it.message}")
|
errors.add(Date() to "${manga.title} - ${it.message}")
|
||||||
@ -396,7 +443,7 @@ class BackupRestoreService : Service() {
|
|||||||
*/
|
*/
|
||||||
private fun trackingFetchObservable(manga: Manga, tracks: List<Track>): Observable<Track> {
|
private fun trackingFetchObservable(manga: Manga, tracks: List<Track>): Observable<Track> {
|
||||||
return Observable.from(tracks)
|
return Observable.from(tracks)
|
||||||
.concatMap { track ->
|
.flatMap { track ->
|
||||||
val service = trackManager.getService(track.sync_id)
|
val service = trackManager.getService(track.sync_id)
|
||||||
if (service != null && service.isLogged) {
|
if (service != null && service.isLogged) {
|
||||||
service.refresh(track)
|
service.refresh(track)
|
||||||
@ -406,7 +453,7 @@ class BackupRestoreService : Service() {
|
|||||||
track
|
track
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.add(Date() to "${manga.title} - ${service?.name} not logged in")
|
errors.add(Date() to "${manga.title} - ${getString(R.string.tracker_not_logged_in, service?.name)}")
|
||||||
Observable.empty()
|
Observable.empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.backup
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonParser
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||||
|
|
||||||
|
object BackupRestoreValidator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for critical backup file data.
|
||||||
|
*
|
||||||
|
* @throws Exception if version or manga cannot be found.
|
||||||
|
* @return List of required sources.
|
||||||
|
*/
|
||||||
|
fun validate(context: Context, uri: Uri): Map<Long, String> {
|
||||||
|
val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader())
|
||||||
|
val json = JsonParser.parseReader(reader).asJsonObject
|
||||||
|
|
||||||
|
val version = json.get(Backup.VERSION)
|
||||||
|
val mangasJson = json.get(Backup.MANGAS)
|
||||||
|
if (version == null || mangasJson == null) {
|
||||||
|
throw Exception(context.getString(R.string.invalid_backup_file_missing_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mangasJson.asJsonArray.size() == 0) {
|
||||||
|
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSourceMapping(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSourceMapping(json: JsonObject): Map<Long, String> {
|
||||||
|
val extensionsMapping = json.get(Backup.EXTENSIONS) ?: return emptyMap()
|
||||||
|
|
||||||
|
return extensionsMapping.asJsonArray
|
||||||
|
.map {
|
||||||
|
val items = it.asString.split(":")
|
||||||
|
items[0].toLong() to items[1]
|
||||||
|
}
|
||||||
|
.toMap()
|
||||||
|
}
|
||||||
|
}
|
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.google.gson.Gson
|
||||||
import com.jakewharton.disklrucache.DiskLruCache
|
import com.jakewharton.disklrucache.DiskLruCache
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
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.source.model.Page
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
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 okhttp3.Response
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
@ -37,21 +43,32 @@ class ChapterCache(private val context: Context) {
|
|||||||
|
|
||||||
/** The number of values per cache entry. Must be positive. */
|
/** The number of values per cache entry. Must be positive. */
|
||||||
const val PARAMETER_VALUE_COUNT = 1
|
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. */
|
/** Google Json class used for parsing JSON files. */
|
||||||
private val gson: Gson by injectLazy()
|
private val gson: Gson by injectLazy()
|
||||||
|
|
||||||
|
// --> EH
|
||||||
|
private val prefs: PreferencesHelper by injectLazy()
|
||||||
|
// <-- EH
|
||||||
|
|
||||||
/** Cache class used for cache management. */
|
/** Cache class used for cache management. */
|
||||||
private val diskCache = DiskLruCache.open(
|
// --> EH
|
||||||
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
private var diskCache = setupDiskCache(prefs.eh_cacheSize().get().toLong())
|
||||||
PARAMETER_APP_VERSION,
|
|
||||||
PARAMETER_VALUE_COUNT,
|
init {
|
||||||
PARAMETER_CACHE_SIZE
|
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.
|
* Returns directory of cache.
|
||||||
@ -71,6 +88,18 @@ class ChapterCache(private val context: Context) {
|
|||||||
val readableSize: String
|
val readableSize: String
|
||||||
get() = Formatter.formatFileSize(context, realSize)
|
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.
|
* Remove file from cache.
|
||||||
*
|
*
|
||||||
|
83
app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt
vendored
Normal file → Executable file
83
app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt
vendored
Normal file → Executable file
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.data.cache
|
package eu.kanade.tachiyomi.data.cache
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -17,51 +18,89 @@ import java.io.InputStream
|
|||||||
*/
|
*/
|
||||||
class CoverCache(private val context: Context) {
|
class CoverCache(private val context: Context) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val COVERS_DIR = "covers"
|
||||||
|
private const val CUSTOM_COVERS_DIR = "covers/custom"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache directory used for cache management.
|
* Cache directory used for cache management.
|
||||||
*/
|
*/
|
||||||
private val cacheDir = context.getExternalFilesDir("covers")
|
private val cacheDir = getCacheDir(COVERS_DIR)
|
||||||
?: File(context.filesDir, "covers").also { it.mkdirs() }
|
|
||||||
|
private val customCoverCacheDir = getCacheDir(CUSTOM_COVERS_DIR)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the cover from cache.
|
* Returns the cover from cache.
|
||||||
*
|
*
|
||||||
* @param thumbnailUrl the thumbnail url.
|
* @param manga the manga.
|
||||||
* @return cover image.
|
* @return cover image.
|
||||||
*/
|
*/
|
||||||
fun getCoverFile(thumbnailUrl: String): File {
|
fun getCoverFile(manga: Manga): File? {
|
||||||
return File(cacheDir, DiskUtil.hashKeyForDisk(thumbnailUrl))
|
return manga.thumbnail_url?.let {
|
||||||
|
File(cacheDir, DiskUtil.hashKeyForDisk(it))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy the given stream to this cache.
|
* Returns the custom cover from cache.
|
||||||
*
|
*
|
||||||
* @param thumbnailUrl url of the thumbnail.
|
* @param manga the manga.
|
||||||
|
* @return cover image.
|
||||||
|
*/
|
||||||
|
fun getCustomCoverFile(manga: Manga): File {
|
||||||
|
return File(customCoverCacheDir, DiskUtil.hashKeyForDisk(manga.id.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the given stream as the manga's custom cover to cache.
|
||||||
|
*
|
||||||
|
* @param manga the manga.
|
||||||
* @param inputStream the stream to copy.
|
* @param inputStream the stream to copy.
|
||||||
* @throws IOException if there's any error.
|
* @throws IOException if there's any error.
|
||||||
*/
|
*/
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun copyToCache(thumbnailUrl: String, inputStream: InputStream) {
|
fun setCustomCoverToCache(manga: Manga, inputStream: InputStream) {
|
||||||
// Get destination file.
|
getCustomCoverFile(manga).outputStream().use {
|
||||||
val destFile = getCoverFile(thumbnailUrl)
|
inputStream.copyTo(it)
|
||||||
|
}
|
||||||
destFile.outputStream().use { inputStream.copyTo(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the cover file from the cache.
|
* Delete the cover files of the manga from the cache.
|
||||||
*
|
*
|
||||||
* @param thumbnailUrl the thumbnail url.
|
* @param manga the manga.
|
||||||
* @return status of deletion.
|
* @param deleteCustomCover whether the custom cover should be deleted.
|
||||||
|
* @return number of files that were deleted.
|
||||||
*/
|
*/
|
||||||
fun deleteFromCache(thumbnailUrl: String?): Boolean {
|
fun deleteFromCache(manga: Manga, deleteCustomCover: Boolean = false): Int {
|
||||||
// Check if url is empty.
|
var deleted = 0
|
||||||
if (thumbnailUrl.isNullOrEmpty()) {
|
|
||||||
return false
|
getCoverFile(manga)?.let {
|
||||||
|
if (it.exists() && it.delete()) ++deleted
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove file.
|
if (deleteCustomCover) {
|
||||||
val file = getCoverFile(thumbnailUrl)
|
if (deleteCustomCover(manga)) ++deleted
|
||||||
return file.exists() && file.delete()
|
}
|
||||||
|
|
||||||
|
return deleted
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete custom cover of the manga from the cache
|
||||||
|
*
|
||||||
|
* @param manga the manga.
|
||||||
|
* @return whether the cover was deleted.
|
||||||
|
*/
|
||||||
|
fun deleteCustomCover(manga: Manga): Boolean {
|
||||||
|
return getCustomCoverFile(manga).let {
|
||||||
|
it.exists() && it.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCacheDir(dir: String): File {
|
||||||
|
return context.getExternalFilesDir(dir)
|
||||||
|
?: File(context.filesDir, dir).also { it.mkdirs() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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.MangaCategoryQueries
|
||||||
import eu.kanade.tachiyomi.data.database.queries.MangaQueries
|
import eu.kanade.tachiyomi.data.database.queries.MangaQueries
|
||||||
import eu.kanade.tachiyomi.data.database.queries.TrackQueries
|
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
|
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides operations to manage the database through its interfaces.
|
* This class provides operations to manage the database through its interfaces.
|
||||||
*/
|
*/
|
||||||
open class DatabaseHelper(context: Context) :
|
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)
|
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
||||||
.name(DbOpenCallback.DATABASE_NAME)
|
.name(DbOpenCallback.DATABASE_NAME)
|
||||||
@ -42,7 +51,14 @@ open class DatabaseHelper(context: Context) :
|
|||||||
.addTypeMapping(Category::class.java, CategoryTypeMapping())
|
.addTypeMapping(Category::class.java, CategoryTypeMapping())
|
||||||
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
|
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
|
||||||
.addTypeMapping(History::class.java, HistoryTypeMapping())
|
.addTypeMapping(History::class.java, HistoryTypeMapping())
|
||||||
|
// EXH -->
|
||||||
|
.addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping())
|
||||||
|
.addTypeMapping(SearchTag::class.java, SearchTagTypeMapping())
|
||||||
|
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
|
||||||
|
// EXH <--
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
|
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
59
app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt
Normal file → Executable file
59
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.HistoryTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
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 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) {
|
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||||
|
|
||||||
@ -20,7 +24,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
/**
|
/**
|
||||||
* Version of the database.
|
* Version of the database.
|
||||||
*/
|
*/
|
||||||
const val DATABASE_VERSION = 9
|
const val DATABASE_VERSION = 2 // [SY]
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
||||||
@ -30,6 +34,14 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
execSQL(CategoryTable.createTableQuery)
|
execSQL(CategoryTable.createTableQuery)
|
||||||
execSQL(MangaCategoryTable.createTableQuery)
|
execSQL(MangaCategoryTable.createTableQuery)
|
||||||
execSQL(HistoryTable.createTableQuery)
|
execSQL(HistoryTable.createTableQuery)
|
||||||
|
// EXH -->
|
||||||
|
execSQL(SearchMetadataTable.createTableQuery)
|
||||||
|
execSQL(SearchTagTable.createTableQuery)
|
||||||
|
execSQL(SearchTitleTable.createTableQuery)
|
||||||
|
// EXH <--
|
||||||
|
// AZ -->
|
||||||
|
execSQL(MergedTable.createTableQuery)
|
||||||
|
// AZ <--
|
||||||
|
|
||||||
// DB indexes
|
// DB indexes
|
||||||
execSQL(MangaTable.createUrlIndexQuery)
|
execSQL(MangaTable.createUrlIndexQuery)
|
||||||
@ -37,43 +49,22 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
execSQL(ChapterTable.createMangaIdIndexQuery)
|
execSQL(ChapterTable.createMangaIdIndexQuery)
|
||||||
execSQL(ChapterTable.createUnreadChaptersIndexQuery)
|
execSQL(ChapterTable.createUnreadChaptersIndexQuery)
|
||||||
execSQL(HistoryTable.createChapterIdIndexQuery)
|
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) {
|
override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
if (oldVersion < 2) {
|
if (oldVersion < 2) {
|
||||||
db.execSQL(ChapterTable.sourceOrderUpdateQuery)
|
db.execSQL(MangaTable.addCoverLastModified)
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.models.CategoryImpl
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_FLAGS
|
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_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_NAME
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE
|
||||||
@ -40,6 +41,8 @@ class CategoryPutResolver : DefaultPutResolver<Category>() {
|
|||||||
put(COL_NAME, obj.name)
|
put(COL_NAME, obj.name)
|
||||||
put(COL_ORDER, obj.order)
|
put(COL_ORDER, obj.order)
|
||||||
put(COL_FLAGS, obj.flags)
|
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))
|
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
|
||||||
order = cursor.getInt(cursor.getColumnIndex(COL_ORDER))
|
order = cursor.getInt(cursor.getColumnIndex(COL_ORDER))
|
||||||
flags = cursor.getInt(cursor.getColumnIndex(COL_FLAGS))
|
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
3
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt
Normal file → Executable file
3
app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt
Normal file → Executable file
@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ARTIST
|
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_AUTHOR
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_CHAPTER_FLAGS
|
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_DESCRIPTION
|
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_FAVORITE
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
|
||||||
@ -62,6 +63,7 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
|
|||||||
put(COL_INITIALIZED, obj.initialized)
|
put(COL_INITIALIZED, obj.initialized)
|
||||||
put(COL_VIEWER, obj.viewer)
|
put(COL_VIEWER, obj.viewer)
|
||||||
put(COL_CHAPTER_FLAGS, obj.chapter_flags)
|
put(COL_CHAPTER_FLAGS, obj.chapter_flags)
|
||||||
|
put(COL_COVER_LAST_MODIFIED, obj.cover_last_modified)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +84,7 @@ interface BaseMangaGetResolver {
|
|||||||
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1
|
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1
|
||||||
viewer = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
|
viewer = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
|
||||||
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
|
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
|
||||||
|
cover_last_modified = cursor.getLong(cursor.getColumnIndex(COL_COVER_LAST_MODIFIED))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
2
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
Normal file → Executable file
2
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt
Normal file → Executable file
@ -12,6 +12,8 @@ interface Category : Serializable {
|
|||||||
|
|
||||||
var flags: Int
|
var flags: Int
|
||||||
|
|
||||||
|
var mangaOrder: List<Long>
|
||||||
|
|
||||||
val nameLower: String
|
val nameLower: String
|
||||||
get() = name.toLowerCase()
|
get() = name.toLowerCase()
|
||||||
|
|
||||||
|
2
app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
Normal file → Executable file
2
app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt
Normal file → Executable file
@ -10,6 +10,8 @@ class CategoryImpl : Category {
|
|||||||
|
|
||||||
override var flags: Int = 0
|
override var flags: Int = 0
|
||||||
|
|
||||||
|
override var mangaOrder: List<Long> = emptyList()
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other == null || javaClass != other.javaClass) return false
|
if (other == null || javaClass != other.javaClass) return false
|
||||||
|
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
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt
Normal file → Executable file
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
5
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt
Normal file → Executable file
5
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt
Normal file → Executable file
@ -16,6 +16,8 @@ interface Manga : SManga {
|
|||||||
|
|
||||||
var chapter_flags: Int
|
var chapter_flags: Int
|
||||||
|
|
||||||
|
var cover_last_modified: Long
|
||||||
|
|
||||||
fun setChapterOrder(order: Int) {
|
fun setChapterOrder(order: Int) {
|
||||||
setFlags(order, SORT_MASK)
|
setFlags(order, SORT_MASK)
|
||||||
}
|
}
|
||||||
@ -76,7 +78,8 @@ interface Manga : SManga {
|
|||||||
|
|
||||||
const val SORTING_SOURCE = 0x00000000
|
const val SORTING_SOURCE = 0x00000000
|
||||||
const val SORTING_NUMBER = 0x00000100
|
const val SORTING_NUMBER = 0x00000100
|
||||||
const val SORTING_MASK = 0x00000100
|
const val SORTING_UPLOAD_DATE = 0x00000200
|
||||||
|
const val SORTING_MASK = 0x00000300
|
||||||
|
|
||||||
const val DISPLAY_NAME = 0x00000000
|
const val DISPLAY_NAME = 0x00000000
|
||||||
const val DISPLAY_NUMBER = 0x00100000
|
const val DISPLAY_NUMBER = 0x00100000
|
||||||
|
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
4
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt
Normal file → Executable file
4
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 url: String
|
||||||
|
|
||||||
override lateinit var title: String
|
override var title: String = ""
|
||||||
|
|
||||||
override var artist: String? = null
|
override var artist: String? = null
|
||||||
|
|
||||||
@ -32,6 +32,8 @@ open class MangaImpl : Manga {
|
|||||||
|
|
||||||
override var chapter_flags: Int = 0
|
override var chapter_flags: Int = 0
|
||||||
|
|
||||||
|
override var cover_last_modified: Long = 0
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other == null || javaClass != other.javaClass) return false
|
if (other == null || javaClass != other.javaClass) return false
|
||||||
|
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 {
|
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)
|
.listOfObjects(Chapter::class.java)
|
||||||
.withQuery(
|
.withQuery(
|
||||||
Query.builder()
|
Query.builder()
|
||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
.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()
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
@ -71,6 +82,17 @@ interface ChapterQueries : DbProvider {
|
|||||||
)
|
)
|
||||||
.prepare()
|
.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 insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
||||||
|
|
||||||
fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).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
|
* Returns history of recent manga containing last read chapter
|
||||||
* @param date recent date range
|
* @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)
|
.listOfObjects(MangaChapterHistory::class.java)
|
||||||
.withQuery(
|
.withQuery(
|
||||||
RawQuery.builder()
|
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)
|
.args(date.time)
|
||||||
.observesTables(HistoryTable.TABLE)
|
.observesTables(HistoryTable.TABLE)
|
||||||
.build()
|
.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>) {
|
fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
deleteOldMangasCategories(mangas).executeAsBlocking()
|
mangas.chunked(100) { chunk ->
|
||||||
insertMangasCategories(mangasCategories).executeAsBlocking()
|
deleteOldMangasCategories(chunk).executeAsBlocking()
|
||||||
|
}
|
||||||
|
mangasCategories.chunked(100) { chunk ->
|
||||||
|
insertMangasCategories(chunk).executeAsBlocking()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
66
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt
Normal file → Executable file
66
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt
Normal file → Executable file
@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.data.database.DbProvider
|
|||||||
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
||||||
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaCoverLastModifiedPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
|
||||||
@ -16,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.ChapterTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
|
import exh.metadata.sql.tables.SearchMetadataTable
|
||||||
|
|
||||||
interface MangaQueries : DbProvider {
|
interface MangaQueries : DbProvider {
|
||||||
|
|
||||||
@ -73,6 +75,15 @@ interface MangaQueries : DbProvider {
|
|||||||
)
|
)
|
||||||
.prepare()
|
.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 insertManga(manga: Manga) = db.put().`object`(manga).prepare()
|
||||||
|
|
||||||
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
|
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
|
||||||
@ -102,6 +113,11 @@ interface MangaQueries : DbProvider {
|
|||||||
.withPutResolver(MangaTitlePutResolver())
|
.withPutResolver(MangaTitlePutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
fun updateMangaCoverLastModified(manga: Manga) = db.put()
|
||||||
|
.`object`(manga)
|
||||||
|
.withPutResolver(MangaCoverLastModifiedPutResolver())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
||||||
|
|
||||||
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
||||||
@ -153,4 +169,54 @@ interface MangaQueries : DbProvider {
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.prepare()
|
.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()
|
||||||
}
|
}
|
||||||
|
59
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt
Normal file → Executable file
59
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.HistoryTable as History
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory
|
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.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.
|
* Query to get the manga from the library, with their categories and unread count.
|
||||||
@ -49,7 +76,7 @@ fun getRecentsQuery() =
|
|||||||
* and are read after the given time period
|
* and are read after the given time period
|
||||||
* @return return limit is 25
|
* @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}.*
|
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
||||||
FROM ${Manga.TABLE}
|
FROM ${Manga.TABLE}
|
||||||
@ -64,8 +91,36 @@ fun getRecentMangasQuery() =
|
|||||||
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
|
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}
|
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}
|
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
|
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() =
|
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,0 +1,31 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
class MangaCoverLastModifiedPutResolver : 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_COVER_LAST_MODIFIED, manga.cover_last_modified)
|
||||||
|
}
|
||||||
|
}
|
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_FLAGS = "flags"
|
||||||
|
|
||||||
|
const val COL_MANGA_ORDER = "manga_order"
|
||||||
|
|
||||||
val createTableQuery: String
|
val createTableQuery: String
|
||||||
get() =
|
get() =
|
||||||
"""CREATE TABLE $TABLE(
|
"""CREATE TABLE $TABLE(
|
||||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
$COL_NAME TEXT NOT NULL,
|
$COL_NAME TEXT NOT NULL,
|
||||||
$COL_ORDER INTEGER 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
8
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt
Normal file → Executable file
8
app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt
Normal file → Executable file
@ -38,6 +38,8 @@ object MangaTable {
|
|||||||
|
|
||||||
const val COL_CATEGORY = "category"
|
const val COL_CATEGORY = "category"
|
||||||
|
|
||||||
|
const val COL_COVER_LAST_MODIFIED = "cover_last_modified"
|
||||||
|
|
||||||
val createTableQuery: String
|
val createTableQuery: String
|
||||||
get() =
|
get() =
|
||||||
"""CREATE TABLE $TABLE(
|
"""CREATE TABLE $TABLE(
|
||||||
@ -55,7 +57,8 @@ object MangaTable {
|
|||||||
$COL_LAST_UPDATE LONG,
|
$COL_LAST_UPDATE LONG,
|
||||||
$COL_INITIALIZED BOOLEAN NOT NULL,
|
$COL_INITIALIZED BOOLEAN NOT NULL,
|
||||||
$COL_VIEWER INTEGER NOT NULL,
|
$COL_VIEWER INTEGER NOT NULL,
|
||||||
$COL_CHAPTER_FLAGS INTEGER NOT NULL
|
$COL_CHAPTER_FLAGS INTEGER NOT NULL,
|
||||||
|
$COL_COVER_LAST_MODIFIED LONG NOT NULL
|
||||||
)"""
|
)"""
|
||||||
|
|
||||||
val createUrlIndexQuery: String
|
val createUrlIndexQuery: String
|
||||||
@ -64,4 +67,7 @@ object MangaTable {
|
|||||||
val createLibraryIndexQuery: String
|
val createLibraryIndexQuery: String
|
||||||
get() = "CREATE INDEX library_${COL_FAVORITE}_index ON $TABLE($COL_FAVORITE) " +
|
get() = "CREATE INDEX library_${COL_FAVORITE}_index ON $TABLE($COL_FAVORITE) " +
|
||||||
"WHERE $COL_FAVORITE = 1"
|
"WHERE $COL_FAVORITE = 1"
|
||||||
|
|
||||||
|
val addCoverLastModified: String
|
||||||
|
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_COVER_LAST_MODIFIED LONG NOT NULL DEFAULT 0"
|
||||||
}
|
}
|
||||||
|
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
23
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
Normal file → Executable file
23
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
Normal file → Executable file
@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.source.Source
|
|||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -239,4 +240,26 @@ class DownloadManager(private val context: Context) {
|
|||||||
deleteChapters(chapters, manga, source)
|
deleteChapters(chapters, manga, source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renames an already downloaded chapter
|
||||||
|
*
|
||||||
|
* @param source the source of the manga.
|
||||||
|
* @param manga the manga of the chapter.
|
||||||
|
* @param oldChapter the existing chapter with the old name.
|
||||||
|
* @param newChapter the target chapter with the new name.
|
||||||
|
*/
|
||||||
|
fun renameChapter(source: Source, manga: Manga, oldChapter: Chapter, newChapter: Chapter) {
|
||||||
|
val oldName = provider.getChapterDirName(oldChapter)
|
||||||
|
val newName = provider.getChapterDirName(newChapter)
|
||||||
|
val mangaDir = provider.getMangaDir(manga, source)
|
||||||
|
|
||||||
|
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.", oldName)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
5
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt
Normal file → Executable file
5
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt
Normal file → Executable file
@ -94,8 +94,9 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadingProgressText = context.getString(R.string.chapter_downloading_progress)
|
val downloadingProgressText = context.getString(
|
||||||
.format(download.downloadedImages, download.pages!!.size)
|
R.string.chapter_downloading_progress, download.downloadedImages, download.pages!!.size
|
||||||
|
)
|
||||||
|
|
||||||
if (preferences.hideNotificationContent()) {
|
if (preferences.hideNotificationContent()) {
|
||||||
setContentTitle(downloadingProgressText)
|
setContentTitle(downloadingProgressText)
|
||||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt
Normal file → Executable file
7
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt
Normal file → Executable file
7
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt
Normal file → Executable file
@ -16,9 +16,9 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.util.lang.plusAssign
|
import eu.kanade.tachiyomi.util.lang.plusAssign
|
||||||
|
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||||
import eu.kanade.tachiyomi.util.system.connectivityManager
|
import eu.kanade.tachiyomi.util.system.connectivityManager
|
||||||
import eu.kanade.tachiyomi.util.system.notification
|
import eu.kanade.tachiyomi.util.system.notification
|
||||||
import eu.kanade.tachiyomi.util.system.powerManager
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
@ -70,9 +70,7 @@ class DownloadService : Service() {
|
|||||||
/**
|
/**
|
||||||
* Wake lock to prevent the device to enter sleep mode.
|
* Wake lock to prevent the device to enter sleep mode.
|
||||||
*/
|
*/
|
||||||
private val wakeLock by lazy {
|
private lateinit var wakeLock: PowerManager.WakeLock
|
||||||
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DownloadService:WakeLock")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscriptions to store while the service is running.
|
* Subscriptions to store while the service is running.
|
||||||
@ -85,6 +83,7 @@ class DownloadService : Service() {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
startForeground(Notifications.ID_DOWNLOAD_CHAPTER, getPlaceholderNotification())
|
startForeground(Notifications.ID_DOWNLOAD_CHAPTER, getPlaceholderNotification())
|
||||||
|
wakeLock = acquireWakeLock(javaClass.name)
|
||||||
runningRelay.call(true)
|
runningRelay.call(true)
|
||||||
subscriptions = CompositeSubscription()
|
subscriptions = CompositeSubscription()
|
||||||
listenDownloaderState()
|
listenDownloaderState()
|
||||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt
Normal file → Executable file
14
app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
Normal file → Executable file
14
app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
Normal file → Executable file
@ -181,7 +181,16 @@ class Downloader(
|
|||||||
subscriptions.clear()
|
subscriptions.clear()
|
||||||
|
|
||||||
subscriptions += downloadsRelay.concatMapIterable { it }
|
subscriptions += downloadsRelay.concatMapIterable { it }
|
||||||
.concatMap { downloadChapter(it).subscribeOn(Schedulers.io()) }
|
// Concurrently download from 5 different sources
|
||||||
|
.groupBy { it.source }
|
||||||
|
.flatMap(
|
||||||
|
{ bySource ->
|
||||||
|
bySource.concatMap { download ->
|
||||||
|
downloadChapter(download).subscribeOn(Schedulers.io())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
5
|
||||||
|
)
|
||||||
.onBackpressureBuffer()
|
.onBackpressureBuffer()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
@ -289,7 +298,8 @@ class Downloader(
|
|||||||
// Get all the URLs to the source images, fetch pages if necessary
|
// Get all the URLs to the source images, fetch pages if necessary
|
||||||
.flatMap { download.source.fetchAllImageUrlsFromPageList(it) }
|
.flatMap { download.source.fetchAllImageUrlsFromPageList(it) }
|
||||||
// Start downloading images, consider we can have downloaded images already
|
// Start downloading images, consider we can have downloaded images already
|
||||||
.concatMap { page -> getOrDownloadImage(page, download, tmpDir) }
|
// Concurrently do 5 pages at a time
|
||||||
|
.flatMap({ page -> getOrDownloadImage(page, download, tmpDir) }, 5)
|
||||||
// Do when page is downloaded.
|
// Do when page is downloaded.
|
||||||
.doOnNext { notifier.onProgressChange(download) }
|
.doOnNext { notifier.onProgressChange(download) }
|
||||||
.toList()
|
.toList()
|
||||||
|
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
8
app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt
Normal file → Executable file
8
app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt
Normal file → Executable file
@ -12,7 +12,7 @@ import java.io.IOException
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
open class FileFetcher(private val file: File) : DataFetcher<InputStream> {
|
open class FileFetcher(private val filePath: String = "") : DataFetcher<InputStream> {
|
||||||
|
|
||||||
private var data: InputStream? = null
|
private var data: InputStream? = null
|
||||||
|
|
||||||
@ -20,7 +20,11 @@ open class FileFetcher(private val file: File) : DataFetcher<InputStream> {
|
|||||||
loadFromFile(callback)
|
loadFromFile(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun loadFromFile(callback: DataFetcher.DataCallback<in InputStream>) {
|
private fun loadFromFile(callback: DataFetcher.DataCallback<in InputStream>) {
|
||||||
|
loadFromFile(File(filePath), callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun loadFromFile(file: File, callback: DataFetcher.DataCallback<in InputStream>) {
|
||||||
try {
|
try {
|
||||||
data = FileInputStream(file)
|
data = FileInputStream(file)
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.glide
|
||||||
|
|
||||||
|
import com.bumptech.glide.Priority
|
||||||
|
import com.bumptech.glide.load.data.DataFetcher
|
||||||
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
|
open class LibraryMangaCustomCoverFetcher(
|
||||||
|
private val manga: Manga,
|
||||||
|
private val coverCache: CoverCache
|
||||||
|
) : FileFetcher() {
|
||||||
|
|
||||||
|
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
|
||||||
|
getCustomCoverFile()?.let {
|
||||||
|
loadFromFile(it, callback)
|
||||||
|
} ?: callback.onLoadFailed(Exception("Custom cover file not found"))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun getCustomCoverFile(): File? {
|
||||||
|
return coverCache.getCustomCoverFile(manga).takeIf { it.exists() }
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.glide
|
|||||||
|
|
||||||
import com.bumptech.glide.Priority
|
import com.bumptech.glide.Priority
|
||||||
import com.bumptech.glide.load.data.DataFetcher
|
import com.bumptech.glide.load.data.DataFetcher
|
||||||
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
@ -19,31 +20,41 @@ import java.io.InputStream
|
|||||||
class LibraryMangaUrlFetcher(
|
class LibraryMangaUrlFetcher(
|
||||||
private val networkFetcher: DataFetcher<InputStream>,
|
private val networkFetcher: DataFetcher<InputStream>,
|
||||||
private val manga: Manga,
|
private val manga: Manga,
|
||||||
private val file: File
|
private val coverCache: CoverCache
|
||||||
) :
|
) : LibraryMangaCustomCoverFetcher(manga, coverCache) {
|
||||||
FileFetcher(file) {
|
|
||||||
|
|
||||||
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
|
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
|
||||||
if (!file.exists()) {
|
getCustomCoverFile()?.let {
|
||||||
|
loadFromFile(it, callback)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val cover = coverCache.getCoverFile(manga)
|
||||||
|
if (cover == null) {
|
||||||
|
callback.onLoadFailed(Exception("Null thumbnail url"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cover.exists()) {
|
||||||
networkFetcher.loadData(
|
networkFetcher.loadData(
|
||||||
priority,
|
priority,
|
||||||
object : DataFetcher.DataCallback<InputStream> {
|
object : DataFetcher.DataCallback<InputStream> {
|
||||||
override fun onDataReady(data: InputStream?) {
|
override fun onDataReady(data: InputStream?) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
val tmpFile = File(file.path + ".tmp")
|
val tmpFile = File(cover.path + ".tmp")
|
||||||
try {
|
try {
|
||||||
// Retrieve destination stream, create parent folders if needed.
|
// Retrieve destination stream, create parent folders if needed.
|
||||||
val output = try {
|
val output = try {
|
||||||
tmpFile.outputStream()
|
tmpFile.outputStream()
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
tmpFile.parentFile.mkdirs()
|
tmpFile.parentFile!!.mkdirs()
|
||||||
tmpFile.outputStream()
|
tmpFile.outputStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the file and rename to the original.
|
// Copy the file and rename to the original.
|
||||||
data.use { output.use { data.copyTo(output) } }
|
data.use { output.use { data.copyTo(output) } }
|
||||||
tmpFile.renameTo(file)
|
tmpFile.renameTo(cover)
|
||||||
loadFromFile(callback)
|
loadFromFile(cover, callback)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
tmpFile.delete()
|
tmpFile.delete()
|
||||||
callback.onLoadFailed(e)
|
callback.onLoadFailed(e)
|
||||||
@ -59,7 +70,7 @@ class LibraryMangaUrlFetcher(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
loadFromFile(callback)
|
loadFromFile(cover, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.glide
|
|
||||||
|
|
||||||
import com.bumptech.glide.load.Key
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import java.io.File
|
|
||||||
import java.security.MessageDigest
|
|
||||||
|
|
||||||
class MangaSignature(manga: Manga, file: File) : Key {
|
|
||||||
|
|
||||||
private val key = manga.thumbnail_url + file.lastModified()
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
return if (other is MangaSignature) {
|
|
||||||
key == other.key
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return key.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateDiskCacheKey(md: MessageDigest) {
|
|
||||||
md.update(key.toByteArray(Key.CHARSET))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,15 @@
|
|||||||
package eu.kanade.tachiyomi.data.glide
|
package eu.kanade.tachiyomi.data.glide
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.Key
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
data class MangaThumbnail(val manga: Manga, val url: String?)
|
data class MangaThumbnail(val manga: Manga, val coverLastModified: Long) : Key {
|
||||||
|
val key = manga.url + coverLastModified
|
||||||
|
|
||||||
fun Manga.toMangaThumbnail() = MangaThumbnail(this, this.thumbnail_url)
|
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
|
||||||
|
messageDigest.update(key.toByteArray(Key.CHARSET))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Manga.toMangaThumbnail() = MangaThumbnail(this, cover_last_modified)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.data.glide
|
package eu.kanade.tachiyomi.data.glide
|
||||||
|
|
||||||
import android.util.LruCache
|
|
||||||
import com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher
|
import com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher
|
||||||
import com.bumptech.glide.load.Options
|
import com.bumptech.glide.load.Options
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
@ -14,7 +13,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import java.io.File
|
import eu.kanade.tachiyomi.util.isLocal
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -48,12 +47,6 @@ class MangaThumbnailModelLoader : ModelLoader<MangaThumbnail, InputStream> {
|
|||||||
*/
|
*/
|
||||||
private val defaultClient = Injekt.get<NetworkHelper>().client
|
private val defaultClient = Injekt.get<NetworkHelper>().client
|
||||||
|
|
||||||
/**
|
|
||||||
* LRU cache whose key is the thumbnail url of the manga, and the value contains the request url
|
|
||||||
* and the file where it should be stored in case the manga is a favorite.
|
|
||||||
*/
|
|
||||||
private val lruCache = LruCache<GlideUrl, File>(100)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map where request headers are stored for a source.
|
* Map where request headers are stored for a source.
|
||||||
*/
|
*/
|
||||||
@ -78,7 +71,7 @@ class MangaThumbnailModelLoader : ModelLoader<MangaThumbnail, InputStream> {
|
|||||||
/**
|
/**
|
||||||
* Returns a fetcher for the given manga or null if the url is empty.
|
* Returns a fetcher for the given manga or null if the url is empty.
|
||||||
*
|
*
|
||||||
* @param manga the model.
|
* @param mangaThumbnail the model.
|
||||||
* @param width the width of the view where the resource will be loaded.
|
* @param width the width of the view where the resource will be loaded.
|
||||||
* @param height the height of the view where the resource will be loaded.
|
* @param height the height of the view where the resource will be loaded.
|
||||||
*/
|
*/
|
||||||
@ -88,13 +81,16 @@ class MangaThumbnailModelLoader : ModelLoader<MangaThumbnail, InputStream> {
|
|||||||
height: Int,
|
height: Int,
|
||||||
options: Options
|
options: Options
|
||||||
): ModelLoader.LoadData<InputStream>? {
|
): ModelLoader.LoadData<InputStream>? {
|
||||||
// Check thumbnail is not null or empty
|
|
||||||
val url = mangaThumbnail.url
|
|
||||||
if (url == null || url.isEmpty()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val manga = mangaThumbnail.manga
|
val manga = mangaThumbnail.manga
|
||||||
|
val url = manga.thumbnail_url
|
||||||
|
|
||||||
|
if (url.isNullOrEmpty()) {
|
||||||
|
return if (!manga.favorite || manga.isLocal()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
ModelLoader.LoadData(mangaThumbnail, LibraryMangaCustomCoverFetcher(manga, coverCache))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (url.startsWith("http", true)) {
|
if (url.startsWith("http", true)) {
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource
|
val source = sourceManager.get(manga.source) as? HttpSource
|
||||||
@ -107,19 +103,13 @@ class MangaThumbnailModelLoader : ModelLoader<MangaThumbnail, InputStream> {
|
|||||||
return ModelLoader.LoadData(glideUrl, networkFetcher)
|
return ModelLoader.LoadData(glideUrl, networkFetcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtain the file for this url from the LRU cache, or retrieve and add it to the cache.
|
val libraryFetcher = LibraryMangaUrlFetcher(networkFetcher, manga, coverCache)
|
||||||
val file = lruCache.getOrPut(glideUrl) { coverCache.getCoverFile(url) }
|
|
||||||
|
|
||||||
val libraryFetcher = LibraryMangaUrlFetcher(networkFetcher, manga, file)
|
|
||||||
|
|
||||||
// Return an instance of the fetcher providing the needed elements.
|
// Return an instance of the fetcher providing the needed elements.
|
||||||
return ModelLoader.LoadData(MangaSignature(manga, file), libraryFetcher)
|
return ModelLoader.LoadData(mangaThumbnail, libraryFetcher)
|
||||||
} else {
|
} else {
|
||||||
// Get the file from the url, removing the scheme if present.
|
|
||||||
val file = File(url.substringAfter("file://"))
|
|
||||||
|
|
||||||
// Return an instance of the fetcher providing the needed elements.
|
// Return an instance of the fetcher providing the needed elements.
|
||||||
return ModelLoader.LoadData(MangaSignature(manga, file), FileFetcher(file))
|
return ModelLoader.LoadData(mangaThumbnail, FileFetcher(url.removePrefix("file://")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,15 +131,4 @@ class MangaThumbnailModelLoader : ModelLoader<MangaThumbnail, InputStream> {
|
|||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <K, V> LruCache<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
|
|
||||||
val value = get(key)
|
|
||||||
return if (value == null) {
|
|
||||||
val answer = defaultValue()
|
|
||||||
put(key, answer)
|
|
||||||
answer
|
|
||||||
} else {
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
18
app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt
Normal file → Executable file
18
app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt
Normal file → Executable file
@ -36,12 +36,20 @@ class TachiGlideModule : AppGlideModule() {
|
|||||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||||
val networkFactory = OkHttpUrlLoader.Factory(Injekt.get<NetworkHelper>().client)
|
val networkFactory = OkHttpUrlLoader.Factory(Injekt.get<NetworkHelper>().client)
|
||||||
|
|
||||||
registry.replace(GlideUrl::class.java, InputStream::class.java, networkFactory)
|
registry.replace(
|
||||||
registry.append(MangaThumbnail::class.java, InputStream::class.java, MangaThumbnailModelLoader.Factory())
|
GlideUrl::class.java,
|
||||||
|
InputStream::class.java,
|
||||||
|
networkFactory
|
||||||
|
)
|
||||||
registry.append(
|
registry.append(
|
||||||
InputStream::class.java, InputStream::class.java,
|
MangaThumbnail::class.java,
|
||||||
PassthroughModelLoader
|
InputStream::class.java,
|
||||||
.Factory()
|
MangaThumbnailModelLoader.Factory()
|
||||||
|
)
|
||||||
|
registry.append(
|
||||||
|
InputStream::class.java,
|
||||||
|
InputStream::class.java,
|
||||||
|
PassthroughModelLoader.Factory()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
Normal file → Executable file
7
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
Normal file → Executable file
@ -17,8 +17,11 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
Worker(context, workerParams) {
|
Worker(context, workerParams) {
|
||||||
|
|
||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
LibraryUpdateService.start(context)
|
return if (LibraryUpdateService.start(context)) {
|
||||||
return Result.success()
|
Result.success()
|
||||||
|
} else {
|
||||||
|
Result.failure()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -0,0 +1,302 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.library
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
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.glide.toMangaThumbnail
|
||||||
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
|
import eu.kanade.tachiyomi.util.lang.chop
|
||||||
|
import eu.kanade.tachiyomi.util.system.notification
|
||||||
|
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||||
|
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import java.text.DecimalFormatSymbols
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
class LibraryUpdateNotifier(private val context: Context) {
|
||||||
|
|
||||||
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pending intent of action that cancels the library update
|
||||||
|
*/
|
||||||
|
private val cancelIntent by lazy {
|
||||||
|
NotificationReceiver.cancelLibraryUpdatePendingBroadcast(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bitmap of the app for notifications.
|
||||||
|
*/
|
||||||
|
private val notificationBitmap by lazy {
|
||||||
|
BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cached progress notification to avoid creating a lot.
|
||||||
|
*/
|
||||||
|
val progressNotificationBuilder by lazy {
|
||||||
|
context.notificationBuilder(Notifications.CHANNEL_LIBRARY) {
|
||||||
|
setContentTitle(context.getString(R.string.app_name))
|
||||||
|
setSmallIcon(R.drawable.ic_refresh_24dp)
|
||||||
|
setLargeIcon(notificationBitmap)
|
||||||
|
setOngoing(true)
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
|
addAction(R.drawable.ic_close_24dp, context.getString(android.R.string.cancel), cancelIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the notification containing the currently updating manga and the progress.
|
||||||
|
*
|
||||||
|
* @param manga the manga that's being updated.
|
||||||
|
* @param current the current progress.
|
||||||
|
* @param total the total progress.
|
||||||
|
*/
|
||||||
|
fun showProgressNotification(manga: Manga, current: Int, total: Int) {
|
||||||
|
val title = if (preferences.hideNotificationContent()) {
|
||||||
|
context.getString(R.string.notification_check_updates)
|
||||||
|
} else {
|
||||||
|
manga.title
|
||||||
|
}
|
||||||
|
|
||||||
|
context.notificationManager.notify(
|
||||||
|
Notifications.ID_LIBRARY_PROGRESS,
|
||||||
|
progressNotificationBuilder
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setProgress(total, current, false)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows notification containing update entries that failed with action to open full log.
|
||||||
|
*
|
||||||
|
* @param errors List of entry titles that failed to update.
|
||||||
|
* @param uri Uri for error log file containing all titles that failed.
|
||||||
|
*/
|
||||||
|
fun showUpdateErrorNotification(errors: List<String>, uri: Uri) {
|
||||||
|
if (errors.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
context.notificationManager.notify(
|
||||||
|
Notifications.ID_LIBRARY_ERROR,
|
||||||
|
context.notificationBuilder(Notifications.CHANNEL_LIBRARY) {
|
||||||
|
setContentTitle(context.resources.getQuantityString(R.plurals.notification_update_error, errors.size, errors.size))
|
||||||
|
setStyle(
|
||||||
|
NotificationCompat.BigTextStyle().bigText(
|
||||||
|
errors.joinToString("\n") {
|
||||||
|
it.chop(NOTIF_TITLE_MAX_LEN)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
|
addAction(
|
||||||
|
R.drawable.nnf_ic_file_folder,
|
||||||
|
context.getString(R.string.action_open_log),
|
||||||
|
NotificationReceiver.openErrorLogPendingActivity(context, uri)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the notification containing the result of the update done by the service.
|
||||||
|
*
|
||||||
|
* @param updates a list of manga with new updates.
|
||||||
|
*/
|
||||||
|
fun showUpdateNotifications(updates: List<Pair<Manga, Array<Chapter>>>) {
|
||||||
|
if (updates.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationManagerCompat.from(context).apply {
|
||||||
|
// Parent group notification
|
||||||
|
notify(
|
||||||
|
Notifications.ID_NEW_CHAPTERS,
|
||||||
|
context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
||||||
|
setContentTitle(context.getString(R.string.notification_new_chapters))
|
||||||
|
if (updates.size == 1 && !preferences.hideNotificationContent()) {
|
||||||
|
setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN))
|
||||||
|
} else {
|
||||||
|
setContentText(context.resources.getQuantityString(R.plurals.notification_new_chapters_summary, updates.size, updates.size))
|
||||||
|
|
||||||
|
if (!preferences.hideNotificationContent()) {
|
||||||
|
setStyle(
|
||||||
|
NotificationCompat.BigTextStyle().bigText(
|
||||||
|
updates.joinToString("\n") {
|
||||||
|
it.first.title.chop(NOTIF_TITLE_MAX_LEN)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
|
setLargeIcon(notificationBitmap)
|
||||||
|
|
||||||
|
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
||||||
|
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||||
|
setGroupSummary(true)
|
||||||
|
priority = NotificationCompat.PRIORITY_HIGH
|
||||||
|
|
||||||
|
setContentIntent(getNotificationIntent())
|
||||||
|
setAutoCancel(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Per-manga notification
|
||||||
|
if (!preferences.hideNotificationContent()) {
|
||||||
|
updates.forEach {
|
||||||
|
val (manga, chapters) = it
|
||||||
|
notify(manga.id.hashCode(), createNewChaptersNotification(manga, chapters))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNewChaptersNotification(manga: Manga, chapters: Array<Chapter>): Notification {
|
||||||
|
return context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
||||||
|
setContentTitle(manga.title)
|
||||||
|
|
||||||
|
val description = getNewChaptersDescription(chapters)
|
||||||
|
setContentText(description)
|
||||||
|
setStyle(NotificationCompat.BigTextStyle().bigText(description))
|
||||||
|
|
||||||
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
|
|
||||||
|
val icon = getMangaIcon(manga)
|
||||||
|
if (icon != null) {
|
||||||
|
setLargeIcon(icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
||||||
|
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||||
|
priority = NotificationCompat.PRIORITY_HIGH
|
||||||
|
|
||||||
|
// Open first chapter on tap
|
||||||
|
setContentIntent(NotificationReceiver.openChapterPendingActivity(context, manga, chapters.first()))
|
||||||
|
setAutoCancel(true)
|
||||||
|
|
||||||
|
// Mark chapters as read action
|
||||||
|
addAction(
|
||||||
|
R.drawable.ic_glasses_black_24dp, context.getString(R.string.action_mark_as_read),
|
||||||
|
NotificationReceiver.markAsReadPendingBroadcast(
|
||||||
|
context,
|
||||||
|
manga, chapters, Notifications.ID_NEW_CHAPTERS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// View chapters action
|
||||||
|
addAction(
|
||||||
|
R.drawable.ic_book_24dp, context.getString(R.string.action_view_chapters),
|
||||||
|
NotificationReceiver.openChapterPendingActivity(
|
||||||
|
context,
|
||||||
|
manga, Notifications.ID_NEW_CHAPTERS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the progress notification.
|
||||||
|
*/
|
||||||
|
fun cancelProgressNotification() {
|
||||||
|
context.notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMangaIcon(manga: Manga): Bitmap? {
|
||||||
|
return try {
|
||||||
|
Glide.with(context)
|
||||||
|
.asBitmap()
|
||||||
|
.load(manga.toMangaThumbnail())
|
||||||
|
.dontTransform()
|
||||||
|
.centerCrop()
|
||||||
|
.circleCrop()
|
||||||
|
.override(
|
||||||
|
NOTIF_ICON_SIZE,
|
||||||
|
NOTIF_ICON_SIZE
|
||||||
|
)
|
||||||
|
.submit()
|
||||||
|
.get()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNewChaptersDescription(chapters: Array<Chapter>): String {
|
||||||
|
val formatter = DecimalFormat(
|
||||||
|
"#.###",
|
||||||
|
DecimalFormatSymbols()
|
||||||
|
.apply { decimalSeparator = '.' }
|
||||||
|
)
|
||||||
|
|
||||||
|
val displayableChapterNumbers = chapters
|
||||||
|
.filter { it.isRecognizedNumber }
|
||||||
|
.sortedBy { it.chapter_number }
|
||||||
|
.map { formatter.format(it.chapter_number) }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
return when (displayableChapterNumbers.size) {
|
||||||
|
// No sensible chapter numbers to show (i.e. no chapters have parsed chapter number)
|
||||||
|
0 -> {
|
||||||
|
// "1 new chapter" or "5 new chapters"
|
||||||
|
context.resources.getQuantityString(R.plurals.notification_chapters_generic, chapters.size, chapters.size)
|
||||||
|
}
|
||||||
|
// Only 1 chapter has a parsed chapter number
|
||||||
|
1 -> {
|
||||||
|
val remaining = chapters.size - displayableChapterNumbers.size
|
||||||
|
if (remaining == 0) {
|
||||||
|
// "Chapter 2.5"
|
||||||
|
context.resources.getString(R.string.notification_chapters_single, displayableChapterNumbers.first())
|
||||||
|
} else {
|
||||||
|
// "Chapter 2.5 and 10 more"
|
||||||
|
context.resources.getString(R.string.notification_chapters_single_and_more, displayableChapterNumbers.first(), remaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Everything else (i.e. multiple parsed chapter numbers)
|
||||||
|
else -> {
|
||||||
|
val shouldTruncate = displayableChapterNumbers.size > NOTIF_MAX_CHAPTERS
|
||||||
|
if (shouldTruncate) {
|
||||||
|
// "Chapters 1, 2.5, 3, 4, 5 and 10 more"
|
||||||
|
val remaining = displayableChapterNumbers.size - NOTIF_MAX_CHAPTERS
|
||||||
|
val joinedChapterNumbers = displayableChapterNumbers.take(NOTIF_MAX_CHAPTERS).joinToString(", ")
|
||||||
|
context.resources.getQuantityString(R.plurals.notification_chapters_multiple_and_more, remaining, joinedChapterNumbers, remaining)
|
||||||
|
} else {
|
||||||
|
// "Chapters 1, 2.5, 3"
|
||||||
|
context.resources.getString(R.string.notification_chapters_multiple, displayableChapterNumbers.joinToString(", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an intent to open the main activity.
|
||||||
|
*/
|
||||||
|
private fun getNotificationIntent(): PendingIntent {
|
||||||
|
val intent = Intent(context, MainActivity::class.java).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||||
|
action = MainActivity.SHORTCUT_RECENTLY_UPDATED
|
||||||
|
}
|
||||||
|
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val NOTIF_MAX_CHAPTERS = 5
|
||||||
|
private const val NOTIF_TITLE_MAX_LEN = 45
|
||||||
|
private const val NOTIF_ICON_SIZE = 192
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.library
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class will provide various functions to Rank mangas to efficiently schedule mangas to update.
|
* This class will provide various functions to rank manga to efficiently schedule manga to update.
|
||||||
*/
|
*/
|
||||||
object LibraryUpdateRanker {
|
object LibraryUpdateRanker {
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ object LibraryUpdateRanker {
|
|||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a total ordering over all the Mangas.
|
* Provides a total ordering over all the [Manga]s.
|
||||||
*
|
*
|
||||||
* Assumption: An active [Manga] mActive is expected to have been last updated after an
|
* Assumption: An active [Manga] mActive is expected to have been last updated after an
|
||||||
* inactive [Manga] mInactive.
|
* inactive [Manga] mInactive.
|
||||||
@ -21,23 +21,19 @@ object LibraryUpdateRanker {
|
|||||||
* Using this insight, function returns a Comparator for which mActive appears before mInactive.
|
* Using this insight, function returns a Comparator for which mActive appears before mInactive.
|
||||||
* @return a Comparator that ranks manga based on relevance.
|
* @return a Comparator that ranks manga based on relevance.
|
||||||
*/
|
*/
|
||||||
fun latestFirstRanking(): Comparator<Manga> {
|
private fun latestFirstRanking(): Comparator<Manga> =
|
||||||
return Comparator { mangaFirst: Manga,
|
Comparator { first: Manga, second: Manga ->
|
||||||
mangaSecond: Manga ->
|
compareValues(second.last_update, first.last_update)
|
||||||
compareValues(mangaSecond.last_update, mangaFirst.last_update)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a total ordering over all the Mangas.
|
* Provides a total ordering over all the [Manga]s.
|
||||||
*
|
*
|
||||||
* Order the manga lexicographically.
|
* Order the manga lexicographically.
|
||||||
* @return a Comparator that ranks manga lexicographically based on the title.
|
* @return a Comparator that ranks manga lexicographically based on the title.
|
||||||
*/
|
*/
|
||||||
fun lexicographicRanking(): Comparator<Manga> {
|
private fun lexicographicRanking(): Comparator<Manga> =
|
||||||
return Comparator { mangaFirst: Manga,
|
Comparator { first: Manga, second: Manga ->
|
||||||
mangaSecond: Manga ->
|
compareValues(first.title, second.title)
|
||||||
compareValues(mangaFirst.title, mangaSecond.title)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
414
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
Normal file → Executable file
414
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
Normal file → Executable file
@ -1,20 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.data.library
|
package eu.kanade.tachiyomi.data.library
|
||||||
|
|
||||||
import android.app.Notification
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import androidx.core.app.NotificationCompat
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import androidx.core.app.NotificationCompat.GROUP_ALERT_SUMMARY
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
@ -22,25 +14,21 @@ import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
|
||||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||||
import eu.kanade.tachiyomi.util.lang.chop
|
import eu.kanade.tachiyomi.util.prepUpdateCover
|
||||||
|
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 eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
import eu.kanade.tachiyomi.util.system.notification
|
import exh.LIBRARY_UPDATE_EXCLUDED_SOURCES
|
||||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
import java.io.File
|
||||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
|
||||||
import java.text.DecimalFormat
|
|
||||||
import java.text.DecimalFormatSymbols
|
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -63,7 +51,8 @@ class LibraryUpdateService(
|
|||||||
val sourceManager: SourceManager = Injekt.get(),
|
val sourceManager: SourceManager = Injekt.get(),
|
||||||
val preferences: PreferencesHelper = Injekt.get(),
|
val preferences: PreferencesHelper = Injekt.get(),
|
||||||
val downloadManager: DownloadManager = Injekt.get(),
|
val downloadManager: DownloadManager = Injekt.get(),
|
||||||
val trackManager: TrackManager = Injekt.get()
|
val trackManager: TrackManager = Injekt.get(),
|
||||||
|
val coverCache: CoverCache = Injekt.get()
|
||||||
) : Service() {
|
) : Service() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,45 +60,19 @@ class LibraryUpdateService(
|
|||||||
*/
|
*/
|
||||||
private lateinit var wakeLock: PowerManager.WakeLock
|
private lateinit var wakeLock: PowerManager.WakeLock
|
||||||
|
|
||||||
|
private lateinit var notifier: LibraryUpdateNotifier
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscription where the update is done.
|
* Subscription where the update is done.
|
||||||
*/
|
*/
|
||||||
private var subscription: Subscription? = null
|
private var subscription: Subscription? = null
|
||||||
|
|
||||||
/**
|
|
||||||
* Pending intent of action that cancels the library update
|
|
||||||
*/
|
|
||||||
private val cancelIntent by lazy {
|
|
||||||
NotificationReceiver.cancelLibraryUpdatePendingBroadcast(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bitmap of the app for notifications.
|
|
||||||
*/
|
|
||||||
private val notificationBitmap by lazy {
|
|
||||||
BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cached progress notification to avoid creating a lot.
|
|
||||||
*/
|
|
||||||
private val progressNotificationBuilder by lazy {
|
|
||||||
notificationBuilder(Notifications.CHANNEL_LIBRARY) {
|
|
||||||
setContentTitle(getString(R.string.app_name))
|
|
||||||
setSmallIcon(R.drawable.ic_refresh_24dp)
|
|
||||||
setLargeIcon(notificationBitmap)
|
|
||||||
setOngoing(true)
|
|
||||||
setOnlyAlertOnce(true)
|
|
||||||
addAction(R.drawable.ic_close_24dp, getString(android.R.string.cancel), cancelIntent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines what should be updated within a service execution.
|
* Defines what should be updated within a service execution.
|
||||||
*/
|
*/
|
||||||
enum class Target {
|
enum class Target {
|
||||||
CHAPTERS, // Manga chapters
|
CHAPTERS, // Manga chapters
|
||||||
DETAILS, // Manga metadata
|
COVERS, // Manga covers
|
||||||
TRACKING // Tracking metadata
|
TRACKING // Tracking metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,10 +88,6 @@ class LibraryUpdateService(
|
|||||||
*/
|
*/
|
||||||
const val KEY_TARGET = "target"
|
const val KEY_TARGET = "target"
|
||||||
|
|
||||||
private const val NOTIF_MAX_CHAPTERS = 5
|
|
||||||
private const val NOTIF_TITLE_MAX_LEN = 45
|
|
||||||
private const val NOTIF_ICON_SIZE = 192
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the status of the service.
|
* Returns the status of the service.
|
||||||
*
|
*
|
||||||
@ -182,11 +141,11 @@ class LibraryUpdateService(
|
|||||||
*/
|
*/
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
startForeground(Notifications.ID_LIBRARY_PROGRESS, progressNotificationBuilder.build())
|
|
||||||
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
notifier = LibraryUpdateNotifier(this)
|
||||||
PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock"
|
wakeLock = acquireWakeLock(javaClass.name)
|
||||||
)
|
|
||||||
wakeLock.acquire()
|
startForeground(Notifications.ID_LIBRARY_PROGRESS, notifier.progressNotificationBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -234,7 +193,7 @@ class LibraryUpdateService(
|
|||||||
// Update either chapter list or manga details.
|
// Update either chapter list or manga details.
|
||||||
when (target) {
|
when (target) {
|
||||||
Target.CHAPTERS -> updateChapterList(mangaList)
|
Target.CHAPTERS -> updateChapterList(mangaList)
|
||||||
Target.DETAILS -> updateDetails(mangaList)
|
Target.COVERS -> updateCovers(mangaList)
|
||||||
Target.TRACKING -> updateTrackings(mangaList)
|
Target.TRACKING -> updateTrackings(mangaList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -298,43 +257,43 @@ class LibraryUpdateService(
|
|||||||
// List containing new updates
|
// List containing new updates
|
||||||
val newUpdates = ArrayList<Pair<LibraryManga, Array<Chapter>>>()
|
val newUpdates = ArrayList<Pair<LibraryManga, Array<Chapter>>>()
|
||||||
// List containing failed updates
|
// List containing failed updates
|
||||||
val failedUpdates = ArrayList<Manga>()
|
val failedUpdates = ArrayList<Pair<Manga, String?>>()
|
||||||
// List containing categories that get included in downloads.
|
|
||||||
val categoriesToDownload = preferences.downloadNewCategories().get().map(String::toInt)
|
|
||||||
// Boolean to determine if user wants to automatically download new chapters.
|
|
||||||
val downloadNew = preferences.downloadNew().get()
|
|
||||||
// Boolean to determine if DownloadManager has downloads
|
// Boolean to determine if DownloadManager has downloads
|
||||||
var hasDownloads = false
|
var hasDownloads = false
|
||||||
|
|
||||||
// Emit each manga and update it sequentially.
|
// Emit each manga and update it sequentially.
|
||||||
return Observable.from(mangaToUpdate)
|
return Observable.from(mangaToUpdate)
|
||||||
// Notify manga that will update.
|
// Notify manga that will update.
|
||||||
.doOnNext { showProgressNotification(it, count.andIncrement, mangaToUpdate.size) }
|
.doOnNext { notifier.showProgressNotification(it, count.andIncrement, mangaToUpdate.size) }
|
||||||
// Update the chapters of the manga.
|
// Update the chapters of the manga
|
||||||
.concatMap { manga ->
|
.concatMap { manga ->
|
||||||
updateManga(manga)
|
if (manga.source in LIBRARY_UPDATE_EXCLUDED_SOURCES) {
|
||||||
// If there's any error, return empty update and continue.
|
// Ignore EXH manga, updating chapters for every manga will get you banned
|
||||||
.onErrorReturn {
|
Observable.empty()
|
||||||
failedUpdates.add(manga)
|
} else {
|
||||||
Pair(emptyList(), emptyList())
|
updateManga(manga)
|
||||||
}
|
// If there's any error, return empty update and continue.
|
||||||
// Filter out mangas without new chapters (or failed).
|
.onErrorReturn {
|
||||||
.filter { pair -> pair.first.isNotEmpty() }
|
failedUpdates.add(Pair(manga, it.message))
|
||||||
.doOnNext {
|
Pair(emptyList(), emptyList())
|
||||||
if (downloadNew && (
|
|
||||||
categoriesToDownload.isEmpty() ||
|
|
||||||
manga.category in categoriesToDownload
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
downloadChapters(manga, it.first)
|
|
||||||
hasDownloads = true
|
|
||||||
}
|
}
|
||||||
}
|
// 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.
|
// Convert to the manga that contains new chapters.
|
||||||
.map {
|
.map {
|
||||||
Pair(
|
Pair(
|
||||||
manga,
|
manga,
|
||||||
(it.first.sortedByDescending { ch -> ch.source_order }.toTypedArray())
|
(
|
||||||
|
it.first.sortedByDescending { ch -> ch.source_order }
|
||||||
|
.toTypedArray()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -345,31 +304,30 @@ class LibraryUpdateService(
|
|||||||
}
|
}
|
||||||
// Notify result of the overall update.
|
// Notify result of the overall update.
|
||||||
.doOnCompleted {
|
.doOnCompleted {
|
||||||
|
notifier.cancelProgressNotification()
|
||||||
|
|
||||||
if (newUpdates.isNotEmpty()) {
|
if (newUpdates.isNotEmpty()) {
|
||||||
showUpdateNotifications(newUpdates)
|
notifier.showUpdateNotifications(newUpdates)
|
||||||
if (downloadNew && hasDownloads) {
|
if (hasDownloads) {
|
||||||
DownloadService.start(this)
|
DownloadService.start(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (failedUpdates.isNotEmpty()) {
|
if (preferences.showLibraryUpdateErrors() && failedUpdates.isNotEmpty()) {
|
||||||
Timber.e("Failed updating: ${failedUpdates.map { it.title }}")
|
val errorFile = writeErrorFile(failedUpdates)
|
||||||
|
notifier.showUpdateErrorNotification(
|
||||||
|
failedUpdates.map { it.first.title },
|
||||||
|
errorFile.getUriCompat(this)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelProgressNotification()
|
|
||||||
}
|
}
|
||||||
.map { manga -> manga.first }
|
.map { manga -> manga.first }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
|
private fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
|
||||||
// we need to get the chapters from the db so we have chapter ids
|
|
||||||
val mangaChapters = db.getChapters(manga).executeAsBlocking()
|
|
||||||
val dbChapters = chapters.map {
|
|
||||||
mangaChapters.find { mangaChapter -> mangaChapter.url == it.url }!!
|
|
||||||
}
|
|
||||||
// We don't want to start downloading while the library is updating, because websites
|
// We don't want to start downloading while the library is updating, because websites
|
||||||
// may don't like it and they could ban the user.
|
// may don't like it and they could ban the user.
|
||||||
downloadManager.downloadChapters(manga, dbChapters, false)
|
downloadManager.downloadChapters(manga, chapters, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -379,41 +337,56 @@ class LibraryUpdateService(
|
|||||||
* @return a pair of the inserted and removed chapters.
|
* @return a pair of the inserted and removed chapters.
|
||||||
*/
|
*/
|
||||||
fun updateManga(manga: Manga): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
fun updateManga(manga: Manga): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource ?: return Observable.empty()
|
val source = sourceManager.get(manga.source) ?: return Observable.empty()
|
||||||
|
|
||||||
|
// Update manga details metadata in the background
|
||||||
|
if (preferences.autoUpdateMetadata()) {
|
||||||
|
source.fetchMangaDetails(manga)
|
||||||
|
.map { updatedManga ->
|
||||||
|
// Avoid "losing" existing cover
|
||||||
|
if (!updatedManga.thumbnail_url.isNullOrEmpty()) {
|
||||||
|
manga.prepUpdateCover(coverCache, updatedManga, false)
|
||||||
|
} else {
|
||||||
|
updatedManga.thumbnail_url = manga.thumbnail_url
|
||||||
|
}
|
||||||
|
|
||||||
|
manga.copyFrom(updatedManga)
|
||||||
|
db.insertManga(manga).executeAsBlocking()
|
||||||
|
manga
|
||||||
|
}
|
||||||
|
.onErrorResumeNext { Observable.just(manga) }
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
return source.fetchChapterList(manga)
|
return source.fetchChapterList(manga)
|
||||||
.map { syncChaptersWithSource(db, it, manga, source) }
|
.map { syncChaptersWithSource(db, it, manga, source) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun updateCovers(mangaToUpdate: List<LibraryManga>): Observable<LibraryManga> {
|
||||||
* Method that updates the details of the given list of manga. It's called in a background
|
var count = 0
|
||||||
* thread, so it's safe to do heavy operations or network calls here.
|
|
||||||
*
|
|
||||||
* @param mangaToUpdate the list to update
|
|
||||||
* @return an observable delivering the progress of each update.
|
|
||||||
*/
|
|
||||||
fun updateDetails(mangaToUpdate: List<LibraryManga>): Observable<LibraryManga> {
|
|
||||||
// Initialize the variables holding the progress of the updates.
|
|
||||||
val count = AtomicInteger(0)
|
|
||||||
|
|
||||||
// Emit each manga and update it sequentially.
|
|
||||||
return Observable.from(mangaToUpdate)
|
return Observable.from(mangaToUpdate)
|
||||||
// Notify manga that will update.
|
.doOnNext {
|
||||||
.doOnNext { showProgressNotification(it, count.andIncrement, mangaToUpdate.size) }
|
notifier.showProgressNotification(it, count++, mangaToUpdate.size)
|
||||||
// Update the details of the manga.
|
}
|
||||||
.concatMap { manga ->
|
.flatMap { manga ->
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource
|
val source = sourceManager.get(manga.source)
|
||||||
?: return@concatMap Observable.empty<LibraryManga>()
|
?: return@flatMap Observable.empty<LibraryManga>()
|
||||||
|
|
||||||
source.fetchMangaDetails(manga)
|
source.fetchMangaDetails(manga)
|
||||||
.map { networkManga ->
|
.map { networkManga ->
|
||||||
manga.copyFrom(networkManga)
|
manga.prepUpdateCover(coverCache, networkManga, true)
|
||||||
db.insertManga(manga).executeAsBlocking()
|
networkManga.thumbnail_url?.let {
|
||||||
|
manga.thumbnail_url = it
|
||||||
|
db.insertManga(manga).executeAsBlocking()
|
||||||
|
}
|
||||||
manga
|
manga
|
||||||
}
|
}
|
||||||
.onErrorReturn { manga }
|
.onErrorReturn { manga }
|
||||||
}
|
}
|
||||||
.doOnCompleted {
|
.doOnCompleted {
|
||||||
cancelProgressNotification()
|
notifier.cancelProgressNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,7 +403,7 @@ class LibraryUpdateService(
|
|||||||
// Emit each manga and update it sequentially.
|
// Emit each manga and update it sequentially.
|
||||||
return Observable.from(mangaToUpdate)
|
return Observable.from(mangaToUpdate)
|
||||||
// Notify manga that will update.
|
// Notify manga that will update.
|
||||||
.doOnNext { showProgressNotification(it, count++, mangaToUpdate.size) }
|
.doOnNext { notifier.showProgressNotification(it, count++, mangaToUpdate.size) }
|
||||||
// Update the tracking details.
|
// Update the tracking details.
|
||||||
.concatMap { manga ->
|
.concatMap { manga ->
|
||||||
val tracks = db.getTracks(manga).executeAsBlocking()
|
val tracks = db.getTracks(manga).executeAsBlocking()
|
||||||
@ -449,207 +422,28 @@ class LibraryUpdateService(
|
|||||||
.map { manga }
|
.map { manga }
|
||||||
}
|
}
|
||||||
.doOnCompleted {
|
.doOnCompleted {
|
||||||
cancelProgressNotification()
|
notifier.cancelProgressNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the notification containing the currently updating manga and the progress.
|
* Writes basic file of update errors to cache dir.
|
||||||
*
|
|
||||||
* @param manga the manga that's being updated.
|
|
||||||
* @param current the current progress.
|
|
||||||
* @param total the total progress.
|
|
||||||
*/
|
*/
|
||||||
private fun showProgressNotification(manga: Manga, current: Int, total: Int) {
|
private fun writeErrorFile(errors: List<Pair<Manga, String?>>): File {
|
||||||
val title = if (preferences.hideNotificationContent()) {
|
try {
|
||||||
getString(R.string.notification_check_updates)
|
if (errors.isNotEmpty()) {
|
||||||
} else {
|
val destFile = File(externalCacheDir, "tachiyomi_update_errors.txt")
|
||||||
manga.title
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationManager.notify(
|
destFile.bufferedWriter().use { out ->
|
||||||
Notifications.ID_LIBRARY_PROGRESS,
|
errors.forEach { (manga, error) ->
|
||||||
progressNotificationBuilder
|
out.write("${manga.title}: $error\n")
|
||||||
.setContentTitle(title)
|
|
||||||
.setProgress(total, current, false)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows the notification containing the result of the update done by the service.
|
|
||||||
*
|
|
||||||
* @param updates a list of manga with new updates.
|
|
||||||
*/
|
|
||||||
private fun showUpdateNotifications(updates: List<Pair<Manga, Array<Chapter>>>) {
|
|
||||||
if (updates.isEmpty()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationManagerCompat.from(this).apply {
|
|
||||||
// Parent group notification
|
|
||||||
notify(
|
|
||||||
Notifications.ID_NEW_CHAPTERS,
|
|
||||||
notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
|
||||||
setContentTitle(getString(R.string.notification_new_chapters))
|
|
||||||
if (updates.size == 1 && !preferences.hideNotificationContent()) {
|
|
||||||
setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN))
|
|
||||||
} else {
|
|
||||||
setContentText(resources.getQuantityString(R.plurals.notification_new_chapters_summary, updates.size, updates.size))
|
|
||||||
|
|
||||||
if (!preferences.hideNotificationContent()) {
|
|
||||||
setStyle(
|
|
||||||
NotificationCompat.BigTextStyle().bigText(
|
|
||||||
updates.joinToString("\n") {
|
|
||||||
it.first.title.chop(NOTIF_TITLE_MAX_LEN)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
|
||||||
setLargeIcon(notificationBitmap)
|
|
||||||
|
|
||||||
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
|
||||||
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
|
|
||||||
setGroupSummary(true)
|
|
||||||
priority = NotificationCompat.PRIORITY_HIGH
|
|
||||||
|
|
||||||
setContentIntent(getNotificationIntent())
|
|
||||||
setAutoCancel(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Per-manga notification
|
|
||||||
if (!preferences.hideNotificationContent()) {
|
|
||||||
updates.forEach {
|
|
||||||
val (manga, chapters) = it
|
|
||||||
notify(manga.id.hashCode(), createNewChaptersNotification(manga, chapters))
|
|
||||||
}
|
}
|
||||||
|
return destFile
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createNewChaptersNotification(manga: Manga, chapters: Array<Chapter>): Notification {
|
|
||||||
return notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
|
||||||
setContentTitle(manga.title)
|
|
||||||
|
|
||||||
val description = getNewChaptersDescription(chapters)
|
|
||||||
setContentText(description)
|
|
||||||
setStyle(NotificationCompat.BigTextStyle().bigText(description))
|
|
||||||
|
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
|
||||||
|
|
||||||
val icon = getMangaIcon(manga)
|
|
||||||
if (icon != null) {
|
|
||||||
setLargeIcon(icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
|
||||||
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
|
|
||||||
priority = NotificationCompat.PRIORITY_HIGH
|
|
||||||
|
|
||||||
// Open first chapter on tap
|
|
||||||
setContentIntent(NotificationReceiver.openChapterPendingActivity(this@LibraryUpdateService, manga, chapters.first()))
|
|
||||||
setAutoCancel(true)
|
|
||||||
|
|
||||||
// Mark chapters as read action
|
|
||||||
addAction(
|
|
||||||
R.drawable.ic_glasses_black_24dp, getString(R.string.action_mark_as_read),
|
|
||||||
NotificationReceiver.markAsReadPendingBroadcast(
|
|
||||||
this@LibraryUpdateService,
|
|
||||||
manga, chapters, Notifications.ID_NEW_CHAPTERS
|
|
||||||
)
|
|
||||||
)
|
|
||||||
// View chapters action
|
|
||||||
addAction(
|
|
||||||
R.drawable.ic_book_24dp, getString(R.string.action_view_chapters),
|
|
||||||
NotificationReceiver.openChapterPendingActivity(
|
|
||||||
this@LibraryUpdateService,
|
|
||||||
manga, Notifications.ID_NEW_CHAPTERS
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels the progress notification.
|
|
||||||
*/
|
|
||||||
private fun cancelProgressNotification() {
|
|
||||||
notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getMangaIcon(manga: Manga): Bitmap? {
|
|
||||||
return try {
|
|
||||||
Glide.with(this)
|
|
||||||
.asBitmap()
|
|
||||||
.load(manga.toMangaThumbnail())
|
|
||||||
.dontTransform()
|
|
||||||
.centerCrop()
|
|
||||||
.circleCrop()
|
|
||||||
.override(NOTIF_ICON_SIZE, NOTIF_ICON_SIZE)
|
|
||||||
.submit()
|
|
||||||
.get()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
null
|
// Empty
|
||||||
}
|
}
|
||||||
}
|
return File("")
|
||||||
|
|
||||||
private fun getNewChaptersDescription(chapters: Array<Chapter>): String {
|
|
||||||
val formatter = DecimalFormat(
|
|
||||||
"#.###",
|
|
||||||
DecimalFormatSymbols()
|
|
||||||
.apply { decimalSeparator = '.' }
|
|
||||||
)
|
|
||||||
|
|
||||||
val displayableChapterNumbers = chapters
|
|
||||||
.filter { it.isRecognizedNumber }
|
|
||||||
.sortedBy { it.chapter_number }
|
|
||||||
.map { formatter.format(it.chapter_number) }
|
|
||||||
.toSet()
|
|
||||||
|
|
||||||
return when (displayableChapterNumbers.size) {
|
|
||||||
// No sensible chapter numbers to show (i.e. no chapters have parsed chapter number)
|
|
||||||
0 -> {
|
|
||||||
// "1 new chapter" or "5 new chapters"
|
|
||||||
resources.getQuantityString(R.plurals.notification_chapters_generic, chapters.size, chapters.size)
|
|
||||||
}
|
|
||||||
// Only 1 chapter has a parsed chapter number
|
|
||||||
1 -> {
|
|
||||||
val remaining = chapters.size - displayableChapterNumbers.size
|
|
||||||
if (remaining == 0) {
|
|
||||||
// "Chapter 2.5"
|
|
||||||
resources.getString(R.string.notification_chapters_single, displayableChapterNumbers.first())
|
|
||||||
} else {
|
|
||||||
// "Chapter 2.5 and 10 more"
|
|
||||||
resources.getString(R.string.notification_chapters_single_and_more, displayableChapterNumbers.first(), remaining)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Everything else (i.e. multiple parsed chapter numbers)
|
|
||||||
else -> {
|
|
||||||
val shouldTruncate = displayableChapterNumbers.size > NOTIF_MAX_CHAPTERS
|
|
||||||
if (shouldTruncate) {
|
|
||||||
// "Chapters 1, 2.5, 3, 4, 5 and 10 more"
|
|
||||||
val remaining = displayableChapterNumbers.size - NOTIF_MAX_CHAPTERS
|
|
||||||
val joinedChapterNumbers = displayableChapterNumbers.take(NOTIF_MAX_CHAPTERS).joinToString(", ")
|
|
||||||
resources.getQuantityString(R.plurals.notification_chapters_multiple_and_more, remaining, joinedChapterNumbers, remaining)
|
|
||||||
} else {
|
|
||||||
// "Chapters 1, 2.5, 3"
|
|
||||||
resources.getString(R.string.notification_chapters_multiple, displayableChapterNumbers.joinToString(", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an intent to open the main activity.
|
|
||||||
*/
|
|
||||||
private fun getNotificationIntent(): PendingIntent {
|
|
||||||
val intent = Intent(this, MainActivity::class.java).apply {
|
|
||||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
|
||||||
action = MainActivity.SHORTCUT_RECENTLY_UPDATED
|
|
||||||
}
|
|
||||||
return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
0
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
Normal file → Executable file
@ -25,6 +25,7 @@ object Notifications {
|
|||||||
*/
|
*/
|
||||||
const val CHANNEL_LIBRARY = "library_channel"
|
const val CHANNEL_LIBRARY = "library_channel"
|
||||||
const val ID_LIBRARY_PROGRESS = -101
|
const val ID_LIBRARY_PROGRESS = -101
|
||||||
|
const val ID_LIBRARY_ERROR = -102
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification channel and ids used by the downloader.
|
* Notification channel and ids used by the downloader.
|
||||||
|
96
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
Normal file → Executable file
96
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
Normal file → Executable file
@ -73,10 +73,12 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
const val lastUsedCategory = "last_used_category"
|
const val lastUsedCategory = "last_used_category"
|
||||||
|
|
||||||
const val catalogueAsList = "pref_display_catalogue_as_list"
|
const val catalogueViewSetting = "pref_display_catalogue_view_setting"
|
||||||
|
|
||||||
const val enabledLanguages = "source_languages"
|
const val enabledLanguages = "source_languages"
|
||||||
|
|
||||||
|
const val sourcesSort = "sources_sort"
|
||||||
|
|
||||||
const val backupDirectory = "backup_directory"
|
const val backupDirectory = "backup_directory"
|
||||||
|
|
||||||
const val downloadsDirectory = "download_directory"
|
const val downloadsDirectory = "download_directory"
|
||||||
@ -107,6 +109,10 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
const val filterCompleted = "pref_filter_completed_key"
|
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 librarySortingMode = "library_sorting_mode"
|
||||||
|
|
||||||
const val automaticExtUpdates = "automatic_ext_updates"
|
const val automaticExtUpdates = "automatic_ext_updates"
|
||||||
@ -123,11 +129,15 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
const val hideNotificationContent = "hide_notification_content"
|
const val hideNotificationContent = "hide_notification_content"
|
||||||
|
|
||||||
|
const val autoUpdateMetadata = "auto_update_metadata"
|
||||||
|
|
||||||
|
const val showLibraryUpdateErrors = "show_library_update_errors"
|
||||||
|
|
||||||
const val downloadNew = "download_new"
|
const val downloadNew = "download_new"
|
||||||
|
|
||||||
const val downloadNewCategories = "download_new_categories"
|
const val downloadNewCategories = "download_new_categories"
|
||||||
|
|
||||||
const val libraryAsList = "pref_display_library_as_list"
|
const val libraryViewSetting = "pref_display_library_view_setting"
|
||||||
|
|
||||||
const val lang = "app_language"
|
const val lang = "app_language"
|
||||||
|
|
||||||
@ -141,6 +151,10 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
const val downloadBadge = "display_download_badge"
|
const val downloadBadge = "display_download_badge"
|
||||||
|
|
||||||
|
const val unreadBadge = "display_unread_badge"
|
||||||
|
|
||||||
|
const val skipPreMigration = "skip_pre_migration"
|
||||||
|
|
||||||
const val alwaysShowChapterTransition = "always_show_chapter_transition"
|
const val alwaysShowChapterTransition = "always_show_chapter_transition"
|
||||||
|
|
||||||
const val searchPinnedSourcesOnly = "search_pinned_sources_only"
|
const val searchPinnedSourcesOnly = "search_pinned_sources_only"
|
||||||
@ -150,4 +164,82 @@ object PreferenceKeys {
|
|||||||
fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"
|
fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"
|
||||||
|
|
||||||
fun trackToken(syncId: Int) = "track_token_$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"
|
||||||
}
|
}
|
||||||
|
164
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
Normal file → Executable file
164
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
Normal file → Executable file
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.data.preference
|
package eu.kanade.tachiyomi.data.preference
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
@ -31,28 +30,12 @@ fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> {
|
|||||||
.onEach { block(it) }
|
.onEach { block(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DateFormatConverter : RxPreference.Adapter<DateFormat> {
|
|
||||||
override fun get(key: String, preferences: SharedPreferences): DateFormat {
|
|
||||||
val dateFormat = preferences.getString(Keys.dateFormat, "")!!
|
|
||||||
|
|
||||||
if (dateFormat != "") {
|
|
||||||
return SimpleDateFormat(dateFormat, Locale.getDefault())
|
|
||||||
}
|
|
||||||
|
|
||||||
return DateFormat.getDateInstance(DateFormat.SHORT)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun set(key: String, value: DateFormat, editor: SharedPreferences.Editor) {
|
|
||||||
// No-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class PreferencesHelper(val context: Context) {
|
class PreferencesHelper(val context: Context) {
|
||||||
|
|
||||||
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
private val rxPrefs = RxSharedPreferences.create(prefs)
|
private val rxPrefs = RxSharedPreferences.create(prefs)
|
||||||
private val flowPrefs = FlowSharedPreferences(prefs)
|
val flowPrefs = FlowSharedPreferences(prefs)
|
||||||
|
|
||||||
private val defaultDownloadsDir = Uri.fromFile(
|
private val defaultDownloadsDir = Uri.fromFile(
|
||||||
File(
|
File(
|
||||||
@ -84,6 +67,10 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun hideNotificationContent() = prefs.getBoolean(Keys.hideNotificationContent, false)
|
fun hideNotificationContent() = prefs.getBoolean(Keys.hideNotificationContent, false)
|
||||||
|
|
||||||
|
fun autoUpdateMetadata() = prefs.getBoolean(Keys.autoUpdateMetadata, false)
|
||||||
|
|
||||||
|
fun showLibraryUpdateErrors() = prefs.getBoolean(Keys.showLibraryUpdateErrors, false)
|
||||||
|
|
||||||
fun clear() = prefs.edit().clear().apply()
|
fun clear() = prefs.edit().clear().apply()
|
||||||
|
|
||||||
fun themeMode() = flowPrefs.getString(Keys.themeMode, Values.THEME_MODE_SYSTEM)
|
fun themeMode() = flowPrefs.getString(Keys.themeMode, Values.THEME_MODE_SYSTEM)
|
||||||
@ -92,7 +79,7 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun themeDark() = flowPrefs.getString(Keys.themeDark, Values.THEME_DARK_DEFAULT)
|
fun themeDark() = flowPrefs.getString(Keys.themeDark, Values.THEME_DARK_DEFAULT)
|
||||||
|
|
||||||
fun rotation() = rxPrefs.getInteger(Keys.rotation, 1)
|
fun rotation() = flowPrefs.getInt(Keys.rotation, 1)
|
||||||
|
|
||||||
fun pageTransitions() = flowPrefs.getBoolean(Keys.enableTransitions, true)
|
fun pageTransitions() = flowPrefs.getBoolean(Keys.enableTransitions, true)
|
||||||
|
|
||||||
@ -156,9 +143,11 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun lastVersionCode() = flowPrefs.getInt("last_version_code", 0)
|
fun lastVersionCode() = flowPrefs.getInt("last_version_code", 0)
|
||||||
|
|
||||||
fun catalogueAsList() = rxPrefs.getBoolean(Keys.catalogueAsList, false)
|
fun catalogueViewSetting() = flowPrefs.getInt(Keys.catalogueViewSetting, 0)
|
||||||
|
|
||||||
fun enabledLanguages() = flowPrefs.getStringSet(Keys.enabledLanguages, setOf("en", Locale.getDefault().language))
|
fun enabledLanguages() = flowPrefs.getStringSet(Keys.enabledLanguages, setOf("all", "en", Locale.getDefault().language))
|
||||||
|
|
||||||
|
fun sourceSorting() = flowPrefs.getInt(Keys.sourcesSort, 0)
|
||||||
|
|
||||||
fun trackUsername(sync: TrackService) = prefs.getString(Keys.trackUsername(sync.id), "")
|
fun trackUsername(sync: TrackService) = prefs.getString(Keys.trackUsername(sync.id), "")
|
||||||
|
|
||||||
@ -177,7 +166,10 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun backupsDirectory() = flowPrefs.getString(Keys.backupDirectory, defaultBackupDir.toString())
|
fun backupsDirectory() = flowPrefs.getString(Keys.backupDirectory, defaultBackupDir.toString())
|
||||||
|
|
||||||
fun dateFormat() = rxPrefs.getObject(Keys.dateFormat, DateFormat.getDateInstance(DateFormat.SHORT), DateFormatConverter())
|
fun dateFormat(format: String = flowPrefs.getString(Keys.dateFormat, "").get()): DateFormat = when (format) {
|
||||||
|
"" -> DateFormat.getDateInstance(DateFormat.SHORT)
|
||||||
|
else -> SimpleDateFormat(format, Locale.getDefault())
|
||||||
|
}
|
||||||
|
|
||||||
fun downloadsDirectory() = flowPrefs.getString(Keys.downloadsDirectory, defaultDownloadsDir.toString())
|
fun downloadsDirectory() = flowPrefs.getString(Keys.downloadsDirectory, defaultDownloadsDir.toString())
|
||||||
|
|
||||||
@ -199,17 +191,24 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun libraryUpdatePrioritization() = flowPrefs.getInt(Keys.libraryUpdatePrioritization, 0)
|
fun libraryUpdatePrioritization() = flowPrefs.getInt(Keys.libraryUpdatePrioritization, 0)
|
||||||
|
|
||||||
fun libraryAsList() = flowPrefs.getBoolean(Keys.libraryAsList, false)
|
fun libraryViewSetting() = flowPrefs.getInt(Keys.libraryViewSetting, 0)
|
||||||
|
|
||||||
fun downloadBadge() = flowPrefs.getBoolean(Keys.downloadBadge, false)
|
fun downloadBadge() = flowPrefs.getBoolean(Keys.downloadBadge, false)
|
||||||
|
|
||||||
fun downloadedOnly() = flowPrefs.getBoolean(Keys.downloadedOnly, false)
|
fun downloadedOnly() = flowPrefs.getBoolean(Keys.downloadedOnly, false)
|
||||||
|
|
||||||
fun filterDownloaded() = flowPrefs.getBoolean(Keys.filterDownloaded, false)
|
fun unreadBadge() = flowPrefs.getBoolean(Keys.unreadBadge, true)
|
||||||
|
|
||||||
fun filterUnread() = flowPrefs.getBoolean(Keys.filterUnread, false)
|
// J2K converted from boolean to integer
|
||||||
|
fun filterDownloaded() = flowPrefs.getInt(Keys.filterDownloaded, 0)
|
||||||
|
|
||||||
fun filterCompleted() = flowPrefs.getBoolean(Keys.filterCompleted, false)
|
fun filterUnread() = flowPrefs.getInt(Keys.filterUnread, 0)
|
||||||
|
|
||||||
|
fun filterCompleted() = flowPrefs.getInt(Keys.filterCompleted, 0)
|
||||||
|
|
||||||
|
fun filterTracked() = flowPrefs.getInt(Keys.filterTracked, 0)
|
||||||
|
|
||||||
|
fun filterLewd() = flowPrefs.getInt(Keys.filterLewd, 0)
|
||||||
|
|
||||||
fun librarySortingMode() = flowPrefs.getInt(Keys.librarySortingMode, 0)
|
fun librarySortingMode() = flowPrefs.getInt(Keys.librarySortingMode, 0)
|
||||||
|
|
||||||
@ -223,7 +222,7 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun searchPinnedSourcesOnly() = prefs.getBoolean(Keys.searchPinnedSourcesOnly, false)
|
fun searchPinnedSourcesOnly() = prefs.getBoolean(Keys.searchPinnedSourcesOnly, false)
|
||||||
|
|
||||||
fun hiddenCatalogues() = flowPrefs.getStringSet("hidden_catalogues", emptySet())
|
fun hiddenCatalogues() = flowPrefs.getStringSet("hidden_catalogues", mutableSetOf())
|
||||||
|
|
||||||
fun pinnedCatalogues() = flowPrefs.getStringSet("pinned_catalogues", emptySet())
|
fun pinnedCatalogues() = flowPrefs.getStringSet("pinned_catalogues", emptySet())
|
||||||
|
|
||||||
@ -242,4 +241,115 @@ class PreferencesHelper(val context: Context) {
|
|||||||
fun migrateFlags() = flowPrefs.getInt("migrate_flags", Int.MAX_VALUE)
|
fun migrateFlags() = flowPrefs.getInt("migrate_flags", Int.MAX_VALUE)
|
||||||
|
|
||||||
fun trustedSignatures() = flowPrefs.getStringSet("trusted_signatures", emptySet())
|
fun trustedSignatures() = flowPrefs.getStringSet("trusted_signatures", emptySet())
|
||||||
|
|
||||||
|
// --> AZ J2K CHERRYPICKING
|
||||||
|
|
||||||
|
fun defaultMangaOrder() = flowPrefs.getString("default_manga_order", "")
|
||||||
|
|
||||||
|
fun migrationSources() = flowPrefs.getString("migrate_sources", "")
|
||||||
|
|
||||||
|
fun smartMigration() = flowPrefs.getBoolean("smart_migrate", false)
|
||||||
|
|
||||||
|
fun useSourceWithMost() = flowPrefs.getBoolean("use_source_with_most", false)
|
||||||
|
|
||||||
|
fun skipPreMigration() = flowPrefs.getBoolean(Keys.skipPreMigration, false)
|
||||||
|
|
||||||
|
fun upgradeFilters() {
|
||||||
|
val filterDl = flowPrefs.getBoolean(Keys.filterDownloaded, false).get()
|
||||||
|
val filterUn = flowPrefs.getBoolean(Keys.filterUnread, false).get()
|
||||||
|
val filterCm = flowPrefs.getBoolean(Keys.filterCompleted, false).get()
|
||||||
|
filterDownloaded().set(if (filterDl) 1 else 0)
|
||||||
|
filterUnread().set(if (filterUn) 1 else 0)
|
||||||
|
filterCompleted().set(if (filterCm) 1 else 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// <--
|
||||||
|
|
||||||
|
// --> EH
|
||||||
|
fun eh_isHentaiEnabled() = flowPrefs.getBoolean(Keys.eh_is_hentai_enabled, true)
|
||||||
|
|
||||||
|
fun enableExhentai() = flowPrefs.getBoolean(Keys.eh_enableExHentai, false)
|
||||||
|
|
||||||
|
fun secureEXH() = flowPrefs.getBoolean("secure_exh", true)
|
||||||
|
|
||||||
|
fun imageQuality() = flowPrefs.getString("ehentai_quality", "auto")
|
||||||
|
|
||||||
|
fun useHentaiAtHome() = flowPrefs.getBoolean("enable_hah", true)
|
||||||
|
|
||||||
|
fun useJapaneseTitle() = flowPrefs.getBoolean("use_jp_title", false)
|
||||||
|
|
||||||
|
fun eh_useOriginalImages() = flowPrefs.getBoolean(Keys.eh_useOrigImages, false)
|
||||||
|
|
||||||
|
fun ehTagFilterValue() = flowPrefs.getInt(Keys.eh_tag_filtering_value, 0)
|
||||||
|
|
||||||
|
fun ehTagWatchingValue() = flowPrefs.getInt(Keys.eh_tag_watching_value, 0)
|
||||||
|
|
||||||
|
fun ehSearchSize() = flowPrefs.getString("ex_search_size", "rc_0")
|
||||||
|
|
||||||
|
fun thumbnailRows() = flowPrefs.getString("ex_thumb_rows", "tr_2")
|
||||||
|
|
||||||
|
fun hasPerformedURLMigration() = flowPrefs.getBoolean("performed_url_migration", false)
|
||||||
|
|
||||||
|
// EH Cookies
|
||||||
|
fun memberIdVal() = flowPrefs.getString("eh_ipb_member_id", "")
|
||||||
|
|
||||||
|
fun passHashVal() = flowPrefs.getString("eh_ipb_pass_hash", "")
|
||||||
|
fun igneousVal() = flowPrefs.getString("eh_igneous", "")
|
||||||
|
fun eh_ehSettingsProfile() = flowPrefs.getInt(Keys.eh_ehSettingsProfile, -1)
|
||||||
|
fun eh_exhSettingsProfile() = flowPrefs.getInt(Keys.eh_exhSettingsProfile, -1)
|
||||||
|
fun eh_settingsKey() = flowPrefs.getString(Keys.eh_settingsKey, "")
|
||||||
|
fun eh_sessionCookie() = flowPrefs.getString(Keys.eh_sessionCookie, "")
|
||||||
|
fun eh_hathPerksCookies() = flowPrefs.getString(Keys.eh_hathPerksCookie, "")
|
||||||
|
|
||||||
|
fun eh_nh_useHighQualityThumbs() = flowPrefs.getBoolean(Keys.eh_nh_useHighQualityThumbs, false)
|
||||||
|
|
||||||
|
fun eh_showSyncIntro() = flowPrefs.getBoolean(Keys.eh_showSyncIntro, true)
|
||||||
|
|
||||||
|
fun eh_readOnlySync() = flowPrefs.getBoolean(Keys.eh_readOnlySync, false)
|
||||||
|
|
||||||
|
fun eh_lenientSync() = flowPrefs.getBoolean(Keys.eh_lenientSync, false)
|
||||||
|
|
||||||
|
fun eh_ts_aspNetCookie() = flowPrefs.getString(Keys.eh_ts_aspNetCookie, "")
|
||||||
|
|
||||||
|
fun eh_showSettingsUploadWarning() = flowPrefs.getBoolean(Keys.eh_showSettingsUploadWarning, true)
|
||||||
|
|
||||||
|
fun eh_expandFilters() = flowPrefs.getBoolean(Keys.eh_expandFilters, false)
|
||||||
|
|
||||||
|
fun eh_readerThreads() = flowPrefs.getInt(Keys.eh_readerThreads, 2)
|
||||||
|
|
||||||
|
fun eh_readerInstantRetry() = flowPrefs.getBoolean(Keys.eh_readerInstantRetry, true)
|
||||||
|
|
||||||
|
fun eh_utilAutoscrollInterval() = flowPrefs.getFloat(Keys.eh_utilAutoscrollInterval, 3f)
|
||||||
|
|
||||||
|
fun eh_cacheSize() = flowPrefs.getString(Keys.eh_cacheSize, "75")
|
||||||
|
|
||||||
|
fun eh_preserveReadingPosition() = flowPrefs.getBoolean(Keys.eh_preserveReadingPosition, false)
|
||||||
|
|
||||||
|
fun eh_autoSolveCaptchas() = flowPrefs.getBoolean(Keys.eh_autoSolveCaptchas, false)
|
||||||
|
|
||||||
|
fun eh_delegateSources() = flowPrefs.getBoolean(Keys.eh_delegateSources, true)
|
||||||
|
|
||||||
|
fun eh_lastVersionCode() = flowPrefs.getInt("eh_last_version_code", 0)
|
||||||
|
|
||||||
|
fun eh_savedSearches() = flowPrefs.getStringSet("eh_saved_searches", emptySet())
|
||||||
|
|
||||||
|
fun eh_logLevel() = flowPrefs.getInt(Keys.eh_logLevel, 0)
|
||||||
|
|
||||||
|
fun eh_enableSourceBlacklist() = flowPrefs.getBoolean(Keys.eh_enableSourceBlacklist, true)
|
||||||
|
|
||||||
|
fun eh_autoUpdateFrequency() = flowPrefs.getInt(Keys.eh_autoUpdateFrequency, 1)
|
||||||
|
|
||||||
|
fun eh_autoUpdateRequirements() = prefs.getStringSet(Keys.eh_autoUpdateRestrictions, emptySet())
|
||||||
|
|
||||||
|
fun eh_autoUpdateStats() = flowPrefs.getString(Keys.eh_autoUpdateStats, "")
|
||||||
|
|
||||||
|
fun eh_aggressivePageLoading() = flowPrefs.getBoolean(Keys.eh_aggressivePageLoading, false)
|
||||||
|
|
||||||
|
fun eh_hl_useHighQualityThumbs() = flowPrefs.getBoolean(Keys.eh_hl_useHighQualityThumbs, false)
|
||||||
|
|
||||||
|
fun eh_preload_size() = flowPrefs.getInt(Keys.eh_preload_size, 4)
|
||||||
|
|
||||||
|
fun eh_useNewMangaInterface() = flowPrefs.getBoolean(Keys.eh_use_new_manga_interface, true)
|
||||||
|
|
||||||
|
fun eh_useAutoWebtoon() = flowPrefs.getBoolean(Keys.eh_use_auto_webtoon, true)
|
||||||
}
|
}
|
||||||
|
0
app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt
Normal file → Executable file
0
app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt
Normal file → Executable file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user