mirror of
https://github.com/mihonapp/mihon.git
synced 2025-07-27 18:05:53 +02:00
Compare commits
1327 Commits
Author | SHA1 | Date | |
---|---|---|---|
6018aa99e2 | |||
99fd2731f5 | |||
e34043f1fe | |||
3c3a1cd448 | |||
0a2df21c5b | |||
277be02682 | |||
1849715418 | |||
dc6d4f9917 | |||
a9d98e5048 | |||
653940613d | |||
23a2d816e4 | |||
8a3a9146db | |||
a605a4ec75 | |||
c83037eeab | |||
25c76f5612 | |||
0d449a9b1d | |||
62cb12a3f1 | |||
9ec4dc5758 | |||
f594f1994b | |||
96b85962e3 | |||
f77e0e2d00 | |||
ce60ac150b | |||
19afd8c9ca | |||
5067160132 | |||
c9906491fb | |||
e51013d2a4 | |||
1aa75f22d0 | |||
8c910f2a2c | |||
dfb3091e38 | |||
98bdef230a | |||
4b594fc11f | |||
71931cf697 | |||
87e3525f88 | |||
a9c7cbf2c4 | |||
e63a52b8e3 | |||
49991d38d9 | |||
33c62ab711 | |||
899bd26956 | |||
a37f3eb709 | |||
9ae71dfe93 | |||
c65a9aecf5 | |||
02e50411de | |||
6e822dfd5b | |||
7292dadd5f | |||
b1067b942e | |||
d6c4af89c4 | |||
cf6f7c521c | |||
c6601c1f94 | |||
68899aea61 | |||
c3edf9b5d0 | |||
97e04392d3 | |||
3d178737b1 | |||
bf737cf95c | |||
c91ec9a33b | |||
a8040cb21a | |||
f60782f11f | |||
7d6e1bdafc | |||
5854ad97e0 | |||
4b8fa059d5 | |||
3dc2f9a711 | |||
8033a94ee2 | |||
028da099dd | |||
e6c6c32d81 | |||
bce6af62fc | |||
6510a9617a | |||
14510f1d26 | |||
f115edf2ea | |||
8a8362203f | |||
f3336fc5c3 | |||
727289c8eb | |||
9c91ddd4e3 | |||
3ea026e311 | |||
36f307e3bb | |||
89678ebb17 | |||
80b7d14af1 | |||
bbd8098a61 | |||
f8ef0f143b | |||
a3ef3604ee | |||
c4ceda59df | |||
7e053b5862 | |||
ac8ed3c028 | |||
8321ff6000 | |||
9c899e97a9 | |||
556f5a42a7 | |||
850813820c | |||
dba5e6fbfd | |||
c17ada2c98 | |||
32bed9b041 | |||
e0a0942015 | |||
8409ebe4eb | |||
493da5c3f4 | |||
4e221397ce | |||
8a7d6a328a | |||
22589a9c30 | |||
ec478cbb1b | |||
b5e3f429fc | |||
83130f9bf9 | |||
6f34c5e894 | |||
74931fad86 | |||
6ab8e1e73d | |||
1cdaa761b7 | |||
901b77f55c | |||
54f4711f7b | |||
3d0d5c0472 | |||
f0a0ecfd4a | |||
f3b7eaf4a3 | |||
5bba7af24a | |||
32c3269291 | |||
a1e84911be | |||
6bb77bcf1a | |||
ccec5c3efe | |||
8735836498 | |||
8b65fd5751 | |||
f0710df356 | |||
3afcee81f4 | |||
9c120e6231 | |||
4b208fc7ce | |||
a9b0ac43c4 | |||
fca4f25122 | |||
bfb0d31ff6 | |||
8939274b5c | |||
087da2b2f3 | |||
4571dc6b56 | |||
f31bc47757 | |||
950b4a6c90 | |||
2d7650537d | |||
80d6d412f3 | |||
446b146f95 | |||
6887d98f15 | |||
6d74a86711 | |||
5908bd1930 | |||
1a559124eb | |||
54ba1d719e | |||
93cbeca5c0 | |||
19f0175a56 | |||
bf3899d04a | |||
dcf0379496 | |||
9f90ee358b | |||
565317d99c | |||
83a67feb48 | |||
a51108cbe8 | |||
b9fd416fc6 | |||
c10cd6c808 | |||
c62cd6e997 | |||
7ae17e6aac | |||
f20980b4c9 | |||
02cd2d2ca3 | |||
3847d4f4cf | |||
f9b57800b1 | |||
387159b5af | |||
09531e7f5a | |||
c6356fe4b2 | |||
ff3bc66055 | |||
13b3bec8ad | |||
8aaf8df708 | |||
c00f05a1c1 | |||
db3ddf07ee | |||
cd16522805 | |||
5fec881387 | |||
3ac68e810d | |||
e6fe5c827c | |||
65e1e2cf4f | |||
e36a2c68f1 | |||
add9357257 | |||
ad3d915fc5 | |||
36f400d542 | |||
dd1a19745a | |||
58daedc89e | |||
d20a8fcf13 | |||
e56bf82c31 | |||
0f9895eec8 | |||
f776c36e70 | |||
1ef01b53f2 | |||
720169dce3 | |||
0d09039e5f | |||
cc56fde9fe | |||
3a0b3de175 | |||
47e544b710 | |||
44d6c4fe44 | |||
e5693ed668 | |||
8c21aa86e9 | |||
f7c5b42435 | |||
e3404cd3d3 | |||
8b57169e92 | |||
ab9a26f6bd | |||
8779b263ab | |||
3135db4bb2 | |||
734cb0be6e | |||
1f259f9298 | |||
427fbfdf5e | |||
0c860c0fe9 | |||
5b2a099203 | |||
ccadfc8fe5 | |||
3aead3a2a9 | |||
6a48fed170 | |||
ea1684133b | |||
e5263d0345 | |||
24e1b4034e | |||
dfa5c229b3 | |||
87be54aa4a | |||
82d9ae31bd | |||
e5518b7615 | |||
e5a22eafe7 | |||
7a52afd223 | |||
296201d6b7 | |||
162b639705 | |||
5dda32bb81 | |||
8ce8b60092 | |||
8ff2c01bf2 | |||
e22eebfd02 | |||
4fcdde4913 | |||
e41668862f | |||
a74a689c90 | |||
d85a76484c | |||
82bdf63419 | |||
9ce0bc6b5f | |||
bf524595e2 | |||
27c4db752c | |||
ca54984344 | |||
46aeab9a7a | |||
f365b53a0f | |||
d4dfa9a2c2 | |||
cf9e60fd92 | |||
21ae04d25d | |||
f1778ac5b4 | |||
ba10093ddc | |||
a5c9469698 | |||
75314c78e0 | |||
53edae1b6b | |||
356fc5b524 | |||
60150423d7 | |||
bcc42dd259 | |||
d59cb9c1e3 | |||
3006604922 | |||
1fbf8ca079 | |||
695813ef7d | |||
e3b70ca08d | |||
8857b7e0c1 | |||
4a7c20f5a0 | |||
29368fc953 | |||
0696e4bce0 | |||
255ed50685 | |||
00afee83b8 | |||
0d1bced122 | |||
46e734fc8e | |||
c39ae21f4a | |||
69aa13bc56 | |||
2c032ff70d | |||
0af4703b78 | |||
ea15bc782a | |||
9ec0f73e87 | |||
f9fb034330 | |||
6eb5a25ea1 | |||
45d8411f98 | |||
d9e2317e62 | |||
336221a972 | |||
dd998be1e7 | |||
3c3b09209c | |||
4a6571d310 | |||
cb67f1de52 | |||
402e2c47fb | |||
58b2895ec9 | |||
00b2853d3d | |||
634ceeec50 | |||
d7442d771b | |||
8f22480ec9 | |||
9d974273af | |||
3a8aa3e8cd | |||
9e67abcc8a | |||
d0bcd30909 | |||
b97aa23548 | |||
e6ca54fd04 | |||
4502902fb0 | |||
5f34539525 | |||
953f5fb025 | |||
4f3a0b3523 | |||
1d144e6767 | |||
056dbaefda | |||
3a15c6b843 | |||
db20d04c4b | |||
c5e8c9f01f | |||
64c50c1283 | |||
4146c4c31d | |||
69223df27c | |||
4b225a4ff1 | |||
4a2ee0b596 | |||
8644d90bd4 | |||
f30ab56fd0 | |||
5d91b77c93 | |||
aca36f9625 | |||
d5e8c38075 | |||
6d538db5f2 | |||
b3d7c92475 | |||
d862d83511 | |||
8a1625ec79 | |||
2ee895ee3c | |||
7cf2ce2994 | |||
cb8ea5eab0 | |||
ce7bf396eb | |||
1aa5222c99 | |||
298c49f3ab | |||
ce5e10be95 | |||
64ad25d1b5 | |||
4868dd2d03 | |||
443d56f69b | |||
7457a18aee | |||
d80ba2e807 | |||
118d3b7fcc | |||
eed57b80be | |||
c46c39d4ae | |||
d7d7a6d2fc | |||
17b90d2491 | |||
9ecec5d468 | |||
0bdd3f79d4 | |||
7dccde0930 | |||
c8d68590db | |||
94448faf97 | |||
28028c789c | |||
f8834ee764 | |||
7c703b17d3 | |||
f77ade7dda | |||
91712daee8 | |||
8057f067b9 | |||
548f7f415a | |||
d9c0b1ce7d | |||
0a0b686119 | |||
092d930175 | |||
3b7ed9bc6d | |||
012854dd1e | |||
6d1e520c6c | |||
dcc3141080 | |||
7326598475 | |||
fcba2306e9 | |||
f84868a264 | |||
15423bfc84 | |||
8e4cedf173 | |||
19965e0bdb | |||
3a35c13575 | |||
489d22720a | |||
e1b3345b94 | |||
c53172265b | |||
8626a55fe4 | |||
1302461518 | |||
8f3681d79f | |||
c4ce3dd46f | |||
22df12a680 | |||
e572abb041 | |||
ea99d77fda | |||
2bf77f1d81 | |||
f79f0a7e97 | |||
82a9d36df7 | |||
447bcb28ef | |||
0be7ac5871 | |||
d18022c259 | |||
5619a4c0d9 | |||
8a7bbfddda | |||
0026f96fad | |||
c492efcb31 | |||
c386d375de | |||
540fb1bb7c | |||
81448f5d01 | |||
7c01201055 | |||
90d3dd2242 | |||
97b4d1f13d | |||
79b37df647 | |||
b7d282235d | |||
77ebc362f6 | |||
8568d5d6c3 | |||
7ed99fbbd6 | |||
94cba9324c | |||
6dab94a937 | |||
0f42b9f154 | |||
730f3a6e52 | |||
72024aa44a | |||
9c688b08c0 | |||
c66a4fa7a7 | |||
e47f4cc177 | |||
6462472d16 | |||
78aa50bb35 | |||
7f0f67d752 | |||
df332860b8 | |||
8a8afa46e9 | |||
509bee0563 | |||
afb1ee2200 | |||
66a938779d | |||
ed506f8495 | |||
c8e226acb2 | |||
86edce0d87 | |||
4e69bf993a | |||
56d2464870 | |||
5de72b7d32 | |||
de92b1351f | |||
77a8a4229c | |||
d4290f6f59 | |||
b08d604d2a | |||
9e04f14a7b | |||
6663abebaf | |||
e5f83d0c6e | |||
3ad7add3b5 | |||
66aacade9a | |||
fe3a710ed0 | |||
f9754f4f58 | |||
8824c7dbe3 | |||
ccc9a5a052 | |||
f5e0cee36c | |||
36f1e0e476 | |||
2dd2db7225 | |||
3d0e750519 | |||
26c5d761da | |||
1668be8587 | |||
86a3fc77c6 | |||
cc018cee18 | |||
d9d143e6be | |||
3f0db60a99 | |||
87f3d4bd05 | |||
5c3d655d9e | |||
66b175a3c8 | |||
3cd3f45c8a | |||
816d7815e9 | |||
dbc7fe4d54 | |||
d29b7c4e57 | |||
772db51593 | |||
87530f506e | |||
98d6ce2eaf | |||
7644d7c31e | |||
dde2f42138 | |||
6922792ad1 | |||
f32243899d | |||
13dc54df70 | |||
6d9a8a30e9 | |||
2bf263e301 | |||
c06beac660 | |||
74f74eef56 | |||
3aafec482c | |||
ed80ac3154 | |||
eeeaae4570 | |||
d1c956401c | |||
1be7949275 | |||
5572b28d01 | |||
4e68b62881 | |||
4e31e6a2fa | |||
bc692ebfc6 | |||
96f6a5abc2 | |||
3411ac40c0 | |||
8a6a104987 | |||
efa7a3a167 | |||
67bc81ebde | |||
0a3ce8ebe4 | |||
3ebf39bd55 | |||
8f395d98e7 | |||
26b3eb696c | |||
627f07408e | |||
7146913c71 | |||
39c6bcccd8 | |||
6259bbaa5e | |||
cb4b8ac0dc | |||
4b7acdb022 | |||
af0fdfa3b7 | |||
d874f20362 | |||
8680accd8e | |||
7308090288 | |||
400ca48456 | |||
9b6567f5e4 | |||
7798186c32 | |||
9dc66c7c8d | |||
10b0ef9b6d | |||
81cd765543 | |||
c9a1bd86b5 | |||
dfbbbadfac | |||
0f21d16263 | |||
d65f9c2916 | |||
5718983f41 | |||
f7b335e4fb | |||
aa6937baf2 | |||
59f7d2273f | |||
cd91ea9b77 | |||
6a558ad119 | |||
f5936e9456 | |||
9df351da0a | |||
90325d48aa | |||
e2abf283fe | |||
db788d519d | |||
f3e9d5f346 | |||
fd30c0adcd | |||
3ad4f1114a | |||
fe90546821 | |||
3892c4caac | |||
cb639f4e90 | |||
6d69caf59e | |||
cdc1c5efa3 | |||
77bfd0c099 | |||
8ff0c9d61a | |||
b6620434b3 | |||
abae9bf37d | |||
2556e9f08c | |||
7aa172c512 | |||
81cf232bcb | |||
ee26d6dffd | |||
7b2764e8f7 | |||
46e3b9e40d | |||
8d00ff1b40 | |||
cf14831fbe | |||
7a4680603d | |||
99f12b1fbf | |||
5c73045aa4 | |||
ac306547a0 | |||
3f868c0435 | |||
262ce3473f | |||
43b9b104f5 | |||
c7f0a54a37 | |||
ca789dca0e | |||
ef7b285151 | |||
dd3ca0c131 | |||
8b46e8edad | |||
30f845139d | |||
818471b7e1 | |||
a3a3f44056 | |||
22c6dbda3f | |||
34f7caa0fc | |||
01553b1ed8 | |||
a24afa9a76 | |||
ec08ba05fc | |||
30bea8b753 | |||
09e4b5a9cd | |||
5467104b95 | |||
e0733c1a4c | |||
1cf7f9be54 | |||
fb99577836 | |||
28131ac135 | |||
e40b8d537c | |||
12e7ee9d0c | |||
54733e6ceb | |||
22e8050fff | |||
a629db2884 | |||
cbcec8c4d9 | |||
2f05f7b91f | |||
a3a9699e8a | |||
f01a312c23 | |||
0fffde50ff | |||
8775596a82 | |||
2f0133986a | |||
3bd2cad45f | |||
48f7a2de41 | |||
3aa6e7ae0e | |||
813d7e49cd | |||
710ebfb7a5 | |||
87bdee5990 | |||
efabe801be | |||
9a817e49be | |||
a577f5534f | |||
d0f52ea93d | |||
6063efd101 | |||
0759936226 | |||
1e3d9a00f2 | |||
7c62453280 | |||
226272f686 | |||
16cbcecd99 | |||
b008223661 | |||
f8cf3db4a4 | |||
a585d46e7a | |||
8cc42bce5a | |||
67c6dbea0d | |||
db33437577 | |||
8287c9d193 | |||
d32409bd6e | |||
cf3f2d0380 | |||
53c6230afe | |||
4882896f4d | |||
4d67066de3 | |||
6fe5e6e21b | |||
8c5496b53f | |||
235a587e42 | |||
3125d78706 | |||
bb8f3c63f1 | |||
20faaaa908 | |||
44cc6f11c7 | |||
bae391c2c1 | |||
0ac5f3b93c | |||
b79ef5dc79 | |||
7d26ca046f | |||
d99f4697e8 | |||
bb3fdef40b | |||
2a7cca6ea4 | |||
6ed2748846 | |||
7c90fe0f7d | |||
8a5e443ca5 | |||
a07e0df815 | |||
88e9fefa59 | |||
c0fd47b066 | |||
ee684cbef5 | |||
1f618d6634 | |||
7d4af1f8cc | |||
fe82cdb9c8 | |||
b354e37cc3 | |||
f344831d58 | |||
2eca8511cb | |||
f2b0d74b4c | |||
42bc2b07ce | |||
e2d6269a38 | |||
fcfa62f220 | |||
25b0458930 | |||
6808fbbb21 | |||
b36b3bfcab | |||
7f0ed58b54 | |||
b4393ff741 | |||
b8af1621b5 | |||
4a75f82a6f | |||
740e370465 | |||
245985bf42 | |||
344f5afd50 | |||
1c6e5605f9 | |||
0871208023 | |||
ee95c1439f | |||
e323f3c25a | |||
9766399539 | |||
fc4fd487f9 | |||
dddba7bb6f | |||
9ec8d770ea | |||
cf777d9893 | |||
0d9f8e8743 | |||
841f80f935 | |||
39a7356ed1 | |||
438054a0ec | |||
34b9c82cd0 | |||
405a75438a | |||
39e4568460 | |||
0d96791a84 | |||
531e1c62bb | |||
1a1f16f44a | |||
431f8772f8 | |||
8a5382042c | |||
8f4bc71cf7 | |||
0ac38297f4 | |||
4c65c2311e | |||
f48f212001 | |||
c90f344910 | |||
a458bd9fdb | |||
ed5a56be60 | |||
899fe57f15 | |||
bac42edabb | |||
8735f3566f | |||
46efd4c134 | |||
dfd38db7e3 | |||
0189fc1f66 | |||
929a881943 | |||
152fdec855 | |||
9c07451d95 | |||
e3b2720924 | |||
3ae1e37c40 | |||
d8998aacb4 | |||
efdff9a21a | |||
1824adb2ed | |||
38445673f3 | |||
5a9889b562 | |||
5ca7c39751 | |||
44609c494c | |||
0810d3db69 | |||
d4fb9995ef | |||
22a4372583 | |||
a4d86a2e1e | |||
b8716ff6fe | |||
73118d4af7 | |||
c27bf4e866 | |||
f50f5c4b54 | |||
fb38d30775 | |||
a3a9c8ac8e | |||
b4bb855675 | |||
6263a52777 | |||
96defd6b05 | |||
8df9bce1b4 | |||
bcd90be525 | |||
22afae4449 | |||
8fae92034e | |||
ce81b76150 | |||
f70d5ea976 | |||
dbbf6c5de0 | |||
2379df7e60 | |||
e3ce3ff418 | |||
4395202703 | |||
84acae27b7 | |||
71f6e07e71 | |||
6f59c6c6bb | |||
d36cf5ce15 | |||
332d9ff61b | |||
7bb1ccf6f7 | |||
05a7d5174a | |||
b051e37ab7 | |||
44383ff950 | |||
1b25290d39 | |||
2f5eb73d29 | |||
f0dd33ee4c | |||
e15b945e16 | |||
5c7d88c2ed | |||
bbe0ab1dd0 | |||
cb2d43c0d1 | |||
fce9cb820c | |||
08e4863d94 | |||
9a10656bf0 | |||
3c79777e66 | |||
f5ad95d78a | |||
14c465d36f | |||
921a988c4a | |||
99378ddf20 | |||
c623258e8c | |||
6ce42dc167 | |||
f63573f25f | |||
b328f0e344 | |||
02864ebd60 | |||
eed91f6360 | |||
f317193bec | |||
f459515dd7 | |||
9339ea4196 | |||
6bdc1b676e | |||
7451c13edd | |||
ccd4143d9d | |||
c590f55030 | |||
c21813a8b5 | |||
2cb08e6bb1 | |||
058ee4c86b | |||
ea6e5eebac | |||
9cc25ff345 | |||
c9805b8612 | |||
41c89eb61d | |||
392c3492b3 | |||
f7cd3929a3 | |||
20bec66a9d | |||
3ce9a9ff97 | |||
a8f17a3fab | |||
ef3d2c14b4 | |||
44619febd3 | |||
c48accb357 | |||
418e6a8b3a | |||
67b4e53a58 | |||
d62d94f587 | |||
265934d77a | |||
2a218cca90 | |||
e23cc8f83a | |||
0b125b7106 | |||
320587e36e | |||
26f3995595 | |||
94c94b2d88 | |||
41cc1fe723 | |||
03a344e9c1 | |||
add228407f | |||
2c6e025063 | |||
ba30dfe7e2 | |||
97e6f1ea9a | |||
5c1a81d8ca | |||
c615f4d458 | |||
9e09a20e65 | |||
7115a9b9fe | |||
fd8b97fc87 | |||
4dd67e4348 | |||
10973bf3cd | |||
934ed0551a | |||
38428c6ebe | |||
bf85e147e7 | |||
d2dd34c2e5 | |||
c4ab2b4675 | |||
aa2ec5940f | |||
79323de326 | |||
08e6487a9a | |||
4498b10a10 | |||
6f2bb18d72 | |||
7e56cba060 | |||
dc569fb20a | |||
c6ac992798 | |||
8a18e10cc2 | |||
d6b9711e45 | |||
8ab7e63293 | |||
4bcd623829 | |||
18acf66cb8 | |||
4816b4b53a | |||
60d8650860 | |||
bfb7b5afd5 | |||
a2627d70af | |||
6662a97b2f | |||
564a0980b9 | |||
e3fbd26880 | |||
c1e23ec18e | |||
b7cd7b8b4e | |||
776d36caf1 | |||
182e642cfc | |||
88bf1a706b | |||
d25ba23079 | |||
75460e01c8 | |||
c9bd3a5314 | |||
7c6a5dc43b | |||
274218cf22 | |||
c7d6509565 | |||
bc0b9e536a | |||
7a1b599462 | |||
1dd62af188 | |||
6f1099b710 | |||
be8e2f119f | |||
18f9e5ba6b | |||
d1bf857079 | |||
1814b3b22c | |||
be54b8862e | |||
1a61130f0b | |||
1de4bc9586 | |||
1986042277 | |||
e932983494 | |||
35d381144d | |||
0bcc22822d | |||
1ff78173f7 | |||
ee45f46193 | |||
290efb0283 | |||
8d7a7919a9 | |||
953720472f | |||
f94d902bb6 | |||
da25322572 | |||
cb4699a5bb | |||
2e5efadf42 | |||
e5e18c2030 | |||
ac0596a53d | |||
7ec5a51eb8 | |||
3cca460282 | |||
d703fb7946 | |||
859601a46e | |||
cdc160afc2 | |||
14d1bcacc9 | |||
abd23b6826 | |||
7d8a865cac | |||
dfdb688b43 | |||
c955ac6a66 | |||
f3ca4e76a8 | |||
2769525b2c | |||
843e748de3 | |||
d160cfaa0e | |||
81af97df77 | |||
18e55aa25f | |||
4d3e13b0d1 | |||
a335b4ee9e | |||
47a2d06682 | |||
ce66ed0389 | |||
c0f94ae8af | |||
ed32a511e7 | |||
17ed4873e8 | |||
09acc53483 | |||
bebd4be43d | |||
9b77759f24 | |||
e458de5e9c | |||
737a303df7 | |||
477dd37981 | |||
e917349bb7 | |||
ad4912803b | |||
f96f0c5889 | |||
2b9acadc5b | |||
9caa0d147b | |||
c6e5f8abd9 | |||
1abf01c4a0 | |||
b41565f879 | |||
f03a834136 | |||
f7f2072621 | |||
5b2e937d5f | |||
f27dc19b37 | |||
2368c50ebb | |||
0505906e7a | |||
4efca04765 | |||
b12c7cf963 | |||
487622c592 | |||
26d422b0ae | |||
79a7b68837 | |||
63048d2f0b | |||
79662a5866 | |||
ed6809fa28 | |||
86b9262a7e | |||
7ec87e76db | |||
a0e76d2fd9 | |||
ec3ce74af8 | |||
83a4e34095 | |||
84a0044d51 | |||
92132c59f5 | |||
36ae388332 | |||
bd47eafeec | |||
9432d2d06a | |||
fa61c8fe6f | |||
92bd98e45f | |||
fd7c993b0b | |||
779df32e98 | |||
f4e843f114 | |||
c0e2eb211d | |||
0bd56ab77c | |||
6b03dca5f4 | |||
bd7b21337c | |||
60a3ba5a5c | |||
7c2eb0b881 | |||
93523ef50b | |||
10d7349506 | |||
3d7c136320 | |||
a6d6a5ed87 | |||
b690de55e5 | |||
83fda20078 | |||
f656a37045 | |||
c58b495433 | |||
242aeb6a68 | |||
d9969cea8a | |||
d61db5931e | |||
0ea3ac9807 | |||
f9e43f574f | |||
5ef11e61d0 | |||
48546c3db4 | |||
4d87ed496c | |||
06d12e6562 | |||
ec49411bee | |||
3f7911235c | |||
727399611d | |||
94232a4937 | |||
07fdb74fbc | |||
d400ac2a49 | |||
dd71c76a8f | |||
58a0add4f6 | |||
bfe143015a | |||
e3cf863230 | |||
ee818bc7c5 | |||
f816196df2 | |||
753bf7de5d | |||
3634b52e3a | |||
ef863335e6 | |||
ceaf579cb0 | |||
b49280e347 | |||
d3dadf71e8 | |||
ffa8c8fd07 | |||
0ef7650c1a | |||
4635e58405 | |||
dc2eaf0788 | |||
d02b0ca2db | |||
4d607c4aed | |||
be4072c86b | |||
2970eca9e4 | |||
42954609b9 | |||
6348cbaeb7 | |||
a7cb33d8c9 | |||
ec46b2281b | |||
3a2dc46ff0 | |||
e052bdef96 | |||
d522d6d545 | |||
7b118eba22 | |||
f6e6a7ddf1 | |||
5ce64ac7ff | |||
1671a56f42 | |||
ab6dfe9e25 | |||
bff98ca768 | |||
32b9b261f0 | |||
23432e4405 | |||
34a586ce48 | |||
ad762f8303 | |||
389b039679 | |||
ef9dacde79 | |||
13bb45b4be | |||
bd2cb97179 | |||
0d8f1c8560 | |||
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 |
.editorconfig
.github
.gitignore.idea
CODE_OF_CONDUCT.mdCONTRIBUTING.mdREADME.mdapp
.gitignorebuild.gradle.ktsproguard-android-optimize.txtproguard-rules.proshortcuts.xml
build.gradle.ktssrc
debug
res
drawable
mipmap-anydpi-v26
mipmap-hdpi
mipmap-mdpi
mipmap-xhdpi
mipmap-xxhdpi
mipmap-xxxhdpi
main
AndroidManifest.xmlbaseline-prof.txt
java
eu
kanade
core
preference
util
data
domain
DomainModule.kt
backup
service
base
category
interactor
chapter
interactor
GetAvailableScanlators.ktGetChapterByMangaId.ktSetReadStatus.ktSyncChaptersWithSource.ktSyncChaptersWithTrackServiceTwoWay.kt
model
download
interactor
extension
interactor
history
interactor
model
library
service
manga
interactor
GetDuplicateLibraryManga.ktGetExcludedScanlators.ktGetMangaWithChapters.ktSetExcludedScanlators.ktSetMangaViewerFlags.ktUpdateManga.kt
model
source
interactor
GetEnabledSources.ktGetLanguagesWithSources.ktGetSourcesWithFavoriteCount.ktToggleLanguage.ktToggleSource.ktToggleSourcePin.kt
model
repository
service
track
interactor
model
service
store
ui
updates
presentation
browse
BrowseSourceScreen.ktBrowseSourceState.ktExtensionDetailsScreen.ktExtensionDetailsState.ktExtensionFilterScreen.ktExtensionFilterState.ktExtensionsScreen.ktExtensionsState.ktGlobalSearchScreen.ktMigrateMangaScreen.ktMigrateMangaState.ktMigrateSearchScreen.ktMigrateSourceScreen.ktMigrateSourceState.ktSourceSearchScreen.ktSourcesFilterScreen.ktSourcesFilterState.ktSourcesScreen.ktSourcesState.kt
components
category
components
AdaptiveSheet.ktAppBar.ktBadges.ktBanners.ktChangeCategoryDialog.ktDateText.ktDeleteLibraryMangaDialog.ktDownloadDropdownMenu.ktDropdownMenu.ktDuplicateMangaDialog.ktEmptyScreen.ktPreferences.ktRelativeDateHeader.ktScaffold.ktSwipeRefresh.ktTabbedDialog.ktTabbedScreen.ktTabs.kt
crash
history
library
manga
more
LogoHeader.ktMoreScreen.ktNewUpdateScreen.kt
onboarding
settings
Preference.ktPreferenceItem.ktPreferenceScaffold.ktPreferenceScreen.kt
screen
AboutScreen.ktCommons.ktLicensesScreen.ktSearchableSettings.ktSettingsAdvancedScreen.ktSettingsAppearanceScreen.ktSettingsBackupScreen.ktSettingsBrowseScreen.ktSettingsDataScreen.ktSettingsDownloadScreen.ktSettingsGeneralScreen.ktSettingsLibraryScreen.ktSettingsMainScreen.ktSettingsReaderScreen.ktSettingsSearchScreen.ktSettingsSecurityScreen.ktSettingsTrackingScreen.kt
about
advanced
appearance
browse
data
debug
widget
stats
reader
ChapterTransition.ktDisplayRefreshHost.ktOrientationSelectDialog.ktPageIndicatorText.ktReaderContentOverlay.ktReaderPageActionsDialog.ktReadingModeSelectDialog.kt
appbars
components
settings
theme
TachiyomiTheme.kt
colorscheme
track
TrackInfoDialogHome.ktTrackInfoDialogHomePreviewProvider.ktTrackInfoDialogSelector.ktTrackerSearch.ktTrackerSearchPreviewProvider.kt
components
updates
util
ChapterNumberFormatter.ktConstants.ktExceptionFormatter.ktModifier.ktNavigator.ktPermissions.ktResources.ktScrollable.ktTimeUtils.ktWindowSize.kt
webview
tachiyomi
App.ktAppInfo.ktMigrations.kt
crash
data
backup
BackupConst.ktBackupCreatorJob.ktBackupDecoder.ktBackupFileValidator.ktBackupManager.ktBackupNotifier.ktBackupRestoreService.ktBackupRestorer.kt
create
models
Backup.ktBackupCategory.ktBackupChapter.ktBackupHistory.ktBackupManga.ktBackupPreference.ktBackupSerializer.ktBackupSource.ktBackupTracking.kt
restore
cache
coil
database
download
DownloadCache.ktDownloadJob.ktDownloadManager.ktDownloadNotifier.ktDownloadPendingDeleter.ktDownloadProvider.ktDownloadService.ktDownloadStore.ktDownloader.kt
model
library
notification
preference
saver
track
BaseTracker.ktDeletableTracker.ktEnhancedTrackService.ktEnhancedTracker.ktNoLoginTrackService.ktTrackManager.ktTrackService.ktTracker.ktTrackerManager.kt
anilist
bangumi
Avatar.ktBangumi.ktBangumiApi.ktBangumiInterceptor.ktBangumiModels.ktCollection.ktOAuth.ktStatus.ktUser.kt
job
kavita
kitsu
komga
mangaupdates
model
myanimelist
shikimori
suwayomi
updater
di
extension
glance
source
ui
base
activity
controller
BaseController.ktComposeController.ktConductorExtensions.ktDialogController.ktNucleusController.ktOneWayFadeChangeHandler.ktRootController.ktSearchableNucleusController.kt
delegate
presenter
browse
BrowseController.ktBrowsePresenter.ktBrowseTab.kt
extension
ExtensionFilterController.ktExtensionFilterPresenter.ktExtensionFilterScreen.ktExtensionFilterScreenModel.ktExtensionsScreenModel.ktExtensionsTab.kt
details
migration
MigrationFlags.kt
manga
search
MigrateDialog.ktMigrateSearchScreen.ktMigrateSearchScreenDialogScreenModel.ktMigrateSearchScreenModel.ktSearchController.ktSearchPresenter.ktSourceSearchController.ktSourceSearchScreen.kt
sources
source
SourcesFilterController.ktSourcesFilterPresenter.ktSourcesFilterScreen.ktSourcesFilterScreenModel.ktSourcesPresenter.ktSourcesScreenModel.ktSourcesTab.kt
browse
BrowseSourceController.ktBrowseSourcePresenter.ktBrowseSourceScreen.ktBrowseSourceScreenModel.ktSourceFilterDialog.ktSourceFilterSheet.kt
filter
CheckboxItem.ktGroupItem.ktHeaderItem.ktSectionItems.ktSelectItem.ktSeparatorItem.ktSortGroup.ktSortItem.ktTextItem.ktTriStateItem.kt
globalsearch
category
deeplink
download
DownloadAdapter.ktDownloadController.ktDownloadItem.ktDownloadPresenter.ktDownloadQueueScreen.ktDownloadQueueScreenModel.kt
history
home
library
LibraryController.ktLibraryItem.ktLibraryPresenter.ktLibraryScreenModel.ktLibrarySettingsScreenModel.ktLibrarySettingsSheet.ktLibraryTab.kt
main
manga
MangaController.ktMangaCoverScreenModel.ktMangaPresenter.ktMangaScreen.ktMangaScreenModel.kt
chapter
info
track
more
MoreController.ktMorePresenter.ktMoreTab.ktNewUpdateDialogController.ktNewUpdateScreen.ktOnboardingScreen.kt
reader
PageIndicatorTextView.ktReaderActivity.ktReaderColorFilterView.ktReaderNavigationOverlayView.ktReaderPageSheet.ktReaderPresenter.ktReaderSlider.ktReaderViewModel.ktSaveImageNotifier.kt
loader
ChapterLoader.ktDirectoryPageLoader.ktDownloadPageLoader.ktEpubPageLoader.ktHttpPageLoader.ktPageLoader.ktRarPageLoader.ktZipPageLoader.kt
model
setting
OrientationType.ktReaderColorFilterSettings.ktReaderGeneralSettings.ktReaderOrientation.ktReaderPreferences.ktReaderReadingModeSettings.ktReaderSettingsScreenModel.ktReaderSettingsSheet.ktReadingMode.ktReadingModeType.kt
viewer
recent
security
setting
stats
updates
webview
util
CrashLogUtil.ktMangaExtensions.ktPkceUtil.kt
chapter
lang
preference
storage
system
ActivityExtensions.ktAnimationExtensions.ktAuthenticatorUtil.ktBooleanExtensions.ktContextExtensions.ktDisplayExtensions.ktDrawableExtensions.ktGLUtil.ktIntentExtensions.ktLocaleHelper.ktNetworkExtensions.ktNetworkStateTracker.ktNotificationExtensions.ktWorkManagerExtensions.kt
view
widget
AutofitRecyclerView.ktDialogCheckboxView.ktEmptyView.ktExtendedNavigationView.ktMaterialFastScroll.ktMaterialSpinnerView.ktMinMaxNumberPicker.ktOutlineSpan.ktSimpleNavigationView.ktTachiyomiAppBarLayout.ktTachiyomiBottomNavigationView.ktTachiyomiChangeHandlerFrameLayout.ktTachiyomiCoordinatorLayout.ktTachiyomiFullscreenDialog.ktTachiyomiScrollingViewBehavior.ktTachiyomiSearchView.ktTachiyomiTextInputEditText.kt
listener
sheet
test
res
anim-v33
shared_axis_x_pop_enter.xmlshared_axis_x_pop_exit.xmlshared_axis_x_push_enter.xmlshared_axis_x_push_exit.xml
anim
bottom_sheet_slide_in.xmlbottom_sheet_slide_out.xmlenter_from_bottom.xmlenter_from_top.xmlexit_to_bottom.xmlexit_to_top.xmlfade_in_short.xmlfade_out_short.xmlshared_axis_x_pop_enter.xmlshared_axis_x_pop_exit.xmlshared_axis_x_push_enter.xmlshared_axis_x_push_exit.xml
color
library_item_background.xmlripple_toolbar_fainter.xmlslider_active_track.xmlslider_inactive_track.xml
drawable-nodpi
drawable-v26
drawable
anim_browse_leave.xmlanim_caret_down.xmlanim_library_leave.xmlanim_more_enter.xmlanim_updates_leave.xmlempty_drawable_32dp.xmlic_arrow_down_white_32dp.xmlic_arrow_forward_24dp.xmlic_arrow_up_white_32dp.xmlic_blank_24dp.xmlic_book_24dp.xmlic_bookmark_24dp.xmlic_bookmark_border_24dp.xmlic_brightness_5_24dp.xmlic_browse_filled_24dp.xmlic_browse_outline_24dp.xmlic_browse_selector_24dp.xmlic_check_24dp.xmlic_check_box_24dp.xmlic_check_box_outline_blank_24dp.xmlic_check_box_x_24dp.xmlic_close_24dp.xmlic_crop_24dp.xmlic_crop_off_24dp.xmlic_delete_24dp.xmlic_discord_24dp.xmlic_done_24dp.xmlic_done_prev_24dp.xmlic_drag_handle_24dp.xmlic_expand_less_24dp.xmlic_expand_more_24dp.xmlic_extension_24dp.xmlic_facebook_24dp.xmlic_folder_24dp.xmlic_github_24dp.xmlic_glasses_24dp.xmlic_history_24dp.xmlic_history_selector_24dp.xmlic_info_24dp.xmlic_launcher_background.xmlic_launcher_foreground.xmlic_launcher_monochrome.xmlic_library_filled_24dp.xmlic_library_outline_24dp.xmlic_library_selector_24dp.xmlic_mihon.xmlic_mihon_splash.xmlic_more_24dp.xmlic_more_selector_24dp.xmlic_more_vert_24.xmlic_offline_pin_24dp.xmlic_overflow_24dp.xmlic_pause_24dp.xmlic_photo_24dp.xmlic_play_arrow_24dp.xmlic_reader_continuous_vertical_24dp.xmlic_reader_default_24dp.xmlic_reader_ltr_24dp.xmlic_reader_rtl_24dp.xmlic_reader_vertical_24dp.xmlic_reader_webtoon_24dp.xmlic_reddit_24dp.xmlic_refresh_24dp.xmlic_save_24dp.xmlic_screen_lock_landscape_24dp.xmlic_screen_lock_portrait_24dp.xmlic_screen_rotation_24dp.xmlic_search_24dp.xmlic_settings_24dp.xmlic_share_24dp.xmlic_skip_next_24dp.xmlic_stay_current_landscape_24dp.xmlic_stay_current_portrait_24dp.xmlic_system_update_alt_white_24dp.xmlic_tachi.xmlic_tachi_monochrome_launcher.xmlic_twitter_24dp.xmlic_updates_filled_24dp.xmlic_updates_outline_24dp.xmlic_updates_selector_24dp.xmlic_warning_white_24dp.xmllibrary_item_selector.xmllist_item_selector.xmlmaterial_popup_background.xmlmaterial_thumb_drawable.xmlsc_collections_bookmark_48dp.xmlsc_explore_48dp.xmlsc_history_48dp.xmlsc_new_releases_48dp.xmltransparent_tabs_background.xml
layout-sw720dp
layout
common_dialog_with_checkbox.xmlcommon_spinner_item.xmlcommon_tabbed_sheet.xmlcompose_controller.xmldownload_list.xmlglobal_search_controller.xmlglobal_search_controller_card.xmlglobal_search_controller_card_item.xmlmain_activity.xmlmaterial_fastscroll.xmlnavigation_view_checkbox.xmlnavigation_view_checkedtext.xmlnavigation_view_group.xmlnavigation_view_radio.xmlnavigation_view_spinner.xmlnavigation_view_text.xmlpref_spinner.xmlreader_activity.xmlreader_color_filter_settings.xmlreader_general_settings.xmlreader_page_sheet.xmlreader_pager_settings.xmlreader_reading_mode_settings.xmlreader_transition_view.xmlreader_webtoon_settings.xmlsource_filter_sheet.xmlsource_preferences_controller.xmltrack_chapters_dialog.xmltrack_controller.xmltrack_item.xmltrack_score_dialog.xmltrack_search_dialog.xmltrack_search_item.xml
menu
default_chapter_filter.xmldownload_single.xmlglobal_search.xmlmain_nav.xmlreader.xmltrack_item.xmltrack_item_date.xml
mipmap-anydpi-v26
mipmap-hdpi
mipmap-mdpi
mipmap-xhdpi
mipmap-xxhdpi
mipmap-xxxhdpi
mipmap
values-sw720dp
values-v28
values
xml
sqldelight
standard
test
java
eu
kanade
tachiyomi
util
chapter
buildSrc
core-metadata
core
build.gradle.kts
src
main
java
eu
kanade
tachiyomi
tachiyomi
data
.gitignorebuild.gradle.ktsconsumer-rules.proproguard-rules.pro
src
main
AndroidManifest.xml
java
tachiyomi
data
AndroidDatabaseHandler.ktDatabaseAdapter.ktDatabaseHandler.ktQueryPagingSource.ktTransactionContext.kt
category
chapter
history
manga
release
source
track
updates
sqldelight
domain
.gitignorebuild.gradle.ktsconsumer-rules.proproguard-rules.pro
gradle.propertiessrc
main
AndroidManifest.xml
java
tachiyomi
domain
backup
service
category
interactor
CreateCategoryWithName.ktDeleteCategory.ktGetCategories.ktRenameCategory.ktReorderCategory.ktResetCategoryFlags.ktSetDisplayMode.ktSetMangaCategories.ktSetSortModeForCategory.ktUpdateCategory.kt
model
repository
chapter
interactor
GetChapter.ktGetChapterByUrlAndMangaId.ktGetChaptersByMangaId.ktSetMangaDefaultChapterFlags.ktShouldUpdateDbChapter.ktUpdateChapter.kt
model
repository
service
download
service
history
interactor
model
repository
library
manga
interactor
FetchInterval.ktGetDuplicateLibraryManga.ktGetFavorites.ktGetLibraryManga.ktGetManga.ktGetMangaByUrlAndSourceId.ktGetMangaWithChapters.ktNetworkToLocalManga.ktResetViewerFlags.ktSetMangaChapterFlags.kt
model
repository
release
source
interactor
model
repository
service
storage
track
interactor
model
repository
updates
test
java
tachiyomi
domain
chapter
library
model
manga
interactor
release
interactor
gradle
gradlewgradlew.bati18n
.gitignoreREADME.mdbuild.gradle.kts
src
androidMain
commonMain
resources
MR
am
ar
base
be
bg
bn
ca
ceb
cs
cv
da
de
el
eo
es
eu
fa
fi
fil
fr
gl
he
hi
hr
hu
in
it
ja
jv
ka-rGE
kk
km
kn
ko
lt
lv
mr
ms
nb-rNO
ne
nl
nn
pl
pt-rBR
pt
ro
ru
sa
sah
sc
sdh
sk
sq
sr
sv
te
th
tr
uk
uz
vi
zh-rCN
zh-rTW
main
res
values-b+es+419
values-gl
values-ml
values-my
values-ne
values-or
values-si
values-sr
values-ta
values-ti
values-ur-rPK
values-ur
values-uz
macrobenchmark
presentation-core
.gitignorebuild.gradle.ktsconsumer-rules.proproguard-rules.pro
src
main
AndroidManifest.xml
java
tachiyomi
presentation
core
components
ActionButton.ktAdaptiveSheet.ktBadges.ktCircularProgressIndicator.ktCollapsibleBox.ktLabeledCheckbox.ktLazyColumnWithAction.ktLazyGrid.ktLazyList.ktLinkIcon.ktListGroupHeader.ktPager.ktPill.ktSectionCard.ktSettingsItems.ktTwoPanelBox.ktVerticalFastScroller.ktWheelPicker.kt
material
i18n
icons
screens
theme
util
res
presentation-widget
.gitignorebuild.gradle.ktsconsumer-rules.proproguard-rules.pro
settings.gradle.ktssrc
main
source-api
build.gradle.kts
src
androidMain
commonMain
kotlin
main
java
eu
kanade
tachiyomi
source-local
.gitignorebuild.gradle.ktsconsumer-rules.proproguard-rules.pro
src
androidMain
commonMain
kotlin
tachiyomi
@ -1,4 +1,5 @@
|
||||
[*.{kt,kts}]
|
||||
max_line_length = 120
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
|
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1 +0,0 @@
|
||||
ko_fi: inorichi
|
6
.github/ISSUE_TEMPLATE.md
vendored
6
.github/ISSUE_TEMPLATE.md
vendored
@ -3,10 +3,10 @@
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated:
|
||||
- To the latest version of the app (stable is v0.14.0)
|
||||
- To the latest version of the app (stable is v0.15.3)
|
||||
- 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 gone through the FAQ (https://mihon.app/docs/faq/general) and troubleshooting guide (https://mihon.app/docs/guides/troubleshooting/)
|
||||
- If this is an issue with an official extension, that I should be opening an issue in https://github.com/tachiyomiorg/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
|
||||
|
||||
|
12
.github/ISSUE_TEMPLATE/config.yml
vendored
12
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,11 +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
|
||||
url: https://github.com/tachiyomiorg/extensions/issues/new/choose
|
||||
about: Issues and requests for official extensions and sources should be opened in the extensions repository instead
|
||||
- name: 📦 Mihon extensions
|
||||
url: https://mihon.app/extensions/
|
||||
about: List of all available extensions with download links
|
||||
- name: 🖥️ Tachiyomi website
|
||||
url: https://tachiyomi.org/help/
|
||||
- name: 🖥️ Mihon website
|
||||
url: https://mihon.app/
|
||||
about: Guides, troubleshooting, and answers to common questions
|
||||
|
16
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
16
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -1,5 +1,5 @@
|
||||
name: 🐞 Issue report
|
||||
description: Report an issue in Tachiyomi
|
||||
description: Report an issue in Mihon
|
||||
labels: [Bug]
|
||||
body:
|
||||
|
||||
@ -48,12 +48,12 @@ body:
|
||||
You can paste the crash logs in plain text or upload it as an attachment.
|
||||
|
||||
- type: input
|
||||
id: tachiyomi-version
|
||||
id: mihon-version
|
||||
attributes:
|
||||
label: Tachiyomi version
|
||||
description: You can find your Tachiyomi version in **More → About**.
|
||||
label: Mihon version
|
||||
description: You can find your Mihon version in **More → About**.
|
||||
placeholder: |
|
||||
Example: "0.14.0"
|
||||
Example: "0.16.1"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@ -94,11 +94,11 @@ body:
|
||||
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).
|
||||
- label: If this is an issue with an official extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/extensions/issues/new/choose).
|
||||
required: true
|
||||
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
|
||||
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
|
||||
required: true
|
||||
- label: I have updated the app to version **[0.14.0](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
||||
- label: I have updated the app to version **[0.16.1](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
required: true
|
||||
- label: I have updated all installed extensions.
|
||||
required: true
|
||||
|
8
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
8
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -1,5 +1,5 @@
|
||||
name: ⭐ Feature request
|
||||
description: Suggest a feature to improve Tachiyomi
|
||||
description: Suggest a feature to improve Mihon
|
||||
labels: [Feature request]
|
||||
body:
|
||||
|
||||
@ -7,7 +7,7 @@ body:
|
||||
id: feature-description
|
||||
attributes:
|
||||
label: Describe your suggested feature
|
||||
description: How can Tachiyomi be improved?
|
||||
description: How can Mihon be improved?
|
||||
placeholder: |
|
||||
Example:
|
||||
"It should work like this..."
|
||||
@ -31,9 +31,9 @@ body:
|
||||
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).
|
||||
- label: If this is an issue with an official extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/extensions/issues/new/choose).
|
||||
required: true
|
||||
- label: I have updated the app to version **[0.14.0](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
||||
- label: I have updated the app to version **[0.16.1](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
required: true
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
required: true
|
||||
|
BIN
.github/readme-images/app-icon.png
vendored
BIN
.github/readme-images/app-icon.png
vendored
Binary file not shown.
Before ![]() (image error) Size: 1.1 KiB After ![]() (image error) Size: 736 B ![]() ![]() |
14
.github/renovate.json
vendored
14
.github/renovate.json
vendored
@ -1,14 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"schedule": ["every sunday"],
|
||||
"ignoreDeps": [
|
||||
"androidx.core:core-splashscreen",
|
||||
"androidx.work:work-runtime-ktx",
|
||||
"info.android15.nucleus:nucleus-support-v7",
|
||||
"info.android15.nucleus:nucleus",
|
||||
"com.android.tools:r8",
|
||||
"com.google.guava:guava"
|
||||
]
|
||||
}
|
17
.github/renovate.json5
vendored
Normal file
17
.github/renovate.json5
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"schedule": ["every sunday"],
|
||||
"packageRules": [
|
||||
{
|
||||
// Compiler plugins are tightly coupled to Kotlin version
|
||||
"groupName": "Kotlin",
|
||||
"matchPackagePrefixes": [
|
||||
"androidx.compose.compiler",
|
||||
"org.jetbrains.kotlin",
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
15
.github/workflows/build_pull_request.yml
vendored
15
.github/workflows/build_pull_request.yml
vendored
@ -3,7 +3,8 @@ on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'i18n/src/main/res/**/strings.xml'
|
||||
- 'i18n/src/commonMain/resources/**/strings.xml'
|
||||
- 'i18n/src/commonMain/resources/**/plurals.xml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
@ -19,21 +20,21 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@v2
|
||||
uses: actions/dependency-review-action@v3
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
distribution: adopt
|
||||
|
||||
- name: Build app and run unit tests
|
||||
uses: gradle/gradle-command-action@v2
|
||||
with:
|
||||
arguments: assembleStandardRelease testStandardReleaseUnitTest
|
||||
arguments: ktlintCheck assembleStandardRelease testReleaseUnitTest
|
60
.github/workflows/build_push.yml
vendored
60
.github/workflows/build_push.yml
vendored
@ -2,7 +2,7 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
tags:
|
||||
- v*
|
||||
|
||||
@ -17,32 +17,36 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
- name: Setup Android SDK
|
||||
run: |
|
||||
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
distribution: adopt
|
||||
|
||||
- name: Build app and run unit tests
|
||||
uses: gradle/gradle-command-action@v2
|
||||
with:
|
||||
arguments: assembleStandardRelease testStandardReleaseUnitTest
|
||||
arguments: ktlintCheck assembleStandardRelease testReleaseUnitTest
|
||||
|
||||
# Sign APK and create release for tags
|
||||
|
||||
- name: Get tag name
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||
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'
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
with:
|
||||
releaseDirectory: app/build/outputs/apk/standard/release
|
||||
@ -52,36 +56,36 @@ jobs:
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Clean up build artifacts
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||
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 }'`
|
||||
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk mihon-${{ env.VERSION_TAG }}.apk
|
||||
sha=`sha256sum mihon-${{ 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 }'`
|
||||
cp app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk mihon-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
||||
sha=`sha256sum mihon-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 }'`
|
||||
cp app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk mihon-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
||||
sha=`sha256sum mihon-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 }'`
|
||||
cp app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk mihon-x86-${{ env.VERSION_TAG }}.apk
|
||||
sha=`sha256sum mihon-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 }'`
|
||||
cp app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk mihon-x86_64-${{ env.VERSION_TAG }}.apk
|
||||
sha=`sha256sum mihon-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'
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ env.VERSION_TAG }}
|
||||
name: Tachiyomi ${{ env.VERSION_TAG }}
|
||||
name: Mihon ${{ env.VERSION_TAG }}
|
||||
body: |
|
||||
---
|
||||
|
||||
@ -94,13 +98,15 @@ jobs:
|
||||
| armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }}
|
||||
| x86 | ${{ env.APK_X86_SHA }} |
|
||||
| x86_64 | ${{ env.APK_X86_64_SHA }} |
|
||||
|
||||
If you are unsure which version to choose then go with mihon-${{ env.VERSION_TAG }}.apk
|
||||
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
|
||||
mihon-${{ env.VERSION_TAG }}.apk
|
||||
mihon-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
||||
mihon-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
||||
mihon-x86-${{ env.VERSION_TAG }}.apk
|
||||
mihon-x86_64-${{ env.VERSION_TAG }}.apk
|
||||
draft: true
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||
|
12
.github/workflows/issue_moderator.yml
vendored
12
.github/workflows/issue_moderator.yml
vendored
@ -11,9 +11,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Moderate issues
|
||||
uses: tachiyomiorg/issue-moderator-action@v1
|
||||
uses: tachiyomiorg/issue-moderator-action@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
duplicate-label: Duplicate
|
||||
|
||||
auto-close-rules: |
|
||||
[
|
||||
{
|
||||
@ -31,5 +33,13 @@ jobs:
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"type": "both",
|
||||
"regex": ".*(?:fail(?:ed|ure|s)?|can\\s*(?:no|')?t|(?:not|un).*able|(?<!n[o']?t )blocked by|error) (?:to )?(?:get past|by ?pass|penetrate)?.*cloud ?fl?are.*",
|
||||
"ignoreCase": true,
|
||||
"labels": ["Cloudflare protected"],
|
||||
"message": "Refer to the **Solving Cloudflare issues** section at https://mihon.app/docs/guides/troubleshooting/#cloudflare. If it doesn't work, migrate to other sources or wait until they lower their protection."
|
||||
}
|
||||
]
|
||||
auto-close-ignore-label: do-not-autoclose
|
||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v3
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '2'
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -2,7 +2,8 @@
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
.DS_Store
|
||||
.idea/
|
||||
.idea/*
|
||||
!.idea/icon.png
|
||||
*iml
|
||||
*.iml
|
||||
|
||||
@ -11,3 +12,6 @@
|
||||
/build
|
||||
*.apk
|
||||
app/**/output.json
|
||||
|
||||
# Unnecessary file
|
||||
*.swp
|
BIN
.idea/icon.png
generated
Normal file
BIN
.idea/icon.png
generated
Normal file
Binary file not shown.
After ![]() (image error) Size: 26 KiB |
@ -60,7 +60,7 @@ representative at an online or offline event.
|
||||
|
||||
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).
|
||||
the [Mihon Discord server](https://discord.gg/mihon).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community moderators are obligated to respect the privacy and security of the
|
||||
|
@ -1,15 +1,15 @@
|
||||
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).
|
||||
Looking to report an issue/bug or make a feature request? Please refer to the [README file](https://github.com/mihonapp/mihon#issues-feature-requests-and-contributing).
|
||||
|
||||
---
|
||||
|
||||
Thanks for your interest in contributing to Tachiyomi!
|
||||
Thanks for your interest in contributing to Mihon!
|
||||
|
||||
|
||||
# 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.
|
||||
If you're interested in taking on [an open issue](https://github.com/mihonapp/mihon/issues), please comment on it so others are aware.
|
||||
You do not need to ask for permission nor an assignment.
|
||||
|
||||
## Prerequisites
|
||||
@ -24,27 +24,30 @@ Before you start, please note that the ability to use following technologies is
|
||||
- [Android Studio](https://developer.android.com/studio)
|
||||
- Emulator or phone with developer options enabled to test changes.
|
||||
|
||||
## Linting
|
||||
|
||||
To auto-fix some linting errors, run the `ktlintFormat` Gradle task.
|
||||
|
||||
## Getting help
|
||||
|
||||
- Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing.
|
||||
- Join [the Discord server](https://discord.gg/mihon) 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.
|
||||
Translations are done externally via Weblate. See [our website](https://mihon.app/docs/contribute#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).
|
||||
Forks are allowed so long as they abide by [the project's LICENSE](https://github.com/mihonapp/mihon/blob/main/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)
|
||||
- Change or disable the [app update checker](https://github.com/mihonapp/mihon/blob/main/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)
|
||||
- Change the `applicationId` in [`build.gradle.kts`](https://github.com/mihonapp/mihon/blob/main/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
|
||||
- If you want to use Firebase analytics, replace [`google-services.json`](https://github.com/mihonapp/mihon/blob/main/app/src/standard/google-services.json) with your own
|
||||
|
31
README.md
31
README.md
@ -1,10 +1,9 @@
|
||||
| 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) |
|
||||
| Build | Stable | Weekly Beta | Support Server |
|
||||
|-------|--------|-------------|----------------|
|
||||
| [](https://github.com/mihonapp/mihon/actions/workflows/build_push.yml) | [](https://github.com/mihonapp/mihon/releases) | [](https://github.com/mihonapp/mihon-preview/releases) | [](https://discord.gg/mihon) |
|
||||
|
||||
|
||||
# Tachiyomi
|
||||
Tachiyomi is a free and open source manga reader for Android 6.0 and above.
|
||||
# Mihon
|
||||
Mihon is a free and open source manga reader for Android 8.0 and above.
|
||||
|
||||
## Features
|
||||
|
||||
@ -19,9 +18,9 @@ Features include:
|
||||
* 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).
|
||||
Get the app from our [releases page](https://github.com/mihonapp/mihon/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).
|
||||
If you want to try new features before they get to the stable release, you can download the beta version [here](https://github.com/mihonapp/mihon-preview/releases).
|
||||
|
||||
## Issues, Feature Requests and Contributing
|
||||
|
||||
@ -29,8 +28,8 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
||||
|
||||
<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)
|
||||
1. **Before reporting a new issue, take a look at the [FAQ](https://mihon.app/docs/faq/general), the [changelog](https://mihon.app/changelogs/) and the already opened [issues](https://github.com/mihonapp/mihon/issues).**
|
||||
2. If you are unsure, ask here: [](https://discord.gg/mihon)
|
||||
|
||||
</details>
|
||||
|
||||
@ -38,16 +37,12 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
||||
|
||||
* 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
|
||||
* Beta version is equal to the number of commits as seen on 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>
|
||||
@ -55,7 +50,7 @@ DON'T: https://github.com/tachiyomiorg/tachiyomi/issues/75
|
||||
* 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.
|
||||
Source requests are not accepted.
|
||||
</details>
|
||||
|
||||
<details><summary>Contributing</summary>
|
||||
@ -70,8 +65,8 @@ See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
||||
|
||||
## FAQ
|
||||
|
||||
[See our website.](https://tachiyomi.org/)
|
||||
You can also reach out to us on [Discord](https://discord.gg/tachiyomi).
|
||||
[See our website.](https://mihon.app/)
|
||||
You can also reach out to us on [Discord](https://discord.gg/mihon).
|
||||
|
||||
## License
|
||||
|
||||
|
1
app/.gitignore
vendored
1
app/.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
/build
|
||||
*iml
|
||||
*.iml
|
||||
custom.gradle
|
||||
|
@ -1,4 +1,3 @@
|
||||
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
@ -7,7 +6,6 @@ plugins {
|
||||
kotlin("android")
|
||||
kotlin("plugin.serialization")
|
||||
id("com.github.zellius.shortcut-helper")
|
||||
id("com.squareup.sqldelight")
|
||||
}
|
||||
|
||||
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
||||
@ -20,15 +18,12 @@ val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
android {
|
||||
namespace = "eu.kanade.tachiyomi"
|
||||
compileSdk = AndroidConfig.compileSdk
|
||||
ndkVersion = AndroidConfig.ndk
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "eu.kanade.tachiyomi"
|
||||
minSdk = AndroidConfig.minSdk
|
||||
targetSdk = AndroidConfig.targetSdk
|
||||
versionCode = 89
|
||||
versionName = "0.14.0"
|
||||
applicationId = "app.mihon"
|
||||
|
||||
versionCode = 2
|
||||
versionName = "0.16.1"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||
@ -36,9 +31,6 @@ android {
|
||||
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
|
||||
}
|
||||
@ -59,6 +51,7 @@ android {
|
||||
named("debug") {
|
||||
versionNameSuffix = "-${getCommitCount()}"
|
||||
applicationIdSuffix = ".debug"
|
||||
isPseudoLocalesEnabled = true
|
||||
}
|
||||
named("release") {
|
||||
isShrinkResources = true
|
||||
@ -69,11 +62,11 @@ android {
|
||||
initWith(getByName("release"))
|
||||
buildConfigField("boolean", "PREVIEW", "true")
|
||||
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
matchingFallbacks.add("release")
|
||||
val debugType = getByName("debug")
|
||||
signingConfig = debugType.signingConfig
|
||||
versionNameSuffix = debugType.versionNameSuffix
|
||||
applicationIdSuffix = debugType.applicationIdSuffix
|
||||
matchingFallbacks.add("release")
|
||||
}
|
||||
create("benchmark") {
|
||||
initWith(getByName("release"))
|
||||
@ -81,6 +74,7 @@ android {
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
matchingFallbacks.add("release")
|
||||
isDebuggable = false
|
||||
isProfileable = true
|
||||
versionNameSuffix = "-benchmark"
|
||||
applicationIdSuffix = ".benchmark"
|
||||
}
|
||||
@ -99,13 +93,15 @@ android {
|
||||
dimension = "default"
|
||||
}
|
||||
create("dev") {
|
||||
resourceConfigurations.addAll(listOf("en", "xxhdpi"))
|
||||
// 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(
|
||||
packaging {
|
||||
resources.excludes.addAll(
|
||||
listOf(
|
||||
"META-INF/DEPENDENCIES",
|
||||
"LICENSE.txt",
|
||||
"META-INF/LICENSE",
|
||||
@ -113,8 +109,8 @@ android {
|
||||
"META-INF/README.md",
|
||||
"META-INF/NOTICE",
|
||||
"META-INF/*.kotlin_module",
|
||||
"META-INF/*.version",
|
||||
))
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
@ -124,6 +120,7 @@ android {
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
compose = true
|
||||
buildConfig = true
|
||||
|
||||
// Disable some unused things
|
||||
aidl = false
|
||||
@ -139,57 +136,44 @@ android {
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = compose.versions.compiler.get()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
sqldelight {
|
||||
database("Database") {
|
||||
packageName = "eu.kanade.tachiyomi"
|
||||
dialect = "sqlite:3.24"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":i18n"))
|
||||
implementation(project(":core"))
|
||||
implementation(project(":core-metadata"))
|
||||
implementation(project(":source-api"))
|
||||
implementation(project(":source-local"))
|
||||
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.material3.adapter)
|
||||
implementation(compose.material.core)
|
||||
implementation(compose.material.icons)
|
||||
implementation(compose.animation)
|
||||
implementation(compose.animation.graphics)
|
||||
implementation(compose.ui.tooling)
|
||||
debugImplementation(compose.ui.tooling)
|
||||
implementation(compose.ui.tooling.preview)
|
||||
implementation(compose.ui.util)
|
||||
implementation(compose.accompanist.webview)
|
||||
implementation(compose.accompanist.swiperefresh)
|
||||
implementation(compose.accompanist.flowlayout)
|
||||
implementation(compose.accompanist.pager.core)
|
||||
implementation(compose.accompanist.pager.indicators)
|
||||
implementation(compose.accompanist.permissions)
|
||||
implementation(compose.accompanist.systemuicontroller)
|
||||
lintChecks(compose.lintchecks)
|
||||
|
||||
implementation(androidx.paging.runtime)
|
||||
implementation(androidx.paging.compose)
|
||||
|
||||
implementation(libs.bundles.sqlite)
|
||||
implementation(androidx.sqlite)
|
||||
implementation(libs.sqldelight.android.driver)
|
||||
implementation(libs.sqldelight.coroutines)
|
||||
implementation(libs.sqldelight.android.paging)
|
||||
|
||||
implementation(kotlinx.reflect)
|
||||
implementation(kotlinx.immutables)
|
||||
|
||||
implementation(platform(kotlinx.coroutines.bom))
|
||||
implementation(kotlinx.bundles.coroutines)
|
||||
|
||||
// AndroidX libraries
|
||||
@ -197,31 +181,26 @@ dependencies {
|
||||
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.glance)
|
||||
implementation(androidx.profileinstaller)
|
||||
|
||||
implementation(androidx.bundles.lifecycle)
|
||||
|
||||
// Job scheduling
|
||||
implementation(androidx.bundles.workmanager)
|
||||
implementation(androidx.workmanager)
|
||||
|
||||
// RX
|
||||
implementation(libs.bundles.reactivex)
|
||||
implementation(libs.flowreactivenetwork)
|
||||
// RxJava
|
||||
implementation(libs.rxjava)
|
||||
|
||||
// Network client
|
||||
// Networking
|
||||
implementation(libs.bundles.okhttp)
|
||||
implementation(libs.okio)
|
||||
implementation(libs.conscrypt.android) // TLS 1.3 support for Android < 10
|
||||
|
||||
// TLS 1.3 support for Android < 10
|
||||
implementation(libs.conscrypt.android)
|
||||
|
||||
// Data serialization (JSON, protobuf)
|
||||
// Data serialization (JSON, protobuf, xml)
|
||||
implementation(kotlinx.bundles.serialization)
|
||||
|
||||
// HTML parser
|
||||
@ -235,56 +214,42 @@ dependencies {
|
||||
// Preferences
|
||||
implementation(libs.preferencektx)
|
||||
|
||||
// Model View Presenter
|
||||
implementation(libs.bundles.nucleus)
|
||||
|
||||
// Dependency injection
|
||||
implementation(libs.injekt.core)
|
||||
|
||||
// Image loading
|
||||
implementation(platform(libs.coil.bom))
|
||||
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.markwon)
|
||||
implementation(libs.bundles.richtext)
|
||||
implementation(libs.aboutLibraries.compose)
|
||||
implementation(libs.cascade)
|
||||
implementation(libs.numberpicker)
|
||||
implementation(libs.bundles.voyager)
|
||||
|
||||
// Conductor
|
||||
implementation(libs.bundles.conductor)
|
||||
|
||||
// FlowBinding
|
||||
implementation(libs.bundles.flowbinding)
|
||||
implementation(libs.compose.materialmotion)
|
||||
implementation(libs.swipe)
|
||||
|
||||
// Logging
|
||||
implementation(libs.logcat)
|
||||
|
||||
// Crash reports/analytics
|
||||
implementation(libs.acra.http)
|
||||
"standardImplementation"(libs.firebase.analytics)
|
||||
|
||||
// Shizuku
|
||||
implementation(libs.bundles.shizuku)
|
||||
|
||||
// Tests
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.bundles.test)
|
||||
|
||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||
// debugImplementation(libs.leakcanary.android)
|
||||
@ -295,45 +260,51 @@ androidComponents {
|
||||
beforeVariants { variantBuilder ->
|
||||
// Disables standardBenchmark
|
||||
if (variantBuilder.buildType == "benchmark") {
|
||||
variantBuilder.enable = variantBuilder.productFlavors.containsAll(listOf("default" to "dev"))
|
||||
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<Test> {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED)
|
||||
}
|
||||
}
|
||||
|
||||
withType<org.jmailen.gradle.kotlinter.tasks.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.pager.ExperimentalPagerApi",
|
||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
||||
"-Xcontext-receivers",
|
||||
"-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=coil.annotation.ExperimentalCoilApi",
|
||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||
)
|
||||
}
|
||||
|
||||
preBuild {
|
||||
val ktlintTask = if (System.getenv("GITHUB_BASE_REF") == null) formatKotlin else lintKotlin
|
||||
dependsOn(ktlintTask)
|
||||
if (project.findProperty("tachiyomi.enableComposeCompilerMetrics") == "true") {
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||
)
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
-dontusemixedcaseclassnames
|
||||
-ignorewarnings
|
||||
-verbose
|
||||
|
||||
-keepattributes *Annotation*
|
||||
|
12
app/proguard-rules.pro
vendored
12
app/proguard-rules.pro
vendored
@ -1,14 +1,18 @@
|
||||
-dontobfuscate
|
||||
|
||||
-keep,allowoptimization class eu.kanade.**
|
||||
-keep,allowoptimization class tachiyomi.**
|
||||
|
||||
# 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 kotlin.time.** { 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 rx.** { public protected *; }
|
||||
-keep,allowoptimization class app.cash.quickjs.** { public protected *; }
|
||||
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
|
||||
|
||||
@ -41,7 +45,7 @@
|
||||
|
||||
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
||||
-keepattributes *Annotation*, InnerClasses
|
||||
-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
|
||||
-dontnote kotlinx.serialization.** # core serialization annotations
|
||||
|
||||
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
|
||||
-keepclassmembers class kotlinx.serialization.json.** {
|
||||
@ -67,3 +71,7 @@
|
||||
|
||||
# XmlUtil
|
||||
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
||||
|
||||
# Firebase
|
||||
-keep class com.google.firebase.installations.** { *; }
|
||||
-keep interface com.google.firebase.installations.** { *; }
|
||||
|
@ -1,5 +1,4 @@
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<shortcut
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/sc_collections_bookmark_48dp"
|
||||
|
16
app/src/debug/res/drawable/ic_launcher_background.xml
Normal file
16
app/src/debug/res/drawable/ic_launcher_background.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="432"
|
||||
android:viewportHeight="432">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h432v432h-432z"/>
|
||||
<path
|
||||
android:pathData="M0,0h432v432h-432z"
|
||||
android:fillColor="#FAFAFA"/>
|
||||
<path
|
||||
android:pathData="M0,0h432v432h-432z"
|
||||
android:fillColor="#2E3943"/>
|
||||
</group>
|
||||
</vector>
|
@ -1,27 +1,16 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108.0"
|
||||
android:viewportHeight="108.0">
|
||||
android:viewportWidth="432"
|
||||
android:viewportHeight="432">
|
||||
<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"/>
|
||||
android:pathData="M337,216C337,282.83 282.83,337 216,337C149.17,337 95,282.83 95,216C95,149.17 149.17,95 216,95C282.83,95 337,149.17 337,216Z"
|
||||
android:fillColor="#F2FAFF"/>
|
||||
<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"/>
|
||||
android:pathData="M216,314.31C270.3,314.31 314.31,270.3 314.31,216C314.31,161.7 270.3,117.69 216,117.69C161.7,117.69 117.69,161.7 117.69,216C117.69,270.3 161.7,314.31 216,314.31ZM216,337C282.83,337 337,282.83 337,216C337,149.17 282.83,95 216,95C149.17,95 95,149.17 95,216C95,282.83 149.17,337 216,337Z"
|
||||
android:fillColor="#7EBBED"
|
||||
android:fillType="evenOdd"/>
|
||||
<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"/>
|
||||
android:pathData="M163.71,174.79L162.66,150.56C165.82,151.16 169.58,151.31 178.16,151.31C188.55,151.31 202.24,150.71 209.32,149.81C212.32,149.51 213.53,149.05 215.49,148L231.74,161.85C230.23,163.95 229.78,164.86 228.13,169.07C226.77,172.38 220.6,191.49 218.2,199.62C229.33,201.88 235.5,203.53 243.93,206.99C244.98,199.62 245.13,195.71 245.13,182.31C245.13,178.85 244.98,176.9 244.53,173.74L270.72,174.64C269.96,178.25 269.82,179.76 269.66,185.62C269.06,199.77 268.46,206.54 266.95,216.78C277.34,222.04 277.34,222.04 282.61,224.9C285.32,226.41 285.92,226.71 287.72,227.31L278.99,255.45C274.78,251.69 268.91,247.63 260.33,242.81C252.81,260.72 240.32,273.82 221.35,284.2C215.03,275.77 210.22,270.65 202.54,264.63C213.68,259.37 218.8,256.06 224.67,250.49C230.38,244.92 234.15,239.5 237.76,231.38C228.13,227.01 221.96,225.05 211.27,222.8C205.1,241.16 200.13,252.9 195.77,259.97C189.9,269.45 181.93,274.42 172.74,274.42C165.67,274.42 158.45,271.26 153.18,265.84C147.16,259.67 144,251.09 144,241.16C144,226.41 151.07,213.62 163.41,205.64C171.39,200.52 179.82,198.27 193.21,197.51C195.92,188.63 198.18,180.96 200.29,172.38C193.66,172.98 185.39,173.43 175.3,173.88C169.88,174.04 168.08,174.19 163.71,174.79ZM186.59,220.54C179.52,221.74 175.3,224 171.54,228.82C168.68,232.13 167.33,236.04 167.33,240.25C167.33,244.92 169.58,248.38 172.44,248.38C175.9,248.38 179.82,240.55 186.59,220.54Z"
|
||||
android:fillColor="#031019"/>
|
||||
</vector>
|
||||
|
@ -1,5 +0,0 @@
|
||||
<?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"/>
|
||||
</adaptive-icon>
|
@ -1,6 +0,0 @@
|
||||
<?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>
|
Binary file not shown.
Before ![]() (image error) Size: 2.6 KiB |
Binary file not shown.
Before ![]() (image error) Size: 4.1 KiB |
Binary file not shown.
Before ![]() (image error) Size: 1.5 KiB |
Binary file not shown.
Before ![]() (image error) Size: 2.3 KiB |
Binary file not shown.
Before ![]() (image error) Size: 2.9 KiB |
Binary file not shown.
Before ![]() (image error) Size: 5.3 KiB |
Binary file not shown.
Before ![]() (image error) Size: 5.5 KiB |
Binary file not shown.
Before ![]() (image error) Size: 10 KiB |
Binary file not shown.
Before ![]() (image error) Size: 6.4 KiB |
Binary file not shown.
Before ![]() (image error) Size: 13 KiB |
@ -8,7 +8,9 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
|
||||
<!-- Storage -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
|
||||
<!-- For background jobs -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
@ -20,42 +22,87 @@
|
||||
<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.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_APP_SPECIFIC_LOCALES"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<!-- Remove permission from Firebase dependency -->
|
||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID"
|
||||
<uses-permission
|
||||
android:name="com.google.android.gms.permission.AD_ID"
|
||||
tools:node="remove" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="false"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:preserveLegacyExternalStorage="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/Theme.Tachiyomi"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<!-- enable profiling by macrobenchmark -->
|
||||
<profileable
|
||||
android:shell="true"
|
||||
tools:targetApi="q" />
|
||||
android:theme="@style/Theme.Tachiyomi">
|
||||
|
||||
<activity
|
||||
android:name=".ui.main.MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/Theme.Tachiyomi.SplashScreen"
|
||||
android:exported="true">
|
||||
android:theme="@style/Theme.Tachiyomi.SplashScreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Deep link to add repos -->
|
||||
<intent-filter android:label="@string/action_add_repo">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="tachiyomi" />
|
||||
<data android:host="add-repo" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Open backup files -->
|
||||
<intent-filter android:label="@string/pref_restore_backup">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:host="*" />
|
||||
<data android:mimeType="*/*" />
|
||||
<!--
|
||||
Work around Android's ugly primitive PatternMatcher
|
||||
implementation that can't cope with finding a . early in
|
||||
the path unless it's explicitly matched.
|
||||
|
||||
See https://stackoverflow.com/a/31028507
|
||||
-->
|
||||
<data android:pathPattern=".*\\.tachibk" />
|
||||
<data android:pathPattern=".*\\..*\\.tachibk" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.tachibk" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.tachibk" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.tachibk" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.tachibk" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.tachibk" />
|
||||
</intent-filter>
|
||||
|
||||
<!--suppress AndroidDomInspection -->
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
@ -63,16 +110,16 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:process=":error_handler"
|
||||
android:name=".crash.CrashActivity"
|
||||
android:exported="false" />
|
||||
android:exported="false"
|
||||
android:process=":error_handler" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.main.DeepLinkActivity"
|
||||
android:name=".ui.deeplink.DeepLinkActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/action_search"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:label="@string/action_global_search"
|
||||
android:exported="true">
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
||||
@ -96,20 +143,21 @@
|
||||
|
||||
<activity
|
||||
android:name=".ui.reader.ReaderActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="false">
|
||||
android:exported="false"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
|
||||
<meta-data
|
||||
android:name="com.samsung.android.support.REMOTE_ACTION"
|
||||
android:resource="@xml/s_pen_actions" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.security.UnlockActivity"
|
||||
android:theme="@style/Theme.Tachiyomi"
|
||||
android:exported="false" />
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.Tachiyomi" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.webview.WebViewActivity"
|
||||
@ -118,67 +166,25 @@
|
||||
|
||||
<activity
|
||||
android:name=".extension.util.ExtensionInstallActivity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||
android:exported="false" />
|
||||
android:exported="false"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.setting.track.AnilistLoginActivity"
|
||||
android:label="Anilist"
|
||||
android:exported="true">
|
||||
android:name=".ui.setting.track.TrackLoginActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/track_activity_name">
|
||||
<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="anilist-auth"
|
||||
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" />
|
||||
<data android:scheme="mihon" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<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" />
|
||||
|
||||
<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" />
|
||||
<data android:host="anilist-auth" />
|
||||
<data android:host="bangumi-auth" />
|
||||
<data android:host="myanimelist-auth" />
|
||||
<data android:host="shikimori-auth" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
@ -186,38 +192,10 @@
|
||||
android:name=".data.notification.NotificationReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name=".glance.UpdatesGridGlanceReceiver"
|
||||
android:enabled="@bool/glance_appwidget_available"
|
||||
<service
|
||||
android:name=".extension.util.ExtensionInstallService"
|
||||
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" />
|
||||
|
||||
<service
|
||||
android:name=".data.download.DownloadService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".data.updater.AppUpdateService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".data.backup.BackupRestoreService"
|
||||
android:exported="false" />
|
||||
|
||||
<service android:name=".extension.util.ExtensionInstallService"
|
||||
android:exported="false" />
|
||||
android:foregroundServiceType="shortService" />
|
||||
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
@ -228,6 +206,11 @@
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
tools:node="merge" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
@ -241,9 +224,9 @@
|
||||
<provider
|
||||
android:name="rikka.shizuku.ShizukuProvider"
|
||||
android:authorities="${applicationId}.shizuku"
|
||||
android:multiprocess="false"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:multiprocess="false"
|
||||
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||
|
||||
<meta-data
|
||||
|
File diff suppressed because it is too large
Load Diff
10
app/src/main/java/eu/kanade/core/preference/CheckboxState.kt
Normal file
10
app/src/main/java/eu/kanade/core/preference/CheckboxState.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package eu.kanade.core.preference
|
||||
|
||||
import androidx.compose.ui.state.ToggleableState
|
||||
import tachiyomi.core.preference.CheckboxState
|
||||
|
||||
fun <T> CheckboxState.TriState<T>.asToggleableState() = when (this) {
|
||||
is CheckboxState.TriState.Exclude -> ToggleableState.Indeterminate
|
||||
is CheckboxState.TriState.Include -> ToggleableState.On
|
||||
is CheckboxState.TriState.None -> ToggleableState.Off
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
package eu.kanade.core.prefs
|
||||
package eu.kanade.core.preference
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import eu.kanade.tachiyomi.core.preference.Preference
|
||||
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>,
|
||||
@ -31,6 +31,8 @@ class PreferenceMutableState<T>(
|
||||
}
|
||||
|
||||
override fun component2(): (T) -> Unit {
|
||||
return { preference.set(it) }
|
||||
return preference::set
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Preference<T>.asState(scope: CoroutineScope) = PreferenceMutableState(this, scope)
|
138
app/src/main/java/eu/kanade/core/util/CollectionUtils.kt
Normal file
138
app/src/main/java/eu/kanade/core/util/CollectionUtils.kt
Normal file
@ -0,0 +1,138 @@
|
||||
package eu.kanade.core.util
|
||||
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
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)
|
||||
val after = getOrNull(i + 1)
|
||||
val separator = generator.invoke(before, after)
|
||||
separator?.let(newList::add)
|
||||
}
|
||||
return newList
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package eu.kanade.core.util
|
||||
|
||||
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
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
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,
|
||||
)
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package eu.kanade.data
|
||||
|
||||
import com.squareup.sqldelight.ColumnAdapter
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import java.util.Date
|
||||
|
||||
val dateAdapter = object : ColumnAdapter<Date, Long> {
|
||||
override fun decode(databaseValue: Long): Date = Date(databaseValue)
|
||||
override fun encode(value: Date): Long = value.time
|
||||
}
|
||||
|
||||
private const val listOfStringsSeparator = ", "
|
||||
val listOfStringsAdapter = object : ColumnAdapter<List<String>, String> {
|
||||
override fun decode(databaseValue: String) =
|
||||
if (databaseValue.isEmpty()) {
|
||||
listOf()
|
||||
} else {
|
||||
databaseValue.split(listOfStringsSeparator)
|
||||
}
|
||||
override fun encode(value: List<String>) = value.joinToString(separator = listOfStringsSeparator)
|
||||
}
|
||||
|
||||
val updateStrategyAdapter = object : ColumnAdapter<UpdateStrategy, Long> {
|
||||
private val enumValues by lazy { UpdateStrategy.values() }
|
||||
|
||||
override fun decode(databaseValue: Long): UpdateStrategy =
|
||||
enumValues.getOrElse(databaseValue.toInt()) { UpdateStrategy.ALWAYS_UPDATE }
|
||||
|
||||
override fun encode(value: UpdateStrategy): Long = value.ordinal.toLong()
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package eu.kanade.data.category
|
||||
|
||||
import eu.kanade.domain.category.model.Category
|
||||
|
||||
val categoryMapper: (Long, String, Long, Long) -> Category = { id, name, order, flags ->
|
||||
Category(
|
||||
id = id,
|
||||
name = name,
|
||||
order = order,
|
||||
flags = flags,
|
||||
)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package eu.kanade.data.chapter
|
||||
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
|
||||
val chapterMapper: (Long, Long, String, String, String?, Boolean, Boolean, Long, Float, Long, Long, Long) -> Chapter =
|
||||
{ id, mangaId, url, name, scanlator, read, bookmark, lastPageRead, chapterNumber, sourceOrder, dateFetch, dateUpload ->
|
||||
Chapter(
|
||||
id = id,
|
||||
mangaId = mangaId,
|
||||
read = read,
|
||||
bookmark = bookmark,
|
||||
lastPageRead = lastPageRead,
|
||||
dateFetch = dateFetch,
|
||||
sourceOrder = sourceOrder,
|
||||
url = url,
|
||||
name = name,
|
||||
dateUpload = dateUpload,
|
||||
chapterNumber = chapterNumber,
|
||||
scanlator = scanlator,
|
||||
)
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package eu.kanade.data.history
|
||||
|
||||
import eu.kanade.domain.history.model.History
|
||||
import eu.kanade.domain.history.model.HistoryWithRelations
|
||||
import eu.kanade.domain.manga.model.MangaCover
|
||||
import java.util.Date
|
||||
|
||||
val historyMapper: (Long, Long, Date?, Long) -> History = { id, chapterId, readAt, readDuration ->
|
||||
History(
|
||||
id = id,
|
||||
chapterId = chapterId,
|
||||
readAt = readAt,
|
||||
readDuration = readDuration,
|
||||
)
|
||||
}
|
||||
|
||||
val historyWithRelationsMapper: (Long, Long, Long, String, String?, Long, Boolean, Long, Float, Date?, Long) -> HistoryWithRelations = {
|
||||
historyId, mangaId, chapterId, title, thumbnailUrl, sourceId, isFavorite, coverLastModified, chapterNumber, readAt, readDuration ->
|
||||
HistoryWithRelations(
|
||||
id = historyId,
|
||||
chapterId = chapterId,
|
||||
mangaId = mangaId,
|
||||
title = title,
|
||||
chapterNumber = chapterNumber,
|
||||
readAt = readAt,
|
||||
readDuration = readDuration,
|
||||
coverData = MangaCover(
|
||||
mangaId = mangaId,
|
||||
sourceId = sourceId,
|
||||
isMangaFavorite = isFavorite,
|
||||
url = thumbnailUrl,
|
||||
lastModified = coverLastModified,
|
||||
),
|
||||
)
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package eu.kanade.data.manga
|
||||
|
||||
import eu.kanade.domain.library.model.LibraryManga
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
|
||||
val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy) -> Manga =
|
||||
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, _, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, updateStrategy ->
|
||||
Manga(
|
||||
id = id,
|
||||
source = source,
|
||||
favorite = favorite,
|
||||
lastUpdate = lastUpdate ?: 0,
|
||||
dateAdded = dateAdded,
|
||||
viewerFlags = viewerFlags,
|
||||
chapterFlags = chapterFlags,
|
||||
coverLastModified = coverLastModified,
|
||||
url = url,
|
||||
title = title,
|
||||
artist = artist,
|
||||
author = author,
|
||||
description = description,
|
||||
genre = genre,
|
||||
status = status,
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
updateStrategy = updateStrategy,
|
||||
initialized = initialized,
|
||||
)
|
||||
}
|
||||
|
||||
val libraryManga: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy, Long, Long, Long, Long, Long, Long, Long) -> LibraryManga =
|
||||
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, updateStrategy, totalCount, readCount, latestUpload, chapterFetchedAt, lastRead, bookmarkCount, category ->
|
||||
LibraryManga(
|
||||
manga = mangaMapper(
|
||||
id,
|
||||
source,
|
||||
url,
|
||||
artist,
|
||||
author,
|
||||
description,
|
||||
genre,
|
||||
title,
|
||||
status,
|
||||
thumbnailUrl,
|
||||
favorite,
|
||||
lastUpdate,
|
||||
nextUpdate,
|
||||
initialized,
|
||||
viewerFlags,
|
||||
chapterFlags,
|
||||
coverLastModified,
|
||||
dateAdded,
|
||||
updateStrategy,
|
||||
),
|
||||
category = category,
|
||||
totalChapters = totalCount,
|
||||
readCount = readCount,
|
||||
bookmarkCount = bookmarkCount,
|
||||
latestUpload = latestUpload,
|
||||
chapterFetchedAt = chapterFetchedAt,
|
||||
lastRead = lastRead,
|
||||
)
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
package eu.kanade.data.source
|
||||
|
||||
class NoResultsException : Exception()
|
@ -1,23 +0,0 @@
|
||||
package eu.kanade.data.source
|
||||
|
||||
import eu.kanade.data.DatabaseHandler
|
||||
import eu.kanade.domain.source.model.SourceData
|
||||
import eu.kanade.domain.source.repository.SourceDataRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class SourceDataRepositoryImpl(
|
||||
private val handler: DatabaseHandler,
|
||||
) : SourceDataRepository {
|
||||
|
||||
override fun subscribeAll(): Flow<List<SourceData>> {
|
||||
return handler.subscribeToList { sourcesQueries.findAll(sourceDataMapper) }
|
||||
}
|
||||
|
||||
override suspend fun getSourceData(id: Long): SourceData? {
|
||||
return handler.awaitOneOrNull { sourcesQueries.findOne(id, sourceDataMapper) }
|
||||
}
|
||||
|
||||
override suspend fun upsertSourceData(id: Long, lang: String, name: String) {
|
||||
handler.await { sourcesQueries.upsert(id, lang, name) }
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package eu.kanade.data.source
|
||||
|
||||
import eu.kanade.domain.source.model.Source
|
||||
import eu.kanade.domain.source.model.SourceData
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
val sourceDataMapper: (Long, String, String) -> SourceData = { id, lang, name ->
|
||||
SourceData(id, lang, name)
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package eu.kanade.data.source
|
||||
|
||||
import eu.kanade.data.DatabaseHandler
|
||||
import eu.kanade.domain.source.model.Source
|
||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
||||
import eu.kanade.domain.source.model.SourceWithCount
|
||||
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
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package eu.kanade.data.track
|
||||
|
||||
import eu.kanade.domain.track.model.Track
|
||||
|
||||
val trackMapper: (Long, Long, Long, Long, Long?, String, Double, Long, Long, Float, String, Long, Long) -> Track =
|
||||
{ id, mangaId, syncId, remoteId, libraryId, title, lastChapterRead, totalChapters, status, score, remoteUrl, startDate, finishDate ->
|
||||
Track(
|
||||
id = id,
|
||||
mangaId = mangaId,
|
||||
syncId = syncId,
|
||||
remoteId = remoteId,
|
||||
libraryId = libraryId,
|
||||
title = title,
|
||||
lastChapterRead = lastChapterRead,
|
||||
totalChapters = totalChapters,
|
||||
status = status,
|
||||
score = score,
|
||||
remoteUrl = remoteUrl,
|
||||
startDate = startDate,
|
||||
finishDate = finishDate,
|
||||
)
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package eu.kanade.data.updates
|
||||
|
||||
import eu.kanade.domain.manga.model.MangaCover
|
||||
import eu.kanade.domain.updates.model.UpdatesWithRelations
|
||||
|
||||
val updateWithRelationMapper: (Long, String, Long, String, String?, Boolean, Boolean, Long, Boolean, String?, Long, Long, Long) -> UpdatesWithRelations = {
|
||||
mangaId, mangaTitle, chapterId, chapterName, scanlator, read, bookmark, sourceId, favorite, thumbnailUrl, coverLastModified, _, dateFetch ->
|
||||
UpdatesWithRelations(
|
||||
mangaId = mangaId,
|
||||
mangaTitle = mangaTitle,
|
||||
chapterId = chapterId,
|
||||
chapterName = chapterName,
|
||||
scanlator = scanlator,
|
||||
read = read,
|
||||
bookmark = bookmark,
|
||||
sourceId = sourceId,
|
||||
dateFetch = dateFetch,
|
||||
coverData = MangaCover(
|
||||
mangaId = mangaId,
|
||||
sourceId = sourceId,
|
||||
isMangaFavorite = favorite,
|
||||
url = thumbnailUrl,
|
||||
lastModified = coverLastModified,
|
||||
),
|
||||
)
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package eu.kanade.data.updates
|
||||
|
||||
import eu.kanade.data.DatabaseHandler
|
||||
import eu.kanade.domain.updates.model.UpdatesWithRelations
|
||||
import eu.kanade.domain.updates.repository.UpdatesRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class UpdatesRepositoryImpl(
|
||||
val databaseHandler: DatabaseHandler,
|
||||
) : UpdatesRepository {
|
||||
|
||||
override fun subscribeAll(after: Long): Flow<List<UpdatesWithRelations>> {
|
||||
return databaseHandler.subscribeToList {
|
||||
updatesViewQueries.updates(after, updateWithRelationMapper)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,72 +1,88 @@
|
||||
package eu.kanade.domain
|
||||
|
||||
import eu.kanade.data.category.CategoryRepositoryImpl
|
||||
import eu.kanade.data.chapter.ChapterRepositoryImpl
|
||||
import eu.kanade.data.history.HistoryRepositoryImpl
|
||||
import eu.kanade.data.manga.MangaRepositoryImpl
|
||||
import eu.kanade.data.source.SourceDataRepositoryImpl
|
||||
import eu.kanade.data.source.SourceRepositoryImpl
|
||||
import eu.kanade.data.track.TrackRepositoryImpl
|
||||
import eu.kanade.data.updates.UpdatesRepositoryImpl
|
||||
import eu.kanade.domain.category.interactor.CreateCategoryWithName
|
||||
import eu.kanade.domain.category.interactor.DeleteCategory
|
||||
import eu.kanade.domain.category.interactor.GetCategories
|
||||
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.category.repository.CategoryRepository
|
||||
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.GetAvailableScanlators
|
||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||
import eu.kanade.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||
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.chapter.repository.ChapterRepository
|
||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||
import eu.kanade.domain.extension.interactor.CreateExtensionRepo
|
||||
import eu.kanade.domain.extension.interactor.DeleteExtensionRepo
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionRepos
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||
import eu.kanade.domain.history.interactor.DeleteAllHistory
|
||||
import eu.kanade.domain.history.interactor.GetHistory
|
||||
import eu.kanade.domain.history.interactor.GetNextChapter
|
||||
import eu.kanade.domain.history.interactor.RemoveHistoryById
|
||||
import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
|
||||
import eu.kanade.domain.history.interactor.UpsertHistory
|
||||
import eu.kanade.domain.history.repository.HistoryRepository
|
||||
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.extension.interactor.TrustExtension
|
||||
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
|
||||
import eu.kanade.domain.manga.interactor.SetExcludedScanlators
|
||||
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.repository.MangaRepository
|
||||
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.SourceDataRepository
|
||||
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.InsertTrack
|
||||
import eu.kanade.domain.track.repository.TrackRepository
|
||||
import eu.kanade.domain.updates.interactor.GetUpdates
|
||||
import eu.kanade.domain.updates.repository.UpdatesRepository
|
||||
import eu.kanade.domain.track.interactor.AddTracks
|
||||
import eu.kanade.domain.track.interactor.RefreshTracks
|
||||
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
|
||||
import eu.kanade.domain.track.interactor.TrackChapter
|
||||
import tachiyomi.data.category.CategoryRepositoryImpl
|
||||
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
||||
import tachiyomi.data.history.HistoryRepositoryImpl
|
||||
import tachiyomi.data.manga.MangaRepositoryImpl
|
||||
import tachiyomi.data.release.ReleaseServiceImpl
|
||||
import tachiyomi.data.source.SourceRepositoryImpl
|
||||
import tachiyomi.data.source.StubSourceRepositoryImpl
|
||||
import tachiyomi.data.track.TrackRepositoryImpl
|
||||
import tachiyomi.data.updates.UpdatesRepositoryImpl
|
||||
import tachiyomi.domain.category.interactor.CreateCategoryWithName
|
||||
import tachiyomi.domain.category.interactor.DeleteCategory
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.category.interactor.RenameCategory
|
||||
import tachiyomi.domain.category.interactor.ReorderCategory
|
||||
import tachiyomi.domain.category.interactor.ResetCategoryFlags
|
||||
import tachiyomi.domain.category.interactor.SetDisplayMode
|
||||
import tachiyomi.domain.category.interactor.SetMangaCategories
|
||||
import tachiyomi.domain.category.interactor.SetSortModeForCategory
|
||||
import tachiyomi.domain.category.interactor.UpdateCategory
|
||||
import tachiyomi.domain.category.repository.CategoryRepository
|
||||
import tachiyomi.domain.chapter.interactor.GetChapter
|
||||
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
|
||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||
import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
||||
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.history.interactor.GetHistory
|
||||
import tachiyomi.domain.history.interactor.GetNextChapters
|
||||
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.interactor.FetchInterval
|
||||
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
||||
import tachiyomi.domain.manga.interactor.GetFavorites
|
||||
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
||||
import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
|
||||
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
|
||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
||||
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
||||
import tachiyomi.domain.release.service.ReleaseService
|
||||
import tachiyomi.domain.source.interactor.GetRemoteManga
|
||||
import tachiyomi.domain.source.interactor.GetSourcesWithNonLibraryManga
|
||||
import tachiyomi.domain.source.repository.SourceRepository
|
||||
import tachiyomi.domain.source.repository.StubSourceRepository
|
||||
import tachiyomi.domain.track.interactor.DeleteTrack
|
||||
import tachiyomi.domain.track.interactor.GetTracks
|
||||
import tachiyomi.domain.track.interactor.GetTracksPerManga
|
||||
import tachiyomi.domain.track.interactor.InsertTrack
|
||||
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
|
||||
@ -79,7 +95,7 @@ class DomainModule : InjektModule {
|
||||
addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) }
|
||||
addFactory { GetCategories(get()) }
|
||||
addFactory { ResetCategoryFlags(get(), get()) }
|
||||
addFactory { SetDisplayModeForCategory(get(), get()) }
|
||||
addFactory { SetDisplayMode(get()) }
|
||||
addFactory { SetSortModeForCategory(get(), get()) }
|
||||
addFactory { CreateCategoryWithName(get(), get()) }
|
||||
addFactory { RenameCategory(get()) }
|
||||
@ -92,36 +108,48 @@ class DomainModule : InjektModule {
|
||||
addFactory { GetFavorites(get()) }
|
||||
addFactory { GetLibraryManga(get()) }
|
||||
addFactory { GetMangaWithChapters(get(), get()) }
|
||||
addFactory { GetMangaByUrlAndSourceId(get()) }
|
||||
addFactory { GetManga(get()) }
|
||||
addFactory { GetNextChapter(get(), get(), get(), get()) }
|
||||
addFactory { GetNextChapters(get(), get(), get()) }
|
||||
addFactory { ResetViewerFlags(get()) }
|
||||
addFactory { SetMangaChapterFlags(get()) }
|
||||
addFactory { FetchInterval(get()) }
|
||||
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
||||
addFactory { SetMangaViewerFlags(get()) }
|
||||
addFactory { NetworkToLocalManga(get()) }
|
||||
addFactory { UpdateManga(get()) }
|
||||
addFactory { UpdateManga(get(), get()) }
|
||||
addFactory { SetMangaCategories(get()) }
|
||||
addFactory { GetExcludedScanlators(get()) }
|
||||
addFactory { SetExcludedScanlators(get()) }
|
||||
|
||||
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||
addFactory { GetApplicationRelease(get(), get()) }
|
||||
|
||||
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
||||
addFactory { TrackChapter(get(), get(), get(), get()) }
|
||||
addFactory { AddTracks(get(), get(), get(), get()) }
|
||||
addFactory { RefreshTracks(get(), get(), get(), get()) }
|
||||
addFactory { DeleteTrack(get()) }
|
||||
addFactory { GetTracksPerManga(get()) }
|
||||
addFactory { GetTracks(get()) }
|
||||
addFactory { InsertTrack(get()) }
|
||||
addFactory { SyncChapterProgressWithTrack(get(), get(), get()) }
|
||||
|
||||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||
addFactory { GetChapter(get()) }
|
||||
addFactory { GetChapterByMangaId(get()) }
|
||||
addFactory { GetChaptersByMangaId(get()) }
|
||||
addFactory { GetChapterByUrlAndMangaId(get()) }
|
||||
addFactory { UpdateChapter(get()) }
|
||||
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
||||
addFactory { ShouldUpdateDbChapter() }
|
||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get()) }
|
||||
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
|
||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||
addFactory { GetAvailableScanlators(get()) }
|
||||
|
||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||
addFactory { DeleteAllHistory(get()) }
|
||||
addFactory { GetHistory(get()) }
|
||||
addFactory { UpsertHistory(get()) }
|
||||
addFactory { RemoveHistoryById(get()) }
|
||||
addFactory { RemoveHistoryByMangaId(get()) }
|
||||
addFactory { RemoveHistory(get()) }
|
||||
addFactory { GetTotalReadDuration(get()) }
|
||||
|
||||
addFactory { DeleteDownload(get(), get()) }
|
||||
|
||||
@ -130,10 +158,10 @@ class DomainModule : InjektModule {
|
||||
addFactory { GetExtensionLanguages(get(), get()) }
|
||||
|
||||
addSingletonFactory<UpdatesRepository> { UpdatesRepositoryImpl(get()) }
|
||||
addFactory { GetUpdates(get(), get()) }
|
||||
addFactory { GetUpdates(get()) }
|
||||
|
||||
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
|
||||
addSingletonFactory<SourceDataRepository> { SourceDataRepositoryImpl(get()) }
|
||||
addSingletonFactory<StubSourceRepository> { StubSourceRepositoryImpl(get()) }
|
||||
addFactory { GetEnabledSources(get(), get()) }
|
||||
addFactory { GetLanguagesWithSources(get(), get()) }
|
||||
addFactory { GetRemoteManga(get()) }
|
||||
@ -143,5 +171,10 @@ class DomainModule : InjektModule {
|
||||
addFactory { ToggleLanguage(get()) }
|
||||
addFactory { ToggleSource(get()) }
|
||||
addFactory { ToggleSourcePin(get()) }
|
||||
addFactory { TrustExtension(get()) }
|
||||
|
||||
addFactory { CreateExtensionRepo(get()) }
|
||||
addFactory { DeleteExtensionRepo(get()) }
|
||||
addFactory { GetExtensionRepos(get()) }
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
package eu.kanade.domain.backup.service
|
||||
|
||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
||||
import eu.kanade.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)
|
||||
}
|
@ -1,30 +1,31 @@
|
||||
package eu.kanade.domain.base
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
||||
import eu.kanade.tachiyomi.core.preference.getEnum
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import tachiyomi.core.preference.Preference
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.i18n.MR
|
||||
|
||||
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() = preferenceStore.getEnum(
|
||||
"extension_installer",
|
||||
if (DeviceUtil.isMiui) PreferenceValues.ExtensionInstaller.LEGACY else PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER,
|
||||
fun downloadedOnly() = preferenceStore.getBoolean(
|
||||
Preference.appStateKey("pref_downloaded_only"),
|
||||
false,
|
||||
)
|
||||
|
||||
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
||||
fun incognitoMode() = preferenceStore.getBoolean(Preference.appStateKey("incognito_mode"), false)
|
||||
|
||||
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
||||
|
||||
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
|
||||
|
||||
enum class ExtensionInstaller(val titleRes: StringResource, val requiresSystemPermission: Boolean) {
|
||||
LEGACY(MR.strings.ext_installer_legacy, true),
|
||||
PACKAGEINSTALLER(MR.strings.ext_installer_packageinstaller, true),
|
||||
SHIZUKU(MR.strings.ext_installer_shizuku, false),
|
||||
PRIVATE(MR.strings.ext_installer_private, false),
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
package eu.kanade.domain.base
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.domain.base.BasePreferences.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.entries.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)
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import eu.kanade.domain.category.model.Category
|
||||
import eu.kanade.domain.category.model.anyWithName
|
||||
import eu.kanade.domain.category.repository.CategoryRepository
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import logcat.LogPriority
|
||||
|
||||
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()
|
||||
if (categories.anyWithName(name)) {
|
||||
return@withNonCancellableContext Result.NameAlreadyExistsError
|
||||
}
|
||||
|
||||
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()
|
||||
object NameAlreadyExistsError : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import eu.kanade.domain.category.model.Category
|
||||
import eu.kanade.domain.category.model.CategoryUpdate
|
||||
import eu.kanade.domain.category.model.anyWithName
|
||||
import eu.kanade.domain.category.repository.CategoryRepository
|
||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import logcat.LogPriority
|
||||
|
||||
class RenameCategory(
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(categoryId: Long, name: String) = withNonCancellableContext {
|
||||
val categories = categoryRepository.getAll()
|
||||
if (categories.anyWithName(name)) {
|
||||
return@withNonCancellableContext Result.NameAlreadyExistsError
|
||||
}
|
||||
|
||||
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()
|
||||
object NameAlreadyExistsError : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import eu.kanade.domain.category.model.Category
|
||||
import eu.kanade.domain.category.model.CategoryUpdate
|
||||
import eu.kanade.domain.category.repository.CategoryRepository
|
||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import logcat.LogPriority
|
||||
|
||||
class ReorderCategory(
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(categoryId: Long, newPosition: Int) = withNonCancellableContext {
|
||||
val categories = categoryRepository.getAll().filterNot(Category::isSystemCategory)
|
||||
|
||||
val currentIndex = categories.indexOfFirst { it.id == categoryId }
|
||||
if (currentIndex == newPosition) {
|
||||
return@withNonCancellableContext Result.Unchanged
|
||||
}
|
||||
|
||||
val reorderedCategories = categories.toMutableList()
|
||||
val reorderedCategory = reorderedCategories.removeAt(currentIndex)
|
||||
reorderedCategories.add(newPosition, reorderedCategory)
|
||||
|
||||
val updates = reorderedCategories.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)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun await(category: Category, newPosition: Long): Result =
|
||||
await(category.id, newPosition.toInt())
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
object Unchanged : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import eu.kanade.domain.category.repository.CategoryRepository
|
||||
import eu.kanade.domain.library.model.plus
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import eu.kanade.domain.category.model.Category
|
||||
import eu.kanade.domain.category.model.CategoryUpdate
|
||||
import eu.kanade.domain.category.repository.CategoryRepository
|
||||
import eu.kanade.domain.library.model.LibraryDisplayMode
|
||||
import eu.kanade.domain.library.model.plus
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import eu.kanade.domain.category.model.Category
|
||||
import eu.kanade.domain.category.model.CategoryUpdate
|
||||
import eu.kanade.domain.category.repository.CategoryRepository
|
||||
import eu.kanade.domain.library.model.LibrarySort
|
||||
import eu.kanade.domain.library.model.plus
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
|
||||
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.chapter.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
|
||||
class GetAvailableScanlators(
|
||||
private val repository: ChapterRepository,
|
||||
) {
|
||||
|
||||
private fun List<String>.cleanupAvailableScanlators(): Set<String> {
|
||||
return mapNotNull { it.ifBlank { null } }.toSet()
|
||||
}
|
||||
|
||||
suspend fun await(mangaId: Long): Set<String> {
|
||||
return repository.getScanlatorsByMangaId(mangaId)
|
||||
.cleanupAvailableScanlators()
|
||||
}
|
||||
|
||||
fun subscribe(mangaId: Long): Flow<Set<String>> {
|
||||
return repository.getScanlatorsByMangaIdAsFlow(mangaId)
|
||||
.map { it.cleanupAvailableScanlators() }
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import logcat.LogPriority
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.chapter.model.ChapterUpdate
|
||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||
import eu.kanade.domain.download.service.DownloadPreferences
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.manga.repository.MangaRepository
|
||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
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.download.service.DownloadPreferences
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
|
||||
class SetReadStatus(
|
||||
private val downloadPreferences: DownloadPreferences,
|
||||
@ -27,7 +27,12 @@ class SetReadStatus(
|
||||
}
|
||||
|
||||
suspend fun await(read: Boolean, vararg chapters: Chapter): Result = withNonCancellableContext {
|
||||
val chaptersToUpdate = chapters.filterNot { it.read == read }
|
||||
val chaptersToUpdate = chapters.filter {
|
||||
when (read) {
|
||||
true -> !it.read
|
||||
false -> it.read || it.lastPageRead > 0
|
||||
}
|
||||
}
|
||||
if (chaptersToUpdate.isEmpty()) {
|
||||
return@withNonCancellableContext Result.NoChapters
|
||||
}
|
||||
@ -67,9 +72,9 @@ class SetReadStatus(
|
||||
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()
|
||||
sealed interface Result {
|
||||
data object Success : Result
|
||||
data object NoChapters : Result
|
||||
data class InternalError(val error: Throwable) : Result
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,39 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import eu.kanade.data.chapter.CleanupChapterName
|
||||
import eu.kanade.data.chapter.NoChaptersException
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.chapter.model.toChapterUpdate
|
||||
import eu.kanade.domain.chapter.model.toDbChapter
|
||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
||||
import eu.kanade.domain.chapter.model.copyFromSChapter
|
||||
import eu.kanade.domain.chapter.model.toSChapter
|
||||
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
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 uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import tachiyomi.data.chapter.ChapterSanitizer
|
||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||
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.chapter.service.ChapterRecognition
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.source.local.isLocal
|
||||
import java.lang.Long.max
|
||||
import java.util.Date
|
||||
import java.time.ZonedDateTime
|
||||
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(),
|
||||
private val downloadManager: DownloadManager,
|
||||
private val downloadProvider: DownloadProvider,
|
||||
private val chapterRepository: ChapterRepository,
|
||||
private val shouldUpdateDbChapter: ShouldUpdateDbChapter,
|
||||
private val updateManga: UpdateManga,
|
||||
private val updateChapter: UpdateChapter,
|
||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||
private val getExcludedScanlators: GetExcludedScanlators,
|
||||
) {
|
||||
|
||||
/**
|
||||
@ -43,50 +48,46 @@ class SyncChaptersWithSource(
|
||||
rawSourceChapters: List<SChapter>,
|
||||
manga: Manga,
|
||||
source: Source,
|
||||
manualFetch: Boolean = false,
|
||||
fetchWindow: Pair<Long, Long> = Pair(0, 0),
|
||||
): List<Chapter> {
|
||||
if (rawSourceChapters.isEmpty() && !source.isLocal()) {
|
||||
throw NoChaptersException()
|
||||
}
|
||||
|
||||
val now = ZonedDateTime.now()
|
||||
val nowMillis = now.toInstant().toEpochMilli()
|
||||
|
||||
val sourceChapters = rawSourceChapters
|
||||
.distinctBy { it.url }
|
||||
.mapIndexed { i, sChapter ->
|
||||
Chapter.create()
|
||||
.copyFromSChapter(sChapter)
|
||||
.copy(name = CleanupChapterName.await(sChapter.name, manga.title))
|
||||
.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)
|
||||
val dbChapters = getChaptersByMangaId.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 ->
|
||||
val newChapters = mutableListOf<Chapter>()
|
||||
val updatedChapters = mutableListOf<Chapter>()
|
||||
val removedChapters = 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)
|
||||
source.prepareNewChapter(sChapter, manga.toSManga())
|
||||
chapter = chapter.copyFromSChapter(sChapter)
|
||||
}
|
||||
|
||||
@ -98,20 +99,22 @@ class SyncChaptersWithSource(
|
||||
|
||||
if (dbChapter == null) {
|
||||
val toAddChapter = if (chapter.dateUpload == 0L) {
|
||||
val altDateUpload = if (maxSeenUploadDate == 0L) rightNow else maxSeenUploadDate
|
||||
val altDateUpload = if (maxSeenUploadDate == 0L) nowMillis else maxSeenUploadDate
|
||||
chapter.copy(dateUpload = altDateUpload)
|
||||
} else {
|
||||
maxSeenUploadDate = max(maxSeenUploadDate, sourceChapter.dateUpload)
|
||||
chapter
|
||||
}
|
||||
toAdd.add(toAddChapter)
|
||||
newChapters.add(toAddChapter)
|
||||
} else {
|
||||
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
||||
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
|
||||
downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, manga.title, manga.source)
|
||||
downloadManager.isChapterDownloaded(
|
||||
dbChapter.name, dbChapter.scanlator, manga.title, manga.source,
|
||||
)
|
||||
|
||||
if (shouldRenameChapter) {
|
||||
downloadManager.renameChapter(source, manga, dbChapter.toDbChapter(), chapter.toDbChapter())
|
||||
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
||||
}
|
||||
var toChangeChapter = dbChapter.copy(
|
||||
name = chapter.name,
|
||||
@ -122,36 +125,43 @@ class SyncChaptersWithSource(
|
||||
if (chapter.dateUpload != 0L) {
|
||||
toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload)
|
||||
}
|
||||
toChange.add(toChangeChapter)
|
||||
updatedChapters.add(toChangeChapter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
||||
// Return if there's nothing to add, delete, or update to avoid unnecessary db transactions.
|
||||
if (newChapters.isEmpty() && removedChapters.isEmpty() && updatedChapters.isEmpty()) {
|
||||
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
|
||||
updateManga.awaitUpdateFetchInterval(
|
||||
manga,
|
||||
now,
|
||||
fetchWindow,
|
||||
)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val reAdded = mutableListOf<Chapter>()
|
||||
|
||||
val deletedChapterNumbers = TreeSet<Float>()
|
||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
||||
val deletedBookmarkedChapterNumbers = TreeSet<Float>()
|
||||
val deletedChapterNumbers = TreeSet<Double>()
|
||||
val deletedReadChapterNumbers = TreeSet<Double>()
|
||||
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
|
||||
|
||||
toDelete.forEach { chapter ->
|
||||
removedChapters.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 }
|
||||
val deletedChapterNumberDateFetchMap = removedChapters.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--)
|
||||
var itemCount = newChapters.size
|
||||
var updatedToAdd = newChapters.map { toAddItem ->
|
||||
var chapter = toAddItem.copy(dateFetch = nowMillis + itemCount--)
|
||||
|
||||
if (chapter.isRecognizedNumber.not() || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
||||
|
||||
@ -170,8 +180,8 @@ class SyncChaptersWithSource(
|
||||
chapter
|
||||
}
|
||||
|
||||
if (toDelete.isNotEmpty()) {
|
||||
val toDeleteIds = toDelete.map { it.id }
|
||||
if (removedChapters.isNotEmpty()) {
|
||||
val toDeleteIds = removedChapters.map { it.id }
|
||||
chapterRepository.removeChaptersWithIds(toDeleteIds)
|
||||
}
|
||||
|
||||
@ -179,10 +189,11 @@ class SyncChaptersWithSource(
|
||||
updatedToAdd = chapterRepository.addAll(updatedToAdd)
|
||||
}
|
||||
|
||||
if (toChange.isNotEmpty()) {
|
||||
val chapterUpdates = toChange.map { it.toChapterUpdate() }
|
||||
if (updatedChapters.isNotEmpty()) {
|
||||
val chapterUpdates = updatedChapters.map { it.toChapterUpdate() }
|
||||
updateChapter.awaitAll(chapterUpdates)
|
||||
}
|
||||
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
|
||||
|
||||
// Set this manga as updated since chapters were changed
|
||||
// Note that last_update actually represents last time the chapter list changed at all
|
||||
@ -190,6 +201,10 @@ class SyncChaptersWithSource(
|
||||
|
||||
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
||||
|
||||
return updatedToAdd.filterNot { it.url in reAddedUrls }
|
||||
val excludedScanlators = getExcludedScanlators.await(manga.id).toHashSet()
|
||||
|
||||
return updatedToAdd.filterNot {
|
||||
it.url in reAddedUrls || it.scanlator in excludedScanlators
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
package eu.kanade.domain.chapter.interactor
|
||||
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.chapter.model.toChapterUpdate
|
||||
import eu.kanade.domain.track.interactor.InsertTrack
|
||||
import eu.kanade.domain.track.model.Track
|
||||
import eu.kanade.domain.track.model.toDbTrack
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import logcat.LogPriority
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -2,64 +2,30 @@ 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
|
||||
|
||||
data class Chapter(
|
||||
val id: Long,
|
||||
val mangaId: Long,
|
||||
val read: Boolean,
|
||||
val bookmark: Boolean,
|
||||
val lastPageRead: Long,
|
||||
val dateFetch: Long,
|
||||
val sourceOrder: Long,
|
||||
val url: String,
|
||||
val name: String,
|
||||
val dateUpload: Long,
|
||||
val chapterNumber: Float,
|
||||
val scanlator: String?,
|
||||
) {
|
||||
val isRecognizedNumber: Boolean
|
||||
get() = chapterNumber >= 0f
|
||||
|
||||
fun toSChapter(): SChapter {
|
||||
// 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.chapter_number = chapterNumber.toFloat()
|
||||
it.scanlator = scanlator
|
||||
}
|
||||
}
|
||||
|
||||
fun copyFromSChapter(sChapter: SChapter): Chapter {
|
||||
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 },
|
||||
chapterNumber = sChapter.chapter_number.toDouble(),
|
||||
scanlator = sChapter.scanlator?.ifBlank { null }?.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create() = Chapter(
|
||||
id = -1,
|
||||
mangaId = -1,
|
||||
read = false,
|
||||
bookmark = false,
|
||||
lastPageRead = 0,
|
||||
dateFetch = 0,
|
||||
sourceOrder = 0,
|
||||
url = "",
|
||||
name = "",
|
||||
dateUpload = -1,
|
||||
chapterNumber = -1f,
|
||||
scanlator = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove when all deps are migrated
|
||||
fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
||||
it.id = id
|
||||
it.manga_id = mangaId
|
||||
@ -71,6 +37,6 @@ fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
||||
it.last_page_read = lastPageRead.toInt()
|
||||
it.date_fetch = dateFetch
|
||||
it.date_upload = dateUpload
|
||||
it.chapter_number = chapterNumber
|
||||
it.chapter_number = chapterNumber.toFloat()
|
||||
it.source_order = sourceOrder.toInt()
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
package eu.kanade.domain.chapter.model
|
||||
|
||||
import eu.kanade.domain.manga.model.downloadedFilter
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.service.getChapterSort
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.applyFilter
|
||||
import tachiyomi.source.local.isLocal
|
||||
|
||||
/**
|
||||
* 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 -> applyFilter(unreadFilter) { !chapter.read } }
|
||||
.filter { chapter -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
|
||||
.filter { chapter ->
|
||||
applyFilter(downloadedFilter) {
|
||||
val downloaded = downloadManager.isChapterDownloaded(
|
||||
chapter.name,
|
||||
chapter.scanlator,
|
||||
manga.title,
|
||||
manga.source,
|
||||
)
|
||||
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<ChapterList.Item>.applyFilters(manga: Manga): Sequence<ChapterList.Item> {
|
||||
val isLocalManga = manga.isLocal()
|
||||
val unreadFilter = manga.unreadFilter
|
||||
val downloadedFilter = manga.downloadedFilter
|
||||
val bookmarkedFilter = manga.bookmarkedFilter
|
||||
return asSequence()
|
||||
.filter { (chapter) -> applyFilter(unreadFilter) { !chapter.read } }
|
||||
.filter { (chapter) -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
|
||||
.filter { applyFilter(downloadedFilter) { it.isDownloaded || isLocalManga } }
|
||||
.sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) }
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
package eu.kanade.domain.download.interactor
|
||||
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.chapter.model.toDbChapter
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
|
||||
class DeleteDownload(
|
||||
private val sourceManager: SourceManager,
|
||||
@ -14,7 +13,7 @@ class DeleteDownload(
|
||||
|
||||
suspend fun awaitAll(manga: Manga, vararg chapters: Chapter) = withNonCancellableContext {
|
||||
sourceManager.get(manga.source)?.let { source ->
|
||||
downloadManager.deleteChapters(chapters.map { it.toDbChapter() }, manga, source)
|
||||
downloadManager.deleteChapters(chapters.toList(), manga, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package eu.kanade.domain.extension.interactor
|
||||
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import tachiyomi.core.preference.plusAssign
|
||||
|
||||
class CreateExtensionRepo(private val preferences: SourcePreferences) {
|
||||
|
||||
fun await(name: String): Result {
|
||||
// Do not allow invalid formats
|
||||
if (!name.matches(repoRegex)) {
|
||||
return Result.InvalidUrl
|
||||
}
|
||||
|
||||
preferences.extensionRepos() += name.removeSuffix("/index.min.json")
|
||||
|
||||
return Result.Success
|
||||
}
|
||||
|
||||
sealed interface Result {
|
||||
data object InvalidUrl : Result
|
||||
data object Success : Result
|
||||
}
|
||||
}
|
||||
|
||||
private val repoRegex = """^https://.*/index\.min\.json$""".toRegex()
|
@ -0,0 +1,11 @@
|
||||
package eu.kanade.domain.extension.interactor
|
||||
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import tachiyomi.core.preference.minusAssign
|
||||
|
||||
class DeleteExtensionRepo(private val preferences: SourcePreferences) {
|
||||
|
||||
fun await(repo: String) {
|
||||
preferences.extensionRepos() -= repo
|
||||
}
|
||||
}
|
@ -25,10 +25,7 @@ class GetExtensionLanguages(
|
||||
}
|
||||
.distinct()
|
||||
.sortedWith(
|
||||
compareBy(
|
||||
{ it !in enabledLanguage },
|
||||
{ LocaleHelper.getDisplayName(it) },
|
||||
),
|
||||
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
package eu.kanade.domain.extension.interactor
|
||||
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class GetExtensionRepos(private val preferences: SourcePreferences) {
|
||||
|
||||
fun subscribe(): Flow<Set<String>> {
|
||||
return preferences.extensionRepos().changes()
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ 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 eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionSourceItem
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@ -30,3 +29,9 @@ class GetExtensionSources(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ExtensionSourceItem(
|
||||
val source: Source,
|
||||
val enabled: Boolean,
|
||||
val labelAsName: Boolean,
|
||||
)
|
||||
|
@ -0,0 +1,31 @@
|
||||
package eu.kanade.domain.extension.interactor
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import tachiyomi.core.preference.getAndSet
|
||||
|
||||
class TrustExtension(
|
||||
private val preferences: SourcePreferences,
|
||||
) {
|
||||
|
||||
fun isTrusted(pkgInfo: PackageInfo, signatureHash: String): Boolean {
|
||||
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:$signatureHash"
|
||||
return key in preferences.trustedExtensions().get()
|
||||
}
|
||||
|
||||
fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
|
||||
preferences.trustedExtensions().getAndSet { exts ->
|
||||
// Remove previously trusted versions
|
||||
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
|
||||
|
||||
removed.also {
|
||||
it += "$pkgName:$versionCode:$signatureHash"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun revokeAll() {
|
||||
preferences.trustedExtensions().delete()
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package eu.kanade.domain.history.interactor
|
||||
|
||||
import eu.kanade.domain.history.repository.HistoryRepository
|
||||
|
||||
class DeleteAllHistory(
|
||||
private val repository: HistoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): Boolean {
|
||||
return repository.deleteAllHistory()
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package eu.kanade.domain.history.interactor
|
||||
|
||||
import eu.kanade.domain.history.model.HistoryWithRelations
|
||||
import eu.kanade.domain.history.repository.HistoryRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class GetHistory(
|
||||
private val repository: HistoryRepository,
|
||||
) {
|
||||
fun subscribe(query: String): Flow<List<HistoryWithRelations>> {
|
||||
return repository.getHistory(query)
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package eu.kanade.domain.history.interactor
|
||||
|
||||
import eu.kanade.domain.chapter.interactor.GetChapter
|
||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.history.repository.HistoryRepository
|
||||
import eu.kanade.domain.manga.interactor.GetManga
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
||||
|
||||
class GetNextChapter(
|
||||
private val getChapter: GetChapter,
|
||||
private val getChapterByMangaId: GetChapterByMangaId,
|
||||
private val getManga: GetManga,
|
||||
private val historyRepository: HistoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): Chapter? {
|
||||
val history = historyRepository.getLastHistory() ?: return null
|
||||
return await(history.mangaId, history.chapterId)
|
||||
}
|
||||
|
||||
suspend fun await(mangaId: Long, chapterId: Long): Chapter? {
|
||||
val chapter = getChapter.await(chapterId)!!
|
||||
val manga = getManga.await(mangaId)!!
|
||||
|
||||
if (!chapter.read) return chapter
|
||||
|
||||
val chapters = getChapterByMangaId.await(mangaId)
|
||||
.sortedWith(getChapterSort(manga, sortDescending = false))
|
||||
|
||||
val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id }
|
||||
return when (manga.sorting) {
|
||||
Manga.CHAPTER_SORTING_SOURCE -> chapters.getOrNull(currChapterIndex + 1)
|
||||
Manga.CHAPTER_SORTING_NUMBER -> {
|
||||
val chapterNumber = chapter.chapterNumber
|
||||
|
||||
((currChapterIndex + 1) until chapters.size)
|
||||
.map { chapters[it] }
|
||||
.firstOrNull {
|
||||
it.chapterNumber > chapterNumber && it.chapterNumber <= chapterNumber + 1
|
||||
}
|
||||
}
|
||||
Manga.CHAPTER_SORTING_UPLOAD_DATE -> {
|
||||
chapters.drop(currChapterIndex + 1)
|
||||
.firstOrNull { it.dateUpload >= chapter.dateUpload }
|
||||
}
|
||||
else -> throw NotImplementedError("Invalid chapter sorting method: ${manga.sorting}")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package eu.kanade.domain.history.interactor
|
||||
|
||||
import eu.kanade.domain.history.model.HistoryWithRelations
|
||||
import eu.kanade.domain.history.repository.HistoryRepository
|
||||
|
||||
class RemoveHistoryById(
|
||||
private val repository: HistoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(history: HistoryWithRelations) {
|
||||
repository.resetHistory(history.id)
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package eu.kanade.domain.history.interactor
|
||||
|
||||
import eu.kanade.domain.history.repository.HistoryRepository
|
||||
|
||||
class RemoveHistoryByMangaId(
|
||||
private val repository: HistoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long) {
|
||||
repository.resetHistoryByMangaId(mangaId)
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package eu.kanade.domain.history.model
|
||||
|
||||
import java.util.Date
|
||||
|
||||
data class History(
|
||||
val id: Long,
|
||||
val chapterId: Long,
|
||||
val readAt: Date?,
|
||||
val readDuration: Long,
|
||||
)
|
@ -1,111 +0,0 @@
|
||||
package eu.kanade.domain.library.service
|
||||
|
||||
import eu.kanade.domain.library.model.LibraryDisplayMode
|
||||
import eu.kanade.domain.library.model.LibrarySort
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
||||
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
|
||||
|
||||
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", 24)
|
||||
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)
|
||||
|
||||
// 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 unreadBadge() = preferenceStore.getBoolean("display_unread_badge", true)
|
||||
|
||||
fun languageBadge() = preferenceStore.getBoolean("display_language_badge", false)
|
||||
|
||||
fun showUpdatesNavBadge() = preferenceStore.getBoolean("library_update_show_tab_badge", false)
|
||||
fun unreadUpdatesCount() = preferenceStore.getInt("library_unread_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
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.manga.repository.MangaRepository
|
||||
|
||||
class GetDuplicateLibraryManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(title: String, sourceId: Long): Manga? {
|
||||
return mangaRepository.getDuplicateLibraryManga(title.lowercase(), sourceId)
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import tachiyomi.data.DatabaseHandler
|
||||
|
||||
class GetExcludedScanlators(
|
||||
private val handler: DatabaseHandler,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long): Set<String> {
|
||||
return handler.awaitList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||
}
|
||||
.toSet()
|
||||
}
|
||||
|
||||
fun subscribe(mangaId: Long): Flow<Set<String>> {
|
||||
return handler.subscribeToList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||
}
|
||||
.map { it.toSet() }
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.manga.repository.MangaRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
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,22 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.data.DatabaseHandler
|
||||
|
||||
class SetExcludedScanlators(
|
||||
private val handler: DatabaseHandler,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long, excludedScanlators: Set<String>) {
|
||||
handler.await(inTransaction = true) {
|
||||
val currentExcluded = handler.awaitList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||
}.toSet()
|
||||
val toAdd = excludedScanlators.minus(currentExcluded)
|
||||
for (scanlator in toAdd) {
|
||||
excluded_scanlatorsQueries.insert(mangaId, scanlator)
|
||||
}
|
||||
val toRemove = currentExcluded.minus(excludedScanlators)
|
||||
excluded_scanlatorsQueries.remove(mangaId, toRemove)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +1,30 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import eu.kanade.domain.manga.model.MangaUpdate
|
||||
import eu.kanade.domain.manga.repository.MangaRepository
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||
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) {
|
||||
suspend fun awaitSetReadingMode(id: Long, flag: Long) {
|
||||
val manga = mangaRepository.getMangaById(id)
|
||||
mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = id,
|
||||
viewerFlags = flag.setFlag(flag, ReadingModeType.MASK.toLong()),
|
||||
viewerFlags = manga.viewerFlags.setFlag(flag, ReadingMode.MASK.toLong()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitSetOrientationType(id: Long, flag: Long) {
|
||||
suspend fun awaitSetOrientation(id: Long, flag: Long) {
|
||||
val manga = mangaRepository.getMangaById(id)
|
||||
mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = id,
|
||||
viewerFlags = flag.setFlag(flag, OrientationType.MASK.toLong()),
|
||||
viewerFlags = manga.viewerFlags.setFlag(flag, ReaderOrientation.MASK.toLong()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -1,19 +1,21 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.manga.model.MangaUpdate
|
||||
import eu.kanade.domain.manga.model.hasCustomCover
|
||||
import eu.kanade.domain.manga.model.isLocal
|
||||
import eu.kanade.domain.manga.model.toDbManga
|
||||
import eu.kanade.domain.manga.repository.MangaRepository
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Date
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
class UpdateManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
private val fetchInterval: FetchInterval,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
|
||||
@ -44,14 +46,14 @@ class UpdateManga(
|
||||
// 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.isLocal() -> Instant.now().toEpochMilli()
|
||||
localManga.hasCustomCover(coverCache) -> {
|
||||
coverCache.deleteFromCache(localManga.toDbManga(), false)
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
coverCache.deleteFromCache(localManga.toDbManga(), false)
|
||||
Date().time
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
Instant.now().toEpochMilli()
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,17 +76,27 @@ class UpdateManga(
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFetchInterval(
|
||||
manga: Manga,
|
||||
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||
window: Pair<Long, Long> = fetchInterval.getWindow(dateTime),
|
||||
): Boolean {
|
||||
return mangaRepository.update(
|
||||
fetchInterval.toMangaUpdate(manga, dateTime, window),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
||||
return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Date().time))
|
||||
return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Instant.now().toEpochMilli()))
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateCoverLastModified(mangaId: Long): Boolean {
|
||||
return mangaRepository.update(MangaUpdate(id = mangaId, coverLastModified = Date().time))
|
||||
return mangaRepository.update(MangaUpdate(id = mangaId, coverLastModified = Instant.now().toEpochMilli()))
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFavorite(mangaId: Long, favorite: Boolean): Boolean {
|
||||
val dateAdded = when (favorite) {
|
||||
true -> Date().time
|
||||
true -> Instant.now().toEpochMilli()
|
||||
false -> 0
|
||||
}
|
||||
return mangaRepository.update(
|
||||
|
@ -1,60 +0,0 @@
|
||||
package eu.kanade.domain.manga.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import nl.adaptivity.xmlutil.serialization.XmlSerialName
|
||||
import nl.adaptivity.xmlutil.serialization.XmlValue
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("ComicInfo", "", "")
|
||||
data class ComicInfo(
|
||||
val series: ComicInfoSeries?,
|
||||
val summary: ComicInfoSummary?,
|
||||
val writer: ComicInfoWriter?,
|
||||
val penciller: ComicInfoPenciller?,
|
||||
val inker: ComicInfoInker?,
|
||||
val colorist: ComicInfoColorist?,
|
||||
val letterer: ComicInfoLetterer?,
|
||||
val coverArtist: ComicInfoCoverArtist?,
|
||||
val genre: ComicInfoGenre?,
|
||||
val tags: ComicInfoTags?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Series", "", "")
|
||||
data class ComicInfoSeries(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Summary", "", "")
|
||||
data class ComicInfoSummary(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Writer", "", "")
|
||||
data class ComicInfoWriter(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Penciller", "", "")
|
||||
data class ComicInfoPenciller(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Inker", "", "")
|
||||
data class ComicInfoInker(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Colorist", "", "")
|
||||
data class ComicInfoColorist(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Letterer", "", "")
|
||||
data class ComicInfoLetterer(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("CoverArtist", "", "")
|
||||
data class ComicInfoCoverArtist(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Genre", "", "")
|
||||
data class ComicInfoGenre(@XmlValue(true) val value: String = "")
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Tags", "", "")
|
||||
data class ComicInfoTags(@XmlValue(true) val value: String = "")
|
@ -1,93 +1,44 @@
|
||||
package eu.kanade.domain.manga.model
|
||||
|
||||
import eu.kanade.data.listOfStringsAdapter
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.Serializable
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga as DbManga
|
||||
|
||||
data class Manga(
|
||||
val id: Long,
|
||||
val source: Long,
|
||||
val favorite: Boolean,
|
||||
val lastUpdate: Long,
|
||||
val dateAdded: Long,
|
||||
val viewerFlags: Long,
|
||||
val chapterFlags: Long,
|
||||
val coverLastModified: Long,
|
||||
val url: String,
|
||||
val title: String,
|
||||
val artist: String?,
|
||||
val author: String?,
|
||||
val description: String?,
|
||||
val genre: List<String>?,
|
||||
val status: Long,
|
||||
val thumbnailUrl: String?,
|
||||
val updateStrategy: UpdateStrategy,
|
||||
val initialized: Boolean,
|
||||
) : Serializable {
|
||||
// TODO: move these into the domain model
|
||||
val Manga.readingMode: Long
|
||||
get() = viewerFlags and ReadingMode.MASK.toLong()
|
||||
|
||||
val sorting: Long
|
||||
get() = chapterFlags and CHAPTER_SORTING_MASK
|
||||
val Manga.readerOrientation: Long
|
||||
get() = viewerFlags and ReaderOrientation.MASK.toLong()
|
||||
|
||||
val displayMode: Long
|
||||
get() = chapterFlags and CHAPTER_DISPLAY_MASK
|
||||
|
||||
val unreadFilterRaw: Long
|
||||
get() = chapterFlags and CHAPTER_UNREAD_MASK
|
||||
|
||||
val downloadedFilterRaw: Long
|
||||
get() = chapterFlags and CHAPTER_DOWNLOADED_MASK
|
||||
|
||||
val bookmarkedFilterRaw: Long
|
||||
get() = chapterFlags and CHAPTER_BOOKMARKED_MASK
|
||||
|
||||
val unreadFilter: TriStateFilter
|
||||
get() = when (unreadFilterRaw) {
|
||||
CHAPTER_SHOW_UNREAD -> TriStateFilter.ENABLED_IS
|
||||
CHAPTER_SHOW_READ -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
}
|
||||
|
||||
val downloadedFilter: TriStateFilter
|
||||
val Manga.downloadedFilter: TriState
|
||||
get() {
|
||||
if (forceDownloaded()) return TriStateFilter.ENABLED_IS
|
||||
if (forceDownloaded()) return TriState.ENABLED_IS
|
||||
return when (downloadedFilterRaw) {
|
||||
CHAPTER_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS
|
||||
CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
||||
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
||||
else -> TriState.DISABLED
|
||||
}
|
||||
}
|
||||
|
||||
val bookmarkedFilter: TriStateFilter
|
||||
get() = when (bookmarkedFilterRaw) {
|
||||
CHAPTER_SHOW_BOOKMARKED -> TriStateFilter.ENABLED_IS
|
||||
CHAPTER_SHOW_NOT_BOOKMARKED -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
fun Manga.chaptersFiltered(): Boolean {
|
||||
return unreadFilter != TriState.DISABLED ||
|
||||
downloadedFilter != TriState.DISABLED ||
|
||||
bookmarkedFilter != TriState.DISABLED
|
||||
}
|
||||
|
||||
fun chaptersFiltered(): Boolean {
|
||||
return unreadFilter != TriStateFilter.DISABLED ||
|
||||
downloadedFilter != TriStateFilter.DISABLED ||
|
||||
bookmarkedFilter != TriStateFilter.DISABLED
|
||||
}
|
||||
|
||||
fun forceDownloaded(): Boolean {
|
||||
fun Manga.forceDownloaded(): Boolean {
|
||||
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
||||
}
|
||||
|
||||
fun sortDescending(): Boolean {
|
||||
return chapterFlags and CHAPTER_SORT_DIR_MASK == CHAPTER_SORT_DESC
|
||||
}
|
||||
|
||||
fun toSManga(): SManga = SManga.create().also {
|
||||
fun Manga.toSManga(): SManga = SManga.create().also {
|
||||
it.url = url
|
||||
it.title = title
|
||||
it.artist = artist
|
||||
@ -99,7 +50,7 @@ data class Manga(
|
||||
it.initialized = initialized
|
||||
}
|
||||
|
||||
fun copyFrom(other: SManga): Manga {
|
||||
fun Manga.copyFrom(other: SManga): Manga {
|
||||
val author = other.author ?: author
|
||||
val artist = other.artist ?: artist
|
||||
val description = other.description ?: description
|
||||
@ -121,118 +72,7 @@ data class Manga(
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Generic filter that does not filter anything
|
||||
const val SHOW_ALL = 0x00000000L
|
||||
|
||||
const val CHAPTER_SORT_DESC = 0x00000000L
|
||||
const val CHAPTER_SORT_ASC = 0x00000001L
|
||||
const val CHAPTER_SORT_DIR_MASK = 0x00000001L
|
||||
|
||||
const val CHAPTER_SHOW_UNREAD = 0x00000002L
|
||||
const val CHAPTER_SHOW_READ = 0x00000004L
|
||||
const val CHAPTER_UNREAD_MASK = 0x00000006L
|
||||
|
||||
const val CHAPTER_SHOW_DOWNLOADED = 0x00000008L
|
||||
const val CHAPTER_SHOW_NOT_DOWNLOADED = 0x00000010L
|
||||
const val CHAPTER_DOWNLOADED_MASK = 0x00000018L
|
||||
|
||||
const val CHAPTER_SHOW_BOOKMARKED = 0x00000020L
|
||||
const val CHAPTER_SHOW_NOT_BOOKMARKED = 0x00000040L
|
||||
const val CHAPTER_BOOKMARKED_MASK = 0x00000060L
|
||||
|
||||
const val CHAPTER_SORTING_SOURCE = 0x00000000L
|
||||
const val CHAPTER_SORTING_NUMBER = 0x00000100L
|
||||
const val CHAPTER_SORTING_UPLOAD_DATE = 0x00000200L
|
||||
const val CHAPTER_SORTING_MASK = 0x00000300L
|
||||
|
||||
const val CHAPTER_DISPLAY_NAME = 0x00000000L
|
||||
const val CHAPTER_DISPLAY_NUMBER = 0x00100000L
|
||||
const val CHAPTER_DISPLAY_MASK = 0x00100000L
|
||||
|
||||
fun create() = Manga(
|
||||
id = -1L,
|
||||
url = "",
|
||||
title = "",
|
||||
source = -1L,
|
||||
favorite = false,
|
||||
lastUpdate = 0L,
|
||||
dateAdded = 0L,
|
||||
viewerFlags = 0L,
|
||||
chapterFlags = 0L,
|
||||
coverLastModified = 0L,
|
||||
artist = null,
|
||||
author = null,
|
||||
description = null,
|
||||
genre = null,
|
||||
status = 0L,
|
||||
thumbnailUrl = null,
|
||||
updateStrategy = UpdateStrategy.ALWAYS_UPDATE,
|
||||
initialized = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum class TriStateFilter {
|
||||
DISABLED, // Disable filter
|
||||
ENABLED_IS, // Enabled with "is" filter
|
||||
ENABLED_NOT, // Enabled with "not" filter
|
||||
}
|
||||
|
||||
fun TriStateFilter.toTriStateGroupState(): ExtendedNavigationView.Item.TriStateGroup.State {
|
||||
return when (this) {
|
||||
TriStateFilter.DISABLED -> ExtendedNavigationView.Item.TriStateGroup.State.IGNORE
|
||||
TriStateFilter.ENABLED_IS -> ExtendedNavigationView.Item.TriStateGroup.State.INCLUDE
|
||||
TriStateFilter.ENABLED_NOT -> ExtendedNavigationView.Item.TriStateGroup.State.EXCLUDE
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove when all deps are migrated
|
||||
fun Manga.toDbManga(): DbManga = MangaImpl().also {
|
||||
it.id = id
|
||||
it.source = source
|
||||
it.favorite = favorite
|
||||
it.last_update = lastUpdate
|
||||
it.date_added = dateAdded
|
||||
it.viewer_flags = viewerFlags.toInt()
|
||||
it.chapter_flags = chapterFlags.toInt()
|
||||
it.cover_last_modified = coverLastModified
|
||||
it.url = url
|
||||
it.title = title
|
||||
it.artist = artist
|
||||
it.author = author
|
||||
it.description = description
|
||||
it.genre = genre?.let(listOfStringsAdapter::encode)
|
||||
it.status = status.toInt()
|
||||
it.thumbnail_url = thumbnailUrl
|
||||
it.update_strategy = updateStrategy
|
||||
it.initialized = initialized
|
||||
}
|
||||
|
||||
fun Manga.toMangaUpdate(): MangaUpdate {
|
||||
return MangaUpdate(
|
||||
id = id,
|
||||
source = source,
|
||||
favorite = favorite,
|
||||
lastUpdate = lastUpdate,
|
||||
dateAdded = dateAdded,
|
||||
viewerFlags = viewerFlags,
|
||||
chapterFlags = chapterFlags,
|
||||
coverLastModified = coverLastModified,
|
||||
url = url,
|
||||
title = title,
|
||||
artist = artist,
|
||||
author = author,
|
||||
description = description,
|
||||
genre = genre,
|
||||
status = status,
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
updateStrategy = updateStrategy,
|
||||
initialized = initialized,
|
||||
)
|
||||
}
|
||||
|
||||
fun SManga.toDomainManga(): Manga {
|
||||
fun SManga.toDomainManga(sourceId: Long): Manga {
|
||||
return Manga.create().copy(
|
||||
url = url,
|
||||
title = title,
|
||||
@ -244,11 +84,40 @@ fun SManga.toDomainManga(): Manga {
|
||||
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()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ComicInfo instance based on the manga and chapter metadata.
|
||||
*/
|
||||
fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String, categories: List<String>?) = ComicInfo(
|
||||
title = ComicInfo.Title(chapter.name),
|
||||
series = ComicInfo.Series(manga.title),
|
||||
number = chapter.chapterNumber.takeIf { it >= 0 }?.let {
|
||||
if ((it.rem(1) == 0.0)) {
|
||||
ComicInfo.Number(it.toInt().toString())
|
||||
} else {
|
||||
ComicInfo.Number(it.toString())
|
||||
}
|
||||
},
|
||||
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),
|
||||
),
|
||||
categories = categories?.let { ComicInfo.CategoriesTachiyomi(it.joinToString()) },
|
||||
inker = null,
|
||||
colorist = null,
|
||||
letterer = null,
|
||||
coverArtist = null,
|
||||
tags = null,
|
||||
)
|
||||
|
@ -1,12 +0,0 @@
|
||||
package eu.kanade.domain.manga.model
|
||||
|
||||
/**
|
||||
* Contains the required data for MangaCoverFetcher
|
||||
*/
|
||||
data class MangaCover(
|
||||
val mangaId: Long,
|
||||
val sourceId: Long,
|
||||
val isMangaFavorite: Boolean,
|
||||
val url: String?,
|
||||
val lastModified: Long,
|
||||
)
|
@ -1,24 +0,0 @@
|
||||
package eu.kanade.domain.manga.model
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
|
||||
data class MangaUpdate(
|
||||
val id: Long,
|
||||
val source: Long? = null,
|
||||
val favorite: Boolean? = null,
|
||||
val lastUpdate: Long? = null,
|
||||
val dateAdded: Long? = null,
|
||||
val viewerFlags: Long? = null,
|
||||
val chapterFlags: Long? = null,
|
||||
val coverLastModified: Long? = null,
|
||||
val url: String? = null,
|
||||
val title: String? = null,
|
||||
val artist: String? = null,
|
||||
val author: String? = null,
|
||||
val description: String? = null,
|
||||
val genre: List<String>? = null,
|
||||
val status: Long? = null,
|
||||
val thumbnailUrl: String? = null,
|
||||
val updateStrategy: UpdateStrategy? = null,
|
||||
val initialized: Boolean? = null,
|
||||
)
|
@ -1,14 +1,14 @@
|
||||
package eu.kanade.domain.source.interactor
|
||||
|
||||
import eu.kanade.domain.source.model.Pin
|
||||
import eu.kanade.domain.source.model.Pins
|
||||
import eu.kanade.domain.source.model.Source
|
||||
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
|
||||
import tachiyomi.domain.source.repository.SourceRepository
|
||||
import tachiyomi.source.local.isLocal
|
||||
|
||||
class GetEnabledSources(
|
||||
private val repository: SourceRepository,
|
||||
@ -23,9 +23,8 @@ class GetEnabledSources(
|
||||
preferences.lastUsedSource().changes(),
|
||||
repository.getSources(),
|
||||
) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources ->
|
||||
val duplicatePins = preferences.duplicatePinnedSources().get()
|
||||
sources
|
||||
.filter { it.lang in enabledLanguages || it.id == LocalSource.ID }
|
||||
.filter { it.lang in enabledLanguages || it.isLocal() }
|
||||
.filterNot { it.id.toString() in disabledSources }
|
||||
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||
.flatMap {
|
||||
@ -35,10 +34,6 @@ class GetEnabledSources(
|
||||
if (source.id == lastUsedSource) {
|
||||
toFlatten.add(source.copy(isUsedLast = true, pin = source.pin - Pin.Actual))
|
||||
}
|
||||
if (duplicatePins && Pin.Pinned in source.pin) {
|
||||
toFlatten[0] = toFlatten[0].copy(pin = source.pin + Pin.Forced)
|
||||
toFlatten.add(source.copy(pin = source.pin - Pin.Actual))
|
||||
}
|
||||
toFlatten
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
package eu.kanade.domain.source.interactor
|
||||
|
||||
import eu.kanade.domain.source.model.Source
|
||||
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
|
||||
import tachiyomi.domain.source.repository.SourceRepository
|
||||
import java.util.SortedMap
|
||||
|
||||
class GetLanguagesWithSources(
|
||||
private val repository: SourceRepository,
|
||||
private val preferences: SourcePreferences,
|
||||
) {
|
||||
|
||||
fun subscribe(): Flow<Map<String, List<Source>>> {
|
||||
fun subscribe(): Flow<SortedMap<String, List<Source>>> {
|
||||
return combine(
|
||||
preferences.enabledLanguages().changes(),
|
||||
preferences.disabledSources().changes(),
|
||||
@ -23,12 +24,10 @@ class GetLanguagesWithSources(
|
||||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
||||
)
|
||||
|
||||
sortedSources.groupBy { it.lang }
|
||||
sortedSources
|
||||
.groupBy { it.lang }
|
||||
.toSortedMap(
|
||||
compareBy(
|
||||
{ it !in enabledLanguage },
|
||||
{ LocaleHelper.getDisplayName(it) },
|
||||
),
|
||||
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
package eu.kanade.domain.source.interactor
|
||||
|
||||
import eu.kanade.domain.source.model.Source
|
||||
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 java.text.Collator
|
||||
import tachiyomi.core.util.lang.compareToWithCollator
|
||||
import tachiyomi.domain.source.model.Source
|
||||
import tachiyomi.domain.source.repository.SourceRepository
|
||||
import tachiyomi.source.local.isLocal
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
|
||||
class GetSourcesWithFavoriteCount(
|
||||
private val repository: SourceRepository,
|
||||
@ -20,7 +20,9 @@ class GetSourcesWithFavoriteCount(
|
||||
preferences.migrationSortingMode().changes(),
|
||||
repository.getSourcesWithFavoriteCount(),
|
||||
) { direction, mode, list ->
|
||||
list.sortedWith(sortFn(direction, mode))
|
||||
list
|
||||
.filterNot { it.first.isLocal() }
|
||||
.sortedWith(sortFn(direction, mode))
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,17 +30,13 @@ class GetSourcesWithFavoriteCount(
|
||||
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))
|
||||
else -> a.first.name.lowercase().compareToWithCollator(b.first.name.lowercase())
|
||||
}
|
||||
}
|
||||
SetMigrateSorting.Mode.TOTAL -> {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user