Compare commits
4344 Commits
Author | SHA1 | Date | |
---|---|---|---|
c615f4d458 | |||
9e09a20e65 | |||
7115a9b9fe | |||
fd8b97fc87 | |||
4dd67e4348 | |||
10973bf3cd | |||
934ed0551a | |||
38428c6ebe | |||
bf85e147e7 | |||
d2dd34c2e5 | |||
c4ab2b4675 | |||
aa2ec5940f | |||
79323de326 | |||
08e6487a9a | |||
4498b10a10 | |||
6f2bb18d72 | |||
b690de55e5 | |||
83fda20078 | |||
f656a37045 | |||
c58b495433 | |||
242aeb6a68 | |||
d9969cea8a | |||
d61db5931e | |||
0ea3ac9807 | |||
f9e43f574f | |||
5ef11e61d0 | |||
48546c3db4 | |||
4d87ed496c | |||
06d12e6562 | |||
477e3d9b94 | |||
3c16082636 | |||
29aee68ec7 | |||
75e23299b4 | |||
935ff1ee98 | |||
c672cb81ec | |||
7559c133c0 | |||
589bdba0b1 | |||
aca65f13bb | |||
7bf30a094a | |||
5454279a8e | |||
006bcdf934 | |||
b00f00730d | |||
f2c48480b6 | |||
1730dd6af1 | |||
2501fef9e4 | |||
12e41b6e6f | |||
c892c793a8 | |||
3a82b4d924 | |||
b4b3a4d286 | |||
448702e5be | |||
2ef1f07aae | |||
1a319601de | |||
cdf242e8c8 | |||
aee785a8bb | |||
d45fc1e245 | |||
14500ba4f8 | |||
345e9c2a9a | |||
b53e24e0db | |||
d3a73fc228 | |||
c2812fca24 | |||
856847a60a | |||
748e2480d3 | |||
2ebc8d9ae5 | |||
e28b015580 | |||
e4bc8990fb | |||
a179327d9d | |||
823749fc1e | |||
2b5d9fd76b | |||
7a972dfdb7 | |||
c31e75f02f | |||
b56b8b55b4 | |||
2695a4d8c7 | |||
1a4dad72a9 | |||
b7e6b4c28a | |||
dc2d470413 | |||
293b967858 | |||
c637172ee0 | |||
e468554fd9 | |||
5b5eb92184 | |||
58ebf14691 | |||
992bab4f79 | |||
6fe650319d | |||
f301dc64f0 | |||
33a2219716 | |||
62480f090b | |||
e7937fe562 | |||
287489d7d0 | |||
2df0236669 | |||
c54d77333f | |||
8c494f314c | |||
8cea78de83 | |||
b6468c7e31 | |||
1967923a94 | |||
91004ad514 | |||
a2ee4e63ae | |||
4d8289cd36 | |||
289264878e | |||
768bb7b503 | |||
db4ae134aa | |||
7329f03bc5 | |||
82ea643c7d | |||
741c10e0b9 | |||
34bb90f3c2 | |||
f04cf72c0c | |||
157438e0c1 | |||
75b23c99ec | |||
6bb3070c57 | |||
7df10b076c | |||
2245658363 | |||
46774771ec | |||
6263817bb4 | |||
60456fe0e9 | |||
a0f47d3f1b | |||
6efcb8ccfa | |||
0d128b75e2 | |||
0067d474c8 | |||
cf393b217b | |||
e265b929a1 | |||
4cd01428ed | |||
3be05fbf9b | |||
5d90ba8aa0 | |||
48cab708ce | |||
5d9753d6a7 | |||
425e48bec6 | |||
a42be4a833 | |||
30e030bb8e | |||
2a3c3d8d6a | |||
7b026cec8d | |||
d8b528a4e0 | |||
0f45907144 | |||
c4c9931ae2 | |||
68345e636e | |||
0861c5618c | |||
817418f7c9 | |||
4eb2cd85b2 | |||
086eac5975 | |||
addd6bffbd | |||
1e65313fa7 | |||
c4c6e41c46 | |||
920ca405a2 | |||
6d3a3b3f39 | |||
50d46fe7f6 | |||
91e282d7e5 | |||
a0f10f868e | |||
6a423f0650 | |||
5cc84403e1 | |||
ab61a65b4a | |||
01ec26842d | |||
bbf5817805 | |||
50981cb102 | |||
611ec8103c | |||
12c672667c | |||
db3c98fe72 | |||
f401574f5a | |||
3251fb36c8 | |||
94a410f50f | |||
a14c01c1de | |||
ca3b948628 | |||
a8230ad574 | |||
8e1b5b4803 | |||
8552838bda | |||
46417fe427 | |||
dac04f2929 | |||
63da463e02 | |||
817e144ff6 | |||
9d2d78ae5b | |||
c44db54d9f | |||
376bbeb724 | |||
0e2bdb7863 | |||
235bc77457 | |||
593172f891 | |||
e20c66b156 | |||
5f4825465e | |||
bc6a12a4f7 | |||
90db3acefd | |||
2f2f59279d | |||
4992f87cb1 | |||
7608cb0da3 | |||
9dd9e741f3 | |||
171db639ff | |||
3ede42252c | |||
a94ca175e2 | |||
3749cee28f | |||
ca500da4d8 | |||
820ed6a468 | |||
7cbe18d325 | |||
8937e22ce4 | |||
82a3a98a5a | |||
d97eab0328 | |||
a61e2799db | |||
1009e15aa6 | |||
01c6e46a71 | |||
ed5e013874 | |||
f8e4153dbf | |||
f7a92cf6ac | |||
e748d91d4a | |||
2c4ddca38e | |||
6ca32710be | |||
f05e251991 | |||
a3f3f9d562 | |||
410fcb73c5 | |||
b6d6de6b9f | |||
09cebf20f3 | |||
a8c732d67b | |||
843c9c7e57 | |||
c88b79fa17 | |||
3f9820ac79 | |||
c288e6b8fa | |||
8945ef8880 | |||
99a717f849 | |||
4622b18c99 | |||
4f5270cb7d | |||
719d427956 | |||
d7a21771a5 | |||
be854b3e90 | |||
47f079891f | |||
696dc59ea5 | |||
5f6666a438 | |||
f284a656d7 | |||
1c3d566f8d | |||
373463e995 | |||
7be9b49143 | |||
059a79debb | |||
1a70ebe7ea | |||
beda99bbe0 | |||
bb1e7816e1 | |||
b0dc20e00c | |||
3d66eaea83 | |||
5313a5d5d2 | |||
5b189a909b | |||
75a687138d | |||
ba91b483a0 | |||
3a8b5e1b5e | |||
94d1b68598 | |||
8eda4df71f | |||
8ad9337863 | |||
cd13e187cf | |||
bcc21e55bd | |||
5fbecfd7b7 | |||
3480b45098 | |||
5076ab3049 | |||
44366ac058 | |||
4f2a794fba | |||
fe6aa4358f | |||
f99b62a069 | |||
ac1bed38f9 | |||
217b03a292 | |||
28bceffc6f | |||
09266a155c | |||
3d7591feca | |||
e14909fff4 | |||
fe579c4865 | |||
37118088d4 | |||
5c9e9bd2c4 | |||
db35ba53b1 | |||
758d223776 | |||
21a9bf2463 | |||
a54d9912d0 | |||
7e74949d38 | |||
a8c5780963 | |||
f4ac754d02 | |||
0347d3970a | |||
acc2312384 | |||
7d34ff214c | |||
e2179a6669 | |||
5c37347cec | |||
ef3a6c80a7 | |||
2a2c6cee5f | |||
7dff3cc6cb | |||
8c1171a722 | |||
2c850d0e33 | |||
f1b85ff39d | |||
b7fa25777d | |||
2d86f69caa | |||
e22896a956 | |||
be5802e473 | |||
434c90d378 | |||
eb6ba96b57 | |||
5325e590ec | |||
6ad6dae191 | |||
3f34fa1f58 | |||
d12ea86b55 | |||
a8e45beb51 | |||
ba2a528886 | |||
d60367768b | |||
db6528d3fa | |||
f5873d70c6 | |||
10e349f76e | |||
b1ccebf329 | |||
3407eb84c5 | |||
6017229d1b | |||
4f00af3173 | |||
9da232dcd8 | |||
acd43005df | |||
c31cf2a03a | |||
51c964de3a | |||
dad24e785b | |||
a908283e86 | |||
d8725c7b7f | |||
262f8449b4 | |||
bdf035d60a | |||
0270878748 | |||
6ada3c90ff | |||
4e628fe6de | |||
a8eebd824a | |||
afa0a0a0e2 | |||
92b039fac7 | |||
acc65529a0 | |||
3061f198e9 | |||
6fc1f4fc21 | |||
a0f49b16c5 | |||
c6c4c1c393 | |||
811931ccc0 | |||
08d5633d81 | |||
c76d5dd30c | |||
340357d158 | |||
11ed47397d | |||
6ce54eb845 | |||
d0236aaecf | |||
00059848b4 | |||
e45f6d0c92 | |||
18ccde082d | |||
21bc0f1952 | |||
a37be747e9 | |||
bc3bb82651 | |||
ba00d9e5d2 | |||
bf9edda04c | |||
9c9357639a | |||
3733871d2f | |||
54471a014f | |||
8749be518f | |||
6d880c938a | |||
34aa4eb291 | |||
280b0f42db | |||
65387d0089 | |||
d41c103a72 | |||
0b93b9e059 | |||
ea3f933e95 | |||
b006fe3a22 | |||
37ff3b4920 | |||
1e93d785e5 | |||
999bd4efee | |||
3222247969 | |||
dd6c9ce2fe | |||
7446b28ff1 | |||
38c6702b8f | |||
afcf4b2988 | |||
ebb96a6ff4 | |||
8b0affe9bd | |||
1a25cea0d6 | |||
2ecbcdf4bd | |||
642b392d44 | |||
8dce7b3e9e | |||
33e90d6449 | |||
50b17d5d34 | |||
7818885406 | |||
26af7ccc77 | |||
5d1f79012e | |||
cac80daa71 | |||
fc184f1cfa | |||
725fcbba0e | |||
bdeb209d43 | |||
a078f1ab1b | |||
86c3d8c064 | |||
156191af44 | |||
57bba9e5ab | |||
dd1923fe88 | |||
df773ee15c | |||
f5451a6881 | |||
fcec1581b7 | |||
11cc789e36 | |||
16f9fb2f40 | |||
6bfaa85e84 | |||
8f43fb9530 | |||
04d2a3399b | |||
054bf8ec5d | |||
8417f5a63c | |||
26b46cace0 | |||
0849111247 | |||
f9c25b350e | |||
5b12c144da | |||
f38130d086 | |||
4b60138d41 | |||
fde7bfa3d1 | |||
69635ee66a | |||
224f29077d | |||
e1ab1fdb65 | |||
3e86cb094b | |||
9fbd3fe33f | |||
073e9f94ff | |||
64c0d9506d | |||
f638092ab9 | |||
d0c4463ab3 | |||
ad107860b9 | |||
5efb31bd71 | |||
e4a2f35907 | |||
e49781de7a | |||
37cb4ec0c2 | |||
401134fa8e | |||
87391832ba | |||
e36d31bf0f | |||
37b7efbc87 | |||
6e4a30e593 | |||
ebddb96373 | |||
0288abb66e | |||
d869a13ef9 | |||
ccdfc37c97 | |||
37c55abc2a | |||
c50b1a5c66 | |||
187e9f94aa | |||
1704dc062d | |||
0657a52924 | |||
ccc4144f3c | |||
d5b4bb49b1 | |||
5b3f9e082e | |||
ca06516900 | |||
3fb42b6ce9 | |||
2cbe946e7e | |||
3b5b9a1ae5 | |||
a834ff3a44 | |||
82b552ac9a | |||
15f7e53e4f | |||
9792a6cb78 | |||
f30150c0f0 | |||
5c868d7846 | |||
39e41510d0 | |||
78b76a186c | |||
6e04822f5e | |||
4ff5c1148e | |||
bd285920cd | |||
fb04401460 | |||
42bf91779d | |||
2ab744c525 | |||
4a244a598b | |||
d0bff298b7 | |||
152eb5b951 | |||
d558f9e1d6 | |||
b3557e844c | |||
9c8ccb8e0e | |||
4138a17e29 | |||
fbda243c0d | |||
eb742b29f8 | |||
d2e62ffb19 | |||
2921be620a | |||
c61a51438d | |||
7e40680af0 | |||
93925a7286 | |||
b04807e53a | |||
01e13e59e5 | |||
2cf1009f70 | |||
93827aba34 | |||
3318314c4a | |||
44cabf2f0b | |||
a8ca7b690f | |||
824d5e22bc | |||
7a360779b3 | |||
4b5f965cea | |||
d03cbbe0cd | |||
84bcd8d1d2 | |||
6756bfab75 | |||
8d97b980e3 | |||
2d19729869 | |||
f5bde3726a | |||
ea092fa175 | |||
9c4051a5ba | |||
fed914827a | |||
ea33f8dba5 | |||
4f91d80765 | |||
4178f945c9 | |||
558aad1a71 | |||
d6cbff2837 | |||
aea0cadbfb | |||
e4292719d3 | |||
69cdba71eb | |||
5c5468f9af | |||
6635dd2990 | |||
27e5256305 | |||
b6dbf63633 | |||
551e6a8b62 | |||
570fec6ea6 | |||
7da32750b2 | |||
a2b21e5ad6 | |||
dbd93cf5d1 | |||
c2eaf1c86b | |||
890f1a3c7b | |||
3fdcd636d7 | |||
3d7e44726d | |||
147455f99c | |||
b25ca7617d | |||
bc1fbfac9d | |||
7e92921f84 | |||
e1adb89ff8 | |||
4e544005fe | |||
31bc2c4420 | |||
02b3718aa1 | |||
26a42ba9c0 | |||
b1e104319f | |||
a3afb35539 | |||
fba244423f | |||
8500add09f | |||
23bfa1f18f | |||
b4f2da12ea | |||
b84a31ba92 | |||
d0950cb026 | |||
404f53b16b | |||
737d0fb8f3 | |||
b95a30e424 | |||
0d9c1e6e9c | |||
3bfbd58402 | |||
bd9a08c73d | |||
41dc41f285 | |||
50f959e5f4 | |||
4b4be58d0d | |||
4bba7a8bab | |||
60bcebe4d1 | |||
cf6407c4d4 | |||
5f8252447f | |||
dcd5541e96 | |||
7be6863910 | |||
caf9219d99 | |||
3b62396442 | |||
bbe1608006 | |||
b8fa326c21 | |||
1cf1b34e7f | |||
ff4fb83bff | |||
e24501da09 | |||
0ca14c61c2 | |||
6be9cccc7a | |||
a5a70defc8 | |||
db3cbac310 | |||
de23226591 | |||
ea8383978b | |||
b04d1e5f50 | |||
98c459a6b6 | |||
00f442b77e | |||
42b0e3e438 | |||
8d1f99a480 | |||
bef8342aa5 | |||
2131294b22 | |||
5c22cbf28e | |||
488276d498 | |||
6ac17363ed | |||
58c47c4c50 | |||
80b2ebc45b | |||
ef2c9460b5 | |||
ad84a8c3e9 | |||
8b9a06e298 | |||
6b1d597d34 | |||
5a37f2398a | |||
98a4f6cccb | |||
633bd6eb46 | |||
f19c288bec | |||
e2ce3f68bf | |||
56722140c9 | |||
e90b39b29d | |||
f4c684b4b8 | |||
869396b1a4 | |||
7f9222f7b7 | |||
a35f947892 | |||
ec272f6c4e | |||
f0af3858e8 | |||
db91d04e82 | |||
9859b38f32 | |||
0190c36d20 | |||
ba533f30ce | |||
29fa93e829 | |||
0fabe4bd01 | |||
f98b4f4e39 | |||
b8c1257645 | |||
467ceacb17 | |||
2d22baba62 | |||
750f90614d | |||
d28ded4525 | |||
4b4a138eee | |||
b5dca2eb09 | |||
747cbd24cb | |||
d3520419d4 | |||
acb8ab15b2 | |||
5cdcc1679f | |||
b37b3767f3 | |||
2d56ad1ad9 | |||
5d3bc7245e | |||
e82963c9ef | |||
ec34977a64 | |||
2ced56e490 | |||
e568951396 | |||
e275897bf9 | |||
2b089648a3 | |||
c2a831dded | |||
c740558327 | |||
0e3176a77c | |||
f85cbb1582 | |||
20bbda78e6 | |||
0225711f6f | |||
7ec822503a | |||
83871fc013 | |||
b668364afb | |||
877ae041a4 | |||
1395343f11 | |||
30b3b2d3ff | |||
f3cecd3cde | |||
0086743a53 | |||
bc8c45832e | |||
4a3070265a | |||
f54adb49a1 | |||
ec30026333 | |||
4ea512f6c2 | |||
829aadd0bd | |||
9d28def387 | |||
86fe850794 | |||
30ac94181b | |||
48d3d454c0 | |||
6865c21c75 | |||
82cd316493 | |||
7270c48f26 | |||
9e5d79aec3 | |||
c51e83c048 | |||
52fa28c16a | |||
935c8e7d82 | |||
19be0d68b6 | |||
f9bbbce466 | |||
eb5ef72747 | |||
0215b66098 | |||
3dea10bcb9 | |||
cd3cb72b65 | |||
5b474e96b7 | |||
9ce1d71a45 | |||
b8cdf7fbff | |||
28594bba2c | |||
d5c207d8a3 | |||
56826fb477 | |||
171d7f2b8c | |||
5ec5829e77 | |||
448978ac8a | |||
fb9791f597 | |||
07d1b9f3ba | |||
6b91f65457 | |||
0c7b1bda7f | |||
032b377de7 | |||
26d8e47bb9 | |||
970ff7841e | |||
3f62837260 | |||
d55c854ebf | |||
6b2b21edfa | |||
99270e370e | |||
c7d09d098a | |||
21804bfc45 | |||
38950f7bc8 | |||
bbf5c86b46 | |||
3fa68ed217 | |||
cc6aef693e | |||
5a320d87e8 | |||
da95ecb686 | |||
774a87a42a | |||
ff4a217730 | |||
a43754e1a6 | |||
8ef200861c | |||
ddd180e56a | |||
30b86e530b | |||
2f26982e34 | |||
504844a892 | |||
4c1da1bd1d | |||
dc62d0ea8b | |||
fddca15182 | |||
81f49f34ef | |||
c39a1b7867 | |||
d4b764fa31 | |||
bb54a81ef0 | |||
d85af2fec6 | |||
90c08303fa | |||
92e83f702c | |||
084e6a964e | |||
532f662b05 | |||
53f5ea7fe9 | |||
fc6946ed61 | |||
f5c7aa1142 | |||
761635b572 | |||
488d8ab8cf | |||
8efb20439a | |||
43c195e14a | |||
8a3a7418d0 | |||
32190b6cac | |||
880407442c | |||
3b34a878a7 | |||
b79340989f | |||
0e526c36be | |||
a83d29f058 | |||
be7108a2ee | |||
8e9b1124cd | |||
1948d55d5d | |||
9c49a5ed22 | |||
0bb20a92af | |||
cd82c88b9a | |||
8d40e20b7d | |||
31b62b2779 | |||
88b56121a3 | |||
d6c0a5ef8b | |||
5732fc61e8 | |||
655fa25b51 | |||
aab5f083db | |||
03b9950fa1 | |||
2453d1a886 | |||
4b9a6541d1 | |||
a70b848646 | |||
ce44c0615b | |||
f207e87722 | |||
2e81e1b7d8 | |||
605c3de150 | |||
7aa073ddca | |||
4b0f549666 | |||
40749dc767 | |||
3599d53c61 | |||
2156844b87 | |||
763288ab13 | |||
58e6479438 | |||
6d6c38ecaf | |||
3760b310df | |||
47b56644de | |||
301cae13f0 | |||
1fe9b7bda7 | |||
324ae3fcfb | |||
e36e9d9d5c | |||
4228bbb88e | |||
09abfc7843 | |||
1f34f5277c | |||
80b4b7bee6 | |||
1f9f9662bc | |||
97656935a2 | |||
2d690a09b3 | |||
29348677b8 | |||
1f79444a53 | |||
daaa23e8e0 | |||
1d6aa9a277 | |||
7497e02979 | |||
f34dc3be90 | |||
65261356eb | |||
4291cc8eb1 | |||
8811d951d0 | |||
9dbc1aa7a3 | |||
b0520df1dd | |||
a89651810d | |||
431c04e54f | |||
f461c71625 | |||
b635789740 | |||
f00e03e5ea | |||
6db2becd30 | |||
5f378e28b6 | |||
4ebceac07f | |||
aab5a56892 | |||
e58945a209 | |||
03e4eb1061 | |||
09a3509d79 | |||
b3a11eca0f | |||
650c2dc6e7 | |||
d4adb664cc | |||
5194bdb229 | |||
87ec71142b | |||
85f2996ae9 | |||
e296d56e09 | |||
dd676b6d14 | |||
7c7bd72c8e | |||
c7e44aa22f | |||
ac4f98e152 | |||
e0d23cd688 | |||
3966a917ee | |||
be33a57d43 | |||
4a71022a60 | |||
83129385e2 | |||
34ac39e7e5 | |||
1474c8ffb3 | |||
441e7bf8b1 | |||
71fc5d6d35 | |||
ff996d282a | |||
11f640cfee | |||
1cbe225a94 | |||
d6f1534ee8 | |||
24e64f52e2 | |||
b0da0753d9 | |||
e511f24979 | |||
22e83f408b | |||
ec96a81735 | |||
7892cc1519 | |||
f7b11f2ce9 | |||
b4e15263db | |||
96c3116af6 | |||
7845f9430e | |||
16abfeeff0 | |||
3bc6b1e202 | |||
3c2e237d63 | |||
7701672d7a | |||
2993e3f0f2 | |||
688cc64dff | |||
9f0052eceb | |||
19eb4aaac9 | |||
a2bb81b7db | |||
5e68fe4fe9 | |||
914831d51f | |||
5315467908 | |||
807987f0d3 | |||
b3426f37e7 | |||
afceac15c8 | |||
3d4e56948d | |||
737cf9898d | |||
322f3a07e8 | |||
6c7b3d7811 | |||
bfd22f8f2d | |||
2ca62c4eda | |||
a2d53c439e | |||
29e1976b90 | |||
4efb736e56 | |||
58acf0a8aa | |||
9f5f101858 | |||
2a875fe9b8 | |||
bb5a5ea25f | |||
039fe4a618 | |||
0c9c4c0347 | |||
819577a15d | |||
b563e85c3b | |||
99ac30e59f | |||
4774deb1ef | |||
d49ec41f3a | |||
f90e1b935c | |||
db93d1da76 | |||
7d74b174e0 | |||
e513487caa | |||
483b204fb5 | |||
56028aff55 | |||
7336714306 | |||
8bde35298f | |||
3fe5e53b25 | |||
dcafdac036 | |||
f8d8cf9f6a | |||
5bb1133f0f | |||
2b96709799 | |||
1c8da5fa97 | |||
73901f50c0 | |||
76057b84b2 | |||
164de67a56 | |||
aeffb5eeb8 | |||
6f94777530 | |||
2e15be59af | |||
bc1f6ba517 | |||
59f8c1a288 | |||
cd9487f94c | |||
978489fade | |||
07c9af4901 | |||
d6977e5676 | |||
a843054388 | |||
098a7d1deb | |||
9ef0af0069 | |||
c751851941 | |||
9f2ddaadde | |||
fc328e141c | |||
0e19c245e9 | |||
27bac4fffb | |||
4bf4b167a5 | |||
2b8d1bcc02 | |||
e8b7743826 | |||
8ea05e852e | |||
3547d0142f | |||
4d9d587366 | |||
e2510c144a | |||
00519e3b93 | |||
473dc688f0 | |||
b635f02d93 | |||
d8fb6b893f | |||
bdc5d557d1 | |||
cbfe9c30bb | |||
459b369feb | |||
3192d47837 | |||
f6f5b6aeab | |||
1b2c12385f | |||
80c7a45328 | |||
2096df301d | |||
0b78028cf6 | |||
46ac9fe970 | |||
b034f503f8 | |||
9ebeff04e6 | |||
fa73e2403b | |||
35ec593658 | |||
905c96922b | |||
018ca71336 | |||
383f7089c4 | |||
a21aa8125e | |||
83e193f1ab | |||
bdbe1c4d0f | |||
e5eadb0261 | |||
4ee1d72b6f | |||
902bb35ba7 | |||
4684797dfb | |||
386b8945c8 | |||
86a018ebad | |||
ba93060e59 | |||
788583e66f | |||
cbcab5a545 | |||
634ee86bbd | |||
64f60c36e6 | |||
0b4f3f5532 | |||
d977b89af1 | |||
487ce37d91 | |||
1551891c15 | |||
b15073fd61 | |||
e56f6c1017 | |||
34906a7425 | |||
86bacbe586 | |||
14a08f0668 | |||
9385b86ecb | |||
da7a64b40d | |||
ab1a44e108 | |||
26ddc6e3aa | |||
1dc4a52f61 | |||
473a4fec70 | |||
1919c2d925 | |||
71e31e6c03 | |||
c01df7f0a1 | |||
6024f6175b | |||
33500e5b69 | |||
17899a6d6d | |||
4c3eb68d3a | |||
29ced9642d | |||
af82591d85 | |||
5bc4a446ec | |||
83e93b254e | |||
49c7dd0cac | |||
96d2fb62e4 | |||
c76a136d3f | |||
940409a4c3 | |||
071dd88ef8 | |||
a58a4634e2 | |||
5979e72662 | |||
010436e797 | |||
980709cccb | |||
fe80356756 | |||
cecf532ffd | |||
6cb255e60a | |||
b46fb7d1e1 | |||
8874193927 | |||
a4515ad251 | |||
55b0b57699 | |||
aab7795b4c | |||
196a8e6829 | |||
972cd98d7b | |||
a16b5d241b | |||
bfa918140f | |||
0721de5b81 | |||
a409fde519 | |||
8e34a30dce | |||
ba43462041 | |||
c8ae936ce9 | |||
853f949140 | |||
615b01a006 | |||
0eb5a3176b | |||
867a5a3ea0 | |||
5159eabc5d | |||
9357af2bcf | |||
038532897b | |||
325a5e37aa | |||
7d3fe0ed43 | |||
eef95cef33 | |||
591df8abcc | |||
46734c525f | |||
a3378e6080 | |||
3791d82540 | |||
a3ab8746bf | |||
069bd90c0f | |||
68697e59d7 | |||
b3dd8b7355 | |||
eb2a904b61 | |||
17951cfd68 | |||
6d6237e370 | |||
851a5ab7e4 | |||
d3ce46a367 | |||
74c5b29484 | |||
20453dc08f | |||
9e3b454b1b | |||
29633b64aa | |||
76c0ead1db | |||
2674570792 | |||
21771e62aa | |||
5d77ee37d2 | |||
2dfbfd0958 | |||
05085fe57f | |||
ff32ab09fb | |||
deaded5af2 | |||
f3c50ee9a3 | |||
3072296919 | |||
1f10b79ee8 | |||
b9e108eb4d | |||
f26cfa58e4 | |||
e1525a5125 | |||
388dc2f103 | |||
7e4c45858f | |||
d476431707 | |||
284445c364 | |||
08d1ecfba7 | |||
0969226fd3 | |||
0c856438fa | |||
e44bb30996 | |||
7440086ef1 | |||
ef3acb8c43 | |||
ee38671400 | |||
5b8cd68cf3 | |||
53decfd47b | |||
65264e3ef5 | |||
4ca0fc7a4d | |||
7b294478e4 | |||
04f0ca7846 | |||
61a44101a2 | |||
924dfa19cf | |||
9ff6ae81bd | |||
c33e5c8a17 | |||
6129bbc9ab | |||
b34b10c6b8 | |||
ad106bd884 | |||
37fe25ac06 | |||
0e0c1dcdc5 | |||
c9770eea2f | |||
80d2d9d258 | |||
3ca1ce4636 | |||
8ec91cddab | |||
470a576441 | |||
33a778873a | |||
cf7ca5bd28 | |||
a77bce7b37 | |||
8e985eb0db | |||
915e38f636 | |||
e3b1053c03 | |||
c2520bff12 | |||
cd5bcc3673 | |||
254f021903 | |||
8fedd2d5f1 | |||
cb1830d747 | |||
68c47a3238 | |||
e644772731 | |||
11f1482818 | |||
a7decdb62d | |||
005b9b595c | |||
e6a9d0b090 | |||
82879a129e | |||
9f66c85281 | |||
0f5731360b | |||
3fd9e021fa | |||
4c3af7bf36 | |||
28e7009b49 | |||
dd983c803b | |||
1b804e61cb | |||
02eb3cb6b5 | |||
c5d84b4f24 | |||
ae88252cb1 | |||
3c3d787a2b | |||
6aee4fc464 | |||
4ef337f1e9 | |||
017f6b22f0 | |||
602168bc48 | |||
fdf384b809 | |||
284880d096 | |||
a446b37c1f | |||
ad75d137b0 | |||
d2f4c43526 | |||
6bc484617e | |||
9d5b7de1d8 | |||
a01c370d63 | |||
fd5da2de3a | |||
3c9f96d621 | |||
693cc103ea | |||
5ccde61ae1 | |||
b96686e6ad | |||
e7695aef78 | |||
5bb78eb77f | |||
5fbf454652 | |||
d098eca69d | |||
120943a8b3 | |||
1e64542f14 | |||
e15a867106 | |||
349e6ca98f | |||
da8669c826 | |||
59837bbb90 | |||
20c14a0a00 | |||
06fdfcdb23 | |||
cf48bbc176 | |||
40f5d26945 | |||
18ea6c4f65 | |||
7a661747c5 | |||
177a642afc | |||
161c8bcf9e | |||
e3f8aedd5a | |||
7fdbf40cd2 | |||
5ea03fad87 | |||
dd5da56695 | |||
0e1e57c1c3 | |||
6ddd6ed0e3 | |||
b80a992fdb | |||
20de02dffb | |||
a3a85ea49f | |||
4560033e66 | |||
11c61d42dc | |||
d1be221d7a | |||
cd0294b1b6 | |||
0dbe82c781 | |||
ad9ef81a77 | |||
b36ca92dd9 | |||
c8468c29f1 | |||
3c40010aff | |||
63238b388d | |||
809da49301 | |||
6b14f38cfa | |||
e1e1c20dbe | |||
b0360b83d4 | |||
f7881651c5 | |||
d71224b40b | |||
241c4ad857 | |||
87661eb85a | |||
ad17eb1386 | |||
0c631a4990 | |||
9b0d85bf6c | |||
3b2362c784 | |||
aa2370b381 | |||
3e07100dc2 | |||
1949fb1abe | |||
28be423e65 | |||
9a75232ca4 | |||
64da16f58f | |||
fd9510e18f | |||
4be9b03ac6 | |||
2761d27aaa | |||
6d154b1e4f | |||
bbb69482e1 | |||
f4e344f686 | |||
395a840fc4 | |||
6580f5771f | |||
b21bcc2d45 | |||
4481c54376 | |||
7b242bf118 | |||
f2a478288a | |||
01e04e31bf | |||
cbc114608b | |||
63987f952e | |||
9f42306f79 | |||
d61bfd7caf | |||
13943f77f7 | |||
1c94ecdcdf | |||
fb83a07f84 | |||
3e2d7d76b9 | |||
7a0915964a | |||
aef97c5563 | |||
4c9331c4e9 | |||
6fb5552d57 | |||
bdb55ef881 | |||
628a3bc16c | |||
c77396dbdb | |||
5002692bda | |||
71bb8ed975 | |||
6d011ebe32 | |||
f1ab34e27c | |||
6d655ff757 | |||
63627c81eb | |||
5dc688dc2e | |||
08fb2fe467 | |||
f1afeac0bc | |||
f75d632740 | |||
b26daf8824 | |||
393fc14630 | |||
c7707dc50e | |||
37199a10bf | |||
b950370f12 | |||
598e4516b3 | |||
cd8392bae2 | |||
ae7df4fb7f | |||
8bee5accb7 | |||
cf024b0e61 | |||
d3f9232a3f | |||
9f655e0d41 | |||
e7ed130f2a | |||
e421eb61bc | |||
bc053580ad | |||
11c01235ac | |||
c49d862fc5 | |||
6993e88265 | |||
681e9396b3 | |||
85ef40d0ff | |||
39c0b74250 | |||
a9e629aea6 | |||
aa11902aa1 | |||
c4088bad12 | |||
49d3ddb830 | |||
6d802063b4 | |||
55a1cdb1c7 | |||
ed8a54bd2a | |||
5bd5b21543 | |||
aec980662f | |||
aef1dc6eaf | |||
bd45bf7407 | |||
e3f6cfa2df | |||
609f552c8d | |||
5763201307 | |||
dee7830793 | |||
23f8f35354 | |||
cccd09fb5c | |||
bf6d59cd21 | |||
6ef6eab994 | |||
ccff333123 | |||
891406cc7f | |||
a5d767042c | |||
9fdc803c14 | |||
8798c295e6 | |||
7abb407897 | |||
78207d48ba | |||
70698e6494 | |||
adf02e53fd | |||
259c370eb9 | |||
7261fcccda | |||
a4a4503311 | |||
2752540330 | |||
0b77b78f6a | |||
06bec0ad54 | |||
f1126c55ca | |||
2caf220b18 | |||
4d23f35b9d | |||
f6fdb12db2 | |||
7773deabc0 | |||
91ed3a4a5f | |||
20145f7a12 | |||
883945e3e8 | |||
3feea71146 | |||
5e32b8e49f | |||
08e63e5fab | |||
0ec9496d26 | |||
29a0989f28 | |||
558b18899c | |||
6e95fde4ec | |||
e691e17efc | |||
80ea14bf7f | |||
c25cffafc6 | |||
8933b41937 | |||
7e2f1d729f | |||
d6c87ec10e | |||
070abd79ce | |||
2d01933c28 | |||
bf0bb5aa88 | |||
1b4d9fc4e9 | |||
2b79295240 | |||
42eaaa497f | |||
96c894ce5b | |||
c0214103a9 | |||
2b76a97989 | |||
9d77052d9c | |||
b4981058a2 | |||
032aa64195 | |||
7c8e8317a8 | |||
eb1cfc4cd4 | |||
f1e5cccee7 | |||
bc2ed763bd | |||
a35995b898 | |||
b1f46ed830 | |||
6c1565a7d4 | |||
2ca6b655ad | |||
a83a481ac8 | |||
65a8b63b3b | |||
b20ca36db9 | |||
189f92d7e8 | |||
cdd4ec6233 | |||
ef1bb4e800 | |||
c475acd1ea | |||
7d50d7ff52 | |||
28522f4f90 | |||
ec3a227a02 | |||
89decf3474 | |||
0b2794e843 | |||
554dfb5874 | |||
9c30fa1da3 | |||
e81bd61e24 | |||
7a0b54bb38 | |||
f060daf8c4 | |||
c1976ef599 | |||
f16fb4e1e4 | |||
5da2c82f47 | |||
d443245d66 | |||
9be3eea5fd | |||
07a9fd061d | |||
2a070c0b1e | |||
7b5106d206 | |||
821d9cdb02 | |||
28575936d3 | |||
83a04da4a0 | |||
0894b1394f | |||
eb33d3c991 | |||
d7f01abf3a | |||
80635343ae | |||
2b38b4e022 | |||
4ecde9fc39 | |||
445ee274c5 | |||
f2bdc514e8 | |||
5afff31f72 | |||
2dfafa387b | |||
7318f4f5dd | |||
175b77fe6f | |||
346652e508 | |||
f0eb42e72d | |||
37100f0937 | |||
ac980a4dbf | |||
a8b53499af | |||
a8aeae329e | |||
52911539b8 | |||
3026ff241b | |||
2466a079d5 | |||
ed9fdf49e2 | |||
668d962233 | |||
996f770935 | |||
041a6dd919 | |||
dbad60d03b | |||
27a60423dc | |||
5a37d38a84 | |||
6f566e67d5 | |||
dd490f2ac9 | |||
5409af0a6c | |||
0ed0d903cc | |||
85be4c492d | |||
c06ad8b87e | |||
b89acb5853 | |||
7890511a53 | |||
3aa4e6eb93 | |||
f8eb9f94f4 | |||
c581b9eeb9 | |||
ffd9c6995a | |||
ef600c0956 | |||
5c0a43e8d6 | |||
8e332dba30 | |||
cd07027192 | |||
da2b30268a | |||
1163aa4e4e | |||
ddb856edc7 | |||
9c426bc216 | |||
382852d0bd | |||
87ae86e1be | |||
9547311d7d | |||
1613d561c1 | |||
538478cac8 | |||
267ecce958 | |||
fae43fedfa | |||
c447022092 | |||
56042ad0b6 | |||
45da036789 | |||
b47b702a52 | |||
869424cd16 | |||
b9fd01315b | |||
a72098b862 | |||
86016de6cb | |||
592b9fedb9 | |||
d06984e3a3 | |||
6b55ee250d | |||
10eef282fa | |||
f312936629 | |||
d53bb4c337 | |||
1a605e27bc | |||
08ee858f64 | |||
af70fe3e7e | |||
29c5c0af50 | |||
9420b750d2 | |||
6f5328f663 | |||
90214d02d7 | |||
2f07f226b8 | |||
a8ad19a89d | |||
57c07250fd | |||
4a3e4a7c5c | |||
c284a23afb | |||
fad1449de3 | |||
f18d161eaf | |||
88054b453a | |||
c560373596 | |||
d698d03521 | |||
d8c8d7c588 | |||
9120e82517 | |||
e214746536 | |||
142396400c | |||
51d48bdde6 | |||
44b055c019 | |||
790d7b9170 | |||
d8719ceee9 | |||
71ddb16574 | |||
2932ed670f | |||
ae2a6a3d4f | |||
30061ada58 | |||
a131e28b60 | |||
8c1662cfdb | |||
299e52e877 | |||
95b253db09 | |||
067cb2452e | |||
45e4092335 | |||
7659a997cf | |||
aa5e428222 | |||
319e4360c8 | |||
f5c6e80dbb | |||
7108993936 | |||
b6553bdc34 | |||
19fe689969 | |||
d6386cef41 | |||
b88f8ae9d2 | |||
408c7b2ca6 | |||
271253fd0b | |||
5348154c42 | |||
e1b1f4f3fc | |||
75a2110626 | |||
9857d3d6ea | |||
836a2649d3 | |||
59cba2533c | |||
a6ac2fbc9a | |||
3da8677e32 | |||
4d0d7d5ad6 | |||
8c4ece4b2d | |||
bf3bb8a378 | |||
cf5e60f8eb | |||
7de707c60a | |||
5cd11ad8c3 | |||
6bba52a2b6 | |||
54b476df4e | |||
a68f123594 | |||
08ad4f96b9 | |||
77a3acf5cc | |||
dea585e69b | |||
879dacfba6 | |||
b459234ddc | |||
76d2c676fd | |||
d5015d37e1 | |||
1b71e4cee7 | |||
18ef5c6ff9 | |||
35e0561950 | |||
adab8e3ed8 | |||
89dbb4d300 | |||
e3f3686b8a | |||
9984e983b4 | |||
4ebe67ef53 | |||
1a11d4153e | |||
cd7cf3583e | |||
66a180bc36 | |||
eb06667455 | |||
0ff8966a27 | |||
2cc6794db5 | |||
0cb4094dd9 | |||
edd213343b | |||
46ec655db5 | |||
769efd9d06 | |||
49cb3b6aa7 | |||
8ad98b67d2 | |||
8a8f1d3205 | |||
4a27f0546c | |||
727a7e4b2d | |||
2b5e8241ab | |||
3dc4fd8dd1 | |||
375a27a93d | |||
544387d1a0 | |||
cb8120d38f | |||
78a261f5d3 | |||
b8f7653fb2 | |||
e0d2a01bc8 | |||
560be9f553 | |||
47723042c5 | |||
d04d676d2f | |||
3435636ca0 | |||
2e1572d7cc | |||
938339690e | |||
dbb2c523c1 | |||
0b9d436753 | |||
2d03f3ce1e | |||
c4a476d0d2 | |||
5122aed332 | |||
5336c5b46e | |||
22615f5981 | |||
bdf4b4b679 | |||
548e300c4b | |||
8a5d8c96ef | |||
78c2631b6f | |||
7c246ffc71 | |||
8bb85753cc | |||
abfdde28ef | |||
9801f1edfa | |||
fc3a200a63 | |||
6a00658119 | |||
353485054e | |||
800583b5e2 | |||
2db2b7348d | |||
f3718257f5 | |||
5500762acd | |||
4c8f5e1f7a | |||
733cf99bb4 | |||
58c2f22120 | |||
42accebeca | |||
1c5c370c12 | |||
448645d83a | |||
09b6a3b41e | |||
74206d60ce | |||
c3a0de7fab | |||
7edf7a434f | |||
b701821550 | |||
d022bf2673 | |||
7eed8c440c | |||
1ab12e380a | |||
728e14e8e4 | |||
8aa402526a | |||
4793ee4786 | |||
a09d6c0470 | |||
9e83130bd8 | |||
2ed01af723 | |||
afc80d6a7c | |||
532a1b1aba | |||
65062b4bcb | |||
c16206d816 | |||
185283f864 | |||
7d1f5c7383 | |||
945afc71ef | |||
818fe50f77 | |||
6fddad7a77 | |||
38d131be37 | |||
aeff846e1f | |||
6b52fc1e2d | |||
0671b530ba | |||
207f9c26ae | |||
6367ce5e5e | |||
ba1a2e9942 | |||
7f998ecdbd | |||
ecd5414287 | |||
6107f5f3d2 | |||
13afa9f476 | |||
cd87c7e88e | |||
ed4dea8686 | |||
808177f8c9 | |||
aed51251b3 | |||
1c2730163d | |||
0de86dfe6f | |||
7a1b99be46 | |||
9b64b0139c | |||
0a6160d7cf | |||
e51a6d332e | |||
a9d2741e6a | |||
12bd7268d2 | |||
be0a23d9ad | |||
458a0e608a | |||
32f3a50def | |||
7de4226d80 | |||
6a39c8fc13 | |||
dc39669321 | |||
be4f27028c | |||
60e73e2d1f | |||
e8f284d377 | |||
3ea3b0bf2e | |||
e1a43d2e7d | |||
2e918fe1d6 | |||
601309c7cc | |||
10ddeeb799 | |||
3463d6c752 | |||
8acce011b5 | |||
fe9ea50356 | |||
e6f29ae57f | |||
6cfd2c510b | |||
430ff80198 | |||
230fa76d57 | |||
46a4b0e0b6 | |||
5b3cadb7a8 | |||
3153071a8a | |||
bba7372556 | |||
9fe1a7e2ae | |||
98822a39d9 | |||
a2c830b908 | |||
bdef2cfdfb | |||
f229a5e2ec | |||
845e061382 | |||
e7d4eb1ae3 | |||
b4ba56bfb4 | |||
25784d1fe5 | |||
619eca7a51 | |||
f3d85655a0 | |||
9600675677 | |||
ce8a759192 | |||
88bc0bf613 | |||
b508e4208a | |||
c74d8cf499 | |||
a34c2b082f | |||
ad49a02879 | |||
e985ffc690 | |||
6cbb02f02d | |||
c0d0ff66b6 | |||
1e4d7f8c6e | |||
a8a761aa5f | |||
41952f0215 | |||
bfcc883f01 | |||
39722055f5 | |||
f85dfa90b8 | |||
0a4163d236 | |||
78de11a9e3 | |||
d2fc6d9f44 | |||
abf31f4a79 | |||
f28dd4f4de | |||
55b64899f5 | |||
d4aeeadb26 | |||
7ce0110158 | |||
7c1e55eb7f | |||
27542bc81d | |||
9ebbfb2d90 | |||
701b1ee744 | |||
0edc981cd2 | |||
da5942b398 | |||
709de81814 | |||
90b312a56e | |||
459759bfe5 | |||
00817aacfe | |||
e306eb0874 | |||
33a02b47d5 | |||
f0a5557e60 | |||
58a871c8cc | |||
4f56071786 | |||
f8b2c79aef | |||
8f00d34b0b | |||
6129519e5a | |||
593091a5e3 | |||
22ed163c8f | |||
93e2b88d41 | |||
7cd54dc8f0 | |||
ccd7c8df53 | |||
5b3bd3f470 | |||
bf1b7f44b6 | |||
538dd60580 | |||
f453236840 | |||
bfe7aa1ed2 | |||
9e2ef82902 | |||
9352e249ee | |||
3800065230 | |||
ebc2c4f73a | |||
f057440cc1 | |||
506f9cfca8 | |||
8a70c3353f | |||
3d8f123e05 | |||
a8c8f15e07 | |||
21e647017b | |||
2a1bb3dc27 | |||
55a3094a65 | |||
b4490e209b | |||
9aa676333c | |||
71b23e57ff | |||
2c76bc99fc | |||
bb06895145 | |||
684965f3e5 | |||
e621f4e2fa | |||
028ea57232 | |||
718fa25c10 | |||
90c9f28818 | |||
cb9c5a35cb | |||
fadaefeaef | |||
b17b882a3b | |||
f0f3afd5f1 | |||
42026b49bf | |||
151193c4c3 | |||
3448751e0e | |||
aae011ed83 | |||
c95a269460 | |||
98c0e5271f | |||
f343131802 | |||
ea34ba53b9 | |||
b8d8cf19d9 | |||
c9be4093e7 | |||
082eef708f | |||
9106fc5b94 | |||
918502742d | |||
f32f1eeaa5 | |||
2d1404d155 | |||
a56997e98c | |||
ef918078d1 | |||
7e61900cf5 | |||
e98f90b099 | |||
2e127dff1f | |||
828db19e02 | |||
99aa3f5713 | |||
1a568e2961 | |||
e863e8c64b | |||
f5b591430c | |||
8cfaf8eb51 | |||
675c0cefc3 | |||
1a52385b78 | |||
372e500590 | |||
cc1a317439 | |||
6d650518a1 | |||
7940117577 | |||
b0f87fdd21 | |||
dc92ffed87 | |||
4af578e310 | |||
e22825d818 | |||
e2da6259e7 | |||
d149017c60 | |||
afc400121b | |||
ef993515c6 | |||
edb1d21ddc | |||
ba8abd94a8 | |||
c6d4e4c15f | |||
09f0ac866f | |||
7ed25704d6 | |||
2196dac63e | |||
c8f70efded | |||
ea97488670 | |||
c2255b0a0f | |||
f754b081ce | |||
07771cb5e4 | |||
690d8e43ae | |||
82f14a7d59 | |||
b284384f0a | |||
1ae0d1b5d0 | |||
9de08c8166 | |||
a2d007f2a9 | |||
774f818bbb | |||
0ec7121b8f | |||
d7d46f4447 | |||
45fad147bf | |||
3664195c71 | |||
fce3cd00a1 | |||
33b3be0d0e | |||
cfd1b4a6c6 | |||
d45fefd6f0 | |||
f125ab01ee | |||
be001d090c | |||
971d8a7e40 | |||
a2cf210a52 | |||
3eec207166 | |||
b5d83bdb56 | |||
2c495c4119 | |||
7c72d6cb7c | |||
8362bf0886 | |||
1a8155c45b | |||
3f2f946019 | |||
2c14a8dee1 | |||
917a283bd1 | |||
3e403d5ab3 | |||
746d35b52b | |||
9a7a03e327 | |||
a051079c6a | |||
7b3c18bb97 | |||
52daf3d58c | |||
f41bde5ee1 | |||
6151318ac1 | |||
b45c322729 | |||
b00e8768dc | |||
156feb6e8e | |||
e942b8a402 | |||
abdb67a123 | |||
ee20787c5e | |||
ec4e631760 | |||
02b430a5bf | |||
7878053df2 | |||
12a593c3c6 | |||
6b1f130750 | |||
bde4c0a648 | |||
5ae4621da1 | |||
5ea8d0546e | |||
8a064c118f | |||
2f91c27df2 | |||
763bd54707 | |||
0ea3cc7ce4 | |||
0de3558ab3 | |||
069f4e12d8 | |||
ae4dfc9956 | |||
ee711dc0fb | |||
c316e7faab | |||
7083b3d912 | |||
2d3a1b6a9e | |||
0df23ab878 | |||
7ed8de2ef4 | |||
d935e22f0d | |||
0e26abf7a6 | |||
59aef13200 | |||
9d1f6c4416 | |||
b9f7660a91 | |||
18b5250ed1 | |||
f683f21ee2 | |||
bd033db84c | |||
ab036312a4 | |||
634da15191 | |||
cea1720ea0 | |||
3f2f542265 | |||
b77edb2b5b | |||
1b699bb814 | |||
333c035fed | |||
ce29914c56 | |||
70e5361146 | |||
e7d6dfff53 | |||
eebfad5a95 | |||
77c0a93ac6 | |||
63a3e126b3 | |||
3ea84cf0ce | |||
7fa80ae556 | |||
925f71af15 | |||
c666dd623d | |||
2cd8733212 | |||
4b2a9bc621 | |||
12a9d0575d | |||
edcfa28b0b | |||
3155829994 | |||
d25707554e | |||
38df44ef4b | |||
df683375b1 | |||
cc3cbbc4bb | |||
6922394b8e | |||
24fd82d773 | |||
57aefcd917 | |||
b3854ad382 | |||
5f5fc77877 | |||
0493e77cff | |||
6240fe1dfc | |||
beb7f90908 | |||
a3917972b4 | |||
7094fef37f | |||
0f41e56a24 | |||
52b283283f | |||
ebb15bf96c | |||
6c527d52fb | |||
b8ea57e097 | |||
909aed4262 | |||
4d2fff9538 | |||
9a45983f17 | |||
11926014da | |||
72002c13d6 | |||
6ed767ae84 | |||
3826b307f7 | |||
887b157056 | |||
d36dd39743 | |||
dd008bc13a | |||
50b282f58b | |||
f8a7efbce7 | |||
7d2caeb270 | |||
708e71a35a | |||
4eaccc966e | |||
3670d649b8 | |||
90ab04e81d | |||
26b8df5354 | |||
11a8046c5f | |||
da16110e1c | |||
914b686c8e | |||
27133520fc | |||
24b967ad5c | |||
ca4b4a3f1e | |||
faef35ec47 | |||
326d4c2641 | |||
83436c9550 | |||
2084822731 | |||
071bad1232 | |||
ae1a76da2b | |||
fbc6965c4e | |||
57a5862840 | |||
91fbccdbaa | |||
0ab0dd95ae | |||
bc41040fd3 | |||
4c8dfd0c0c | |||
2b9dbfb390 | |||
84d546b724 | |||
63053b9940 | |||
2256030a2a | |||
79da33b597 | |||
7d67450e58 | |||
8aa11951bf | |||
f23f22ab01 | |||
96a64c7bd2 | |||
d1bb0fdf1d | |||
feca30d7ed | |||
b650151693 | |||
bb3afd0dc9 | |||
5e77ae208d | |||
24e5a4d7ec | |||
1d10d29fa9 | |||
9b00e91773 | |||
cd73c30d6f | |||
7bbba0c7d9 | |||
7907a4fc24 | |||
2f94f62a56 | |||
85791a9336 | |||
a4eba50cfd | |||
03980b2f27 | |||
664e5cfb59 | |||
b9736df7e0 | |||
f48b2681e3 | |||
ab46bd56b0 | |||
c23506e887 | |||
9ad67a7b7d | |||
7a1b6142df | |||
478256d766 | |||
4d92caacef | |||
fd45de5c58 | |||
bcaa9674fe | |||
40aa3b7e18 | |||
5aea21a194 | |||
b5e118e2b4 | |||
dfec0e45ed | |||
ff2a4e6952 | |||
7660751f7f | |||
78b9ac4766 | |||
d5c75571dc | |||
16b9c459ab | |||
41c060e28b | |||
a3090e62f5 | |||
39b7024be0 | |||
d019c5999b | |||
20264eecb9 | |||
cc55453076 | |||
6cab2427f5 | |||
511bcc9197 | |||
00ac632d8f | |||
649209890d | |||
f2fca0f13d | |||
4084d5e69a | |||
e8beb7103c | |||
0e4ce0f1ae | |||
c42d517f6b | |||
356cd4ef52 | |||
88619145d8 | |||
6ba779fb7a | |||
8bd965267c | |||
7f76ffa5cb | |||
4acc7cee3d | |||
be28e0b559 | |||
116fec208b | |||
fece92e15a | |||
dce3049446 | |||
fcd6fe5d8a | |||
a69a833716 | |||
697b082591 | |||
b2d58e04d2 | |||
8bfc5f0450 | |||
a252a8acee | |||
447ee4bd09 | |||
3cd6382795 | |||
5d1134dfa8 | |||
05e7b0dc22 | |||
c0647c3110 | |||
ef84ed4982 | |||
a1e83b9f19 | |||
4ce4ee3c00 | |||
0d62aedfbb | |||
b7c2890250 | |||
ae97bb0445 | |||
117fd4bd0f | |||
bd424ce460 | |||
1dddba7f25 | |||
7fd75b7501 | |||
423f07033e | |||
ef9c457681 | |||
a6d4a3b785 | |||
2e487f8a3f | |||
2423a70abd | |||
13d39fc942 | |||
b7547a8458 | |||
8931dbb657 | |||
52416ff3a8 | |||
3dbfee91f6 | |||
09d4901781 | |||
62955e7385 | |||
1ef7722504 | |||
24bb2f02dc | |||
627698d81f | |||
d4c8480dee | |||
015e8deb79 | |||
714aa4b4ba | |||
8d5f798591 | |||
e65f59b3df | |||
341c3d179e | |||
67128937ca | |||
d9ea621e54 | |||
fb35d7af59 | |||
c254aa6fcc | |||
37d30eb887 | |||
49cdcc644c | |||
07e5525c74 | |||
776194f5b2 | |||
ed80ee98a7 | |||
040bac3da2 | |||
9df721d158 | |||
c50ede8b2c | |||
ba0907ae59 | |||
e9dce32a98 | |||
535cc0d81e | |||
5801297d78 | |||
51a33a47cd | |||
01a1a9ebab | |||
438bad9649 | |||
fe3b36caeb | |||
83588e14d9 | |||
64b1c9636b | |||
db0c1b2634 | |||
568c4d8c8e | |||
d645507eeb | |||
3548112ab2 | |||
0cb042cd93 | |||
0eadc028b6 | |||
82f3677168 | |||
70ed49e478 | |||
3c67a36b60 | |||
e5621246ec | |||
cb71d44024 | |||
7e3ea9074c | |||
e2cf157857 | |||
60890147c3 | |||
64c95305b9 | |||
feddd9285d | |||
d1b393965f | |||
e31a39b9d5 | |||
98fc028d39 | |||
88fd799a30 | |||
ef937f277e | |||
c3fb5af3fc | |||
859e8deb02 | |||
932c92412c | |||
05771ddf6d | |||
848d387ec4 | |||
ac6b4235b9 | |||
ab73e98075 | |||
aecdd04e04 | |||
e5cdf74587 | |||
8d25ce7323 | |||
8deca3b63a | |||
9b967177c5 | |||
4dfb3cc972 | |||
73e5e9ecd9 | |||
653b7ffcd0 | |||
8791b72cb1 | |||
d961492380 | |||
07de367476 | |||
31d96c2bf0 | |||
fb8aafb69f | |||
3d58b78062 | |||
ec5e6958ef | |||
71bd5fe367 | |||
6385c71c72 | |||
d43255e688 | |||
3527dedc99 | |||
de50f53be4 | |||
f2e4b2fc99 | |||
e6f3cd03bb | |||
a1e31549a2 | |||
71d225c562 | |||
7c23212850 | |||
fdf178d4df | |||
04ebca8413 | |||
edeee54fb2 | |||
a906e9b302 | |||
fff72b61df | |||
74381ef59e | |||
64f95af3e5 | |||
85a1eb75c9 | |||
597cec3064 | |||
b03ebc1fa4 | |||
6c53bb4d51 | |||
fb7a458747 | |||
db25a9ae4f | |||
c69420373a | |||
2b8347f899 | |||
281a3911f6 | |||
9b77dd9a2b | |||
cb8cff3179 | |||
3db85c7274 | |||
b41ac355a0 | |||
88d9ffe92e | |||
5113c78ab6 | |||
3854995ef2 | |||
36e14b951a | |||
9299a4beff | |||
d681bea395 | |||
0f3f1e9226 | |||
79ab492a5b | |||
62db4bb09d | |||
7be2cbb75b | |||
5b1fe3460f | |||
31997fe50a | |||
5e5ceef122 | |||
40edbac7f0 | |||
5bb1f72c28 | |||
8622e6492c | |||
1feac9c559 | |||
fce81dd6d9 | |||
aa50554f06 | |||
034506f56b | |||
2d8858edb4 | |||
b2601ad696 | |||
8099f561c5 | |||
8a014ddb0c | |||
3d9383ce67 | |||
9de07c11a6 | |||
9f744bc445 | |||
aed6e12119 | |||
c57d0046bc | |||
07b9fc9b31 | |||
2c6bcb85a0 | |||
fefa519486 | |||
11a232a2df | |||
8dcd919ff0 | |||
d9c27e7109 | |||
8af8c57bb4 | |||
a1a4916abf | |||
9be8f675ac | |||
a271c3726e | |||
8c18a14dfd | |||
9a801cfdfb | |||
4af13e3536 | |||
e76e903060 | |||
3d89a317c1 | |||
d8251224cb | |||
acd927a937 | |||
a498f940c6 | |||
948cb31d1a | |||
179cb8eb50 | |||
47f865aa72 | |||
b47face2f8 | |||
69869115f6 | |||
0fb9ca3e8b | |||
eaf9c9b2d8 | |||
70d9b0c390 | |||
e57a999c9c | |||
3b49289cfb | |||
176e984b56 | |||
b5a700276a | |||
3c186a3c8d | |||
a462ce3626 | |||
065cf42aea | |||
986b709f2c | |||
fed6f44995 | |||
1b52acdad7 | |||
10a638c6b8 | |||
7875f363a8 | |||
685736b9ec | |||
aefd2bf6f8 | |||
ce9fb2f1fe | |||
974275a429 | |||
98461f9bca | |||
094f78fb41 | |||
33dcdc1599 | |||
8870ccb18c | |||
2a7ed1375a | |||
107727eea9 | |||
54b50cca71 | |||
1c10ba7925 | |||
2b8df691ff | |||
15da856303 | |||
cef5343a24 | |||
f96b85fcb2 | |||
a62628423f | |||
ef8a87a30f | |||
89fb943733 | |||
147978b932 | |||
c741920ec0 | |||
bbbcb18b91 | |||
d6b3b0baf7 | |||
dbe8931cf0 | |||
d2eb5d7f45 | |||
562dce60ee | |||
569df39fb8 | |||
2f7f00c7a2 | |||
afd59eabbb | |||
cf99446a12 | |||
68286b2acc | |||
a410184e0a | |||
d3ceecf620 | |||
940c5b3838 | |||
17c321286d | |||
0dbb79359b | |||
19f39fcdb0 | |||
ab021c1302 | |||
3b11ad8de8 | |||
cf4b870846 | |||
5e37f72d74 | |||
6843dbf7e1 | |||
09c07faafd | |||
8e7c235ff0 | |||
7fb4cbb8a0 | |||
fa872f6cf7 | |||
ef53d4ec07 | |||
c68e7c8da7 | |||
de35a4c62a | |||
fcde6c2b84 | |||
9cbe053e79 | |||
818468c58f | |||
7ba43ae5c2 | |||
5700c7a0c7 | |||
4bfd395d9f | |||
5069d8dee6 | |||
47c120e58c | |||
8d7ab13f5c | |||
122cdae5bc | |||
157d8db68c | |||
998da965cd | |||
8d58a8d548 | |||
b453be081e | |||
3c947f323f | |||
cb203ef02c | |||
908c9bc624 | |||
fe373a95a2 | |||
60f18f3b5a | |||
284c019b32 | |||
32434471e5 | |||
6a4c280235 | |||
f0eacf4218 | |||
0afe3011bc | |||
0fef546a0d | |||
93e6136795 | |||
7d23fd8ef5 | |||
71c9df5279 | |||
224fcada17 | |||
9278407b85 | |||
dad3292bdd | |||
cfdf319972 | |||
89619b7836 | |||
6aff438a16 | |||
13324dd1a1 | |||
ae9bf06b46 | |||
5236834911 | |||
bf80dd622c | |||
662b71436e | |||
f608cb55eb | |||
6ba82da029 | |||
f407e30b6e | |||
4e7b8c98f9 | |||
5f9574541f | |||
08a6db7d6e | |||
b485e1d657 | |||
e8d8621f06 | |||
4cefbce7c3 | |||
fa31369f99 | |||
d0bf93ebb7 | |||
41a747c7e7 | |||
8882cd4787 | |||
6676490e09 | |||
68bea8a196 | |||
25995c09a0 | |||
0eb8d7d081 | |||
554f890ae3 | |||
dd1743698f | |||
b092e98ac9 | |||
9ee6262aed | |||
24a2d86f41 | |||
b5c5c66336 | |||
7654feb6a8 | |||
a598ac3993 | |||
cab919d74c | |||
60a929b92c | |||
356b7c346a | |||
ad57fde1c5 | |||
17f7dea21b | |||
b40af7c3c6 | |||
9065362fde | |||
d264b03ca1 | |||
ad9bad3d17 | |||
dfd858034f | |||
58ad8fa8c0 | |||
38610d8a24 | |||
27cec697bf | |||
024f9a8c76 | |||
f7cc36f2f0 | |||
ef5148ebb4 | |||
6dbc0a6fd5 | |||
fba3f9d501 | |||
d9f8137362 | |||
28416489b2 | |||
54a23ddd1f | |||
3287ca9cf2 | |||
a59e134862 | |||
1f8c5b0120 | |||
c7f839ea4a | |||
d981245723 | |||
1f729f1cb3 | |||
b4577d6676 | |||
544adb9940 | |||
1875c4a752 | |||
5f0493f1e5 | |||
c749e50bec | |||
a4e5e3ece5 | |||
2a69d1b051 | |||
126e1e2d9d | |||
0586e1d3ad | |||
07cb1c237e | |||
f4f1efe5fa | |||
37fdf4d434 | |||
99b46096a4 | |||
12e90ae35e | |||
023311a874 | |||
155a4dd463 | |||
15bed1ac4c | |||
27f55f8098 | |||
00598879e2 | |||
df274a0a78 | |||
0dc4862d79 | |||
a3f1b72126 | |||
5ff10799e4 | |||
a82e5f5452 | |||
e10cb0e632 | |||
c7e07a6df0 | |||
2e0c778090 | |||
592050c668 | |||
02c9191525 | |||
d421401626 | |||
b2d4e5ab84 | |||
84e023607c | |||
f145fd0dec | |||
42a9f911d8 | |||
9567d55312 | |||
531cd99247 | |||
f3660d88dd | |||
3accb9a08b | |||
63ce7371bb | |||
01c3498dbf | |||
b3471234ad | |||
b2d697131c | |||
ef49fc91d8 | |||
6222b47a4f | |||
f58e3c390a | |||
7504621a24 | |||
88e49a9b8b | |||
5b23f29d06 | |||
c1bdebee78 | |||
ddd4cc10ff | |||
0ca62a4acc | |||
4f1275ac01 | |||
b2fee7035f | |||
e15d7cb548 | |||
3257cbe21f | |||
1237af1ff3 | |||
68600b337e | |||
dac2072eaa | |||
1b921f9845 | |||
a3992d9fbe | |||
efd2a0cb7b | |||
fba428257b | |||
ff36901007 | |||
940d8389b5 | |||
f7a6cbe5e2 | |||
7aa379a857 | |||
443024cebb | |||
1657f04d55 | |||
407e798fdb | |||
4054f2a6a0 | |||
468cdf603c | |||
988ec6a224 | |||
bdbdf211e2 | |||
0437703cbf | |||
71aa592111 | |||
d501c02f8b | |||
9daf0e78b8 | |||
dfa07a5f35 | |||
437c995d12 | |||
cc6ae9d1a8 | |||
c58e4f4dee | |||
c87b0e77de | |||
355d5af8ae | |||
3d99a8ebdb | |||
c4b975b777 | |||
2911fe7a1a | |||
14c114756d | |||
e7a8107279 | |||
bff73b1b40 | |||
c255f57d95 | |||
64c47bbaed | |||
e0b7698d40 | |||
a01792ac9a | |||
3ba078f64c | |||
a16240f123 | |||
e5a120e778 | |||
2ba60e9114 | |||
472ce5a5e4 | |||
99ba84c810 | |||
78285bdf37 | |||
5a7f2684b3 | |||
d912a42249 | |||
6d8c4fb8b1 | |||
a63cecbfcb | |||
4a5bceb4e4 | |||
86541445b7 | |||
4e826aa8e7 | |||
b6e6f490e9 | |||
2145e878a4 | |||
355f6db255 | |||
bc7632bf02 | |||
609d8c9685 | |||
2f08515455 | |||
7f450e185d | |||
747879b4ec | |||
4193870fa6 | |||
cdc5de3f1b | |||
bc34d4fa88 | |||
6fd4af8736 | |||
b5c2934270 | |||
94f5117941 | |||
112e233498 | |||
18b1326f3a | |||
1e58b05ead | |||
938919bd9b | |||
b6b78994d8 | |||
fddd8ce305 | |||
ccff337975 | |||
fde6b7af4f | |||
0657db7dcb | |||
d1c2eaf6d5 | |||
91bb6b9016 | |||
90351c6e9e | |||
dd4740e54f | |||
48e7cbd76c | |||
f51e32f39b | |||
ae42f59102 | |||
5c8006f9b7 | |||
aa5861d3ca | |||
7a64bf55cb | |||
d4c9ab793f | |||
48d2849d97 | |||
776610d0e6 | |||
3a790f3d66 | |||
7382042288 | |||
33992d80bf | |||
a92b0e567b | |||
829a65e515 | |||
03ad48c055 | |||
89837e4ced | |||
ace1db21d1 | |||
8bb69c455b | |||
2dae706198 | |||
3eda2a220a | |||
61e5440b7c | |||
2e2663bad9 | |||
f4dd150b70 | |||
2b35d22e25 | |||
f590378761 | |||
f5f592be91 | |||
7a373fb43a | |||
aded11e599 | |||
41d7cee020 | |||
f2ef6a20e6 | |||
a398c3fb81 | |||
2a454b44cc | |||
7b66ece895 | |||
b5017eebbf | |||
aa67229daf | |||
5af68186d6 | |||
545bc0e605 | |||
291168f4de | |||
9facb51f22 | |||
5b7d8c5e37 | |||
5945937e4b | |||
9f9f9872eb | |||
3566072f4a | |||
b85cd86b24 | |||
79c3767fff | |||
cf1609a429 | |||
3aeac7e7b5 | |||
1557f713f4 | |||
b63d24ac1a | |||
348c1ff29d | |||
717e55497f | |||
d84b5e8b46 | |||
5f9ddf9ff5 | |||
bbee093c63 | |||
e8c35ae4e1 | |||
1607658c30 | |||
2e9ef373f3 | |||
ec6eef6d37 | |||
45a19d15ec | |||
7191552126 | |||
cfa07490e5 | |||
ae40990eb9 | |||
9f2fe33ce0 | |||
33660de6b1 | |||
13d25e0849 | |||
6662e2002f | |||
d4081dc899 | |||
62dffb8226 | |||
cb6aa18480 | |||
d5cfbef42b | |||
535abcbb8b | |||
c34b548a3e | |||
9bf452856c | |||
17109ab760 | |||
6bc6e1a1d1 | |||
7eef4f7fbf | |||
75bec6a8e3 | |||
0a10f66053 | |||
58860b51a2 | |||
3ee652b61a | |||
426ed7308b | |||
0ecfef3f70 | |||
5f7e34b6a1 | |||
34cb24fe34 | |||
1490112135 | |||
c4716a3f4c | |||
0a54901eb0 | |||
fea2e0a265 | |||
d3c087375b | |||
a93c0577ac | |||
e4dc35674d | |||
8a668ba7b9 | |||
ee9a68b040 | |||
78e8d40649 | |||
660e13b701 | |||
0685382083 | |||
04a993c997 | |||
7cae3095c4 | |||
e288bf902b | |||
a083e1f71a | |||
86b9d7e843 | |||
628bd5d6b4 | |||
00285a782c | |||
16be469ecb | |||
fdcbc4cffa | |||
fc548304cf | |||
7c7ff8165e | |||
496a476c13 | |||
441fc6e45b | |||
cf7ec6aa76 | |||
db2dd4b6c6 | |||
a68417a0b0 | |||
2a5102a457 | |||
837d8f5f30 | |||
1a5858e99b | |||
4044427d93 | |||
f667f85fa5 | |||
5cddc0c387 | |||
cbc01dd6f1 | |||
b820c7debf | |||
2bee072cba | |||
80710b0b94 | |||
3319ccfd41 | |||
878008e93b | |||
0cd551d4fd | |||
f85194ec46 | |||
271489bdfd | |||
bd5f22a049 | |||
189f18b112 | |||
df166184ea | |||
ce42cba096 | |||
9670863a41 | |||
1ae52bd33f | |||
c9cf9cfff0 | |||
2ffbee3db2 | |||
96b8beb9cd | |||
365b849046 | |||
8e613d03e3 | |||
b18a794eca | |||
c620c924f9 | |||
9db81a5a49 | |||
6fb7a85e8a | |||
36f81b4a62 | |||
2caecc01b2 | |||
dedb8d2d68 | |||
7192b26402 | |||
762f5bdc33 | |||
bebb52b4e8 | |||
2c9f8bb9ce | |||
efbefabb01 | |||
990fb22d3e | |||
9b2c22b2d9 | |||
df7e0d2f2f | |||
5cfda1b1bf | |||
ac9bf1f3ff | |||
7eb0868791 | |||
8a792e6d76 | |||
d8a3692d92 | |||
95ce0e39ef | |||
17b70ab38c | |||
07e76f35fa | |||
a4cab9876a | |||
c06a932c95 | |||
7d713b87b1 | |||
b1167146c5 | |||
2d0a5eb02c | |||
8d68859c2a | |||
444cefc9a2 | |||
d0deceabbd | |||
175c1df0b8 | |||
9cc6491c2a | |||
710179f4b4 | |||
d11c72fd48 | |||
0af505828e | |||
135cf9960f | |||
3bf7c74f93 | |||
cea4911c4d | |||
54dc01253d | |||
4db9a90da2 | |||
d69e9034ab | |||
71ece73d99 | |||
3bb2102eb4 | |||
b7914909d0 | |||
63398fe491 | |||
bf32bf28da | |||
dcb6bfb18d | |||
8f605dc0f6 | |||
47e770948b | |||
9ab29f5b7f | |||
10bf430ce6 | |||
67eb4e8180 | |||
141f9b7730 | |||
139a589ad6 | |||
591873a185 | |||
97a308b114 | |||
430714e67f | |||
a49adbd09c | |||
3df98d576e | |||
8135136c86 | |||
cef1c4b8a1 | |||
2e8791a101 | |||
0e2b8b10d1 | |||
3cb64669e4 | |||
bc0d32f330 | |||
0db17beacc | |||
931efed784 | |||
6378a41b6d | |||
23bf7faf9f | |||
01ff3af63f | |||
8f98055e9e | |||
84ae61f72c | |||
6dd280205b | |||
1365d553a4 | |||
61a594493c | |||
62ab70f889 | |||
eaccfdde59 | |||
a8e536478c | |||
e94d5626dd | |||
be3e31ddc4 | |||
b92b6520cb | |||
ea33179a95 | |||
6fcf6ae1f5 | |||
f2a9247b68 | |||
dc3ed7fffc | |||
271de31d51 | |||
1268caf3e0 | |||
c0cef58e39 | |||
d363d205c3 | |||
2fd5a9e883 | |||
e7ef974a39 | |||
0b62fa8b76 | |||
2d28750782 | |||
e2054a0ab7 | |||
7ae5c3b2e7 | |||
6e7fefb8b2 | |||
450bef278b | |||
0affc0d58b | |||
3d153b6c8e | |||
04fff91e23 | |||
28a23452f2 | |||
6d403851cf | |||
395a749bce | |||
2cc2a90941 | |||
c87ba6231d | |||
c5ca739b49 | |||
00fe4cdf2d | |||
69be3e1e87 | |||
2cb3984d68 | |||
5901978889 | |||
8bf1cf3cc5 | |||
f6af1184bc | |||
4880741b8b | |||
e8627800fe | |||
907fbb94a2 | |||
fd2028557e | |||
91fa1ec6b2 | |||
628c525599 | |||
bbc00768f0 | |||
5b09461ccf | |||
1a439ecece | |||
836aec4396 | |||
0b5dec9bab | |||
fd56123267 | |||
45ca470789 | |||
a3b1690d38 | |||
a3bad75899 | |||
d1aaafbfff | |||
93d4af99bf | |||
c950595fe3 | |||
8ffd3a8ed2 | |||
b6e246c6b2 | |||
59859e124f | |||
2bb7a33bc3 | |||
c2b8fea291 | |||
08ab7f6aa0 | |||
560f0bba5c | |||
722437a022 | |||
8a44b1dabe | |||
b39191ff50 | |||
9814d20404 | |||
6664dfb048 | |||
3133a63cf8 | |||
c9c0f3d014 | |||
ff66f307dd | |||
e048d66f74 | |||
66e3fa7df8 | |||
019a0f31c7 | |||
749c2071af | |||
322d66d282 | |||
aa98cd0da0 | |||
c8316c7254 | |||
6b9180844d | |||
c0e4863229 | |||
2be9871d05 | |||
776f6a9a16 | |||
10163aab21 | |||
60b2a4ea9d | |||
56e1e3e205 | |||
0f805cd45e | |||
1d7c692e89 | |||
38bc8ec6b4 | |||
2154e3aa2d | |||
56c19e57a9 | |||
d548c690d6 | |||
3fa70dade3 | |||
368c30a2cc | |||
5539e4591f | |||
781971ee81 | |||
1140316d1b | |||
cf6c48744a | |||
eed6db8e92 | |||
858664bfd7 | |||
eceac4d6e3 | |||
47a172df1f | |||
f2c0732c40 | |||
682fae12b6 | |||
a150762c63 | |||
2695bdddf8 | |||
c9b1a425a7 | |||
122b2b1a8e | |||
2351c1b426 | |||
558c4ada06 | |||
779fd9c61a | |||
0b5128dfd1 | |||
c0519e8670 | |||
fd545db1bd | |||
6991c224b2 | |||
7dc70c9eab | |||
e32445f2cf | |||
8aa6486bf7 | |||
d21c147203 | |||
9b10e851d1 | |||
6675508b24 | |||
7310ec4fe4 | |||
b1ce3693ed | |||
deb1ed5623 | |||
0902d7cca9 | |||
95ec903862 | |||
2ab6af6471 | |||
9493577de2 | |||
837ce62844 | |||
2860bbfb12 | |||
a2b1acd70f | |||
f1350bc33e | |||
6af0eb4068 | |||
538c168641 | |||
832a4fa68e | |||
ccb727529d | |||
ed41604f56 | |||
9f05d563f9 | |||
72d114d46a | |||
99b96d80d0 | |||
67418ba853 | |||
60755d0c26 | |||
a689e4e041 | |||
f5aa36c787 | |||
f8d82cb052 | |||
980feb6c96 | |||
e7d6605490 | |||
7a476abb53 | |||
19581792fc | |||
aadc6a56cd | |||
b88e444cbc | |||
6c792d2821 | |||
9afb445620 | |||
efc951191d | |||
a249373bf5 | |||
e1eb030b18 | |||
6aea0f48ed | |||
e637f22540 | |||
842295348e | |||
2992a0f4d8 | |||
e8f5963a57 | |||
4cbe497770 | |||
76a53097b1 | |||
0904692f15 | |||
65bacd288b | |||
11ab3b2c2e | |||
812368e332 | |||
cf39ae0000 | |||
7194f65203 | |||
4b78ff324d | |||
79ccfcd553 | |||
2df6a4dde8 | |||
e88cbc2769 | |||
25d1c40cda | |||
969b57ade9 | |||
bddeb86223 | |||
b5986b509e | |||
9d2adcd512 | |||
fb3756420b | |||
2eab43a669 | |||
caeab0a63b | |||
7c69b1b649 | |||
3784d1a8f2 | |||
458e761b45 | |||
371b0b2132 | |||
5d1ca64768 | |||
972a595c74 | |||
a3c598a3e1 | |||
9ce8c5c160 | |||
79bbc99882 | |||
31867362dc | |||
3bce07e873 | |||
766f9e37b5 | |||
a9bed90d02 | |||
01ad405dd2 | |||
8cef35f4a0 | |||
274f0edd76 | |||
88aea311f8 | |||
477aedbffa | |||
b898442fe3 | |||
528c1b90c2 | |||
205c1e5170 | |||
004e1c98ee | |||
7641bb4d0d | |||
687f3d48ea | |||
da5f10a2f1 | |||
64050e8266 | |||
791a7d5a01 | |||
13930d3706 | |||
2769e27a2a | |||
76c795d0d0 | |||
b20bced3ca | |||
4f2da9a78f | |||
8e0ba3650b | |||
76f6fe4601 | |||
ca1373f36b | |||
fc6c2e083d | |||
c0789cd6ba | |||
af47103707 | |||
c466baaa25 | |||
670294a427 | |||
21ddae6a86 | |||
9f260c3513 | |||
18061d1077 | |||
381c061ebc | |||
d37341d7d0 | |||
a5098e5b5b | |||
b55d394a1f | |||
5e2e177aa9 | |||
86e59977de | |||
66baf01e43 | |||
7a33e198dc | |||
4b493ebbaf | |||
565e8cf00b | |||
738a3999b4 | |||
6f4b84c8fb | |||
29ab99aa1f | |||
d53719b79e | |||
f10fe8bf02 | |||
d7cfe1990c | |||
8bedc8f456 | |||
d9000f6fd1 | |||
50c7b32b00 | |||
78072ad285 | |||
437a34b5dc | |||
3ebea4c305 | |||
8fe315c354 | |||
b10b13a339 | |||
5b5ea5ab8a | |||
1a230b3900 | |||
e90b0aaf8b | |||
fe7c7e72f5 | |||
9ba11a585f | |||
9920ff617b | |||
3f1355c413 | |||
4929e66ecc | |||
02e370c2d8 | |||
4c31e3fc5f | |||
15f49b39b8 | |||
8c82b766e3 | |||
4c8665c9f0 | |||
ba67781431 | |||
4ef25c75b7 | |||
3aafc671f8 | |||
967df6f7a3 | |||
22518f173f | |||
64bdfabbd8 | |||
c8c65ab7b1 | |||
19cd28b66b | |||
4a136ef2aa | |||
9e7a53cb90 | |||
19a7f37efa | |||
c3084ac43a | |||
159146e197 | |||
67ddf4a5b8 | |||
4b9b53a9b8 | |||
e6f025a9fb | |||
aa607e0ecb | |||
65b32ddeb2 | |||
5e9bdc2690 | |||
9ce994168a | |||
748a720199 | |||
8db34eb3dd | |||
b657bba96e | |||
dbaac69fad | |||
b6a1e89535 | |||
cce919750a | |||
9376b223bb | |||
6f047fb5aa | |||
3e6b0117fd | |||
421dfb4a2d | |||
abaca6e676 | |||
8bab1d9798 | |||
13d31669ac | |||
c1dfdeb500 | |||
dda7e677a5 | |||
b1fb401f63 | |||
885ace111e | |||
885552b792 | |||
4f02872a84 | |||
ecec1bd102 | |||
0c07e05a2b | |||
060f0682f4 | |||
88032e11df | |||
493c8b0943 | |||
af2ef0621a | |||
095461e31b | |||
3ddd1033c3 | |||
912687ac78 | |||
40a9595012 | |||
12ff37d052 | |||
4857073f30 | |||
cdbefd9191 | |||
2e9d89574d | |||
569c99496b | |||
ea3b8767de | |||
8e8c30c1eb | |||
d921ba81c8 | |||
ad9f646102 | |||
2ef277bcef | |||
9e396e1624 | |||
9708d84e60 | |||
4efc195548 | |||
0d15cbe334 | |||
e381d9fc8e | |||
85ed7a7457 | |||
b8a98ef5e4 | |||
c8fa90f473 | |||
b47ee8857b | |||
131dfa62c4 | |||
6a5af438dd | |||
ccc0a61158 | |||
e990ad25eb | |||
98a4d1e763 | |||
f762598c5c | |||
ec56c27071 | |||
eb0e0a1952 | |||
01a837fde6 | |||
b9488645d4 | |||
7a94b477cb | |||
99710b45d1 | |||
3813743e3d | |||
9bb2334b69 | |||
7e73ede47a | |||
b0106aa420 | |||
33e5fea96c | |||
f0a1dcd120 | |||
26d5a87bef | |||
52ae208df3 | |||
34aaa7fb0a | |||
a8c784355c | |||
0aed93becf | |||
1ea0804209 | |||
a52fbb012a | |||
2dc47352f8 | |||
e95a5be21d | |||
abd69d4f91 | |||
d749e309f8 | |||
e4d075fb91 | |||
71c6c71081 | |||
d2b14bcfc4 | |||
2c04c81bd1 | |||
dd66c83c50 | |||
9e51d82154 | |||
bdc441a5be | |||
2dcb73700b | |||
ee01686ae4 | |||
9a55cf880e | |||
6742cdeb8b | |||
c37377bffa | |||
76147a9be7 | |||
bf22e69250 | |||
49693934cf | |||
e6a63ee5b2 | |||
08dc57fd02 | |||
cede590696 | |||
c401915fb5 | |||
2a202bd510 | |||
dcd8ed08fc | |||
d3ebedeef2 | |||
d2e2ebbe45 | |||
0cef05dd89 | |||
a443dc3040 | |||
4e6cc013e5 | |||
0c65d54d89 | |||
ccd0e0cdfe | |||
9278ca3f5e | |||
d7a70b962b | |||
fff0f841fa | |||
8ba426350f | |||
8ef548032f | |||
5452e29840 | |||
148f8e6d11 | |||
13a5662a84 | |||
6713a7ae3c | |||
226ad13061 | |||
88ee86b7ef | |||
4bc2288806 | |||
a928d9fa0b | |||
d8f4e6b45f | |||
5ef5087406 | |||
135c371d88 | |||
966c196f4a | |||
dc43e41896 | |||
4809d06d04 | |||
9f7fda0bc5 | |||
1f67695713 | |||
66ef1a8206 | |||
beaffc3870 | |||
8536ecb611 | |||
d7a89b0f8c | |||
943081e80d | |||
3f007a1edd | |||
e33cacf6a4 | |||
23fe848a35 | |||
fa5d2276c0 | |||
d353a3457d | |||
b363b9fc1a | |||
1920568057 | |||
763da19c9d | |||
1813dbbf59 | |||
339169b624 | |||
93960315d9 | |||
479eb1ba71 | |||
962d8e5fd2 | |||
fefd4c0b26 | |||
40639c0933 | |||
7401673ac1 | |||
41f759dafe | |||
73dcc7bcb1 | |||
d8b1f60581 | |||
16fc58bd16 | |||
68df2f4ce7 | |||
367932de69 | |||
3da08cbcbf | |||
68efc0c42f | |||
f5b01b0ca2 | |||
5733429682 | |||
c6d29fc19b | |||
d9a12d79b0 | |||
963cf4c996 | |||
0ef073669a | |||
ed1123feb0 | |||
c2114bbd4f | |||
fedb1d2590 | |||
eef39b75a6 | |||
eca593ac36 | |||
a1917b8c81 | |||
ec6dba12bd | |||
da0671ad62 | |||
04d83e9a6a | |||
2cb7624953 | |||
e6ace844b6 | |||
f2f6628693 | |||
1c33032721 | |||
b3f5f13c39 | |||
a5339969c9 | |||
1681437206 | |||
2eaf083eee | |||
3645d19135 | |||
3b4b1185e2 | |||
406c5bde11 | |||
75d1913aaf | |||
eb254d9c56 | |||
4e633b8936 | |||
e99a27e382 | |||
c8a6a2653f | |||
0ea0eba4f0 | |||
7f88b56d8b | |||
789421c7a0 | |||
d44503cb19 | |||
0258422527 | |||
47327d840d | |||
d0f1a33744 | |||
c54b8e62d7 | |||
3dc738f28c | |||
ca75400467 | |||
65091c05c9 | |||
52e846f3b6 | |||
ce22b2c29a | |||
361b0284fa | |||
b8947a1c50 | |||
8d1effa0e8 | |||
096a9f4cbf | |||
18712b166f | |||
4605e14729 | |||
a768280d82 | |||
cbb8f25645 | |||
24ff7ff67c | |||
eee0bd6cf4 | |||
f176a5179a | |||
381ba86e3c | |||
f4be8e28ca | |||
e17605f8d9 | |||
e06a488af8 | |||
6dc8bfed8d | |||
f642f23366 | |||
c041410f61 | |||
ff5f13eafe | |||
749b240897 | |||
9207762ade | |||
0a22950ad3 | |||
0fcd404b4f | |||
150ea29a70 | |||
f9baff0e90 | |||
6ad3fcb91d | |||
395ca3630c | |||
e9fbdb660b | |||
526e029ebb | |||
763c4522b7 | |||
32e3ac63ed | |||
90f31aab38 | |||
3d44feaf2c | |||
f5a44245e9 | |||
7e7eb9f39f | |||
6c9b982104 | |||
a106104027 | |||
7753161332 | |||
ec9d592cf1 | |||
31015504f4 | |||
bd40ec527d | |||
3899938b25 | |||
ca7373c28b | |||
390bdfa93d | |||
ac2df87954 | |||
0b73f8b1ef | |||
812fcec9f0 | |||
ec7297f8c2 | |||
0fdb19c07d | |||
3bc07b4753 | |||
0b8b13d0bb | |||
4c5bf9bc8e | |||
9f53109414 | |||
bccb1229c8 | |||
40ab3fe0a6 | |||
4a99118cce | |||
58ba29fa16 | |||
54cfb2acdf | |||
0bf14fd31c | |||
7dd9a0211b | |||
3d43473bf8 | |||
2194c4ba28 | |||
744a4f8f47 | |||
67d91f7b69 | |||
bf5065d16b | |||
3e837f8781 | |||
77d378ccd1 | |||
1a542bae71 | |||
e3ed12b5d2 | |||
759795940b | |||
a23d5ab734 | |||
6ee69ef430 | |||
3edf17d322 | |||
8c2b2f99bc | |||
73dc51b3f6 | |||
9a082d4df1 | |||
f430b6f853 | |||
78a352541a | |||
a0f5633094 | |||
0af81c7d05 | |||
52e82b3548 | |||
f05b99ec1f | |||
194897bf3c | |||
7cf26363c8 | |||
3d1dec4c05 | |||
af1935d2e4 | |||
3c4bc17065 | |||
333d1c1ad9 | |||
4e027cec71 | |||
39ae84301a | |||
3bf14623ad | |||
ac8f2923e5 | |||
e9d3b75e2b | |||
e6bc181e7a | |||
a2ece82197 | |||
259946cf0a | |||
4fdb4f14a8 | |||
914f5e569b | |||
9b4ffd1cd5 | |||
ed87dd89a1 | |||
3b41a78e76 | |||
0fccbbc0ca | |||
067627b51a | |||
09816ed5b6 | |||
b457cdb0c2 | |||
5fd1dec347 | |||
647391ef73 | |||
ed029c52ae | |||
102a372df9 | |||
d4ffb09a8b | |||
6ba052d2af | |||
57b63f43f5 | |||
2fb0969c75 | |||
3357e878a5 | |||
471d5d62d5 | |||
e810b343cf | |||
620be2617a | |||
035038a0b6 | |||
b8ffb87f01 | |||
39e1e11f99 | |||
5f9df78ab0 | |||
a00d11701f | |||
1cf74a5396 | |||
8cd27a199d | |||
772929b5c6 | |||
c4ca3606ad | |||
9e830f1c55 | |||
ee8c71c14a | |||
39bd823651 | |||
a9d16fad34 | |||
1da169319d | |||
2bb1eea2be | |||
9cb45b92e1 | |||
4a8d5098da | |||
d875d5ef74 | |||
fc4e290c49 | |||
ccc198e081 | |||
9f0ed77423 | |||
bb3e616890 | |||
573f3a392a | |||
830a834ea6 | |||
e4ea5d0344 | |||
97aed045e6 | |||
46b01c6134 | |||
e208fa4020 | |||
6b71264482 | |||
5723c184b1 | |||
530daeaa3a | |||
dd1b5c7ea7 | |||
3cffc6890d | |||
04d44f19f5 | |||
d46a742a43 | |||
a94fd24fa9 | |||
8a4c4c346a | |||
dc54299e24 | |||
436253dd63 | |||
29feee0095 | |||
63f3180dff | |||
6b3b98cf57 | |||
1442e2b53e | |||
521ebf0678 | |||
a20874f6a1 | |||
40776bdc8d | |||
f853158e6b | |||
c9035b5df9 | |||
150132f4dd | |||
fb97ac47bc | |||
5b53b90495 | |||
c6513d4450 | |||
ce6848b3c0 | |||
d86d861e4b | |||
3b45fcdb21 | |||
3d1250f2f8 | |||
eaf1ef831a | |||
b4c7992726 | |||
03baa21185 | |||
694de99a3f | |||
8383f4fb7b | |||
eb3fff6c51 | |||
4ff3f0bcba | |||
788ea052fc | |||
08ba805bbd | |||
dbd14c6dac | |||
5977fca6b9 | |||
fcf596d36b | |||
dabca5f09e | |||
018dbce57e | |||
bd45cc2024 | |||
abf47deee3 | |||
ce0090f0ca | |||
6cd34614f6 | |||
264e04368b | |||
43c11c6ac5 | |||
ede42cd6aa | |||
33901dd72f | |||
ac4ae8103e | |||
2c93704b4a | |||
e2158af592 | |||
b06189ff95 | |||
d72c51c8dd | |||
e29e94a5a1 | |||
04d7eccc63 | |||
e58896bdfb | |||
d2bb4487e1 | |||
e8623fbd7d | |||
0a6de2b3ea | |||
23a8e31791 | |||
286d2b4cf6 | |||
1ab07d169d | |||
abf2d4b718 | |||
37045c77cb | |||
fd5d52a709 | |||
abcb21491c | |||
33d2b0984f | |||
fc7a040509 | |||
be09dded20 | |||
6493b9a6b3 | |||
f07fbcc196 | |||
cae90ddd08 | |||
7bd9b21e5a | |||
e29fb68375 | |||
33e0a34916 | |||
fccb2e762d | |||
956b3284d3 | |||
937d0852b3 | |||
cf2967cf34 | |||
c1e64a839d | |||
c06928a7a5 | |||
8c0cafc793 | |||
543ca43e24 | |||
31b86f539b | |||
dff1f4dd52 | |||
276cfed832 | |||
bd61c33097 | |||
e2a9c09597 | |||
5059c6295f | |||
916c1c9de2 | |||
2661700d9a | |||
02502c4ea5 | |||
382e85a8a0 | |||
05d830eb58 | |||
7b149425fa | |||
6f0e1965b6 | |||
970f1466b5 | |||
e422b1d6c1 | |||
8992f47915 | |||
fe65f4d6f8 | |||
8077e421e6 | |||
4f806a921f | |||
8efe0502f3 | |||
0eb61f34f6 | |||
cc7735e284 | |||
e24ddb9106 | |||
67d284fc35 | |||
a9d32fea37 | |||
3374481912 | |||
1972cc25a9 | |||
6a3cd0054b | |||
548dbf4b78 | |||
74af40a352 | |||
8f26c4bd07 | |||
1d51b06d3e | |||
305ee3c12e | |||
90ce89193d | |||
2ad3fa1593 | |||
4bb9949768 | |||
c4f1d2f153 | |||
ed6f82af0f | |||
3ba8cff60f | |||
7307ddd1b8 | |||
0601bedb0b | |||
0c988d95ee | |||
d593362ba8 | |||
f97f4c68ba | |||
6d60d14f4a | |||
d13bc9d420 | |||
2a41e4ce68 | |||
850654dccc | |||
2f9dfed073 | |||
57911c42d1 | |||
6064eda68f | |||
fe803a4588 | |||
2787116171 | |||
0a509cb382 | |||
b25ab941ba | |||
6ef59a5949 | |||
17fc8deb19 | |||
45b5c1c262 | |||
7fd547a2d6 | |||
d19d787f6e | |||
52474e39d9 | |||
d243ee4b4c | |||
2c2f8f5853 | |||
64ba127e7d | |||
9d22a9e664 | |||
96c55db7ca | |||
0f48563e29 | |||
7c014292d2 | |||
19507d1837 | |||
81b2dfd9d0 | |||
3f63b320c4 | |||
4da760d614 | |||
a95ac8ad83 | |||
f841d3d259 | |||
902700e1f4 | |||
bbf456a4aa | |||
134dbd2f1d | |||
d371b093d8 | |||
979c49b99a | |||
dad010a891 | |||
9f974c9401 | |||
fba3ed2244 | |||
aa1d927da6 | |||
292655cbdc | |||
a088c9ca7c | |||
8c1ec43500 | |||
1ac1c6dc9e | |||
87ffd6eef2 | |||
41139cea76 | |||
d98b7275d1 | |||
77cca1ce84 | |||
c0bbbdb7ee | |||
8e6b7aaec0 | |||
aae6820fdc | |||
2a4f35959b | |||
f71708e5c5 | |||
6de00b1f21 | |||
2b27b40142 | |||
55101b1a4a | |||
c014fd7f6d | |||
53a3be0703 | |||
04a178e7da | |||
6d5b6b2ff7 | |||
ab97de2763 | |||
34f7e4d448 | |||
f7c139030f | |||
c967308859 | |||
02207f6cfd | |||
badddcf0de | |||
b6f1c516e2 | |||
932a47a8a7 | |||
f69f78db34 | |||
fc2c465bac | |||
f2a7f8efda | |||
e6c172ac22 | |||
d8e7481118 | |||
2485ef8547 | |||
b17762f8d9 | |||
b3611eef9d | |||
a68c1adba6 | |||
42b536e40b | |||
d6e49be268 | |||
3d10dad780 | |||
c9a727594e | |||
cd25e1283a | |||
6675de1a26 | |||
96618e9517 | |||
dff239141d | |||
abbb329ba6 | |||
5bc77fc6e8 | |||
9c820fcca1 | |||
bb064a1ba7 | |||
a5d9fb518a | |||
01dd46c5ed | |||
ed51989796 | |||
bd20977ebc | |||
11e10f6eff | |||
c88265ac04 | |||
3f655ca50a | |||
359d4dc1b2 | |||
ca47446b46 | |||
cffb2db7ae | |||
0272907596 | |||
321a4b24b9 | |||
8a243ffb57 | |||
c2330fe3af | |||
9876732875 | |||
29e453c201 | |||
6c14402992 | |||
bdee525336 | |||
fbf13efe74 | |||
415df2357c | |||
4fc8800a37 | |||
6a532b836d | |||
31b94fd3ff | |||
fd733e819d | |||
7b6d52c613 | |||
df69559b39 | |||
e388f0d563 | |||
85e7b78b21 | |||
42a97f8c40 | |||
5cff247aa4 | |||
f115691365 | |||
deb66436cb | |||
b8152dd7f9 | |||
678c004a64 | |||
d9f44c1f7d | |||
088160ed32 | |||
8de004d281 | |||
bcde4337ac | |||
401210da44 | |||
beb81b657e | |||
f7b3450d65 | |||
29776c739a | |||
bdf322ceb0 | |||
fae763dbb0 | |||
c0792522a4 | |||
c67e62bac3 | |||
9dc184adff | |||
7118817df7 | |||
287b83b6c6 | |||
5d03eef051 | |||
48f7b06549 | |||
4e111cebbe | |||
fe32332e2a | |||
0bb6e1cdc2 | |||
e67bb64311 | |||
9058536406 | |||
5bc9e1632a | |||
a0a8899801 | |||
aedb4749a2 | |||
f52d49ad00 | |||
a1e7592bd8 | |||
c784d24fa6 | |||
0375c0b2c5 | |||
135e55fe27 | |||
1ed291086a | |||
f6e25627de | |||
8fe79a1fb5 | |||
a1df78517f | |||
92fa8d683a | |||
df27138401 | |||
7c7d40ea44 | |||
aa70be525d | |||
f7ac969a4a | |||
4f5e52fdd4 | |||
5183848250 | |||
69af1baf7a | |||
9a28cbc1e4 | |||
c38b457ba0 | |||
1a50f7062a | |||
8cfd80ba84 | |||
e5e14e1f9e | |||
6611464f73 | |||
51d93f0217 | |||
fdc7981d18 | |||
a63d165dd3 | |||
145a744ce0 | |||
df625a998f | |||
21c1a499ac | |||
9a81cabece | |||
b50dc206eb | |||
9044760a10 | |||
0b811773e1 | |||
627a720d4b | |||
89d45e7775 | |||
c0e6e03dc6 | |||
05fd8e2a38 | |||
dd59748bf0 | |||
38ceaf5253 | |||
b2fba5083b | |||
be6a209fe9 | |||
0f0305c602 | |||
d5350fd719 | |||
ea75f63dfb | |||
de8e530b37 | |||
bff927c6eb | |||
985bb44559 | |||
9c44cae5b8 | |||
6872455922 | |||
ce13a5152b | |||
d7c13cc291 | |||
022c0746c0 | |||
06c3f57f62 | |||
9da27cc56e | |||
92c5497eab | |||
f115fe47fe | |||
224f08279b | |||
a3b660a2c9 | |||
89df50da4e | |||
cce3b3a559 | |||
f53cc10338 | |||
61fb4d584c | |||
b93e2ca4cf | |||
325b3c6271 | |||
859e9ca653 | |||
441e2a69d8 | |||
af937f2e31 | |||
912629c2dc | |||
879fa484f6 | |||
e86103fdcc | |||
920ffa8c24 | |||
488f81ef74 | |||
0a5461cbea | |||
40c934c544 | |||
bb43e2aa03 | |||
ed277357cf | |||
bf1fb8b7bd | |||
e615cba9a5 | |||
8adabe1f74 | |||
896b34d1a2 | |||
5fd1865504 | |||
0b318a19c6 | |||
622d6c0cab | |||
3f4140900d | |||
b434bc93a3 | |||
11529f2795 | |||
d86b030796 | |||
a7e4657752 | |||
2f28fcba05 | |||
6da350aee6 | |||
9585f9a1a6 | |||
fd4876be24 | |||
31e2fe6a4c | |||
aa51968603 | |||
a3336368e5 | |||
33c0c6ff3b | |||
eaf37c828b | |||
d65a8e84f0 | |||
b000d96dd6 | |||
cd867f800e | |||
124f0e7093 | |||
fb897e37d1 | |||
3e5a48e5e4 | |||
3f88a67865 | |||
ef0b546d4c | |||
088f8b8b54 | |||
1ebcafb25d | |||
90396153f4 | |||
0fa93cf615 | |||
15a7a2b0ea | |||
446c254bc8 | |||
e41edc1fb7 | |||
738f776d36 | |||
e77db309b8 | |||
a6c1de1cb2 | |||
ace54f8175 | |||
d7043bcaeb | |||
04bbc764a0 | |||
91f7056767 | |||
8299093bf8 | |||
a0dffcf51f | |||
b3daf7d760 | |||
724e1d33b6 | |||
9deaff9181 | |||
b9ea6e8d96 | |||
231771e16c | |||
5b8308b3d2 | |||
f91f9c4862 | |||
24b848faac | |||
7d0ea614da | |||
2615b067e2 | |||
cd1abb60d7 | |||
436ec0ced7 | |||
937fb85376 | |||
a405324907 | |||
fba55711f2 | |||
208552f0b4 | |||
c7cdb950ce | |||
921169b3ad | |||
d7d3731567 | |||
d5ff5fd6f8 | |||
e195257d2a | |||
466ec7b962 | |||
8bfe59c8a8 | |||
de512216c4 | |||
9936b402a6 | |||
bd2dfaad2e | |||
8e539bebea | |||
022cde2c00 | |||
572f58a3a4 | |||
07e2bdac81 | |||
fb00929ee9 | |||
fb5da15746 | |||
48363aa3b0 | |||
090a7794b5 | |||
c63d8e7a30 | |||
d6ea69a115 | |||
4358f9fd2a | |||
af2ef36d68 | |||
316211372c | |||
29a2d41331 | |||
9f8046324d | |||
af05c34da3 | |||
a4410f3a02 | |||
4503199d25 | |||
c275adbb91 | |||
4061232fe3 | |||
507471e318 | |||
f0f613e2cf | |||
0ec8def0d8 | |||
fbaaed1516 | |||
152c196c65 | |||
e4dc84a5d8 | |||
5fb3b0e0e3 | |||
2854fb5f6c | |||
a9adb2f1a2 | |||
51383afd50 | |||
10bdde4459 | |||
4b02ecd6e8 | |||
b84638ac5a | |||
79f2882aaf | |||
1c978f64b1 | |||
5e9496ef36 | |||
8b6268966e | |||
52434819c3 | |||
29eb87b7ef | |||
c38026886a | |||
63e330b83d | |||
164da0fd9f | |||
e30b1de100 | |||
6940ad3fd9 | |||
8ae15141f6 | |||
89007923c7 | |||
4c10b9844b | |||
f3d69599aa | |||
fc8f91baec | |||
853bf3065a | |||
8d712c81d4 | |||
aa0597da2a | |||
a29f33020d | |||
9eb441ac44 | |||
10e7c96379 | |||
6c474daacd | |||
bb7ed73743 | |||
ea749d69a3 | |||
5bc0dfd616 | |||
fef34dfe82 | |||
a3dd5c1e92 | |||
d873d653d0 | |||
d9934ad8db | |||
26e5364aea | |||
91451111a2 | |||
b104bec49d | |||
0ac33b64b1 | |||
82141cec6e | |||
218313428f | |||
44b47b49bc | |||
2f69317f5d | |||
e1eff7b744 | |||
3aa12281c3 | |||
72920130c0 | |||
0fd00331e1 | |||
7a4763ee68 | |||
647a78b791 | |||
82faa91ce3 | |||
ad664dfb9f | |||
005ac9e732 | |||
d8e3fe542d | |||
c40e4f6c5a | |||
9bb3195bca | |||
eee0bc4985 | |||
a24d670f54 | |||
51e049ab78 | |||
01e37dfab8 | |||
74087edebb | |||
db58c9b77f | |||
c4dad1c20b | |||
cae04656b9 | |||
6586c4e387 | |||
4e60a81b36 | |||
464b4b18a4 | |||
e5c0969047 | |||
ab0d144300 | |||
ac3823e10a | |||
d3a4126e27 | |||
3a62acc54d | |||
bf8268adc4 | |||
7d4f25b354 | |||
0f2d480036 | |||
043e3784e8 | |||
58ab06b4f8 | |||
11544fe8ef | |||
8776a45ee9 | |||
a7bb059096 | |||
ba8977b47f | |||
032a6adaab | |||
460fbb18c7 | |||
978ac50015 | |||
b323b9c843 | |||
1afcf34829 | |||
48d9ad00e1 | |||
ca10356fd9 | |||
275bd44e15 | |||
beb2d880ea | |||
61d2107e9c | |||
b06f1c81bc | |||
8bb83782c7 | |||
aa05458f1d | |||
c694160c9c | |||
5b24a8f21d | |||
2c23c42c98 | |||
edcadb7dd1 | |||
3bce3502d2 | |||
9942227c6c | |||
02b5c3da71 | |||
ef533b4c87 | |||
3ecc883944 | |||
a1fadce7c6 | |||
79bc1290ae | |||
10272ef395 | |||
f03c49850b | |||
6677c10173 | |||
3223a3ac54 | |||
497fe1e68a | |||
3b334c4230 | |||
7f115f2e83 | |||
12aa04be93 | |||
a7ece4fdf3 | |||
57d1ed1073 | |||
b04ebb1782 | |||
958dbfdfa5 | |||
eb724336f5 | |||
9cdd4bee97 | |||
74cc77400c | |||
247a39c0a9 | |||
1b0c13a417 | |||
abb2e231f6 | |||
50ef4cc5da | |||
34bf9b729e | |||
0a6f607e22 | |||
9e4c61c139 | |||
144418434b | |||
a20ad68fe3 | |||
a50a3df716 | |||
f29124773b | |||
c1235897df | |||
4a52869d23 | |||
f9098b5379 | |||
6a95ff56df | |||
340829bb71 | |||
8aa48effaa | |||
a5fadcda15 | |||
f515674dff | |||
72ef5d8f8d | |||
a0a077eaaa | |||
8feb4365dd | |||
94b2dd74de | |||
678597cc96 | |||
417d82de58 | |||
f3adff1da1 | |||
0473c36c6f | |||
c9cb75aee1 | |||
36011e124a | |||
b9420040f5 | |||
59b925a028 | |||
7af075633b | |||
09891bb0ad | |||
160ebe01d9 | |||
a096e6b337 | |||
3c41a5e910 | |||
a3e39987d4 | |||
47f5ea881f | |||
faf8f1fbbc | |||
9f9de27a57 | |||
9a3ec56eb4 | |||
4f03ee814a | |||
6f84815801 | |||
9588a582ce | |||
ed8e549ecb | |||
c79ebd4eeb | |||
2a85bb28b9 | |||
13ea1342fb | |||
c707d4bfd8 | |||
aeacdad484 | |||
6d9bec3e0b | |||
1b5554eeda | |||
bf140be75e | |||
eb4c7c6841 | |||
dcd3c709fe | |||
f966187ea4 | |||
a288c0f280 | |||
a746d4cc3a | |||
f1cca207fc | |||
f4bb9b604a | |||
7be6ee9a68 | |||
a22c79c58a | |||
b642e019e8 | |||
578bab5fdd | |||
b48f08e65c | |||
81c14ba610 | |||
4b84fb5ac5 | |||
7f5e650796 | |||
c22e2e8159 | |||
ee8a53188c | |||
98f86a44ef | |||
1b3169e0d0 | |||
8273a396c8 | |||
5bad914411 | |||
879d260202 | |||
ce4d75f62a | |||
f30622424a | |||
09e7d56ff2 | |||
5ca23b5363 | |||
f7e70d25ea | |||
4338c41112 | |||
01f9b25be2 | |||
8b0458cdf6 | |||
bed978a26a | |||
73fbc81067 | |||
0d7f84857c | |||
42d6815ece | |||
d1db9fb659 | |||
b74fb2ef5e | |||
f3e228e8a4 | |||
6b5742c1ff | |||
57595988f5 | |||
81418a7712 | |||
885c7bbb10 | |||
d4c25359bd | |||
44f406b4b9 | |||
427d2fed8c | |||
51d454cded | |||
ab2bdfc508 | |||
3892b93bca | |||
83c2e907c7 | |||
32b7cc68b9 | |||
a61e3cd689 | |||
f922598127 | |||
e11c289150 | |||
1b37c61b5a | |||
2d3bfa9a89 | |||
e414b9edf1 | |||
62d3fc65e0 | |||
262ad45b79 | |||
cd90702fe5 | |||
012b1b56aa | |||
ff999a6dda | |||
797553ce16 | |||
8f82c8ad3d | |||
7a2c132b8e | |||
ba9f6fef99 | |||
6633a96245 | |||
745f8d32b5 | |||
f715478070 | |||
5f2aaeac57 | |||
044a4f7575 | |||
83d5e458ca | |||
f7669b6797 | |||
eb56567812 | |||
489f981e40 | |||
19adbeebd5 | |||
dc93368e03 | |||
8d3166c5fe | |||
07caea8b4e | |||
141b7ac554 | |||
4bc5f1401f | |||
13a2d3dfdd | |||
d62f0de862 | |||
0073ddf237 | |||
e411f54236 | |||
6025b44e5b | |||
f4f427dd2a | |||
ea226a1697 | |||
26c5c9c839 | |||
5cddb269d6 | |||
0d5099f230 | |||
b55814a1c0 | |||
eb5382e0de | |||
39d509a756 | |||
b3f1714ba9 | |||
708525ef9d | |||
df14e6d43e | |||
faedd325be | |||
600fbb2ef8 | |||
b4aedb5f84 | |||
ed7ebf2da1 | |||
dd1e6402c9 | |||
78689e7443 | |||
aa57b1bc77 | |||
491d476cac | |||
f0053a2f78 | |||
10e7a3b35b | |||
4147fd6b19 | |||
2bb903088e | |||
c90f985fcc | |||
2ebaacfc89 | |||
c339bd49d0 | |||
c349fb0e37 | |||
bc825bdefa | |||
ed49ce8e1d | |||
ad2ecd538d | |||
ff8e3f0af4 | |||
698e17178a | |||
ebeee70931 | |||
b8b118bdeb | |||
5ddd7d1b14 | |||
450b23436f | |||
89793ac338 | |||
c456812a46 | |||
6f8f6e9233 | |||
5770d00f81 | |||
a0fb1eff4a | |||
89dc240a22 | |||
ee4f069341 | |||
011bb9f5b1 | |||
08b06e1b4e | |||
0416a2ff15 | |||
3a7cdfcaa4 | |||
c36a47576d | |||
f14af7cf83 | |||
4014c48c62 | |||
6c9135c093 | |||
19993199db | |||
5b9f362925 | |||
80ea9001b3 | |||
24bb94ceac | |||
0f16351f5f | |||
b60b26bbd0 | |||
86e53e08de | |||
d3cb10a74e | |||
7d6cfff719 | |||
cc0fe0a1a9 | |||
25327342fb | |||
e02cf67f85 | |||
bf4bef6d62 | |||
76645bce6e | |||
9276c491bc | |||
fa59b4f8a7 | |||
934a37c36b | |||
5362f62078 | |||
ccd360687e | |||
5a2e8a838c | |||
3abae1cc75 | |||
b68ef8c983 | |||
d5f5ba95bb | |||
e8638cb0b3 | |||
62f9071adc | |||
1d079dd9a4 | |||
cccb56bda1 | |||
5d8dc241d8 | |||
9ba7312caf | |||
8ebda219c4 | |||
47f14e8555 | |||
974a24d03b | |||
15f225537e | |||
a32572fc96 | |||
be3ed9b6af | |||
a0939e1c48 | |||
003dca9d45 | |||
5c1770247c | |||
021dde66eb | |||
5840a3e1e2 | |||
7c6478fe6b | |||
68aca55e6f | |||
ba674935f4 | |||
a053d55fbc | |||
38ba8852a3 | |||
3533359fae | |||
0a988d1c69 | |||
5f9e65cc9b | |||
026188268d | |||
0e3464457c | |||
56195434e7 | |||
ba2194f435 | |||
e7df172da1 | |||
e7606e6dca | |||
8d4c0f505c | |||
8f2878a841 | |||
77296348a0 | |||
a62a7d5330 | |||
bf60aae9d8 | |||
ecc1520100 | |||
f1f6a2b341 | |||
55bf1c31a6 | |||
e47dd3d587 | |||
af0e3a278f | |||
493ad93957 | |||
dbe8f3cfbe | |||
08cdac968d | |||
f12d5ba689 | |||
0afd77d110 | |||
7551941ef2 | |||
9ca0307e1c | |||
9a6f8be28c | |||
9baf3b5a09 | |||
ca3f0873f3 | |||
adb0201449 | |||
cf293642fb | |||
10e1106760 | |||
3f2d375a53 | |||
f8e121ee06 | |||
0ee005579b | |||
6ecd7fced8 | |||
aeaf4d78f8 | |||
7baf0ddcc2 | |||
d79e141fe5 | |||
030071e659 | |||
9cbf226cfd | |||
36aabf23e1 | |||
8b67255186 | |||
3186661420 | |||
46896d9e86 | |||
2c4fd340c8 | |||
ae6d052978 | |||
974891a085 | |||
d44cd16682 | |||
23e99a3ed8 | |||
024a457250 | |||
788cb843fc | |||
790e0908a3 | |||
7a45cd5b56 | |||
f61a8ce51d | |||
fcce29a467 | |||
5f568733f3 | |||
96340de17d | |||
3611f67fb4 | |||
353ccbd444 | |||
3c1179d27b | |||
e502caee9f | |||
da8b870670 | |||
6b26859983 | |||
7afd224aff | |||
62e7bead73 | |||
116f7d1c4a | |||
18f89cc341 | |||
7c99ae1b3b | |||
16dc4d298d | |||
762c378bd6 | |||
515289134e | |||
3d1afe7cf2 | |||
fd825b1049 | |||
136e90638a | |||
9bf071132d | |||
014bb2f426 | |||
56927927c8 | |||
b19a4d2977 | |||
f4b838d8e2 | |||
c6cfd24f19 | |||
10f36f40d6 | |||
9d5cf9163a | |||
9abce0cca3 | |||
c6245f4fa3 | |||
75fc160204 | |||
263198dd89 | |||
345f96055d | |||
51144aa45e | |||
86a599d13f | |||
0cf81e6f7a | |||
8874fe973c | |||
f8a03226ee | |||
32db1e3045 | |||
303e6c0102 | |||
18883f1ba3 | |||
5c31271e91 | |||
00981cf4e8 | |||
968f4a69e8 | |||
e7e1a9bf50 | |||
fe1becb001 | |||
7789171c71 | |||
3fd2222c99 | |||
6de36a88c0 | |||
b37685542d | |||
a2b1b9e746 | |||
8017324033 | |||
7464497c88 | |||
499def3daa | |||
6931b75cc5 | |||
f853610578 | |||
69f51b88bf | |||
e0d680201a | |||
1566b8f8b8 | |||
4bbf78e840 | |||
7ab16a69df | |||
95e60ed775 | |||
d38cd2547a | |||
2159b72e69 | |||
81c23bbf9d | |||
0d5b8edf31 | |||
fcdb80830b | |||
50b48ab25c | |||
31b45666b0 | |||
233e76724a | |||
af637a82c3 | |||
ea32ea11f2 | |||
1b7a0de745 | |||
50e0cb65d9 | |||
ba4807f62c | |||
5efc02a238 | |||
8e50ac67bc | |||
a3c03e8ceb | |||
5a3e30b30a | |||
e3ab90042d | |||
f35c15f7d2 | |||
32387cd034 | |||
cf5c816483 | |||
bf9b9ca54c | |||
0ca2ca33c2 | |||
51f25e96e9 | |||
1875047638 | |||
fa4d61eaf0 | |||
49eb638e15 | |||
fc1f290b85 | |||
9194dc0161 | |||
0d480dbf7c | |||
183e83684a | |||
7b4ac7998a | |||
d75c6b0c36 | |||
40b222f8bc | |||
aa7dfb7bee | |||
6c1453eb54 | |||
c1845aec83 | |||
eb8479ac9a | |||
636c027298 | |||
02e187f066 | |||
854112095b | |||
a71c805959 | |||
c3ced0d089 | |||
80996ea63e | |||
aff51f8af1 | |||
ccbb81e9f5 | |||
f88dd28c51 | |||
a65a71df5d | |||
22f2ecc433 | |||
7f90ad7847 | |||
1292c0ecea | |||
55b7d5025b | |||
6a310bbaa9 | |||
bc8753da85 | |||
7f63e318f1 | |||
6c749319cf | |||
7a4463e104 | |||
e1be4ba925 | |||
34d21c1de3 | |||
fae36aebf4 | |||
75e828923a | |||
b499b87f8c | |||
233dbec4b3 | |||
d56ff9592e | |||
08f6317beb | |||
a75457ad88 | |||
b0482003bd | |||
634356e72f | |||
6d3cc16ab1 | |||
6027671c09 | |||
29d0cb4a15 | |||
fe7001975a | |||
ac88f1c146 | |||
b5b86218c5 | |||
bdcc6e52e6 | |||
0eae817aa6 | |||
8994b42760 | |||
6a63ce992a | |||
9ae6285eef | |||
8f9737f567 | |||
f287d313c3 | |||
e745836404 | |||
08baf798aa | |||
8bcb14c65d | |||
d94dc68830 | |||
297fed6aef | |||
d690d6e0e3 | |||
9ba8d88b07 | |||
34a40b0131 | |||
182bf5f2bd | |||
04638535d8 | |||
d87c8428fe | |||
166fb9a8e4 | |||
28a21d0b8f | |||
d1d1d60c30 | |||
80fd49d60b | |||
34eb1331a3 | |||
bff329a329 | |||
604929d002 | |||
4a9151e4aa | |||
020cc89576 | |||
25898d34ca | |||
6394388714 | |||
d4101c7bdf | |||
c93bf89cbe | |||
88d1f29fe2 | |||
c437a33f2a | |||
e3259f39f1 | |||
cb357b0a16 | |||
a7faf445c4 | |||
82a08f24c0 | |||
afa89ac125 | |||
2060b5cd34 | |||
d69730a333 | |||
9714a30148 | |||
23c0f2c313 | |||
5c4139be45 | |||
6b1a3a20e5 | |||
4ae00c80ca | |||
827792c4f0 | |||
abbe700dac | |||
1347bfe243 | |||
a76ee95b6d | |||
f3689f09cd | |||
d545cfd38c | |||
3631a9fac2 | |||
aee4ad2d3f | |||
f88c86c799 | |||
60ac27e401 | |||
d0567de4e6 | |||
ca30fd6088 | |||
1470e9d5ca | |||
f45efe2aa8 | |||
5b6c475817 | |||
4abd2d709f | |||
d97aff85b3 | |||
deec65446f | |||
5c662b1ae1 | |||
f648940388 | |||
5aae17754f | |||
0ef0f6ece1 | |||
bfd46f28e0 | |||
eaece18afc | |||
67d39b037c | |||
dd3f5a146d | |||
9fdc5b4b9d | |||
1f32d13698 | |||
886b1019ed | |||
3e8ed8a171 | |||
8307daee63 | |||
9b40d10352 | |||
f2a06eab37 | |||
74fd70416f | |||
b85c164195 | |||
75c41b645a | |||
54c8b3ef29 | |||
56bde40035 | |||
ff4a015baa | |||
0db4fcc27e | |||
f3080b6277 | |||
69cbbd5811 | |||
0b85760939 | |||
03f3a4805f | |||
d95adf2631 | |||
e971d40e06 | |||
c65a01a5f0 | |||
8586014e17 | |||
bdfae4ba04 | |||
75cb94b51a | |||
2f6d163a7a | |||
ecfe72bcad | |||
e6ff9e18cc | |||
3c550c1781 | |||
537693f5cf | |||
5ae0589547 | |||
71fc6fc257 | |||
c0d7b16ee6 | |||
f3f7aa9e1d | |||
43355970db | |||
bfa386acba | |||
e8b432485d | |||
a12a34e3bb | |||
b79855c01d | |||
17fe501a6d | |||
8201b367ec | |||
6c242084ca | |||
aefe7b176a | |||
6059b85e58 | |||
aa46c52eee | |||
d3cbfbdb59 | |||
cc9b77b876 | |||
1568ac9e8a | |||
1129dacdfa |
7
.editorconfig
Normal file
@ -0,0 +1,7 @@
|
||||
[*.{kt,kts}]
|
||||
indent_size=4
|
||||
insert_final_newline=true
|
||||
ij_kotlin_allow_trailing_comma=true
|
||||
ij_kotlin_allow_trailing_comma_on_call_site=true
|
||||
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
24
.gitattributes
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
* text=auto
|
||||
* text eol=lf
|
||||
|
||||
# Windows forced line-endings
|
||||
/.idea/* text eol=crlf
|
||||
|
||||
# Gradle wrapper
|
||||
*.jar binary
|
||||
|
||||
# Images
|
||||
*.webp binary
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.gz binary
|
||||
*.zip binary
|
||||
*.7z binary
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.woff binary
|
||||
*.pyc binary
|
||||
*.swp binary
|
34
.github/CONTRIBUTING.md
vendored
@ -1,34 +0,0 @@
|
||||
# Catalogue requests
|
||||
|
||||
* Catalogue requests should be created at https://github.com/inorichi/tachiyomi-extensions/issues, not here
|
||||
|
||||
# Bugs
|
||||
* Include version (Setting > About > Version)
|
||||
* If not latest, try updating, it may have already been solved
|
||||
* Dev version is equal to the number of commits as seen in the main page
|
||||
* Include steps to reproduce (if not obvious from description)
|
||||
* Include screenshot (if needed)
|
||||
* If it could be device-dependent, try reproducing on another device (if possible), include results and device names, OS, modifications (root, Xposed)
|
||||
* **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).**
|
||||
* For large logs use http://pastebin.com/ (or similar)
|
||||
* For multipart issues **use list** like this:
|
||||
* [x] Done
|
||||
* [ ] Not done
|
||||
```
|
||||
* [x] Done
|
||||
* [ ] Not done
|
||||
```
|
||||
* Don't put together too many 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
|
||||
|
||||
# Feature requests
|
||||
|
||||
* Write a detailed issue, explaning what it should do or how. Avoid writing just "like X app does"
|
||||
* Include screenshot (if needed)
|
||||
|
||||
# Translations
|
||||
|
||||
[Wiki](https://github.com/inorichi/tachiyomi/wiki/Translation)
|
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
ko_fi: inorichi
|
35
.github/ISSUE_TEMPLATE.md
vendored
@ -1,7 +1,34 @@
|
||||
**Please read https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md before posting**
|
||||
**PLEASE READ THIS**
|
||||
|
||||
Remove line above and describe your issue here. Fill out version below. Use Preview.
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated:
|
||||
- To the latest version of the app (stable is v0.14.6)
|
||||
- All extensions
|
||||
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open or closed issue
|
||||
- I will fill out the title and the information in this template
|
||||
|
||||
Version: r000 or v0.0.0
|
||||
(other relevant info like OS)
|
||||
Note that the issue will be automatically closed if you do not fill out the title or requested information.
|
||||
|
||||
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
||||
|
||||
---
|
||||
|
||||
## Device information
|
||||
* Tachiyomi version: ?
|
||||
* Android version: ?
|
||||
* Device: ?
|
||||
|
||||
## Steps to reproduce
|
||||
1. First step
|
||||
2. Second step
|
||||
|
||||
## Issue/Request
|
||||
?
|
||||
|
||||
## Other details
|
||||
Additional details and attachments.
|
||||
|
||||
If you're experiencing crashes, share the crash logs from More → Settings → Advanced → Dump crash logs.
|
||||
|
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ⚠️ Extension/source issue
|
||||
url: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
|
||||
about: Issues and requests for extensions and sources should be opened in the tachiyomi-extensions repository instead
|
||||
- name: 📦 Tachiyomi extensions
|
||||
url: https://tachiyomi.org/extensions
|
||||
about: List of all available extensions with download links
|
||||
- name: 🖥️ Tachiyomi website
|
||||
url: https://tachiyomi.org/help/
|
||||
about: Guides, troubleshooting, and answers to common questions
|
106
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
name: 🐞 Issue report
|
||||
description: Report an issue in Tachiyomi
|
||||
labels: [Bug]
|
||||
body:
|
||||
|
||||
- type: textarea
|
||||
id: reproduce-steps
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Provide an example of the issue.
|
||||
placeholder: |
|
||||
Example:
|
||||
1. First step
|
||||
2. Second step
|
||||
3. Issue here
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: Explain what you should expect to happen.
|
||||
placeholder: |
|
||||
Example:
|
||||
"This should happen..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
attributes:
|
||||
label: Actual behavior
|
||||
description: Explain what actually happens.
|
||||
placeholder: |
|
||||
Example:
|
||||
"This happened instead..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: crash-logs
|
||||
attributes:
|
||||
label: Crash logs
|
||||
description: |
|
||||
If you're experiencing crashes, share the crash logs from **More → Settings → Advanced** then press **Dump crash logs**.
|
||||
placeholder: |
|
||||
You can paste the crash logs in plain text or upload it as an attachment.
|
||||
|
||||
- type: input
|
||||
id: tachiyomi-version
|
||||
attributes:
|
||||
label: Tachiyomi version
|
||||
description: You can find your Tachiyomi version in **More → About**.
|
||||
placeholder: |
|
||||
Example: "0.14.6"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: android-version
|
||||
attributes:
|
||||
label: Android version
|
||||
description: You can find this somewhere in your Android settings.
|
||||
placeholder: |
|
||||
Example: "Android 11"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: device
|
||||
attributes:
|
||||
label: Device
|
||||
description: List your device and model.
|
||||
placeholder: |
|
||||
Example: "Google Pixel 5"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: other-details
|
||||
attributes:
|
||||
label: Other details
|
||||
placeholder: |
|
||||
Additional details and attachments.
|
||||
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
||||
options:
|
||||
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
|
||||
required: true
|
||||
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
|
||||
required: true
|
||||
- label: I have updated the app to version **[0.14.6](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
||||
required: true
|
||||
- label: I have updated all installed extensions.
|
||||
required: true
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
required: true
|
39
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
name: ⭐ Feature request
|
||||
description: Suggest a feature to improve Tachiyomi
|
||||
labels: [Feature request]
|
||||
body:
|
||||
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
attributes:
|
||||
label: Describe your suggested feature
|
||||
description: How can Tachiyomi be improved?
|
||||
placeholder: |
|
||||
Example:
|
||||
"It should work like this..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: other-details
|
||||
attributes:
|
||||
label: Other details
|
||||
placeholder: |
|
||||
Additional details and attachments.
|
||||
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
||||
options:
|
||||
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
|
||||
required: true
|
||||
- label: I have updated the app to version **[0.14.6](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
||||
required: true
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
required: true
|
10
.github/mergify.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
#pull_request_rules:
|
||||
# - name: Automatically merge translations
|
||||
# conditions:
|
||||
# - "author = weblate"
|
||||
# - "-conflict"
|
||||
# - "current-day-of-week = Sat"
|
||||
# - "created-at < 1 day ago"
|
||||
# actions:
|
||||
# merge:
|
||||
# method: squash
|
12
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
<!--
|
||||
Please include a summary of the change and which issue is fixed.
|
||||
Also make sure you've tested your code and also done a self-review of it.
|
||||
Don't forget to check all base themes and tablet mode for relevant changes.
|
||||
|
||||
If your changes are visual, please provide images below:
|
||||
|
||||
### Images
|
||||
| Image 1 | Image 2 |
|
||||
| ------- | ------- |
|
||||
|  |  |
|
||||
-->
|
BIN
.github/readme-images/app-icon.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
12
.github/renovate.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"schedule": ["every sunday"],
|
||||
"ignoreDeps": [
|
||||
"androidx.core:core-splashscreen",
|
||||
"com.android.tools:r8",
|
||||
"com.google.guava:guava",
|
||||
"com.github.commandiron:WheelPickerCompose"
|
||||
]
|
||||
}
|
39
.github/workflows/build_pull_request.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
name: PR build check
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'i18n/src/main/res/**/strings.xml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build app
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@v3
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: adopt
|
||||
|
||||
- name: Build app and run unit tests
|
||||
uses: gradle/gradle-command-action@v2
|
||||
with:
|
||||
arguments: lintKotlin assembleStandardRelease testStandardReleaseUnitTest
|
106
.github/workflows/build_push.yml
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- v*
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build app
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: adopt
|
||||
|
||||
- name: Build app and run unit tests
|
||||
uses: gradle/gradle-command-action@v2
|
||||
with:
|
||||
arguments: lintKotlin assembleStandardRelease testStandardReleaseUnitTest
|
||||
|
||||
# Sign APK and create release for tags
|
||||
|
||||
- name: Get tag name
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
||||
run: |
|
||||
set -x
|
||||
echo "VERSION_TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
|
||||
|
||||
- name: Sign APK
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
with:
|
||||
releaseDirectory: app/build/outputs/apk/standard/release
|
||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||
alias: ${{ secrets.ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Clean up build artifacts
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
||||
run: |
|
||||
set -e
|
||||
|
||||
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk tachiyomi-${{ env.VERSION_TAG }}.apk
|
||||
sha=`sha256sum tachiyomi-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
|
||||
|
||||
cp app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
||||
sha=`sha256sum tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
|
||||
|
||||
cp app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
||||
sha=`sha256sum tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
|
||||
|
||||
cp app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk tachiyomi-x86-${{ env.VERSION_TAG }}.apk
|
||||
sha=`sha256sum tachiyomi-x86-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
|
||||
|
||||
cp app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk tachiyomi-x86_64-${{ env.VERSION_TAG }}.apk
|
||||
sha=`sha256sum tachiyomi-x86_64-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
||||
|
||||
- name: Create Release
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ env.VERSION_TAG }}
|
||||
name: Tachiyomi ${{ env.VERSION_TAG }}
|
||||
body: |
|
||||
---
|
||||
|
||||
### Checksums
|
||||
|
||||
| Variant | SHA-256 |
|
||||
| ------- | ------- |
|
||||
| Universal | ${{ env.APK_UNIVERSAL_SHA }}
|
||||
| arm64-v8a | ${{ env.APK_ARM64_V8A_SHA }}
|
||||
| armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }}
|
||||
| x86 | ${{ env.APK_X86_SHA }} |
|
||||
| x86_64 | ${{ env.APK_X86_64_SHA }} |
|
||||
files: |
|
||||
tachiyomi-${{ env.VERSION_TAG }}.apk
|
||||
tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
||||
tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
||||
tachiyomi-x86-${{ env.VERSION_TAG }}.apk
|
||||
tachiyomi-x86_64-${{ env.VERSION_TAG }}.apk
|
||||
draft: true
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
35
.github/workflows/issue_moderator.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: Issue moderator
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited, reopened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
moderate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Moderate issues
|
||||
uses: tachiyomiorg/issue-moderator-action@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto-close-rules: |
|
||||
[
|
||||
{
|
||||
"type": "body",
|
||||
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
|
||||
"message": "The acknowledgment section was not removed."
|
||||
},
|
||||
{
|
||||
"type": "body",
|
||||
"regex": ".*\\* (Tachiyomi version|Android version|Device): \\?.*",
|
||||
"message": "Requested information in the template was not filled out."
|
||||
},
|
||||
{
|
||||
"type": "both",
|
||||
"regex": "^(?!.*myanimelist.*).*(aniyomi|anime).*$",
|
||||
"ignoreCase": true,
|
||||
"message": "Tachiyomi does not support anime, and has no plans to support anime. In addition Tachiyomi is not affiliated with Aniyomi https://github.com/jmir1/aniyomi"
|
||||
}
|
||||
]
|
19
.github/workflows/lock.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: Lock threads
|
||||
|
||||
on:
|
||||
# Daily
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
# Manual trigger
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '2'
|
||||
pr-inactive-days: '2'
|
11
.gitignore
vendored
@ -2,8 +2,15 @@
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
.DS_Store
|
||||
/build
|
||||
.idea/
|
||||
*iml
|
||||
*.iml
|
||||
*/build
|
||||
|
||||
# Built files
|
||||
*/build
|
||||
/build
|
||||
*.apk
|
||||
app/**/output.json
|
||||
|
||||
# Unnecessary file
|
||||
*.swp
|
41
.travis.yml
@ -1,41 +0,0 @@
|
||||
language: android
|
||||
android:
|
||||
components:
|
||||
- platform-tools
|
||||
- tools
|
||||
|
||||
# The BuildTools version used by your project
|
||||
- build-tools-25.0.1
|
||||
- android-25
|
||||
- extra-android-m2repository
|
||||
- extra-google-m2repository
|
||||
- extra-android-support
|
||||
- extra-google-google_play_services
|
||||
|
||||
licenses:
|
||||
- android-sdk-license-.+
|
||||
- '.+'
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
before_script:
|
||||
- chmod +x gradlew
|
||||
|
||||
before_install:
|
||||
- mkdir "$ANDROID_HOME/licenses" || true
|
||||
- echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license"
|
||||
- echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license"
|
||||
|
||||
#Build, and run tests
|
||||
script: "./gradlew clean buildStandardDebug"
|
||||
sudo: false
|
||||
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
env:
|
||||
- GRADLE_OPTS="-XX:MaxPermSize=1024m -XX:+CMSClassUnloadingEnabled -XX:+HeapDumpOnOutOfMemoryError -Xmx2048m"
|
126
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,126 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community moderators are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community moderators have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community moderators responsible for enforcement at
|
||||
the [Tachiyomi Discord server](https://discord.gg/tachiyomi).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community moderators are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community moderators will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community moderators, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/),
|
||||
version 2.1, available at
|
||||
[v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[FAQ](https://www.contributor-covenant.org/faq). Translations are available
|
||||
at [translations](https://www.contributor-covenant.org/translations).
|
50
CONTRIBUTING.md
Normal file
@ -0,0 +1,50 @@
|
||||
Looking to report an issue/bug or make a feature request? Please refer to the [README file](https://github.com/tachiyomiorg/tachiyomi#issues-feature-requests-and-contributing).
|
||||
|
||||
---
|
||||
|
||||
Thanks for your interest in contributing to Tachiyomi!
|
||||
|
||||
|
||||
# Code contributions
|
||||
|
||||
Pull requests are welcome!
|
||||
|
||||
If you're interested in taking on [an open issue](https://github.com/tachiyomiorg/tachiyomi/issues), please comment on it so others are aware.
|
||||
You do not need to ask for permission nor an assignment.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you start, please note that the ability to use following technologies is **required** and that existing contributors will not actively teach them to you.
|
||||
|
||||
- Basic [Android development](https://developer.android.com/)
|
||||
- [Kotlin](https://kotlinlang.org/)
|
||||
|
||||
### Tools
|
||||
|
||||
- [Android Studio](https://developer.android.com/studio)
|
||||
- Emulator or phone with developer options enabled to test changes.
|
||||
|
||||
## Getting help
|
||||
|
||||
- Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing.
|
||||
|
||||
# Translations
|
||||
|
||||
Translations are done externally via Weblate. See [our website](https://tachiyomi.org/help/contribution/#translation) for more details.
|
||||
|
||||
|
||||
# Forks
|
||||
|
||||
Forks are allowed so long as they abide by [the project's LICENSE](https://github.com/tachiyomiorg/tachiyomi/blob/master/LICENSE).
|
||||
|
||||
When creating a fork, remember to:
|
||||
|
||||
- To avoid confusion with the main app:
|
||||
- Change the app name
|
||||
- Change the app icon
|
||||
- Change or disable the [app update checker](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt)
|
||||
- To avoid installation conflicts:
|
||||
- Change the `applicationId` in [`build.gradle.kts`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/build.gradle.kts)
|
||||
- To avoid having your data polluting the main app's analytics and crash report services:
|
||||
- If you want to use Firebase analytics, replace [`google-services.json`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/src/standard/google-services.json) with your own
|
||||
- If you want to use ACRA crash reporting, replace the `ACRA_URI` endpoint in [`build.gradle.kts`](https://github.com/tachiyomiorg/tachiyomi/blob/master/app/build.gradle.kts) with your own
|
26
LICENSE
@ -174,29 +174,3 @@
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
||||
|
||||
|
85
README.md
@ -1,24 +1,77 @@
|
||||
| Build | Download | F-Droid |
|
||||
|-------|----------|-------------|
|
||||
| [](https://teamcity.kanade.eu/project.html?projectId=tachiyomi) [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) [](http://tachiyomi.kanade.eu/latest/app-debug.apk) | [](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi) [](//github.com/inorichi/tachiyomi/wiki/FDroid-for-dev-versions) |
|
||||
| Build | Stable | Weekly Preview | Contribute | Support Server |
|
||||
|-------|----------|---------|------------|---------|
|
||||
|  | [](https://github.com/tachiyomiorg/tachiyomi/releases) | [](https://github.com/tachiyomiorg/tachiyomi-preview/releases) | [](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [](https://discord.gg/tachiyomi) |
|
||||
|
||||
## [Report an issue](https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md)
|
||||
|
||||
**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.**
|
||||
# Tachiyomi
|
||||
Tachiyomi is a free and open source manga reader for Android 6.0 and above.
|
||||
|
||||
Tachiyomi is a free and open source manga reader for Android.
|
||||
## Features
|
||||
|
||||
Keep in mind it's still a beta, so expect it to crash sometimes.
|
||||
|
||||
# Features
|
||||
|
||||
* Online and offline reading
|
||||
* Configurable reader with multiple viewers and settings
|
||||
* MyAnimeList support
|
||||
* Resume from the next unread chapter
|
||||
* Chapter filtering
|
||||
* Schedule searching for updates
|
||||
Features include:
|
||||
* Online reading from a variety of sources
|
||||
* Local reading of downloaded content
|
||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
||||
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [MangaUpdates](https://mangaupdates.com), [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/tachiyomiorg/tachiyomi/releases).
|
||||
|
||||
If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/tachiyomiorg/tachiyomi-preview/releases).
|
||||
|
||||
## 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://tachiyomi.org/help/faq/), the [changelog](https://github.com/tachiyomiorg/tachiyomi/releases) and the already opened [issues](https://github.com/tachiyomiorg/tachiyomi/issues).**
|
||||
2. If you are unsure, ask here: [](https://discord.gg/tachiyomi)
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Bugs</summary>
|
||||
|
||||
* Include version (More → About → Version)
|
||||
* If not latest, try updating, it may have already been solved
|
||||
* Preview version is equal to the number of commits as seen in the main page
|
||||
* Include steps to reproduce (if not obvious from description)
|
||||
* Include screenshot (if needed)
|
||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||
* Don't group unrelated requests into one issue
|
||||
|
||||
DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71
|
||||
|
||||
DON'T: https://github.com/tachiyomiorg/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)
|
||||
|
||||
Source requests should be created at https://github.com/tachiyomiorg/tachiyomi-extensions, they do not belong in this repository.
|
||||
</details>
|
||||
|
||||
<details><summary>Contributing</summary>
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
</details>
|
||||
|
||||
<details><summary>Code of Conduct</summary>
|
||||
|
||||
See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
||||
</details>
|
||||
|
||||
## FAQ
|
||||
|
||||
[See our website.](https://tachiyomi.org/)
|
||||
You can also reach out to us on [Discord](https://discord.gg/tachiyomi).
|
||||
|
||||
## License
|
||||
|
||||
|
2
app/.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
/build
|
||||
*iml
|
||||
*.iml
|
||||
custom.gradle
|
||||
custom.gradle
|
||||
|
278
app/build.gradle
@ -1,278 +0,0 @@
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
if (file("custom.gradle").exists()) {
|
||||
apply from: "custom.gradle"
|
||||
}
|
||||
|
||||
ext {
|
||||
// Git is needed in your system PATH for these commands to work.
|
||||
// If it's not installed, you can return a random value as a workaround
|
||||
getCommitCount = {
|
||||
return 'git rev-list --count HEAD'.execute().text.trim()
|
||||
// return "1"
|
||||
}
|
||||
|
||||
getGitSha = {
|
||||
return 'git rev-parse --short HEAD'.execute().text.trim()
|
||||
// return "1"
|
||||
}
|
||||
|
||||
getBuildTime = {
|
||||
def df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")
|
||||
df.setTimeZone(TimeZone.getTimeZone("UTC"))
|
||||
return df.format(new Date())
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.2"
|
||||
publishNonDefault true
|
||||
|
||||
defaultConfig {
|
||||
applicationId "eu.kanade.tachiyomi"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 25
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
versionCode 23
|
||||
versionName "0.6.0"
|
||||
|
||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
||||
buildConfigField "boolean", "INCLUDE_UPDATER", "false"
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "arm64-v8a", "x86"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
versionNameSuffix "-${getCommitCount()}"
|
||||
applicationIdSuffix ".debug"
|
||||
multiDexEnabled true
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
multiDexEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
standard {
|
||||
buildConfigField "boolean", "INCLUDE_UPDATER", "true"
|
||||
}
|
||||
fdroid {
|
||||
}
|
||||
dev {
|
||||
minSdkVersion 21
|
||||
resConfigs "en", "xxhdpi"
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/DEPENDENCIES'
|
||||
exclude 'LICENSE.txt'
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
exclude 'META-INF/NOTICE'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
// Modified dependencies
|
||||
compile 'com.github.inorichi:subsampling-scale-image-view:01e5385'
|
||||
compile 'com.github.inorichi:junrar-android:634c1f5'
|
||||
|
||||
// Android support library
|
||||
final support_library_version = '25.4.0'
|
||||
compile "com.android.support:support-v4:$support_library_version"
|
||||
compile "com.android.support:appcompat-v7:$support_library_version"
|
||||
compile "com.android.support:cardview-v7:$support_library_version"
|
||||
compile "com.android.support:design:$support_library_version"
|
||||
compile "com.android.support:recyclerview-v7:$support_library_version"
|
||||
compile "com.android.support:support-annotations:$support_library_version"
|
||||
compile "com.android.support:customtabs:$support_library_version"
|
||||
|
||||
compile 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||
|
||||
compile 'com.android.support:multidex:1.0.1'
|
||||
|
||||
// ReactiveX
|
||||
compile 'io.reactivex:rxandroid:1.2.1'
|
||||
compile 'io.reactivex:rxjava:1.3.0'
|
||||
compile 'com.jakewharton.rxrelay:rxrelay:1.2.0'
|
||||
compile 'com.f2prateek.rx.preferences:rx-preferences:1.0.2'
|
||||
compile 'com.github.pwittchen:reactivenetwork:0.7.0'
|
||||
|
||||
// Network client
|
||||
compile "com.squareup.okhttp3:okhttp:3.8.1"
|
||||
compile 'com.squareup.okio:okio:1.13.0'
|
||||
|
||||
// REST
|
||||
final retrofit_version = '2.3.0'
|
||||
compile "com.squareup.retrofit2:retrofit:$retrofit_version"
|
||||
compile "com.squareup.retrofit2:converter-gson:$retrofit_version"
|
||||
compile "com.squareup.retrofit2:adapter-rxjava:$retrofit_version"
|
||||
|
||||
// JSON
|
||||
compile 'com.google.code.gson:gson:2.8.1'
|
||||
compile 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
||||
|
||||
// YAML
|
||||
compile 'com.github.bmoliveira:snake-yaml:v1.18-android'
|
||||
|
||||
// JavaScript engine
|
||||
compile 'com.squareup.duktape:duktape-android:1.1.0'
|
||||
|
||||
// Disk
|
||||
compile 'com.jakewharton:disklrucache:2.0.2'
|
||||
compile 'com.github.seven332:unifile:1.0.0'
|
||||
|
||||
// HTML parser
|
||||
compile 'org.jsoup:jsoup:1.10.3'
|
||||
|
||||
// Job scheduling
|
||||
compile 'com.evernote:android-job:1.1.11'
|
||||
compile 'com.google.android.gms:play-services-gcm:11.0.1'
|
||||
|
||||
// Changelog
|
||||
compile 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
|
||||
|
||||
// Database
|
||||
compile "com.pushtorefresh.storio:sqlite:1.13.0"
|
||||
|
||||
// Model View Presenter
|
||||
final nucleus_version = '3.0.0'
|
||||
compile "info.android15.nucleus:nucleus:$nucleus_version"
|
||||
compile "info.android15.nucleus:nucleus-support-v7:$nucleus_version"
|
||||
|
||||
// Dependency injection
|
||||
compile "uy.kohesive.injekt:injekt-core:1.16.1"
|
||||
|
||||
// Image library
|
||||
compile 'com.github.bumptech.glide:glide:3.8.0'
|
||||
compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0@aar'
|
||||
// Transformations
|
||||
compile 'jp.wasabeef:glide-transformations:2.0.2'
|
||||
|
||||
// Logging
|
||||
compile 'com.jakewharton.timber:timber:4.5.1'
|
||||
|
||||
// Crash reports
|
||||
compile 'ch.acra:acra:4.9.2'
|
||||
|
||||
// Sort
|
||||
compile 'com.github.gpanther:java-nat-sort:natural-comparator-1.1'
|
||||
|
||||
// UI
|
||||
compile 'com.dmitrymalkovich.android:material-design-dimens:1.4'
|
||||
compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
||||
compile 'eu.davidea:flexible-adapter:5.0.0-rc1'
|
||||
compile 'com.nononsenseapps:filepicker:2.5.2'
|
||||
compile 'com.github.amulyakhare:TextDrawable:558677e'
|
||||
compile 'com.afollestad.material-dialogs:core:0.9.4.5'
|
||||
compile 'me.zhanghai.android.systemuihelper:library:1.0.0'
|
||||
compile 'de.hdodenhof:circleimageview:2.1.0'
|
||||
compile 'com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.0.4'
|
||||
|
||||
// Conductor
|
||||
compile "com.bluelinelabs:conductor:2.1.4"
|
||||
compile 'com.github.inorichi:conductor-support-preference:9e36460'
|
||||
|
||||
// RxBindings
|
||||
final rxbindings_version = '1.0.1'
|
||||
compile "com.jakewharton.rxbinding:rxbinding-kotlin:$rxbindings_version"
|
||||
compile "com.jakewharton.rxbinding:rxbinding-appcompat-v7-kotlin:$rxbindings_version"
|
||||
compile "com.jakewharton.rxbinding:rxbinding-support-v4-kotlin:$rxbindings_version"
|
||||
compile "com.jakewharton.rxbinding:rxbinding-recyclerview-v7-kotlin:$rxbindings_version"
|
||||
|
||||
// Tests
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.assertj:assertj-core:1.7.1'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
|
||||
final robolectric_version = '3.1.4'
|
||||
testCompile "org.robolectric:robolectric:$robolectric_version"
|
||||
testCompile "org.robolectric:shadows-multidex:$robolectric_version"
|
||||
testCompile "org.robolectric:shadows-play-services:$robolectric_version"
|
||||
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
}
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.1.3'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
// Workaround to force a support lib version
|
||||
configurations.all {
|
||||
resolutionStrategy.eachDependency { details ->
|
||||
def requested = details.requested
|
||||
if (requested.group == 'com.android.support') {
|
||||
if (!requested.name.startsWith("multidex")) {
|
||||
details.useVersion '25.4.0'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add support for placeholders in resource files
|
||||
//https://code.google.com/p/android/issues/detail?id=69224
|
||||
def replacePlaceholdersInFile(basePath, fileName, placeholders) {
|
||||
def file = new File(basePath, fileName);
|
||||
|
||||
if (!file.exists()) {
|
||||
logger.quiet("Unable to replace placeholders in " + file.toString() + ". File cannot be found.")
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Replacing placeholders in " + file.toString())
|
||||
logger.debug("Placeholders: " + placeholders.toString())
|
||||
|
||||
def content = file.getText('UTF-8')
|
||||
|
||||
placeholders.each { entry ->
|
||||
content = content.replaceAll("\\\$\\{${entry.key}\\}", entry.value)
|
||||
}
|
||||
|
||||
file.write(content, 'UTF-8')
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
android.applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
output.processResources.doFirst {
|
||||
// prepare placeholder map from manifestPlaceholders including applicationId placeholder
|
||||
def placeholders = variant.mergedFlavor.manifestPlaceholders + [applicationId: variant.applicationId]
|
||||
|
||||
replacePlaceholdersInFile(resDir, 'xml-v25/shortcuts.xml', placeholders)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
323
app/build.gradle.kts
Normal file
@ -0,0 +1,323 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("com.mikepenz.aboutlibraries.plugin")
|
||||
kotlin("android")
|
||||
kotlin("plugin.serialization")
|
||||
id("com.github.zellius.shortcut-helper")
|
||||
}
|
||||
|
||||
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
||||
apply<com.google.gms.googleservices.GoogleServicesPlugin>()
|
||||
}
|
||||
|
||||
shortcutHelper.setFilePath("./shortcuts.xml")
|
||||
|
||||
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
android {
|
||||
namespace = "eu.kanade.tachiyomi"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "eu.kanade.tachiyomi"
|
||||
versionCode = 101
|
||||
versionName = "0.14.6"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime()}\"")
|
||||
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
||||
buildConfigField("boolean", "PREVIEW", "false")
|
||||
|
||||
// Please disable ACRA or use your own instance in forked versions of the project
|
||||
buildConfigField("String", "ACRA_URI", "\"https://tachiyomi.kanade.eu/crash_report\"")
|
||||
|
||||
ndk {
|
||||
abiFilters += SUPPORTED_ABIS
|
||||
}
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
isEnable = true
|
||||
reset()
|
||||
include(*SUPPORTED_ABIS.toTypedArray())
|
||||
isUniversalApk = true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
named("debug") {
|
||||
versionNameSuffix = "-${getCommitCount()}"
|
||||
applicationIdSuffix = ".debug"
|
||||
isPseudoLocalesEnabled = true
|
||||
}
|
||||
named("release") {
|
||||
isShrinkResources = true
|
||||
isMinifyEnabled = true
|
||||
proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro")
|
||||
}
|
||||
create("preview") {
|
||||
initWith(getByName("release"))
|
||||
buildConfigField("boolean", "PREVIEW", "true")
|
||||
|
||||
val debugType = getByName("debug")
|
||||
signingConfig = debugType.signingConfig
|
||||
versionNameSuffix = debugType.versionNameSuffix
|
||||
applicationIdSuffix = debugType.applicationIdSuffix
|
||||
matchingFallbacks.add("release")
|
||||
}
|
||||
create("benchmark") {
|
||||
initWith(getByName("release"))
|
||||
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
matchingFallbacks.add("release")
|
||||
isDebuggable = false
|
||||
versionNameSuffix = "-benchmark"
|
||||
applicationIdSuffix = ".benchmark"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("preview").res.srcDirs("src/debug/res")
|
||||
getByName("benchmark").res.srcDirs("src/debug/res")
|
||||
}
|
||||
|
||||
flavorDimensions.add("default")
|
||||
|
||||
productFlavors {
|
||||
create("standard") {
|
||||
buildConfigField("boolean", "INCLUDE_UPDATER", "true")
|
||||
dimension = "default"
|
||||
}
|
||||
create("dev") {
|
||||
// Include pseudolocales: https://developer.android.com/guide/topics/resources/pseudolocales
|
||||
resourceConfigurations.addAll(listOf("en", "en_XA", "ar_XB", "xxhdpi"))
|
||||
dimension = "default"
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources.excludes.addAll(listOf(
|
||||
"META-INF/DEPENDENCIES",
|
||||
"LICENSE.txt",
|
||||
"META-INF/LICENSE",
|
||||
"META-INF/LICENSE.txt",
|
||||
"META-INF/README.md",
|
||||
"META-INF/NOTICE",
|
||||
"META-INF/*.kotlin_module",
|
||||
))
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
compose = true
|
||||
|
||||
// Disable some unused things
|
||||
aidl = false
|
||||
renderScript = false
|
||||
shaders = false
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError = false
|
||||
checkReleaseBuilds = false
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = compose.versions.compiler.get()
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":i18n"))
|
||||
implementation(project(":core"))
|
||||
implementation(project(":source-api"))
|
||||
implementation(project(":data"))
|
||||
implementation(project(":domain"))
|
||||
implementation(project(":presentation-core"))
|
||||
implementation(project(":presentation-widget"))
|
||||
|
||||
// Compose
|
||||
implementation(platform(compose.bom))
|
||||
implementation(compose.activity)
|
||||
implementation(compose.foundation)
|
||||
implementation(compose.material3.core)
|
||||
implementation(compose.material.core)
|
||||
implementation(compose.material.icons)
|
||||
implementation(compose.animation)
|
||||
implementation(compose.animation.graphics)
|
||||
implementation(compose.ui.tooling)
|
||||
implementation(compose.ui.util)
|
||||
implementation(compose.accompanist.webview)
|
||||
implementation(compose.accompanist.flowlayout)
|
||||
implementation(compose.accompanist.permissions)
|
||||
implementation(compose.accompanist.themeadapter)
|
||||
implementation(compose.accompanist.systemuicontroller)
|
||||
|
||||
implementation(androidx.paging.runtime)
|
||||
implementation(androidx.paging.compose)
|
||||
|
||||
implementation(libs.bundles.sqlite)
|
||||
|
||||
implementation(kotlinx.reflect)
|
||||
|
||||
implementation(platform(kotlinx.coroutines.bom))
|
||||
implementation(kotlinx.bundles.coroutines)
|
||||
|
||||
// AndroidX libraries
|
||||
implementation(androidx.annotation)
|
||||
implementation(androidx.appcompat)
|
||||
implementation(androidx.biometricktx)
|
||||
implementation(androidx.constraintlayout)
|
||||
implementation(androidx.coordinatorlayout)
|
||||
implementation(androidx.corektx)
|
||||
implementation(androidx.splashscreen)
|
||||
implementation(androidx.recyclerview)
|
||||
implementation(androidx.viewpager)
|
||||
implementation(androidx.profileinstaller)
|
||||
|
||||
implementation(androidx.bundles.lifecycle)
|
||||
|
||||
// Job scheduling
|
||||
implementation(androidx.bundles.workmanager)
|
||||
|
||||
// RX
|
||||
implementation(libs.bundles.reactivex)
|
||||
implementation(libs.flowreactivenetwork)
|
||||
|
||||
// Network client
|
||||
implementation(libs.bundles.okhttp)
|
||||
implementation(libs.okio)
|
||||
|
||||
// TLS 1.3 support for Android < 10
|
||||
implementation(libs.conscrypt.android)
|
||||
|
||||
// Data serialization (JSON, protobuf)
|
||||
implementation(kotlinx.bundles.serialization)
|
||||
|
||||
// HTML parser
|
||||
implementation(libs.jsoup)
|
||||
|
||||
// Disk
|
||||
implementation(libs.disklrucache)
|
||||
implementation(libs.unifile)
|
||||
implementation(libs.junrar)
|
||||
|
||||
// Preferences
|
||||
implementation(libs.preferencektx)
|
||||
|
||||
// Dependency injection
|
||||
implementation(libs.injekt.core)
|
||||
|
||||
// Image loading
|
||||
implementation(libs.bundles.coil)
|
||||
implementation(libs.subsamplingscaleimageview) {
|
||||
exclude(module = "image-decoder")
|
||||
}
|
||||
implementation(libs.image.decoder)
|
||||
|
||||
// Sort
|
||||
implementation(libs.natural.comparator)
|
||||
|
||||
// UI libraries
|
||||
implementation(libs.material)
|
||||
implementation(libs.flexible.adapter.core)
|
||||
implementation(libs.flexible.adapter.ui)
|
||||
implementation(libs.photoview)
|
||||
implementation(libs.directionalviewpager) {
|
||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||
}
|
||||
implementation(libs.insetter)
|
||||
implementation(libs.bundles.richtext)
|
||||
implementation(libs.aboutLibraries.compose)
|
||||
implementation(libs.cascade)
|
||||
implementation(libs.bundles.voyager)
|
||||
implementation(libs.wheelpicker)
|
||||
implementation(libs.materialmotion.core)
|
||||
|
||||
// Logging
|
||||
implementation(libs.logcat)
|
||||
|
||||
// Crash reports/analytics
|
||||
implementation(libs.acra.http)
|
||||
"standardImplementation"(libs.firebase.analytics)
|
||||
|
||||
// Shizuku
|
||||
implementation(libs.bundles.shizuku)
|
||||
|
||||
// Tests
|
||||
testImplementation(libs.junit)
|
||||
|
||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||
// debugImplementation(libs.leakcanary.android)
|
||||
implementation(libs.leakcanary.plumber)
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
beforeVariants { variantBuilder ->
|
||||
// Disables standardBenchmark
|
||||
if (variantBuilder.buildType == "benchmark") {
|
||||
variantBuilder.enable = variantBuilder.productFlavors.containsAll(listOf("default" to "dev"))
|
||||
}
|
||||
}
|
||||
onVariants(selector().withFlavor("default" to "standard")) {
|
||||
// Only excluding in standard flavor because this breaks
|
||||
// Layout Inspector's Compose tree
|
||||
it.packaging.resources.excludes.add("META-INF/*.version")
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
||||
withType<LintTask>().configureEach {
|
||||
exclude { it.file.path.contains("generated[\\\\/]".toRegex()) }
|
||||
}
|
||||
|
||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
||||
withType<KotlinCompile> {
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||
)
|
||||
|
||||
if (project.findProperty("tachiyomi.enableComposeCompilerMetrics") == "true") {
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||
project.buildDir.absolutePath + "/compose_metrics"
|
||||
)
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||
project.buildDir.absolutePath + "/compose_metrics"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath(kotlinx.gradle)
|
||||
}
|
||||
}
|
33
app/proguard-android-optimize.txt
Normal file
@ -0,0 +1,33 @@
|
||||
-dontusemixedcaseclassnames
|
||||
-verbose
|
||||
|
||||
-keepattributes *Annotation*
|
||||
|
||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
-keep class androidx.annotation.Keep
|
||||
|
||||
-keep @androidx.annotation.Keep class * {*;}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <methods>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <fields>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <init>(...);
|
||||
}
|
102
app/proguard-rules.pro
vendored
@ -1,27 +1,26 @@
|
||||
-dontobfuscate
|
||||
|
||||
-dontwarn eu.kanade.tachiyomi.**
|
||||
-keep class eu.kanade.tachiyomi.**
|
||||
-keep class eu.kanade.tachiyomi.source.model.** { *; }
|
||||
# Keep common dependencies used in extensions
|
||||
-keep,allowoptimization class androidx.preference.** { public protected *; }
|
||||
-keep,allowoptimization class kotlin.** { public protected *; }
|
||||
-keep,allowoptimization class kotlinx.coroutines.** { public protected *; }
|
||||
-keep,allowoptimization class kotlinx.serialization.** { public protected *; }
|
||||
-keep,allowoptimization class okhttp3.** { public protected *; }
|
||||
-keep,allowoptimization class okio.** { public protected *; }
|
||||
-keep,allowoptimization class rx.** { public protected *; }
|
||||
-keep,allowoptimization class org.jsoup.** { public protected *; }
|
||||
-keep,allowoptimization class app.cash.quickjs.** { public protected *; }
|
||||
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
|
||||
|
||||
-keep class com.hippo.image.** { *; }
|
||||
-keep interface com.hippo.image.** { *; }
|
||||
# From extensions-lib
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.interceptor.RateLimitInterceptorKt { public protected *; }
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.interceptor.SpecificHostRateLimitInterceptorKt { public protected *; }
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.NetworkHelper { public protected *; }
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.OkHttpExtensionsKt { public protected *; }
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.network.RequestsKt { public protected *; }
|
||||
-keep,allowoptimization class eu.kanade.tachiyomi.AppInfo { public protected *; }
|
||||
|
||||
# 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 enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
|
||||
# RxJava 1.1.0
|
||||
##---------------Begin: proguard configuration for RxJava 1.x ----------
|
||||
-dontwarn sun.misc.**
|
||||
|
||||
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
|
||||
@ -37,47 +36,34 @@
|
||||
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
||||
}
|
||||
|
||||
### Support v7, Design
|
||||
# http://stackoverflow.com/questions/29679177/cardview-shadow-not-appearing-in-lollipop-after-obfuscate-with-proguard/29698051
|
||||
-keep class android.support.v7.widget.RoundRectDrawable { *; }
|
||||
-dontnote rx.internal.util.PlatformDependent
|
||||
##---------------End: proguard configuration for RxJava 1.x ----------
|
||||
|
||||
-keep public class android.support.v7.widget.** { *; }
|
||||
-keep public class android.support.v7.internal.widget.** { *; }
|
||||
-keep public class android.support.v7.internal.view.menu.** { *; }
|
||||
-keep public class android.support.v7.graphics.drawable.** { *; }
|
||||
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
||||
-keepattributes *Annotation*, InnerClasses
|
||||
-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
|
||||
|
||||
-keep public class * extends android.support.v4.view.ActionProvider {
|
||||
public <init>(android.content.Context);
|
||||
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
|
||||
-keepclassmembers class kotlinx.serialization.json.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class kotlinx.serialization.json.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
-dontwarn android.support.**
|
||||
-dontwarn android.support.design.**
|
||||
-keep class android.support.design.** { *; }
|
||||
-keep interface android.support.design.** { *; }
|
||||
-keep public class android.support.design.R$* { *; }
|
||||
-keep,includedescriptorclasses class eu.kanade.**$$serializer { *; }
|
||||
-keepclassmembers class eu.kanade.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class eu.kanade.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
-keep class kotlinx.serialization.**
|
||||
-keepclassmembers class kotlinx.serialization.** {
|
||||
<methods>;
|
||||
}
|
||||
##---------------End: proguard configuration for kotlinx.serialization ----------
|
||||
|
||||
# ReactiveNetwork
|
||||
-dontwarn com.github.pwittchen.reactivenetwork.**
|
||||
|
||||
## GSON ##
|
||||
|
||||
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
||||
# removes such information by default, so configure it to keep all of it.
|
||||
-keepattributes Signature
|
||||
|
||||
# Gson specific classes
|
||||
-keep class sun.misc.Unsafe { *; }
|
||||
|
||||
# Prevent proguard from stripping interface information from TypeAdapterFactory,
|
||||
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
||||
-keep class * implements com.google.gson.TypeAdapterFactory
|
||||
-keep class * implements com.google.gson.JsonSerializer
|
||||
-keep class * implements com.google.gson.JsonDeserializer
|
||||
|
||||
# SnakeYaml
|
||||
-keep class org.yaml.snakeyaml.** { public protected private *; }
|
||||
-dontwarn org.yaml.snakeyaml.**
|
||||
|
||||
# Duktape
|
||||
-keep class com.squareup.duktape.** { *; }
|
||||
# XmlUtil
|
||||
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
@ -2,50 +2,46 @@
|
||||
|
||||
<shortcut
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/sc_book_48dp"
|
||||
android:icon="@drawable/sc_collections_bookmark_48dp"
|
||||
android:shortcutDisabledMessage="@string/app_not_available"
|
||||
android:shortcutId="show_library"
|
||||
android:shortcutLongLabel="@string/label_library"
|
||||
android:shortcutShortLabel="@string/label_library">
|
||||
<intent
|
||||
android:action="eu.kanade.tachiyomi.SHOW_LIBRARY"
|
||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity"
|
||||
android:targetPackage="${applicationId}" />
|
||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||
</shortcut>
|
||||
<shortcut
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/sc_update_48dp"
|
||||
android:icon="@drawable/sc_new_releases_48dp"
|
||||
android:shortcutDisabledMessage="@string/app_not_available"
|
||||
android:shortcutId="show_recently_updated"
|
||||
android:shortcutLongLabel="@string/label_recent_updates"
|
||||
android:shortcutShortLabel="@string/short_recent_updates">
|
||||
android:shortcutShortLabel="@string/label_recent_updates">
|
||||
<intent
|
||||
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"
|
||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity"
|
||||
android:targetPackage="${applicationId}" />
|
||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||
</shortcut>
|
||||
<shortcut
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/sc_glasses_48dp"
|
||||
android:icon="@drawable/sc_history_48dp"
|
||||
android:shortcutDisabledMessage="@string/app_not_available"
|
||||
android:shortcutId="show_recently_read"
|
||||
android:shortcutLongLabel="@string/label_recent_manga"
|
||||
android:shortcutShortLabel="@string/label_recent_manga">
|
||||
<intent
|
||||
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_READ"
|
||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity"
|
||||
android:targetPackage="${applicationId}" />
|
||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||
</shortcut>
|
||||
<shortcut
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/sc_explore_48dp"
|
||||
android:shortcutDisabledMessage="@string/app_not_available"
|
||||
android:shortcutId="show_catalogues"
|
||||
android:shortcutLongLabel="@string/label_catalogues"
|
||||
android:shortcutShortLabel="@string/label_catalogues">
|
||||
android:shortcutLongLabel="@string/browse"
|
||||
android:shortcutShortLabel="@string/browse">
|
||||
<intent
|
||||
android:action="eu.kanade.tachiyomi.SHOW_CATALOGUES"
|
||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity"
|
||||
android:targetPackage="${applicationId}" />
|
||||
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
27
app/src/debug/res/drawable/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108.0"
|
||||
android:viewportHeight="108.0">
|
||||
<path
|
||||
android:pathData="M14.5,7L86.5,7A7,7 0,0 1,93.5 14L93.5,95A7,7 0,0 1,86.5 102L14.5,102A7,7 0,0 1,7.5 95L7.5,14A7,7 0,0 1,14.5 7z"
|
||||
android:fillColor="#000"/>
|
||||
<path
|
||||
android:pathData="M14.5,7L86.5,7A7,7 0,0 1,93.5 14L93.5,95A7,7 0,0 1,86.5 102L14.5,102A7,7 0,0 1,7.5 95L7.5,14A7,7 0,0 1,14.5 7z"
|
||||
android:fillColor="#455A64"/>
|
||||
<path
|
||||
android:pathData="M7.5,12.01C7.5,9.24 9.74,7 12.5,7L17.5,7L17.5,102L12.5,102C9.74,102 7.5,99.77 7.5,96.99L7.5,12.01Z"
|
||||
android:fillColor="#607D8B"/>
|
||||
<path
|
||||
android:pathData="M54,54.5m-25.5,0a25.5,25.5 0,1 1,51 0a25.5,25.5 0,1 1,-51 0"
|
||||
android:fillColor="#000"/>
|
||||
<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:fillColor="#CE2828"/>
|
||||
<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:fillColor="#FFF"/>
|
||||
<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:fillColor="#000"/>
|
||||
</vector>
|
6
app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@android:color/transparent"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
||||
</adaptive-icon>
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@android:color/transparent"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_tachi_monochrome_launcher" />
|
||||
</adaptive-icon>
|
BIN
app/src/debug/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
app/src/debug/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/debug/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/debug/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
app/src/debug/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 13 KiB |
@ -1,45 +1,131 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="eu.kanade.tachiyomi">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- Internet -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
|
||||
<!-- Storage -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<!-- For background jobs -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
|
||||
<!-- For managing extensions -->
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||
<!-- To view extension packages in API 30+ -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
|
||||
|
||||
<!-- Remove permission from Firebase dependency -->
|
||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID"
|
||||
tools:node="remove" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:allowBackup="false"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:theme="@style/Theme.Tachiyomi">
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/Theme.Tachiyomi"
|
||||
android:supportsRtl="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<!-- enable profiling by macrobenchmark -->
|
||||
<profileable
|
||||
android:shell="true"
|
||||
tools:targetApi="q" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.main.MainActivity"
|
||||
android:launchMode="singleTask">
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/Theme.Tachiyomi.SplashScreen"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts"/>
|
||||
<!--suppress AndroidDomInspection -->
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:process=":error_handler"
|
||||
android:name=".crash.CrashActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.main.DeepLinkActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:label="@string/action_global_search"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="eu.kanade.tachiyomi.SEARCH" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.reader.ReaderActivity"
|
||||
android:theme="@style/Theme.Reader" />
|
||||
android:launchMode="singleTask"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
|
||||
android:resource="@xml/s_pen_actions"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".widget.CustomLayoutPickerActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/FilePickerTheme" />
|
||||
android:name=".ui.security.UnlockActivity"
|
||||
android:theme="@style/Theme.Tachiyomi"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.setting.AnilistLoginActivity"
|
||||
android:label="Anilist">
|
||||
android:name=".ui.webview.WebViewActivity"
|
||||
android:configChanges="uiMode|orientation|screenSize"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".extension.util.ExtensionInstallActivity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.setting.track.AnilistLoginActivity"
|
||||
android:label="Anilist"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@ -51,31 +137,70 @@
|
||||
android:scheme="tachiyomi" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.setting.track.MyAnimeListLoginActivity"
|
||||
android:label="MyAnimeList"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<provider
|
||||
android:name="eu.kanade.tachiyomi.util.ZipContentProvider"
|
||||
android:authorities="${applicationId}.zip-provider"
|
||||
android:exported="false" />
|
||||
<data
|
||||
android:host="myanimelist-auth"
|
||||
android:scheme="tachiyomi" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.setting.track.ShikimoriLoginActivity"
|
||||
android:label="Shikimori"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<provider
|
||||
android:name="eu.kanade.tachiyomi.util.RarContentProvider"
|
||||
android:authorities="${applicationId}.rar-provider"
|
||||
android:exported="false" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="shikimori-auth"
|
||||
android:scheme="tachiyomi" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.setting.track.BangumiLoginActivity"
|
||||
android:label="Bangumi"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="bangumi-auth"
|
||||
android:scheme="tachiyomi" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".data.notification.NotificationReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name="tachiyomi.presentation.widget.UpdatesGridGlanceReceiver"
|
||||
android:enabled="@bool/glance_appwidget_available"
|
||||
android:exported="false"
|
||||
android:label="@string/label_recent_updates">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/updates_grid_glance_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".data.library.LibraryUpdateService"
|
||||
android:exported="false" />
|
||||
@ -85,20 +210,54 @@
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".data.updater.UpdateDownloaderService"
|
||||
android:name=".data.updater.AppUpdateService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".data.backup.BackupCreateService"
|
||||
android:exported="false"/>
|
||||
android:name=".data.backup.BackupRestoreService"
|
||||
android:exported="false" />
|
||||
|
||||
<service android:name=".extension.util.ExtensionInstallService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".data.backup.BackupRestoreService"
|
||||
android:exported="false"/>
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="autoStoreLocales"
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name="rikka.shizuku.ShizukuProvider"
|
||||
android:authorities="${applicationId}.shizuku"
|
||||
android:multiprocess="false"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||
|
||||
<meta-data
|
||||
android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"
|
||||
android:value="GlideModule" />
|
||||
android:name="android.webkit.WebView.EnableSafeBrowsing"
|
||||
android:value="false" />
|
||||
<meta-data
|
||||
android:name="android.webkit.WebView.MetricsOptOut"
|
||||
android:value="true" />
|
||||
|
||||
<!-- Disable advertising ID collection for Firebase -->
|
||||
<meta-data
|
||||
android:name="google_analytics_adid_collection_enabled"
|
||||
android:value="false" />
|
||||
|
||||
</application>
|
||||
|
||||
|
22513
app/src/main/baseline-prof.txt
Normal file
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
55
app/src/main/java/eu/kanade/core/prefs/CheckboxState.kt
Normal file
@ -0,0 +1,55 @@
|
||||
package eu.kanade.core.prefs
|
||||
|
||||
import androidx.compose.ui.state.ToggleableState
|
||||
|
||||
sealed class CheckboxState<T>(open val value: T) {
|
||||
abstract fun next(): CheckboxState<T>
|
||||
|
||||
sealed class State<T>(override val value: T) : CheckboxState<T>(value) {
|
||||
data class Checked<T>(override val value: T) : State<T>(value)
|
||||
data class None<T>(override val value: T) : State<T>(value)
|
||||
|
||||
val isChecked: Boolean
|
||||
get() = this is Checked
|
||||
|
||||
override fun next(): CheckboxState<T> {
|
||||
return when (this) {
|
||||
is Checked -> None(value)
|
||||
is None -> Checked(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
sealed class TriState<T>(override val value: T) : CheckboxState<T>(value) {
|
||||
data class Include<T>(override val value: T) : TriState<T>(value)
|
||||
data class Exclude<T>(override val value: T) : TriState<T>(value)
|
||||
data class None<T>(override val value: T) : TriState<T>(value)
|
||||
|
||||
override fun next(): CheckboxState<T> {
|
||||
return when (this) {
|
||||
is Exclude -> None(value)
|
||||
is Include -> Exclude(value)
|
||||
is None -> Include(value)
|
||||
}
|
||||
}
|
||||
|
||||
fun asState(): ToggleableState {
|
||||
return when (this) {
|
||||
is Exclude -> ToggleableState.Indeterminate
|
||||
is Include -> ToggleableState.On
|
||||
is None -> ToggleableState.Off
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> T.asCheckboxState(condition: (T) -> Boolean): CheckboxState.State<T> {
|
||||
return if (condition(this)) {
|
||||
CheckboxState.State.Checked(this)
|
||||
} else {
|
||||
CheckboxState.State.None(this)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> List<T>.mapAsCheckboxState(condition: (T) -> Boolean): List<CheckboxState.State<T>> {
|
||||
return this.map { it.asCheckboxState(condition) }
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package eu.kanade.core.prefs
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import tachiyomi.core.preference.Preference
|
||||
|
||||
class PreferenceMutableState<T>(
|
||||
private val preference: Preference<T>,
|
||||
scope: CoroutineScope,
|
||||
) : MutableState<T> {
|
||||
|
||||
private val state = mutableStateOf(preference.get())
|
||||
|
||||
init {
|
||||
preference.changes()
|
||||
.onEach { state.value = it }
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
||||
override var value: T
|
||||
get() = state.value
|
||||
set(value) {
|
||||
preference.set(value)
|
||||
}
|
||||
|
||||
override fun component1(): T {
|
||||
return state.value
|
||||
}
|
||||
|
||||
override fun component2(): (T) -> Unit {
|
||||
return { preference.set(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Preference<T>.asState(scope: CoroutineScope) = PreferenceMutableState(this, scope)
|
148
app/src/main/java/eu/kanade/core/util/CollectionUtils.kt
Normal file
@ -0,0 +1,148 @@
|
||||
package eu.kanade.core.util
|
||||
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
fun <T : R, R : Any> List<T>.insertSeparators(
|
||||
generator: (T?, T?) -> R?,
|
||||
): List<R> {
|
||||
if (isEmpty()) return emptyList()
|
||||
val newList = mutableListOf<R>()
|
||||
for (i in -1..lastIndex) {
|
||||
val before = getOrNull(i)
|
||||
before?.let { newList.add(it) }
|
||||
val after = getOrNull(i + 1)
|
||||
val separator = generator.invoke(before, after)
|
||||
separator?.let { newList.add(it) }
|
||||
}
|
||||
return newList
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new map containing only the key entries of [transform] that are not null.
|
||||
*/
|
||||
inline fun <K, V, R> Map<out K, V>.mapNotNullKeys(transform: (Map.Entry<K?, V>) -> R?): ConcurrentHashMap<R, V> {
|
||||
val mutableMap = ConcurrentHashMap<R, V>()
|
||||
forEach { element -> transform(element)?.let { mutableMap[it] = element.value } }
|
||||
return mutableMap
|
||||
}
|
||||
|
||||
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
||||
if (shouldAdd) {
|
||||
add(value)
|
||||
} else {
|
||||
remove(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list containing only elements matching the given [predicate].
|
||||
*
|
||||
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||
* collections that are created by code we control and are known to support random access.
|
||||
*/
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
|
||||
contract { callsInPlace(predicate) }
|
||||
val destination = ArrayList<T>()
|
||||
fastForEach { if (predicate(it)) destination.add(it) }
|
||||
return destination
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list containing all elements not matching the given [predicate].
|
||||
*
|
||||
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||
* collections that are created by code we control and are known to support random access.
|
||||
*/
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
|
||||
contract { callsInPlace(predicate) }
|
||||
val destination = ArrayList<T>()
|
||||
fastForEach { if (!predicate(it)) destination.add(it) }
|
||||
return destination
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list containing only the non-null results of applying the
|
||||
* given [transform] function to each element in the original collection.
|
||||
*
|
||||
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||
* collections that are created by code we control and are known to support random access.
|
||||
*/
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
|
||||
contract { callsInPlace(transform) }
|
||||
val destination = ArrayList<R>()
|
||||
fastForEach { element ->
|
||||
transform(element)?.let { destination.add(it) }
|
||||
}
|
||||
return destination
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the original collection into pair of lists,
|
||||
* where *first* list contains elements for which [predicate] yielded `true`,
|
||||
* while *second* list contains elements for which [predicate] yielded `false`.
|
||||
*
|
||||
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||
* collections that are created by code we control and are known to support random access.
|
||||
*/
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
inline fun <T> List<T>.fastPartition(predicate: (T) -> Boolean): Pair<List<T>, List<T>> {
|
||||
contract { callsInPlace(predicate) }
|
||||
val first = ArrayList<T>()
|
||||
val second = ArrayList<T>()
|
||||
fastForEach {
|
||||
if (predicate(it)) {
|
||||
first.add(it)
|
||||
} else {
|
||||
second.add(it)
|
||||
}
|
||||
}
|
||||
return Pair(first, second)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of entries not matching the given [predicate].
|
||||
*
|
||||
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||
* collections that are created by code we control and are known to support random access.
|
||||
*/
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
inline fun <T> List<T>.fastCountNot(predicate: (T) -> Boolean): Int {
|
||||
contract { callsInPlace(predicate) }
|
||||
var count = size
|
||||
fastForEach { if (predicate(it)) --count }
|
||||
return count
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list containing only elements from the given collection
|
||||
* having distinct keys returned by the given [selector] function.
|
||||
*
|
||||
* Among elements of the given collection with equal keys, only the first one will be present in the resulting list.
|
||||
* The elements in the resulting list are in the same order as they were in the source collection.
|
||||
*
|
||||
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||
* collections that are created by code we control and are known to support random access.
|
||||
*/
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
inline fun <T, K> List<T>.fastDistinctBy(selector: (T) -> K): List<T> {
|
||||
contract { callsInPlace(selector) }
|
||||
val set = HashSet<K>()
|
||||
val list = ArrayList<T>()
|
||||
fastForEach {
|
||||
val key = selector(it)
|
||||
if (set.add(key)) list.add(it)
|
||||
}
|
||||
return list
|
||||
}
|
16
app/src/main/java/eu/kanade/core/util/DurationUtils.kt
Normal file
@ -0,0 +1,16 @@
|
||||
package eu.kanade.core.util
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.R
|
||||
import kotlin.time.Duration
|
||||
|
||||
fun Duration.toDurationString(context: Context, fallback: String): String {
|
||||
return toComponents { days, hours, minutes, seconds, _ ->
|
||||
buildList(4) {
|
||||
if (days != 0L) add(context.getString(R.string.day_short, days))
|
||||
if (hours != 0) add(context.getString(R.string.hour_short, hours))
|
||||
if (minutes != 0 && (days == 0L || hours == 0)) add(context.getString(R.string.minute_short, minutes))
|
||||
if (seconds != 0 && days == 0L && hours == 0) add(context.getString(R.string.seconds_short, seconds))
|
||||
}.joinToString(" ").ifBlank { fallback }
|
||||
}
|
||||
}
|
61
app/src/main/java/eu/kanade/core/util/RxJavaExtensions.kt
Normal file
@ -0,0 +1,61 @@
|
||||
package eu.kanade.core.util
|
||||
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import rx.Emitter
|
||||
import rx.Observable
|
||||
import rx.Observer
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
fun <T : Any> Observable<T>.asFlow(): Flow<T> = callbackFlow {
|
||||
val observer = object : Observer<T> {
|
||||
override fun onNext(t: T) {
|
||||
trySend(t)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
close(e)
|
||||
}
|
||||
|
||||
override fun onCompleted() {
|
||||
close()
|
||||
}
|
||||
}
|
||||
val subscription = subscribe(observer)
|
||||
awaitClose { subscription.unsubscribe() }
|
||||
}
|
||||
|
||||
fun <T : Any> Flow<T>.asObservable(
|
||||
context: CoroutineContext = Dispatchers.Unconfined,
|
||||
backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE,
|
||||
): Observable<T> {
|
||||
return Observable.create(
|
||||
{ emitter ->
|
||||
/*
|
||||
* ATOMIC is used here to provide stable behaviour of subscribe+dispose pair even if
|
||||
* asObservable is already invoked from unconfined
|
||||
*/
|
||||
val job = GlobalScope.launch(context = context, start = CoroutineStart.ATOMIC) {
|
||||
try {
|
||||
collect { emitter.onNext(it) }
|
||||
emitter.onCompleted()
|
||||
} catch (e: Throwable) {
|
||||
// Ignore `CancellationException` as error, since it indicates "normal cancellation"
|
||||
if (e !is CancellationException) {
|
||||
emitter.onError(e)
|
||||
} else {
|
||||
emitter.onCompleted()
|
||||
}
|
||||
}
|
||||
}
|
||||
emitter.setCancellation { job.cancel() }
|
||||
},
|
||||
backpressureMode,
|
||||
)
|
||||
}
|
19
app/src/main/java/eu/kanade/data/source/SourceMapper.kt
Normal file
@ -0,0 +1,19 @@
|
||||
package eu.kanade.data.source
|
||||
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import tachiyomi.domain.source.model.Source
|
||||
|
||||
val sourceMapper: (eu.kanade.tachiyomi.source.Source) -> Source = { source ->
|
||||
Source(
|
||||
source.id,
|
||||
source.lang,
|
||||
source.name,
|
||||
supportsLatest = false,
|
||||
isStub = source is SourceManager.StubSource,
|
||||
)
|
||||
}
|
||||
|
||||
val catalogueSourceMapper: (CatalogueSource) -> Source = { source ->
|
||||
sourceMapper(source).copy(supportsLatest = source.supportsLatest)
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package eu.kanade.data.source
|
||||
|
||||
import androidx.paging.PagingState
|
||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import tachiyomi.core.util.lang.awaitSingle
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
|
||||
abstract class SourcePagingSource(
|
||||
protected val source: CatalogueSource,
|
||||
) : SourcePagingSourceType() {
|
||||
|
||||
abstract suspend fun requestNextPage(currentPage: Int): MangasPage
|
||||
|
||||
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, SManga> {
|
||||
val page = params.key ?: 1
|
||||
|
||||
val mangasPage = try {
|
||||
withIOContext {
|
||||
requestNextPage(page.toInt())
|
||||
.takeIf { it.mangas.isNotEmpty() }
|
||||
?: throw NoResultsException()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return LoadResult.Error(e)
|
||||
}
|
||||
|
||||
return LoadResult.Page(
|
||||
data = mangasPage.mangas,
|
||||
prevKey = null,
|
||||
nextKey = if (mangasPage.hasNextPage) page + 1 else null,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Long, SManga>): Long? {
|
||||
return state.anchorPosition?.let { anchorPosition ->
|
||||
val anchorPage = state.closestPageToPosition(anchorPosition)
|
||||
anchorPage?.prevKey ?: anchorPage?.nextKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SourceSearchPagingSource(source: CatalogueSource, val query: String, val filters: FilterList) : SourcePagingSource(source) {
|
||||
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
||||
return source.fetchSearchManga(currentPage, query, filters).awaitSingle()
|
||||
}
|
||||
}
|
||||
|
||||
class SourcePopularPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
|
||||
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
||||
return source.fetchPopularManga(currentPage).awaitSingle()
|
||||
}
|
||||
}
|
||||
|
||||
class SourceLatestPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
|
||||
override suspend fun requestNextPage(currentPage: Int): MangasPage {
|
||||
return source.fetchLatestUpdates(currentPage).awaitSingle()
|
||||
}
|
||||
}
|
||||
|
||||
class NoResultsException : Exception()
|
@ -0,0 +1,74 @@
|
||||
package eu.kanade.data.source
|
||||
|
||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
||||
import eu.kanade.domain.source.repository.SourceRepository
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import tachiyomi.data.DatabaseHandler
|
||||
import tachiyomi.domain.source.model.Source
|
||||
import tachiyomi.domain.source.model.SourceWithCount
|
||||
|
||||
class SourceRepositoryImpl(
|
||||
private val sourceManager: SourceManager,
|
||||
private val handler: DatabaseHandler,
|
||||
) : SourceRepository {
|
||||
|
||||
override fun getSources(): Flow<List<Source>> {
|
||||
return sourceManager.catalogueSources.map { sources ->
|
||||
sources.map(catalogueSourceMapper)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOnlineSources(): Flow<List<Source>> {
|
||||
return sourceManager.onlineSources.map { sources ->
|
||||
sources.map(sourceMapper)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>> {
|
||||
val sourceIdWithFavoriteCount = handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() }
|
||||
return sourceIdWithFavoriteCount.map { sourceIdsWithCount ->
|
||||
sourceIdsWithCount
|
||||
.filterNot { it.source == LocalSource.ID }
|
||||
.map { (sourceId, count) ->
|
||||
val source = sourceManager.getOrStub(sourceId).run {
|
||||
sourceMapper(this)
|
||||
}
|
||||
source to count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSourcesWithNonLibraryManga(): Flow<List<SourceWithCount>> {
|
||||
val sourceIdWithNonLibraryManga = handler.subscribeToList { mangasQueries.getSourceIdsWithNonLibraryManga() }
|
||||
return sourceIdWithNonLibraryManga.map { sourceId ->
|
||||
sourceId.map { (sourceId, count) ->
|
||||
val source = sourceManager.getOrStub(sourceId)
|
||||
SourceWithCount(sourceMapper(source), count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun search(
|
||||
sourceId: Long,
|
||||
query: String,
|
||||
filterList: FilterList,
|
||||
): SourcePagingSourceType {
|
||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
||||
return SourceSearchPagingSource(source, query, filterList)
|
||||
}
|
||||
|
||||
override fun getPopular(sourceId: Long): SourcePagingSourceType {
|
||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
||||
return SourcePopularPagingSource(source)
|
||||
}
|
||||
|
||||
override fun getLatest(sourceId: Long): SourcePagingSourceType {
|
||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
||||
return SourceLatestPagingSource(source)
|
||||
}
|
||||
}
|
147
app/src/main/java/eu/kanade/domain/DomainModule.kt
Normal file
@ -0,0 +1,147 @@
|
||||
package eu.kanade.domain
|
||||
|
||||
import eu.kanade.data.source.SourceRepositoryImpl
|
||||
import eu.kanade.domain.category.interactor.CreateCategoryWithName
|
||||
import eu.kanade.domain.category.interactor.DeleteCategory
|
||||
import eu.kanade.domain.category.interactor.RenameCategory
|
||||
import eu.kanade.domain.category.interactor.ReorderCategory
|
||||
import eu.kanade.domain.category.interactor.ResetCategoryFlags
|
||||
import eu.kanade.domain.category.interactor.SetDisplayModeForCategory
|
||||
import eu.kanade.domain.category.interactor.SetMangaCategories
|
||||
import eu.kanade.domain.category.interactor.SetSortModeForCategory
|
||||
import eu.kanade.domain.category.interactor.UpdateCategory
|
||||
import eu.kanade.domain.chapter.interactor.GetChapter
|
||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||
import eu.kanade.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
|
||||
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||
import eu.kanade.domain.history.interactor.GetNextChapters
|
||||
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
|
||||
import eu.kanade.domain.manga.interactor.GetFavorites
|
||||
import eu.kanade.domain.manga.interactor.GetLibraryManga
|
||||
import eu.kanade.domain.manga.interactor.GetManga
|
||||
import eu.kanade.domain.manga.interactor.GetMangaWithChapters
|
||||
import eu.kanade.domain.manga.interactor.NetworkToLocalManga
|
||||
import eu.kanade.domain.manga.interactor.ResetViewerFlags
|
||||
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
|
||||
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
||||
import eu.kanade.domain.source.interactor.GetRemoteManga
|
||||
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
||||
import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga
|
||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||
import eu.kanade.domain.source.interactor.ToggleSource
|
||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||
import eu.kanade.domain.source.repository.SourceRepository
|
||||
import eu.kanade.domain.track.interactor.DeleteTrack
|
||||
import eu.kanade.domain.track.interactor.GetTracks
|
||||
import eu.kanade.domain.track.interactor.GetTracksPerManga
|
||||
import eu.kanade.domain.track.interactor.InsertTrack
|
||||
import tachiyomi.data.category.CategoryRepositoryImpl
|
||||
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
||||
import tachiyomi.data.history.HistoryRepositoryImpl
|
||||
import tachiyomi.data.manga.MangaRepositoryImpl
|
||||
import tachiyomi.data.source.SourceDataRepositoryImpl
|
||||
import tachiyomi.data.track.TrackRepositoryImpl
|
||||
import tachiyomi.data.updates.UpdatesRepositoryImpl
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.history.interactor.GetHistory
|
||||
import tachiyomi.domain.history.interactor.GetTotalReadDuration
|
||||
import tachiyomi.domain.history.interactor.RemoveHistory
|
||||
import tachiyomi.domain.history.interactor.UpsertHistory
|
||||
import tachiyomi.domain.history.repository.HistoryRepository
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
import tachiyomi.domain.source.repository.SourceDataRepository
|
||||
import tachiyomi.domain.track.repository.TrackRepository
|
||||
import tachiyomi.domain.updates.interactor.GetUpdates
|
||||
import tachiyomi.domain.updates.repository.UpdatesRepository
|
||||
import uy.kohesive.injekt.api.InjektModule
|
||||
import uy.kohesive.injekt.api.InjektRegistrar
|
||||
import uy.kohesive.injekt.api.addFactory
|
||||
import uy.kohesive.injekt.api.addSingletonFactory
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class DomainModule : InjektModule {
|
||||
|
||||
override fun InjektRegistrar.registerInjectables() {
|
||||
addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) }
|
||||
addFactory { GetCategories(get()) }
|
||||
addFactory { ResetCategoryFlags(get(), get()) }
|
||||
addFactory { SetDisplayModeForCategory(get(), get()) }
|
||||
addFactory { SetSortModeForCategory(get(), get()) }
|
||||
addFactory { CreateCategoryWithName(get(), get()) }
|
||||
addFactory { RenameCategory(get()) }
|
||||
addFactory { ReorderCategory(get()) }
|
||||
addFactory { UpdateCategory(get()) }
|
||||
addFactory { DeleteCategory(get()) }
|
||||
|
||||
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
|
||||
addFactory { GetDuplicateLibraryManga(get()) }
|
||||
addFactory { GetFavorites(get()) }
|
||||
addFactory { GetLibraryManga(get()) }
|
||||
addFactory { GetMangaWithChapters(get(), get()) }
|
||||
addFactory { GetManga(get()) }
|
||||
addFactory { GetNextChapters(get(), get(), get()) }
|
||||
addFactory { ResetViewerFlags(get()) }
|
||||
addFactory { SetMangaChapterFlags(get()) }
|
||||
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
||||
addFactory { SetMangaViewerFlags(get()) }
|
||||
addFactory { NetworkToLocalManga(get()) }
|
||||
addFactory { UpdateManga(get()) }
|
||||
addFactory { SetMangaCategories(get()) }
|
||||
|
||||
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
||||
addFactory { DeleteTrack(get()) }
|
||||
addFactory { GetTracksPerManga(get()) }
|
||||
addFactory { GetTracks(get()) }
|
||||
addFactory { InsertTrack(get()) }
|
||||
|
||||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||
addFactory { GetChapter(get()) }
|
||||
addFactory { GetChapterByMangaId(get()) }
|
||||
addFactory { UpdateChapter(get()) }
|
||||
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
||||
addFactory { ShouldUpdateDbChapter() }
|
||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get()) }
|
||||
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
|
||||
|
||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||
addFactory { GetHistory(get()) }
|
||||
addFactory { UpsertHistory(get()) }
|
||||
addFactory { RemoveHistory(get()) }
|
||||
addFactory { GetTotalReadDuration(get()) }
|
||||
|
||||
addFactory { DeleteDownload(get(), get()) }
|
||||
|
||||
addFactory { GetExtensionsByType(get(), get()) }
|
||||
addFactory { GetExtensionSources(get()) }
|
||||
addFactory { GetExtensionLanguages(get(), get()) }
|
||||
|
||||
addSingletonFactory<UpdatesRepository> { UpdatesRepositoryImpl(get()) }
|
||||
addFactory { GetUpdates(get()) }
|
||||
|
||||
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
|
||||
addSingletonFactory<SourceDataRepository> { SourceDataRepositoryImpl(get()) }
|
||||
addFactory { GetEnabledSources(get(), get()) }
|
||||
addFactory { GetLanguagesWithSources(get(), get()) }
|
||||
addFactory { GetRemoteManga(get()) }
|
||||
addFactory { GetSourcesWithFavoriteCount(get(), get()) }
|
||||
addFactory { GetSourcesWithNonLibraryManga(get()) }
|
||||
addFactory { SetMigrateSorting(get()) }
|
||||
addFactory { ToggleLanguage(get()) }
|
||||
addFactory { ToggleSource(get()) }
|
||||
addFactory { ToggleSourcePin(get()) }
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package eu.kanade.domain.backup.service
|
||||
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.core.provider.FolderProvider
|
||||
|
||||
class BackupPreferences(
|
||||
private val folderProvider: FolderProvider,
|
||||
private val preferenceStore: PreferenceStore,
|
||||
) {
|
||||
|
||||
fun backupsDirectory() = preferenceStore.getString("backup_directory", folderProvider.path())
|
||||
|
||||
fun numberOfBackups() = preferenceStore.getInt("backup_slots", 2)
|
||||
|
||||
fun backupInterval() = preferenceStore.getInt("backup_interval", 12)
|
||||
}
|
24
app/src/main/java/eu/kanade/domain/base/BasePreferences.kt
Normal file
@ -0,0 +1,24 @@
|
||||
package eu.kanade.domain.base
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
|
||||
class BasePreferences(
|
||||
val context: Context,
|
||||
private val preferenceStore: PreferenceStore,
|
||||
) {
|
||||
|
||||
fun confirmExit() = preferenceStore.getBoolean("pref_confirm_exit", false)
|
||||
|
||||
fun downloadedOnly() = preferenceStore.getBoolean("pref_downloaded_only", false)
|
||||
|
||||
fun incognitoMode() = preferenceStore.getBoolean("incognito_mode", false)
|
||||
|
||||
fun automaticExtUpdates() = preferenceStore.getBoolean("automatic_ext_updates", true)
|
||||
|
||||
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
||||
|
||||
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package eu.kanade.domain.base
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.ExtensionInstaller
|
||||
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
|
||||
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import tachiyomi.core.preference.Preference
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.core.preference.getEnum
|
||||
|
||||
class ExtensionInstallerPreference(
|
||||
private val context: Context,
|
||||
preferenceStore: PreferenceStore,
|
||||
) : Preference<ExtensionInstaller> {
|
||||
|
||||
private val basePref = preferenceStore.getEnum(key(), defaultValue())
|
||||
|
||||
override fun key() = "extension_installer"
|
||||
|
||||
val entries get() = ExtensionInstaller.values().run {
|
||||
if (context.hasMiuiPackageInstaller) {
|
||||
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
|
||||
} else {
|
||||
toList()
|
||||
}
|
||||
}
|
||||
|
||||
override fun defaultValue() = if (context.hasMiuiPackageInstaller) {
|
||||
ExtensionInstaller.LEGACY
|
||||
} else {
|
||||
ExtensionInstaller.PACKAGEINSTALLER
|
||||
}
|
||||
|
||||
private fun check(value: ExtensionInstaller): ExtensionInstaller {
|
||||
when (value) {
|
||||
ExtensionInstaller.PACKAGEINSTALLER -> {
|
||||
if (context.hasMiuiPackageInstaller) return ExtensionInstaller.LEGACY
|
||||
}
|
||||
ExtensionInstaller.SHIZUKU -> {
|
||||
if (!context.isShizukuInstalled) return defaultValue()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
override fun get(): ExtensionInstaller {
|
||||
val value = basePref.get()
|
||||
val checkedValue = check(value)
|
||||
if (value != checkedValue) {
|
||||
basePref.set(checkedValue)
|
||||
}
|
||||
return checkedValue
|
||||
}
|
||||
|
||||
override fun set(value: ExtensionInstaller) {
|
||||
basePref.set(check(value))
|
||||
}
|
||||
|
||||
override fun isSet() = basePref.isSet()
|
||||
|
||||
override fun delete() = basePref.delete()
|
||||
|
||||
override fun changes() = basePref.changes()
|
||||
|
||||
override fun stateIn(scope: CoroutineScope) = basePref.stateIn(scope)
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
|
||||
class CreateCategoryWithName(
|
||||
private val categoryRepository: CategoryRepository,
|
||||
private val preferences: LibraryPreferences,
|
||||
) {
|
||||
|
||||
private val initialFlags: Long
|
||||
get() {
|
||||
val sort = preferences.librarySortingMode().get()
|
||||
return preferences.libraryDisplayMode().get().flag or
|
||||
sort.type.flag or
|
||||
sort.direction.flag
|
||||
}
|
||||
|
||||
suspend fun await(name: String): Result = withNonCancellableContext {
|
||||
val categories = categoryRepository.getAll()
|
||||
val nextOrder = categories.maxOfOrNull { it.order }?.plus(1) ?: 0
|
||||
val newCategory = Category(
|
||||
id = 0,
|
||||
name = name,
|
||||
order = nextOrder,
|
||||
flags = initialFlags,
|
||||
)
|
||||
|
||||
try {
|
||||
categoryRepository.insert(newCategory)
|
||||
Result.Success
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
Result.InternalError(e)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.category.model.CategoryUpdate
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
|
||||
class DeleteCategory(
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(categoryId: Long) = withNonCancellableContext {
|
||||
try {
|
||||
categoryRepository.delete(categoryId)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
return@withNonCancellableContext Result.InternalError(e)
|
||||
}
|
||||
|
||||
val categories = categoryRepository.getAll()
|
||||
val updates = categories.mapIndexed { index, category ->
|
||||
CategoryUpdate(
|
||||
id = category.id,
|
||||
order = index.toLong(),
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
categoryRepository.updatePartial(updates)
|
||||
Result.Success
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
Result.InternalError(e)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.category.model.CategoryUpdate
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
|
||||
class RenameCategory(
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(categoryId: Long, name: String) = withNonCancellableContext {
|
||||
val update = CategoryUpdate(
|
||||
id = categoryId,
|
||||
name = name,
|
||||
)
|
||||
|
||||
try {
|
||||
categoryRepository.updatePartial(update)
|
||||
Result.Success
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
Result.InternalError(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun await(category: Category, name: String) = await(category.id, name)
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.category.model.CategoryUpdate
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
import java.util.Collections
|
||||
|
||||
class ReorderCategory(
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) {
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
suspend fun moveUp(category: Category): Result =
|
||||
await(category, MoveTo.UP)
|
||||
|
||||
suspend fun moveDown(category: Category): Result =
|
||||
await(category, MoveTo.DOWN)
|
||||
|
||||
private suspend fun await(category: Category, moveTo: MoveTo) = withNonCancellableContext {
|
||||
mutex.withLock {
|
||||
val categories = categoryRepository.getAll()
|
||||
.filterNot(Category::isSystemCategory)
|
||||
.toMutableList()
|
||||
|
||||
val currentIndex = categories.indexOfFirst { it.id == category.id }
|
||||
if (currentIndex == -1) {
|
||||
return@withNonCancellableContext Result.Unchanged
|
||||
}
|
||||
|
||||
val newPosition = when (moveTo) {
|
||||
MoveTo.UP -> currentIndex - 1
|
||||
MoveTo.DOWN -> currentIndex + 1
|
||||
}.toInt()
|
||||
|
||||
try {
|
||||
Collections.swap(categories, currentIndex, newPosition)
|
||||
|
||||
val updates = categories.mapIndexed { index, category ->
|
||||
CategoryUpdate(
|
||||
id = category.id,
|
||||
order = index.toLong(),
|
||||
)
|
||||
}
|
||||
|
||||
categoryRepository.updatePartial(updates)
|
||||
Result.Success
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
Result.InternalError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
object Unchanged : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
|
||||
private enum class MoveTo {
|
||||
UP,
|
||||
DOWN,
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
import tachiyomi.domain.library.model.plus
|
||||
|
||||
class ResetCategoryFlags(
|
||||
private val preferences: LibraryPreferences,
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await() {
|
||||
val display = preferences.libraryDisplayMode().get()
|
||||
val sort = preferences.librarySortingMode().get()
|
||||
categoryRepository.updateAllFlags(display + sort.type + sort.direction)
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.category.model.CategoryUpdate
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
import tachiyomi.domain.library.model.plus
|
||||
|
||||
class SetDisplayModeForCategory(
|
||||
private val preferences: LibraryPreferences,
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(categoryId: Long, display: LibraryDisplayMode) {
|
||||
val category = categoryRepository.get(categoryId) ?: return
|
||||
val flags = category.flags + display
|
||||
if (preferences.categorizedDisplaySettings().get()) {
|
||||
categoryRepository.updatePartial(
|
||||
CategoryUpdate(
|
||||
id = category.id,
|
||||
flags = flags,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
preferences.libraryDisplayMode().set(display)
|
||||
categoryRepository.updateAllFlags(flags)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun await(category: Category, display: LibraryDisplayMode) {
|
||||
await(category.id, display)
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class SetMangaCategories(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long, categoryIds: List<Long>) {
|
||||
try {
|
||||
mangaRepository.setMangaCategories(mangaId, categoryIds)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.category.model.CategoryUpdate
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
import tachiyomi.domain.library.model.LibrarySort
|
||||
import tachiyomi.domain.library.model.plus
|
||||
|
||||
class SetSortModeForCategory(
|
||||
private val preferences: LibraryPreferences,
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(categoryId: Long, type: LibrarySort.Type, direction: LibrarySort.Direction) {
|
||||
val category = categoryRepository.get(categoryId) ?: return
|
||||
val flags = category.flags + type + direction
|
||||
if (preferences.categorizedDisplaySettings().get()) {
|
||||
categoryRepository.updatePartial(
|
||||
CategoryUpdate(
|
||||
id = category.id,
|
||||
flags = flags,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
preferences.librarySortingMode().set(LibrarySort(type, direction))
|
||||
categoryRepository.updateAllFlags(flags)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun await(category: Category, type: LibrarySort.Type, direction: LibrarySort.Direction) {
|
||||
await(category.id, type, direction)
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.domain.category.model.CategoryUpdate
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
|
||||
class UpdateCategory(
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(payload: CategoryUpdate): Result = withNonCancellableContext {
|
||||
try {
|
||||
categoryRepository.updatePartial(payload)
|
||||
Result.Success
|
||||
} catch (e: Exception) {
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
data class Error(val error: Exception) : Result()
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
|
||||
class GetChapter(
|
||||
private val chapterRepository: ChapterRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(id: Long): Chapter? {
|
||||
return try {
|
||||
chapterRepository.getChapterById(id)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun await(url: String, mangaId: Long): Chapter? {
|
||||
return try {
|
||||
chapterRepository.getChapterByUrlAndMangaId(url, mangaId)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
|
||||
class GetChapterByMangaId(
|
||||
private val chapterRepository: ChapterRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long): List<Chapter> {
|
||||
return try {
|
||||
chapterRepository.getChapterByMangaId(mangaId)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
import eu.kanade.domain.manga.interactor.GetFavorites
|
||||
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
|
||||
class SetMangaDefaultChapterFlags(
|
||||
private val libraryPreferences: LibraryPreferences,
|
||||
private val setMangaChapterFlags: SetMangaChapterFlags,
|
||||
private val getFavorites: GetFavorites,
|
||||
) {
|
||||
|
||||
suspend fun await(manga: Manga) {
|
||||
withNonCancellableContext {
|
||||
with(libraryPreferences) {
|
||||
setMangaChapterFlags.awaitSetAllFlags(
|
||||
mangaId = manga.id,
|
||||
unreadFilter = filterChapterByRead().get(),
|
||||
downloadedFilter = filterChapterByDownloaded().get(),
|
||||
bookmarkedFilter = filterChapterByBookmarked().get(),
|
||||
sortingMode = sortChapterBySourceOrNumber().get(),
|
||||
sortingDirection = sortChapterByAscendingOrDescending().get(),
|
||||
displayMode = displayChapterByNameOrNumber().get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun awaitAll() {
|
||||
withNonCancellableContext {
|
||||
getFavorites.await().forEach { await(it) }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||
import eu.kanade.domain.download.service.DownloadPreferences
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.model.ChapterUpdate
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class SetReadStatus(
|
||||
private val downloadPreferences: DownloadPreferences,
|
||||
private val deleteDownload: DeleteDownload,
|
||||
private val mangaRepository: MangaRepository,
|
||||
private val chapterRepository: ChapterRepository,
|
||||
) {
|
||||
|
||||
private val mapper = { chapter: Chapter, read: Boolean ->
|
||||
ChapterUpdate(
|
||||
read = read,
|
||||
lastPageRead = if (!read) 0 else null,
|
||||
id = chapter.id,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun await(read: Boolean, vararg chapters: Chapter): Result = withNonCancellableContext {
|
||||
val chaptersToUpdate = chapters.filter {
|
||||
when (read) {
|
||||
true -> !it.read
|
||||
false -> it.read || it.lastPageRead > 0
|
||||
}
|
||||
}
|
||||
if (chaptersToUpdate.isEmpty()) {
|
||||
return@withNonCancellableContext Result.NoChapters
|
||||
}
|
||||
|
||||
try {
|
||||
chapterRepository.updateAll(
|
||||
chaptersToUpdate.map { mapper(it, read) },
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
return@withNonCancellableContext Result.InternalError(e)
|
||||
}
|
||||
|
||||
if (read && downloadPreferences.removeAfterMarkedAsRead().get()) {
|
||||
chaptersToUpdate
|
||||
.groupBy { it.mangaId }
|
||||
.forEach { (mangaId, chapters) ->
|
||||
deleteDownload.awaitAll(
|
||||
manga = mangaRepository.getMangaById(mangaId),
|
||||
chapters = chapters.toTypedArray(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Result.Success
|
||||
}
|
||||
|
||||
suspend fun await(mangaId: Long, read: Boolean): Result = withNonCancellableContext {
|
||||
await(
|
||||
read = read,
|
||||
chapters = chapterRepository
|
||||
.getChapterByMangaId(mangaId)
|
||||
.toTypedArray(),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun await(manga: Manga, read: Boolean) =
|
||||
await(manga.id, read)
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
object NoChapters : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import eu.kanade.domain.chapter.model.copyFromSChapter
|
||||
import eu.kanade.domain.chapter.model.toSChapter
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.toSManga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.isLocal
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
|
||||
import tachiyomi.data.chapter.ChapterSanitizer
|
||||
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.model.NoChaptersException
|
||||
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.lang.Long.max
|
||||
import java.util.Date
|
||||
import java.util.TreeSet
|
||||
|
||||
class SyncChaptersWithSource(
|
||||
private val downloadManager: DownloadManager = Injekt.get(),
|
||||
private val downloadProvider: DownloadProvider = Injekt.get(),
|
||||
private val chapterRepository: ChapterRepository = Injekt.get(),
|
||||
private val shouldUpdateDbChapter: ShouldUpdateDbChapter = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
||||
) {
|
||||
|
||||
/**
|
||||
* Method to synchronize db chapters with source ones
|
||||
*
|
||||
* @param rawSourceChapters the chapters from the source.
|
||||
* @param manga the manga the chapters belong to.
|
||||
* @param source the source the manga belongs to.
|
||||
* @return Newly added chapters
|
||||
*/
|
||||
suspend fun await(
|
||||
rawSourceChapters: List<SChapter>,
|
||||
manga: Manga,
|
||||
source: Source,
|
||||
): List<Chapter> {
|
||||
if (rawSourceChapters.isEmpty() && !source.isLocal()) {
|
||||
throw NoChaptersException()
|
||||
}
|
||||
|
||||
val sourceChapters = rawSourceChapters
|
||||
.distinctBy { it.url }
|
||||
.mapIndexed { i, sChapter ->
|
||||
Chapter.create()
|
||||
.copyFromSChapter(sChapter)
|
||||
.copy(name = with(ChapterSanitizer) { sChapter.name.sanitize(manga.title) })
|
||||
.copy(mangaId = manga.id, sourceOrder = i.toLong())
|
||||
}
|
||||
|
||||
// Chapters from db.
|
||||
val dbChapters = getChapterByMangaId.await(manga.id)
|
||||
|
||||
// Chapters from the source not in db.
|
||||
val toAdd = mutableListOf<Chapter>()
|
||||
|
||||
// Chapters whose metadata have changed.
|
||||
val toChange = mutableListOf<Chapter>()
|
||||
|
||||
// Chapters from the db not in source.
|
||||
val toDelete = dbChapters.filterNot { dbChapter ->
|
||||
sourceChapters.any { sourceChapter ->
|
||||
dbChapter.url == sourceChapter.url
|
||||
}
|
||||
}
|
||||
|
||||
val rightNow = Date().time
|
||||
|
||||
// Used to not set upload date of older chapters
|
||||
// to a higher value than newer chapters
|
||||
var maxSeenUploadDate = 0L
|
||||
|
||||
val sManga = manga.toSManga()
|
||||
for (sourceChapter in sourceChapters) {
|
||||
var chapter = sourceChapter
|
||||
|
||||
// Update metadata from source if necessary.
|
||||
if (source is HttpSource) {
|
||||
val sChapter = chapter.toSChapter()
|
||||
source.prepareNewChapter(sChapter, sManga)
|
||||
chapter = chapter.copyFromSChapter(sChapter)
|
||||
}
|
||||
|
||||
// Recognize chapter number for the chapter.
|
||||
val chapterNumber = ChapterRecognition.parseChapterNumber(manga.title, chapter.name, chapter.chapterNumber)
|
||||
chapter = chapter.copy(chapterNumber = chapterNumber)
|
||||
|
||||
val dbChapter = dbChapters.find { it.url == chapter.url }
|
||||
|
||||
if (dbChapter == null) {
|
||||
val toAddChapter = if (chapter.dateUpload == 0L) {
|
||||
val altDateUpload = if (maxSeenUploadDate == 0L) rightNow else maxSeenUploadDate
|
||||
chapter.copy(dateUpload = altDateUpload)
|
||||
} else {
|
||||
maxSeenUploadDate = max(maxSeenUploadDate, sourceChapter.dateUpload)
|
||||
chapter
|
||||
}
|
||||
toAdd.add(toAddChapter)
|
||||
} else {
|
||||
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
||||
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
|
||||
downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, manga.title, manga.source)
|
||||
|
||||
if (shouldRenameChapter) {
|
||||
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
||||
}
|
||||
var toChangeChapter = dbChapter.copy(
|
||||
name = chapter.name,
|
||||
chapterNumber = chapter.chapterNumber,
|
||||
scanlator = chapter.scanlator,
|
||||
sourceOrder = chapter.sourceOrder,
|
||||
)
|
||||
if (chapter.dateUpload != 0L) {
|
||||
toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload)
|
||||
}
|
||||
toChange.add(toChangeChapter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val reAdded = mutableListOf<Chapter>()
|
||||
|
||||
val deletedChapterNumbers = TreeSet<Float>()
|
||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
||||
val deletedBookmarkedChapterNumbers = TreeSet<Float>()
|
||||
|
||||
toDelete.forEach { chapter ->
|
||||
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
||||
if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber)
|
||||
deletedChapterNumbers.add(chapter.chapterNumber)
|
||||
}
|
||||
|
||||
val deletedChapterNumberDateFetchMap = toDelete.sortedByDescending { it.dateFetch }
|
||||
.associate { it.chapterNumber to it.dateFetch }
|
||||
|
||||
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
||||
// Sources MUST return the chapters from most to less recent, which is common.
|
||||
var itemCount = toAdd.size
|
||||
var updatedToAdd = toAdd.map { toAddItem ->
|
||||
var chapter = toAddItem.copy(dateFetch = rightNow + itemCount--)
|
||||
|
||||
if (chapter.isRecognizedNumber.not() || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
||||
|
||||
chapter = chapter.copy(
|
||||
read = chapter.chapterNumber in deletedReadChapterNumbers,
|
||||
bookmark = chapter.chapterNumber in deletedBookmarkedChapterNumbers,
|
||||
)
|
||||
|
||||
// Try to to use the fetch date of the original entry to not pollute 'Updates' tab
|
||||
deletedChapterNumberDateFetchMap[chapter.chapterNumber]?.let {
|
||||
chapter = chapter.copy(dateFetch = it)
|
||||
}
|
||||
|
||||
reAdded.add(chapter)
|
||||
|
||||
chapter
|
||||
}
|
||||
|
||||
if (toDelete.isNotEmpty()) {
|
||||
val toDeleteIds = toDelete.map { it.id }
|
||||
chapterRepository.removeChaptersWithIds(toDeleteIds)
|
||||
}
|
||||
|
||||
if (updatedToAdd.isNotEmpty()) {
|
||||
updatedToAdd = chapterRepository.addAll(updatedToAdd)
|
||||
}
|
||||
|
||||
if (toChange.isNotEmpty()) {
|
||||
val chapterUpdates = toChange.map { it.toChapterUpdate() }
|
||||
updateChapter.awaitAll(chapterUpdates)
|
||||
}
|
||||
|
||||
// Set this manga as updated since chapters were changed
|
||||
// Note that last_update actually represents last time the chapter list changed at all
|
||||
updateManga.awaitUpdateLastUpdate(manga.id)
|
||||
|
||||
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
||||
|
||||
return updatedToAdd.filterNot { it.url in reAddedUrls }
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import eu.kanade.domain.track.interactor.InsertTrack
|
||||
import eu.kanade.domain.track.model.toDbTrack
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||
import tachiyomi.domain.track.model.Track
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class SyncChaptersWithTrackServiceTwoWay(
|
||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||
private val insertTrack: InsertTrack = Injekt.get(),
|
||||
) {
|
||||
|
||||
suspend fun await(
|
||||
chapters: List<Chapter>,
|
||||
remoteTrack: Track,
|
||||
service: TrackService,
|
||||
) {
|
||||
val sortedChapters = chapters.sortedBy { it.chapterNumber }
|
||||
val chapterUpdates = sortedChapters
|
||||
.filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read }
|
||||
.map { it.copy(read = true).toChapterUpdate() }
|
||||
|
||||
// only take into account continuous reading
|
||||
val localLastRead = sortedChapters.takeWhile { it.read }.lastOrNull()?.chapterNumber ?: 0F
|
||||
val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble())
|
||||
|
||||
try {
|
||||
service.update(updatedTrack.toDbTrack())
|
||||
updateChapter.awaitAll(chapterUpdates)
|
||||
insertTrack.await(updatedTrack)
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.WARN, e)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.chapter.model.ChapterUpdate
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
|
||||
class UpdateChapter(
|
||||
private val chapterRepository: ChapterRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(chapterUpdate: ChapterUpdate) {
|
||||
try {
|
||||
chapterRepository.update(chapterUpdate)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun awaitAll(chapterUpdates: List<ChapterUpdate>) {
|
||||
try {
|
||||
chapterRepository.updateAll(chapterUpdates)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
||||
}
|
||||
}
|
42
app/src/main/java/eu/kanade/domain/chapter/model/Chapter.kt
Normal file
@ -0,0 +1,42 @@
|
||||
package eu.kanade.domain.chapter.model
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
|
||||
|
||||
// TODO: Remove when all deps are migrated
|
||||
fun Chapter.toSChapter(): SChapter {
|
||||
return SChapter.create().also {
|
||||
it.url = url
|
||||
it.name = name
|
||||
it.date_upload = dateUpload
|
||||
it.chapter_number = chapterNumber
|
||||
it.scanlator = scanlator
|
||||
}
|
||||
}
|
||||
|
||||
fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
|
||||
return this.copy(
|
||||
name = sChapter.name,
|
||||
url = sChapter.url,
|
||||
dateUpload = sChapter.date_upload,
|
||||
chapterNumber = sChapter.chapter_number,
|
||||
scanlator = sChapter.scanlator?.ifBlank { null },
|
||||
)
|
||||
}
|
||||
|
||||
fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
||||
it.id = id
|
||||
it.manga_id = mangaId
|
||||
it.url = url
|
||||
it.name = name
|
||||
it.scanlator = scanlator
|
||||
it.read = read
|
||||
it.bookmark = bookmark
|
||||
it.last_page_read = lastPageRead.toInt()
|
||||
it.date_fetch = dateFetch
|
||||
it.date_upload = dateUpload
|
||||
it.chapter_number = chapterNumber
|
||||
it.source_order = sourceOrder.toInt()
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package eu.kanade.domain.chapter.model
|
||||
|
||||
import eu.kanade.domain.manga.model.downloadedFilter
|
||||
import eu.kanade.domain.manga.model.isLocal
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.ui.manga.ChapterItem
|
||||
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.TriStateFilter
|
||||
|
||||
/**
|
||||
* Applies the view filters to the list of chapters obtained from the database.
|
||||
* @return an observable of the list of chapters filtered and sorted.
|
||||
*/
|
||||
fun List<Chapter>.applyFilters(manga: Manga, downloadManager: DownloadManager): List<Chapter> {
|
||||
val isLocalManga = manga.isLocal()
|
||||
val unreadFilter = manga.unreadFilter
|
||||
val downloadedFilter = manga.downloadedFilter
|
||||
val bookmarkedFilter = manga.bookmarkedFilter
|
||||
|
||||
return filter { chapter ->
|
||||
when (unreadFilter) {
|
||||
TriStateFilter.DISABLED -> true
|
||||
TriStateFilter.ENABLED_IS -> !chapter.read
|
||||
TriStateFilter.ENABLED_NOT -> chapter.read
|
||||
}
|
||||
}
|
||||
.filter { chapter ->
|
||||
when (bookmarkedFilter) {
|
||||
TriStateFilter.DISABLED -> true
|
||||
TriStateFilter.ENABLED_IS -> chapter.bookmark
|
||||
TriStateFilter.ENABLED_NOT -> !chapter.bookmark
|
||||
}
|
||||
}
|
||||
.filter { chapter ->
|
||||
val downloaded = downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.title, manga.source)
|
||||
val downloadState = when {
|
||||
downloaded -> Download.State.DOWNLOADED
|
||||
else -> Download.State.NOT_DOWNLOADED
|
||||
}
|
||||
when (downloadedFilter) {
|
||||
TriStateFilter.DISABLED -> true
|
||||
TriStateFilter.ENABLED_IS -> downloadState == Download.State.DOWNLOADED || isLocalManga
|
||||
TriStateFilter.ENABLED_NOT -> downloadState != Download.State.DOWNLOADED && !isLocalManga
|
||||
}
|
||||
}
|
||||
.sortedWith(getChapterSort(manga))
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the view filters to the list of chapters obtained from the database.
|
||||
* @return an observable of the list of chapters filtered and sorted.
|
||||
*/
|
||||
fun List<ChapterItem>.applyFilters(manga: Manga): Sequence<ChapterItem> {
|
||||
val isLocalManga = manga.isLocal()
|
||||
val unreadFilter = manga.unreadFilter
|
||||
val downloadedFilter = manga.downloadedFilter
|
||||
val bookmarkedFilter = manga.bookmarkedFilter
|
||||
return asSequence()
|
||||
.filter { (chapter) ->
|
||||
when (unreadFilter) {
|
||||
TriStateFilter.DISABLED -> true
|
||||
TriStateFilter.ENABLED_IS -> !chapter.read
|
||||
TriStateFilter.ENABLED_NOT -> chapter.read
|
||||
}
|
||||
}
|
||||
.filter { (chapter) ->
|
||||
when (bookmarkedFilter) {
|
||||
TriStateFilter.DISABLED -> true
|
||||
TriStateFilter.ENABLED_IS -> chapter.bookmark
|
||||
TriStateFilter.ENABLED_NOT -> !chapter.bookmark
|
||||
}
|
||||
}
|
||||
.filter {
|
||||
when (downloadedFilter) {
|
||||
TriStateFilter.DISABLED -> true
|
||||
TriStateFilter.ENABLED_IS -> it.isDownloaded || isLocalManga
|
||||
TriStateFilter.ENABLED_NOT -> !it.isDownloaded && !isLocalManga
|
||||
}
|
||||
}
|
||||
.sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) }
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package eu.kanade.domain.download.interactor
|
||||
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
|
||||
class DeleteDownload(
|
||||
private val sourceManager: SourceManager,
|
||||
private val downloadManager: DownloadManager,
|
||||
) {
|
||||
|
||||
suspend fun awaitAll(manga: Manga, vararg chapters: Chapter) = withNonCancellableContext {
|
||||
sourceManager.get(manga.source)?.let { source ->
|
||||
downloadManager.deleteChapters(chapters.toList(), manga, source)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package eu.kanade.domain.download.service
|
||||
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.core.provider.FolderProvider
|
||||
|
||||
class DownloadPreferences(
|
||||
private val folderProvider: FolderProvider,
|
||||
private val preferenceStore: PreferenceStore,
|
||||
) {
|
||||
|
||||
fun downloadsDirectory() = preferenceStore.getString("download_directory", folderProvider.path())
|
||||
|
||||
fun downloadOnlyOverWifi() = preferenceStore.getBoolean("pref_download_only_over_wifi_key", true)
|
||||
|
||||
fun saveChaptersAsCBZ() = preferenceStore.getBoolean("save_chapter_as_cbz", true)
|
||||
|
||||
fun splitTallImages() = preferenceStore.getBoolean("split_tall_images", false)
|
||||
|
||||
fun autoDownloadWhileReading() = preferenceStore.getInt("auto_download_while_reading", 0)
|
||||
|
||||
fun removeAfterReadSlots() = preferenceStore.getInt("remove_after_read_slots", -1)
|
||||
|
||||
fun removeAfterMarkedAsRead() = preferenceStore.getBoolean("pref_remove_after_marked_as_read_key", false)
|
||||
|
||||
fun removeBookmarkedChapters() = preferenceStore.getBoolean("pref_remove_bookmarked", false)
|
||||
|
||||
fun removeExcludeCategories() = preferenceStore.getStringSet("remove_exclude_categories", emptySet())
|
||||
|
||||
fun downloadNewChapters() = preferenceStore.getBoolean("download_new", false)
|
||||
|
||||
fun downloadNewChapterCategories() = preferenceStore.getStringSet("download_new_categories", emptySet())
|
||||
|
||||
fun downloadNewChapterCategoriesExclude() = preferenceStore.getStringSet("download_new_categories_exclude", emptySet())
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package eu.kanade.domain.extension.interactor
|
||||
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
class GetExtensionLanguages(
|
||||
private val preferences: SourcePreferences,
|
||||
private val extensionManager: ExtensionManager,
|
||||
) {
|
||||
fun subscribe(): Flow<List<String>> {
|
||||
return combine(
|
||||
preferences.enabledLanguages().changes(),
|
||||
extensionManager.availableExtensionsFlow,
|
||||
) { enabledLanguage, availableExtensions ->
|
||||
availableExtensions
|
||||
.flatMap { ext ->
|
||||
if (ext.sources.isEmpty()) {
|
||||
listOf(ext.lang)
|
||||
} else {
|
||||
ext.sources.map { it.lang }
|
||||
}
|
||||
}
|
||||
.distinct()
|
||||
.sortedWith(
|
||||
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package eu.kanade.domain.extension.interactor
|
||||
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetExtensionSources(
|
||||
private val preferences: SourcePreferences,
|
||||
) {
|
||||
|
||||
fun subscribe(extension: Extension.Installed): Flow<List<ExtensionSourceItem>> {
|
||||
val isMultiSource = extension.sources.size > 1
|
||||
val isMultiLangSingleSource =
|
||||
isMultiSource && extension.sources.map { it.name }.distinct().size == 1
|
||||
|
||||
return preferences.disabledSources().changes().map { disabledSources ->
|
||||
fun Source.isEnabled() = id.toString() !in disabledSources
|
||||
|
||||
extension.sources
|
||||
.map { source ->
|
||||
ExtensionSourceItem(
|
||||
source = source,
|
||||
enabled = source.isEnabled(),
|
||||
labelAsName = isMultiSource && isMultiLangSingleSource.not(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ExtensionSourceItem(
|
||||
val source: Source,
|
||||
val enabled: Boolean,
|
||||
val labelAsName: Boolean,
|
||||
)
|
@ -0,0 +1,60 @@
|
||||
package eu.kanade.domain.extension.interactor
|
||||
|
||||
import eu.kanade.domain.extension.model.Extensions
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
class GetExtensionsByType(
|
||||
private val preferences: SourcePreferences,
|
||||
private val extensionManager: ExtensionManager,
|
||||
) {
|
||||
|
||||
fun subscribe(): Flow<Extensions> {
|
||||
val showNsfwSources = preferences.showNsfwSource().get()
|
||||
|
||||
return combine(
|
||||
preferences.enabledLanguages().changes(),
|
||||
extensionManager.installedExtensionsFlow,
|
||||
extensionManager.untrustedExtensionsFlow,
|
||||
extensionManager.availableExtensionsFlow,
|
||||
) { _activeLanguages, _installed, _untrusted, _available ->
|
||||
val (updates, installed) = _installed
|
||||
.filter { (showNsfwSources || it.isNsfw.not()) }
|
||||
.sortedWith(
|
||||
compareBy<Extension.Installed> { it.isObsolete.not() }
|
||||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
||||
)
|
||||
.partition { it.hasUpdate }
|
||||
|
||||
val untrusted = _untrusted
|
||||
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||
|
||||
val available = _available
|
||||
.filter { extension ->
|
||||
_installed.none { it.pkgName == extension.pkgName } &&
|
||||
_untrusted.none { it.pkgName == extension.pkgName } &&
|
||||
(showNsfwSources || extension.isNsfw.not())
|
||||
}
|
||||
.flatMap { ext ->
|
||||
if (ext.sources.isEmpty()) {
|
||||
return@flatMap if (ext.lang in _activeLanguages) listOf(ext) else emptyList()
|
||||
}
|
||||
ext.sources.filter { it.lang in _activeLanguages }
|
||||
.map {
|
||||
ext.copy(
|
||||
name = it.name,
|
||||
lang = it.lang,
|
||||
pkgName = "${ext.pkgName}-${it.id}",
|
||||
sources = listOf(it),
|
||||
)
|
||||
}
|
||||
}
|
||||
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||
|
||||
Extensions(updates, installed, available, untrusted)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package eu.kanade.domain.extension.model
|
||||
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
|
||||
data class Extensions(
|
||||
val updates: List<Extension.Installed>,
|
||||
val installed: List<Extension.Installed>,
|
||||
val available: List<Extension.Available>,
|
||||
val untrusted: List<Extension.Untrusted>,
|
||||
)
|
@ -0,0 +1,52 @@
|
||||
package eu.kanade.domain.history.interactor
|
||||
|
||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||
import eu.kanade.domain.manga.interactor.GetManga
|
||||
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.history.repository.HistoryRepository
|
||||
import kotlin.math.max
|
||||
|
||||
class GetNextChapters(
|
||||
private val getChapterByMangaId: GetChapterByMangaId,
|
||||
private val getManga: GetManga,
|
||||
private val historyRepository: HistoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(onlyUnread: Boolean = true): List<Chapter> {
|
||||
val history = historyRepository.getLastHistory() ?: return emptyList()
|
||||
return await(history.mangaId, history.chapterId, onlyUnread)
|
||||
}
|
||||
|
||||
suspend fun await(mangaId: Long, onlyUnread: Boolean = true): List<Chapter> {
|
||||
val manga = getManga.await(mangaId) ?: return emptyList()
|
||||
val chapters = getChapterByMangaId.await(mangaId)
|
||||
.sortedWith(getChapterSort(manga, sortDescending = false))
|
||||
|
||||
return if (onlyUnread) {
|
||||
chapters.filterNot { it.read }
|
||||
} else {
|
||||
chapters
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun await(mangaId: Long, fromChapterId: Long, onlyUnread: Boolean = true): List<Chapter> {
|
||||
val chapters = await(mangaId, onlyUnread)
|
||||
val currChapterIndex = chapters.indexOfFirst { it.id == fromChapterId }
|
||||
val nextChapters = chapters.subList(max(0, currChapterIndex), chapters.size)
|
||||
|
||||
if (onlyUnread) {
|
||||
return nextChapters
|
||||
}
|
||||
|
||||
// The "next chapter" is either:
|
||||
// - The current chapter if it isn't completely read
|
||||
// - The chapters after the current chapter if the current one is completely read
|
||||
val fromChapter = chapters.getOrNull(currChapterIndex)
|
||||
return if (fromChapter != null && !fromChapter.read) {
|
||||
nextChapters
|
||||
} else {
|
||||
nextChapters.drop(1)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package eu.kanade.domain.library.service
|
||||
|
||||
import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI
|
||||
import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD
|
||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
|
||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
import tachiyomi.domain.library.model.LibrarySort
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
|
||||
class LibraryPreferences(
|
||||
private val preferenceStore: PreferenceStore,
|
||||
) {
|
||||
|
||||
fun libraryDisplayMode() = preferenceStore.getObject("pref_display_mode_library", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
|
||||
|
||||
fun librarySortingMode() = preferenceStore.getObject("library_sorting_mode", LibrarySort.default, LibrarySort.Serializer::serialize, LibrarySort.Serializer::deserialize)
|
||||
|
||||
fun portraitColumns() = preferenceStore.getInt("pref_library_columns_portrait_key", 0)
|
||||
|
||||
fun landscapeColumns() = preferenceStore.getInt("pref_library_columns_landscape_key", 0)
|
||||
|
||||
fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
|
||||
fun libraryUpdateLastTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L)
|
||||
|
||||
fun libraryUpdateDeviceRestriction() = preferenceStore.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI))
|
||||
fun libraryUpdateMangaRestriction() = preferenceStore.getStringSet("library_update_manga_restriction", setOf(MANGA_HAS_UNREAD, MANGA_NON_COMPLETED, MANGA_NON_READ))
|
||||
|
||||
fun autoUpdateMetadata() = preferenceStore.getBoolean("auto_update_metadata", false)
|
||||
|
||||
fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", false)
|
||||
|
||||
fun showContinueReadingButton() = preferenceStore.getBoolean("display_continue_reading_button", false)
|
||||
|
||||
// region Filter
|
||||
|
||||
fun filterDownloaded() = preferenceStore.getInt("pref_filter_library_downloaded", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
||||
|
||||
fun filterUnread() = preferenceStore.getInt("pref_filter_library_unread", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
||||
|
||||
fun filterStarted() = preferenceStore.getInt("pref_filter_library_started", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
||||
|
||||
fun filterBookmarked() = preferenceStore.getInt("pref_filter_library_bookmarked", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
||||
|
||||
fun filterCompleted() = preferenceStore.getInt("pref_filter_library_completed", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
||||
|
||||
fun filterTracking(name: Int) = preferenceStore.getInt("pref_filter_library_tracked_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
||||
|
||||
// endregion
|
||||
|
||||
// region Badges
|
||||
|
||||
fun downloadBadge() = preferenceStore.getBoolean("display_download_badge", false)
|
||||
|
||||
fun localBadge() = preferenceStore.getBoolean("display_local_badge", true)
|
||||
|
||||
fun languageBadge() = preferenceStore.getBoolean("display_language_badge", false)
|
||||
|
||||
fun newShowUpdatesCount() = preferenceStore.getBoolean("library_show_updates_count", true)
|
||||
fun newUpdatesCount() = preferenceStore.getInt("library_unseen_updates_count", 0)
|
||||
|
||||
// endregion
|
||||
|
||||
// region Category
|
||||
|
||||
fun defaultCategory() = preferenceStore.getInt("default_category", -1)
|
||||
|
||||
fun lastUsedCategory() = preferenceStore.getInt("last_used_category", 0)
|
||||
|
||||
fun categoryTabs() = preferenceStore.getBoolean("display_category_tabs", true)
|
||||
|
||||
fun categoryNumberOfItems() = preferenceStore.getBoolean("display_number_of_items", false)
|
||||
|
||||
fun categorizedDisplaySettings() = preferenceStore.getBoolean("categorized_display", false)
|
||||
|
||||
fun libraryUpdateCategories() = preferenceStore.getStringSet("library_update_categories", emptySet())
|
||||
|
||||
fun libraryUpdateCategoriesExclude() = preferenceStore.getStringSet("library_update_categories_exclude", emptySet())
|
||||
|
||||
// endregion
|
||||
|
||||
// region Chapter
|
||||
|
||||
fun filterChapterByRead() = preferenceStore.getLong("default_chapter_filter_by_read", Manga.SHOW_ALL)
|
||||
|
||||
fun filterChapterByDownloaded() = preferenceStore.getLong("default_chapter_filter_by_downloaded", Manga.SHOW_ALL)
|
||||
|
||||
fun filterChapterByBookmarked() = preferenceStore.getLong("default_chapter_filter_by_bookmarked", Manga.SHOW_ALL)
|
||||
|
||||
// and upload date
|
||||
fun sortChapterBySourceOrNumber() = preferenceStore.getLong("default_chapter_sort_by_source_or_number", Manga.CHAPTER_SORTING_SOURCE)
|
||||
|
||||
fun displayChapterByNameOrNumber() = preferenceStore.getLong("default_chapter_display_by_name_or_number", Manga.CHAPTER_DISPLAY_NAME)
|
||||
|
||||
fun sortChapterByAscendingOrDescending() = preferenceStore.getLong("default_chapter_sort_by_ascending_or_descending", Manga.CHAPTER_SORT_DESC)
|
||||
|
||||
fun setChapterSettingsDefault(manga: Manga) {
|
||||
filterChapterByRead().set(manga.unreadFilterRaw)
|
||||
filterChapterByDownloaded().set(manga.downloadedFilterRaw)
|
||||
filterChapterByBookmarked().set(manga.bookmarkedFilterRaw)
|
||||
sortChapterBySourceOrNumber().set(manga.sorting)
|
||||
displayChapterByNameOrNumber().set(manga.displayMode)
|
||||
sortChapterByAscendingOrDescending().set(if (manga.sortDescending()) Manga.CHAPTER_SORT_DESC else Manga.CHAPTER_SORT_ASC)
|
||||
}
|
||||
|
||||
fun autoClearChapterCache() = preferenceStore.getBoolean("auto_clear_chapter_cache", false)
|
||||
|
||||
// endregion
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class GetDuplicateLibraryManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(title: String): Manga? {
|
||||
return mangaRepository.getDuplicateLibraryManga(title.lowercase())
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class GetFavorites(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): List<Manga> {
|
||||
return mangaRepository.getFavorites()
|
||||
}
|
||||
|
||||
fun subscribe(sourceId: Long): Flow<List<Manga>> {
|
||||
return mangaRepository.getFavoritesBySourceId(sourceId)
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import tachiyomi.domain.library.model.LibraryManga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class GetLibraryManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): List<LibraryManga> {
|
||||
return mangaRepository.getLibraryManga()
|
||||
}
|
||||
|
||||
fun subscribe(): Flow<List<LibraryManga>> {
|
||||
return mangaRepository.getLibraryMangaAsFlow()
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class GetManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(id: Long): Manga? {
|
||||
return try {
|
||||
mangaRepository.getMangaById(id)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun subscribe(id: Long): Flow<Manga> {
|
||||
return mangaRepository.getMangaByIdAsFlow(id)
|
||||
}
|
||||
|
||||
fun subscribe(url: String, sourceId: Long): Flow<Manga?> {
|
||||
return mangaRepository.getMangaByUrlAndSourceIdAsFlow(url, sourceId)
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class GetMangaWithChapters(
|
||||
private val mangaRepository: MangaRepository,
|
||||
private val chapterRepository: ChapterRepository,
|
||||
) {
|
||||
|
||||
suspend fun subscribe(id: Long): Flow<Pair<Manga, List<Chapter>>> {
|
||||
return combine(
|
||||
mangaRepository.getMangaByIdAsFlow(id),
|
||||
chapterRepository.getChapterByMangaIdAsFlow(id),
|
||||
) { manga, chapters ->
|
||||
Pair(manga, chapters)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun awaitManga(id: Long): Manga {
|
||||
return mangaRepository.getMangaById(id)
|
||||
}
|
||||
|
||||
suspend fun awaitChapters(id: Long): List<Chapter> {
|
||||
return chapterRepository.getChapterByMangaId(id)
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class NetworkToLocalManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(manga: Manga): Manga {
|
||||
val localManga = getManga(manga.url, manga.source)
|
||||
return when {
|
||||
localManga == null -> {
|
||||
val id = insertManga(manga)
|
||||
manga.copy(id = id!!)
|
||||
}
|
||||
!localManga.favorite -> {
|
||||
// if the manga isn't a favorite, set its display title from source
|
||||
// if it later becomes a favorite, updated title will go to db
|
||||
localManga.copy(title = manga.title)
|
||||
}
|
||||
else -> {
|
||||
localManga
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getManga(url: String, sourceId: Long): Manga? {
|
||||
return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
|
||||
}
|
||||
|
||||
private suspend fun insertManga(manga: Manga): Long? {
|
||||
return mangaRepository.insert(manga)
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class ResetViewerFlags(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): Boolean {
|
||||
return mangaRepository.resetViewerFlags()
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class SetMangaChapterFlags(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun awaitSetDownloadedFilter(manga: Manga, flag: Long): Boolean {
|
||||
return mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = manga.id,
|
||||
chapterFlags = manga.chapterFlags.setFlag(flag, Manga.CHAPTER_DOWNLOADED_MASK),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitSetUnreadFilter(manga: Manga, flag: Long): Boolean {
|
||||
return mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = manga.id,
|
||||
chapterFlags = manga.chapterFlags.setFlag(flag, Manga.CHAPTER_UNREAD_MASK),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitSetBookmarkFilter(manga: Manga, flag: Long): Boolean {
|
||||
return mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = manga.id,
|
||||
chapterFlags = manga.chapterFlags.setFlag(flag, Manga.CHAPTER_BOOKMARKED_MASK),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitSetDisplayMode(manga: Manga, flag: Long): Boolean {
|
||||
return mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = manga.id,
|
||||
chapterFlags = manga.chapterFlags.setFlag(flag, Manga.CHAPTER_DISPLAY_MASK),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitSetSortingModeOrFlipOrder(manga: Manga, flag: Long): Boolean {
|
||||
val newFlags = manga.chapterFlags.let {
|
||||
if (manga.sorting == flag) {
|
||||
// Just flip the order
|
||||
val orderFlag = if (manga.sortDescending()) {
|
||||
Manga.CHAPTER_SORT_ASC
|
||||
} else {
|
||||
Manga.CHAPTER_SORT_DESC
|
||||
}
|
||||
it.setFlag(orderFlag, Manga.CHAPTER_SORT_DIR_MASK)
|
||||
} else {
|
||||
// Set new flag with ascending order
|
||||
it
|
||||
.setFlag(flag, Manga.CHAPTER_SORTING_MASK)
|
||||
.setFlag(Manga.CHAPTER_SORT_ASC, Manga.CHAPTER_SORT_DIR_MASK)
|
||||
}
|
||||
}
|
||||
return mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = manga.id,
|
||||
chapterFlags = newFlags,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitSetAllFlags(
|
||||
mangaId: Long,
|
||||
unreadFilter: Long,
|
||||
downloadedFilter: Long,
|
||||
bookmarkedFilter: Long,
|
||||
sortingMode: Long,
|
||||
sortingDirection: Long,
|
||||
displayMode: Long,
|
||||
): Boolean {
|
||||
return mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = mangaId,
|
||||
chapterFlags = 0L.setFlag(unreadFilter, Manga.CHAPTER_UNREAD_MASK)
|
||||
.setFlag(downloadedFilter, Manga.CHAPTER_DOWNLOADED_MASK)
|
||||
.setFlag(bookmarkedFilter, Manga.CHAPTER_BOOKMARKED_MASK)
|
||||
.setFlag(sortingMode, Manga.CHAPTER_SORTING_MASK)
|
||||
.setFlag(sortingDirection, Manga.CHAPTER_SORT_DIR_MASK)
|
||||
.setFlag(displayMode, Manga.CHAPTER_DISPLAY_MASK),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Long.setFlag(flag: Long, mask: Long): Long {
|
||||
return this and mask.inv() or (flag and mask)
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class SetMangaViewerFlags(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun awaitSetMangaReadingMode(id: Long, flag: Long) {
|
||||
val manga = mangaRepository.getMangaById(id)
|
||||
mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = id,
|
||||
viewerFlags = manga.viewerFlags.setFlag(flag, ReadingModeType.MASK.toLong()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitSetOrientationType(id: Long, flag: Long) {
|
||||
val manga = mangaRepository.getMangaById(id)
|
||||
mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = id,
|
||||
viewerFlags = manga.viewerFlags.setFlag(flag, OrientationType.MASK.toLong()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Long.setFlag(flag: Long, mask: Long): Long {
|
||||
return this and mask.inv() or (flag and mask)
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import eu.kanade.domain.manga.model.hasCustomCover
|
||||
import eu.kanade.domain.manga.model.isLocal
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Date
|
||||
|
||||
class UpdateManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
|
||||
return mangaRepository.update(mangaUpdate)
|
||||
}
|
||||
|
||||
suspend fun awaitAll(mangaUpdates: List<MangaUpdate>): Boolean {
|
||||
return mangaRepository.updateAll(mangaUpdates)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFromSource(
|
||||
localManga: Manga,
|
||||
remoteManga: SManga,
|
||||
manualFetch: Boolean,
|
||||
coverCache: CoverCache = Injekt.get(),
|
||||
): Boolean {
|
||||
val remoteTitle = try {
|
||||
remoteManga.title
|
||||
} catch (_: UninitializedPropertyAccessException) {
|
||||
""
|
||||
}
|
||||
|
||||
// if the manga isn't a favorite, set its title from source and update in db
|
||||
val title = if (remoteTitle.isEmpty() || localManga.favorite) null else remoteTitle
|
||||
|
||||
val coverLastModified =
|
||||
when {
|
||||
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
||||
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
||||
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
||||
localManga.isLocal() -> Date().time
|
||||
localManga.hasCustomCover(coverCache) -> {
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
Date().time
|
||||
}
|
||||
}
|
||||
|
||||
val thumbnailUrl = remoteManga.thumbnail_url?.takeIf { it.isNotEmpty() }
|
||||
|
||||
return mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = localManga.id,
|
||||
title = title,
|
||||
coverLastModified = coverLastModified,
|
||||
author = remoteManga.author,
|
||||
artist = remoteManga.artist,
|
||||
description = remoteManga.description,
|
||||
genre = remoteManga.getGenres(),
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
status = remoteManga.status.toLong(),
|
||||
updateStrategy = remoteManga.update_strategy,
|
||||
initialized = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
||||
return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Date().time))
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateCoverLastModified(mangaId: Long): Boolean {
|
||||
return mangaRepository.update(MangaUpdate(id = mangaId, coverLastModified = Date().time))
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFavorite(mangaId: Long, favorite: Boolean): Boolean {
|
||||
val dateAdded = when (favorite) {
|
||||
true -> Date().time
|
||||
false -> 0
|
||||
}
|
||||
return mangaRepository.update(
|
||||
MangaUpdate(id = mangaId, favorite = favorite, dateAdded = dateAdded),
|
||||
)
|
||||
}
|
||||
}
|
176
app/src/main/java/eu/kanade/domain/manga/model/ComicInfo.kt
Normal file
@ -0,0 +1,176 @@
|
||||
package eu.kanade.domain.manga.model
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import kotlinx.serialization.Serializable
|
||||
import nl.adaptivity.xmlutil.serialization.XmlElement
|
||||
import nl.adaptivity.xmlutil.serialization.XmlSerialName
|
||||
import nl.adaptivity.xmlutil.serialization.XmlValue
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
|
||||
const val COMIC_INFO_FILE = "ComicInfo.xml"
|
||||
|
||||
/**
|
||||
* Creates a ComicInfo instance based on the manga and chapter metadata.
|
||||
*/
|
||||
fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String) = ComicInfo(
|
||||
title = ComicInfo.Title(chapter.name),
|
||||
series = ComicInfo.Series(manga.title),
|
||||
web = ComicInfo.Web(chapterUrl),
|
||||
summary = manga.description?.let { ComicInfo.Summary(it) },
|
||||
writer = manga.author?.let { ComicInfo.Writer(it) },
|
||||
penciller = manga.artist?.let { ComicInfo.Penciller(it) },
|
||||
translator = chapter.scanlator?.let { ComicInfo.Translator(it) },
|
||||
genre = manga.genre?.let { ComicInfo.Genre(it.joinToString()) },
|
||||
publishingStatus = ComicInfo.PublishingStatusTachiyomi(
|
||||
ComicInfoPublishingStatus.toComicInfoValue(manga.status),
|
||||
),
|
||||
inker = null,
|
||||
colorist = null,
|
||||
letterer = null,
|
||||
coverArtist = null,
|
||||
tags = null,
|
||||
)
|
||||
|
||||
fun SManga.copyFromComicInfo(comicInfo: ComicInfo) {
|
||||
comicInfo.series?.let { title = it.value }
|
||||
comicInfo.writer?.let { author = it.value }
|
||||
comicInfo.summary?.let { description = it.value }
|
||||
|
||||
listOfNotNull(
|
||||
comicInfo.genre?.value,
|
||||
comicInfo.tags?.value,
|
||||
)
|
||||
.flatMap { it.split(", ") }
|
||||
.distinct()
|
||||
.joinToString(", ") { it.trim() }
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.let { genre = it }
|
||||
|
||||
listOfNotNull(
|
||||
comicInfo.penciller?.value,
|
||||
comicInfo.inker?.value,
|
||||
comicInfo.colorist?.value,
|
||||
comicInfo.letterer?.value,
|
||||
comicInfo.coverArtist?.value,
|
||||
)
|
||||
.flatMap { it.split(", ") }
|
||||
.distinct()
|
||||
.joinToString(", ") { it.trim() }
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.let { artist = it }
|
||||
|
||||
status = ComicInfoPublishingStatus.toSMangaValue(comicInfo.publishingStatus?.value)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("ComicInfo", "", "")
|
||||
data class ComicInfo(
|
||||
val title: Title?,
|
||||
val series: Series?,
|
||||
val summary: Summary?,
|
||||
val writer: Writer?,
|
||||
val penciller: Penciller?,
|
||||
val inker: Inker?,
|
||||
val colorist: Colorist?,
|
||||
val letterer: Letterer?,
|
||||
val coverArtist: CoverArtist?,
|
||||
val translator: Translator?,
|
||||
val genre: Genre?,
|
||||
val tags: Tags?,
|
||||
val web: Web?,
|
||||
val publishingStatus: PublishingStatusTachiyomi?,
|
||||
) {
|
||||
@Suppress("UNUSED")
|
||||
@XmlElement(false)
|
||||
@XmlSerialName("xmlns:xsd", "", "")
|
||||
val xmlSchema: String = "http://www.w3.org/2001/XMLSchema"
|
||||
|
||||
@Suppress("UNUSED")
|
||||
@XmlElement(false)
|
||||
@XmlSerialName("xmlns:xsi", "", "")
|
||||
val xmlSchemaInstance: String = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Title", "", "")
|
||||
data class Title(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Series", "", "")
|
||||
data class Series(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Summary", "", "")
|
||||
data class Summary(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Writer", "", "")
|
||||
data class Writer(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Penciller", "", "")
|
||||
data class Penciller(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Inker", "", "")
|
||||
data class Inker(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Colorist", "", "")
|
||||
data class Colorist(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Letterer", "", "")
|
||||
data class Letterer(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("CoverArtist", "", "")
|
||||
data class CoverArtist(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Translator", "", "")
|
||||
data class Translator(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Genre", "", "")
|
||||
data class Genre(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Tags", "", "")
|
||||
data class Tags(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Web", "", "")
|
||||
data class Web(@XmlValue(true) val value: String = "")
|
||||
|
||||
// The spec doesn't have a good field for this
|
||||
@Serializable
|
||||
@XmlSerialName("PublishingStatusTachiyomi", "http://www.w3.org/2001/XMLSchema", "ty")
|
||||
data class PublishingStatusTachiyomi(@XmlValue(true) val value: String = "")
|
||||
}
|
||||
|
||||
private enum class ComicInfoPublishingStatus(
|
||||
val comicInfoValue: String,
|
||||
val sMangaModelValue: Int,
|
||||
) {
|
||||
ONGOING("Ongoing", SManga.ONGOING),
|
||||
COMPLETED("Completed", SManga.COMPLETED),
|
||||
LICENSED("Licensed", SManga.LICENSED),
|
||||
PUBLISHING_FINISHED("Publishing finished", SManga.PUBLISHING_FINISHED),
|
||||
CANCELLED("Cancelled", SManga.CANCELLED),
|
||||
ON_HIATUS("On hiatus", SManga.ON_HIATUS),
|
||||
UNKNOWN("Unknown", SManga.UNKNOWN),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun toComicInfoValue(value: Long): String {
|
||||
return values().firstOrNull { it.sMangaModelValue == value.toInt() }?.comicInfoValue
|
||||
?: UNKNOWN.comicInfoValue
|
||||
}
|
||||
|
||||
fun toSMangaValue(value: String?): Int {
|
||||
return values().firstOrNull { it.comicInfoValue == value }?.sMangaModelValue
|
||||
?: UNKNOWN.sMangaModelValue
|
||||
}
|
||||
}
|
||||
}
|
93
app/src/main/java/eu/kanade/domain/manga/model/Manga.kt
Normal file
@ -0,0 +1,93 @@
|
||||
package eu.kanade.domain.manga.model
|
||||
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.TriStateFilter
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
// TODO: move these into the domain model
|
||||
val Manga.readingModeType: Long
|
||||
get() = viewerFlags and ReadingModeType.MASK.toLong()
|
||||
|
||||
val Manga.orientationType: Long
|
||||
get() = viewerFlags and OrientationType.MASK.toLong()
|
||||
|
||||
val Manga.downloadedFilter: TriStateFilter
|
||||
get() {
|
||||
if (forceDownloaded()) return TriStateFilter.ENABLED_IS
|
||||
return when (downloadedFilterRaw) {
|
||||
Manga.CHAPTER_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS
|
||||
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
}
|
||||
}
|
||||
fun Manga.chaptersFiltered(): Boolean {
|
||||
return unreadFilter != TriStateFilter.DISABLED ||
|
||||
downloadedFilter != TriStateFilter.DISABLED ||
|
||||
bookmarkedFilter != TriStateFilter.DISABLED
|
||||
}
|
||||
fun Manga.forceDownloaded(): Boolean {
|
||||
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
||||
}
|
||||
|
||||
fun Manga.toSManga(): SManga = SManga.create().also {
|
||||
it.url = url
|
||||
it.title = title
|
||||
it.artist = artist
|
||||
it.author = author
|
||||
it.description = description
|
||||
it.genre = genre.orEmpty().joinToString()
|
||||
it.status = status.toInt()
|
||||
it.thumbnail_url = thumbnailUrl
|
||||
it.initialized = initialized
|
||||
}
|
||||
|
||||
fun Manga.copyFrom(other: SManga): Manga {
|
||||
val author = other.author ?: author
|
||||
val artist = other.artist ?: artist
|
||||
val description = other.description ?: description
|
||||
val genres = if (other.genre != null) {
|
||||
other.getGenres()
|
||||
} else {
|
||||
genre
|
||||
}
|
||||
val thumbnailUrl = other.thumbnail_url ?: thumbnailUrl
|
||||
return this.copy(
|
||||
author = author,
|
||||
artist = artist,
|
||||
description = description,
|
||||
genre = genres,
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
status = other.status.toLong(),
|
||||
updateStrategy = other.update_strategy,
|
||||
initialized = other.initialized && initialized,
|
||||
)
|
||||
}
|
||||
|
||||
fun SManga.toDomainManga(sourceId: Long): Manga {
|
||||
return Manga.create().copy(
|
||||
url = url,
|
||||
title = title,
|
||||
artist = artist,
|
||||
author = author,
|
||||
description = description,
|
||||
genre = getGenres(),
|
||||
status = status.toLong(),
|
||||
thumbnailUrl = thumbnail_url,
|
||||
updateStrategy = update_strategy,
|
||||
initialized = initialized,
|
||||
source = sourceId,
|
||||
)
|
||||
}
|
||||
|
||||
fun Manga.isLocal(): Boolean = source == LocalSource.ID
|
||||
|
||||
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
||||
return coverCache.getCustomCoverFile(id).exists()
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package eu.kanade.domain.source.interactor
|
||||
|
||||
import eu.kanade.domain.source.repository.SourceRepository
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import tachiyomi.domain.source.model.Pin
|
||||
import tachiyomi.domain.source.model.Pins
|
||||
import tachiyomi.domain.source.model.Source
|
||||
|
||||
class GetEnabledSources(
|
||||
private val repository: SourceRepository,
|
||||
private val preferences: SourcePreferences,
|
||||
) {
|
||||
|
||||
fun subscribe(): Flow<List<Source>> {
|
||||
return combine(
|
||||
preferences.pinnedSources().changes(),
|
||||
preferences.enabledLanguages().changes(),
|
||||
preferences.disabledSources().changes(),
|
||||
preferences.lastUsedSource().changes(),
|
||||
repository.getSources(),
|
||||
) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources ->
|
||||
sources
|
||||
.filter { it.lang in enabledLanguages || it.id == LocalSource.ID }
|
||||
.filterNot { it.id.toString() in disabledSources }
|
||||
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||
.flatMap {
|
||||
val flag = if ("${it.id}" in pinnedSourceIds) Pins.pinned else Pins.unpinned
|
||||
val source = it.copy(pin = flag)
|
||||
val toFlatten = mutableListOf(source)
|
||||
if (source.id == lastUsedSource) {
|
||||
toFlatten.add(source.copy(isUsedLast = true, pin = source.pin - Pin.Actual))
|
||||
}
|
||||
toFlatten
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package eu.kanade.domain.source.interactor
|
||||
|
||||
import eu.kanade.domain.source.repository.SourceRepository
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import tachiyomi.domain.source.model.Source
|
||||
|
||||
class GetLanguagesWithSources(
|
||||
private val repository: SourceRepository,
|
||||
private val preferences: SourcePreferences,
|
||||
) {
|
||||
|
||||
fun subscribe(): Flow<Map<String, List<Source>>> {
|
||||
return combine(
|
||||
preferences.enabledLanguages().changes(),
|
||||
preferences.disabledSources().changes(),
|
||||
repository.getOnlineSources(),
|
||||
) { enabledLanguage, disabledSource, onlineSources ->
|
||||
val sortedSources = onlineSources.sortedWith(
|
||||
compareBy<Source> { it.id.toString() in disabledSource }
|
||||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
||||
)
|
||||
|
||||
sortedSources.groupBy { it.lang }
|
||||
.toSortedMap(
|
||||
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package eu.kanade.domain.source.interactor
|
||||
|
||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
||||
import eu.kanade.domain.source.repository.SourceRepository
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
|
||||
class GetRemoteManga(
|
||||
private val repository: SourceRepository,
|
||||
) {
|
||||
|
||||
fun subscribe(sourceId: Long, query: String, filterList: FilterList): SourcePagingSourceType {
|
||||
return when (query) {
|
||||
QUERY_POPULAR -> repository.getPopular(sourceId)
|
||||
QUERY_LATEST -> repository.getLatest(sourceId)
|
||||
else -> repository.search(sourceId, query, filterList)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val QUERY_POPULAR = "eu.kanade.domain.source.interactor.POPULAR"
|
||||
const val QUERY_LATEST = "eu.kanade.domain.source.interactor.LATEST"
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package eu.kanade.domain.source.interactor
|
||||
|
||||
import eu.kanade.domain.source.repository.SourceRepository
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import tachiyomi.domain.source.model.Source
|
||||
import java.text.Collator
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
|
||||
class GetSourcesWithFavoriteCount(
|
||||
private val repository: SourceRepository,
|
||||
private val preferences: SourcePreferences,
|
||||
) {
|
||||
|
||||
fun subscribe(): Flow<List<Pair<Source, Long>>> {
|
||||
return combine(
|
||||
preferences.migrationSortingDirection().changes(),
|
||||
preferences.migrationSortingMode().changes(),
|
||||
repository.getSourcesWithFavoriteCount(),
|
||||
) { direction, mode, list ->
|
||||
list.sortedWith(sortFn(direction, mode))
|
||||
}
|
||||
}
|
||||
|
||||
private fun sortFn(
|
||||
direction: SetMigrateSorting.Direction,
|
||||
sorting: SetMigrateSorting.Mode,
|
||||
): java.util.Comparator<Pair<Source, Long>> {
|
||||
val locale = Locale.getDefault()
|
||||
val collator = Collator.getInstance(locale).apply {
|
||||
strength = Collator.PRIMARY
|
||||
}
|
||||
val sortFn: (Pair<Source, Long>, Pair<Source, Long>) -> Int = { a, b ->
|
||||
when (sorting) {
|
||||
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
||||
when {
|
||||
a.first.isStub && b.first.isStub.not() -> -1
|
||||
b.first.isStub && a.first.isStub.not() -> 1
|
||||
else -> collator.compare(a.first.name.lowercase(locale), b.first.name.lowercase(locale))
|
||||
}
|
||||
}
|
||||
SetMigrateSorting.Mode.TOTAL -> {
|
||||
when {
|
||||
a.first.isStub && b.first.isStub.not() -> -1
|
||||
b.first.isStub && a.first.isStub.not() -> 1
|
||||
else -> a.second.compareTo(b.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return when (direction) {
|
||||
SetMigrateSorting.Direction.ASCENDING -> Comparator(sortFn)
|
||||
SetMigrateSorting.Direction.DESCENDING -> Collections.reverseOrder(sortFn)
|
||||
}
|
||||
}
|
||||
}
|