mirror of
https://github.com/mihonapp/mihon.git
synced 2025-07-27 18:05:53 +02:00
Compare commits
912 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
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 | |||
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 |
.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
chapter
interactor
model
download
interactor
extension
library
service
manga
interactor
model
source
interactor
model
service
track
interactor
model
service
store
ui
presentation
browse
BrowseBadges.ktBrowseSourceScreen.ktExtensionDetailsScreen.ktExtensionFilterScreen.ktExtensionsScreen.ktGlobalSearchScreen.ktMigrateMangaScreen.ktMigrateSearchScreen.ktMigrateSourceScreen.ktSourcesFilterScreen.ktSourcesScreen.kt
components
category
components
AdaptiveSheet.ktAlertDialog.ktAppBar.ktBadges.ktBanners.ktChangeCategoryDialog.ktDateText.ktDeleteLibraryMangaDialog.ktDivider.ktDownloadDropdownMenu.ktDropdownMenu.ktDuplicateMangaDialog.ktEmptyScreen.ktPager.ktPullRefresh.ktRelativeDateHeader.ktSettingsItems.ktTabbedDialog.ktTabbedScreen.ktTabs.kt
crash
history
library
manga
more
LogoHeader.ktMoreScreen.ktNewUpdateScreen.kt
onboarding
settings
Preference.ktPreferenceItem.ktPreferenceScaffold.ktPreferenceScreen.kt
screen
Commons.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.ktExceptionFormatter.ktModifier.ktNavigator.ktPermissions.ktPreview.ktResources.ktScrollable.ktTimeUtils.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.ktEnhancedTracker.ktTrackManager.ktTrackService.ktTracker.ktTrackerManager.kt
anilist
bangumi
kavita
kitsu
komga
mangaupdates
model
myanimelist
shikimori
suwayomi
updater
di
extension
source
ui
base
browse
BrowseTab.kt
extension
ExtensionFilterScreen.ktExtensionFilterScreenModel.ktExtensionsScreenModel.ktExtensionsTab.kt
details
migration
MigrationFlags.kt
manga
search
MigrateDialog.ktMigrateSearchScreen.ktMigrateSearchScreenDialogScreenModel.ktMigrateSearchScreenModel.ktSourceSearchScreen.kt
sources
source
category
deeplink
download
history
home
library
LibraryItem.ktLibraryScreenModel.ktLibrarySettingsScreenModel.ktLibrarySettingsSheet.ktLibraryTab.kt
main
manga
more
reader
PageIndicatorTextView.ktReaderActivity.ktReaderColorFilterView.ktReaderNavigationOverlayView.ktReaderPageSheet.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
security
setting
SettingsScreen.kt
track
stats
updates
webview
util
BackupUtil.ktCrashLogUtil.ktMangaExtensions.ktPkceUtil.kt
chapter
lang
preference
storage
system
ActivityExtensions.ktAnimationExtensions.ktAuthenticatorUtil.ktContextExtensions.ktDisplayExtensions.ktDrawableExtensions.ktGLUtil.ktIntentExtensions.ktLocaleHelper.ktNetworkExtensions.ktNetworkStateTracker.ktNotificationExtensions.ktWorkManagerExtensions.kt
view
widget
test
res
anim
bottom_sheet_slide_in.xmlbottom_sheet_slide_out.xmlenter_from_bottom.xmlenter_from_top.xmlexit_to_bottom.xmlexit_to_top.xml
color
drawable-v26
drawable
anim_caret_down.xmlempty_drawable_32dp.xmlic_arrow_down_white_32dp.xmlic_arrow_up_white_32dp.xmlic_blank_24dp.xmlic_book_24dp.xmlic_bookmark_24dp.xmlic_bookmark_border_24dp.xmlic_brightness_5_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_info_24dp.xmlic_launcher_background.xmlic_launcher_foreground.xmlic_launcher_monochrome.xmlic_mihon.xmlic_mihon_splash.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_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_warning_white_24dp.xmlic_webview_24dp.xmllist_item_selector.xmlmaterial_thumb_drawable.xmlsc_collections_bookmark_48dp.xmlsc_explore_48dp.xmlsc_history_48dp.xmlsc_new_releases_48dp.xmltransparent_tabs_background.xml
layout
common_spinner_item.xmlcommon_tabbed_sheet.xmlcompose_controller.xmldownload_list.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.xml
menu
mipmap-anydpi-v26
mipmap-hdpi
mipmap-mdpi
mipmap-xhdpi
mipmap-xxhdpi
mipmap-xxxhdpi
mipmap
values-sw720dp
values-v28
values
xml
standard
buildSrc
core-metadata
core
build.gradle.kts
src
main
java
eu
kanade
tachiyomi
core
security
network
DohProviders.ktJavaScriptEngine.ktNetworkHelper.ktNetworkPreferences.ktOkHttpExtensions.ktProgressResponseBody.kt
interceptor
util
tachiyomi
core
data
build.gradle.kts
src
main
java
tachiyomi
data
AndroidDatabaseHandler.ktDatabaseAdapter.ktDatabaseHandler.ktQueryPagingSource.kt
category
chapter
history
manga
release
source
SourceDataRepositoryImpl.ktSourceMapper.ktSourcePagingSource.ktSourceRepositoryImpl.ktStubSourceRepositoryImpl.kt
track
updates
sqldelight
domain
build.gradle.kts
gradle.propertiessrc
main
java
tachiyomi
domain
backup
service
category
interactor
chapter
interactor
GetChapter.ktGetChapterByUrlAndMangaId.ktGetChaptersByMangaId.ktSetMangaDefaultChapterFlags.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
gradlewi18n
.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-ml
values-my
values-ne
values-or
values-si
values-ta
values-ti
values-ur-rPK
values-ur
macrobenchmark
presentation-core
build.gradle.kts
src
main
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
settings.gradle.ktssource-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,7 +1,8 @@
|
|||||||
[*.{kt,kts}]
|
[*.{kt,kts}]
|
||||||
indent_size=4
|
max_line_length = 120
|
||||||
insert_final_newline=true
|
indent_size = 4
|
||||||
ij_kotlin_allow_trailing_comma=true
|
insert_final_newline = true
|
||||||
ij_kotlin_allow_trailing_comma_on_call_site=true
|
ij_kotlin_allow_trailing_comma = true
|
||||||
|
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||||
ij_kotlin_name_count_to_use_star_import = 2147483647
|
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||||
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||||
|
6
.github/ISSUE_TEMPLATE.md
vendored
6
.github/ISSUE_TEMPLATE.md
vendored
@ -3,10 +3,10 @@
|
|||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated:
|
- I have updated:
|
||||||
- To the latest version of the app (stable is v0.14.6)
|
- To the latest version of the app (stable is v0.15.3)
|
||||||
- All extensions
|
- All extensions
|
||||||
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
- 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 extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
- 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 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
|
- I will fill out the title and the information in this template
|
||||||
|
|
||||||
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
8
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,11 +1,11 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: ⚠️ Extension/source issue
|
- name: ⚠️ Extension/source issue
|
||||||
url: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
|
url: https://github.com/tachiyomiorg/extensions/issues/new/choose
|
||||||
about: Issues and requests for extensions and sources should be opened in the tachiyomi-extensions repository instead
|
about: Issues and requests for official extensions and sources should be opened in the extensions repository instead
|
||||||
- name: 📦 Tachiyomi extensions
|
- name: 📦 Tachiyomi extensions
|
||||||
url: https://tachiyomi.org/extensions
|
url: https://mihon.app/extensions/
|
||||||
about: List of all available extensions with download links
|
about: List of all available extensions with download links
|
||||||
- name: 🖥️ Tachiyomi website
|
- name: 🖥️ Tachiyomi website
|
||||||
url: https://tachiyomi.org/help/
|
url: https://mihon.app/
|
||||||
about: Guides, troubleshooting, and answers to common questions
|
about: Guides, troubleshooting, and answers to common questions
|
||||||
|
8
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
8
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -53,7 +53,7 @@ body:
|
|||||||
label: Tachiyomi version
|
label: Tachiyomi version
|
||||||
description: You can find your Tachiyomi version in **More → About**.
|
description: You can find your Tachiyomi version in **More → About**.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "0.14.6"
|
Example: "0.15.3"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -94,11 +94,11 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have written a short but informative title.
|
- label: I have written a short but informative title.
|
||||||
required: true
|
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
|
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
|
required: true
|
||||||
- label: I have updated the app to version **[0.14.6](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
- label: I have updated the app to version **[0.15.3](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated all installed extensions.
|
- label: I have updated all installed extensions.
|
||||||
required: true
|
required: true
|
||||||
|
4
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
4
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -31,9 +31,9 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have written a short but informative title.
|
- label: I have written a short but informative title.
|
||||||
required: true
|
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
|
required: true
|
||||||
- label: I have updated the app to version **[0.14.6](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
- label: I have updated the app to version **[0.15.3](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I will fill out all of the requested information in this form.
|
- label: I will fill out all of the requested information in this form.
|
||||||
required: true
|
required: true
|
||||||
|
12
.github/renovate.json
vendored
12
.github/renovate.json
vendored
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"config:base"
|
|
||||||
],
|
|
||||||
"schedule": ["every sunday"],
|
|
||||||
"ignoreDeps": [
|
|
||||||
"androidx.core:core-splashscreen",
|
|
||||||
"com.android.tools:r8",
|
|
||||||
"com.google.guava:guava",
|
|
||||||
"com.github.commandiron:WheelPickerCompose"
|
|
||||||
]
|
|
||||||
}
|
|
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",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
13
.github/workflows/build_pull_request.yml
vendored
13
.github/workflows/build_pull_request.yml
vendored
@ -3,7 +3,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- 'i18n/src/main/res/**/strings.xml'
|
- 'i18n/src/commonMain/resources/**/strings.xml'
|
||||||
|
- 'i18n/src/commonMain/resources/**/plurals.xml'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||||
@ -19,7 +20,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
@ -27,13 +28,13 @@ jobs:
|
|||||||
- name: Dependency Review
|
- name: Dependency Review
|
||||||
uses: actions/dependency-review-action@v3
|
uses: actions/dependency-review-action@v3
|
||||||
|
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 17
|
||||||
distribution: adopt
|
distribution: adopt
|
||||||
|
|
||||||
- name: Build app and run unit tests
|
- name: Build app and run unit tests
|
||||||
uses: gradle/gradle-command-action@v2
|
uses: gradle/gradle-command-action@v2
|
||||||
with:
|
with:
|
||||||
arguments: lintKotlin 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:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
tags:
|
tags:
|
||||||
- v*
|
- v*
|
||||||
|
|
||||||
@ -17,32 +17,36 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
- name: Set up JDK 11
|
- name: Setup Android SDK
|
||||||
uses: actions/setup-java@v3
|
run: |
|
||||||
|
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 17
|
||||||
distribution: adopt
|
distribution: adopt
|
||||||
|
|
||||||
- name: Build app and run unit tests
|
- name: Build app and run unit tests
|
||||||
uses: gradle/gradle-command-action@v2
|
uses: gradle/gradle-command-action@v2
|
||||||
with:
|
with:
|
||||||
arguments: lintKotlin assembleStandardRelease testStandardReleaseUnitTest
|
arguments: ktlintCheck assembleStandardRelease testReleaseUnitTest
|
||||||
|
|
||||||
# Sign APK and create release for tags
|
# Sign APK and create release for tags
|
||||||
|
|
||||||
- name: Get tag name
|
- 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: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
echo "VERSION_TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
|
echo "VERSION_TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Sign APK
|
- 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
|
uses: r0adkll/sign-android-release@v1
|
||||||
with:
|
with:
|
||||||
releaseDirectory: app/build/outputs/apk/standard/release
|
releaseDirectory: app/build/outputs/apk/standard/release
|
||||||
@ -52,36 +56,36 @@ jobs:
|
|||||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||||
|
|
||||||
- name: Clean up build artifacts
|
- 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: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk tachiyomi-${{ env.VERSION_TAG }}.apk
|
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk mihon-${{ env.VERSION_TAG }}.apk
|
||||||
sha=`sha256sum tachiyomi-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
sha=`sha256sum mihon-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
|
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
|
cp app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk mihon-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
||||||
sha=`sha256sum tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
sha=`sha256sum mihon-arm64-v8a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
|
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
|
cp app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk mihon-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
||||||
sha=`sha256sum tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
sha=`sha256sum mihon-armeabi-v7a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
|
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
|
cp app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk mihon-x86-${{ env.VERSION_TAG }}.apk
|
||||||
sha=`sha256sum tachiyomi-x86-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
sha=`sha256sum mihon-x86-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
|
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
|
cp app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk mihon-x86_64-${{ env.VERSION_TAG }}.apk
|
||||||
sha=`sha256sum tachiyomi-x86_64-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
sha=`sha256sum mihon-x86_64-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Create Release
|
- 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
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ env.VERSION_TAG }}
|
tag_name: ${{ env.VERSION_TAG }}
|
||||||
name: Tachiyomi ${{ env.VERSION_TAG }}
|
name: Mihon ${{ env.VERSION_TAG }}
|
||||||
body: |
|
body: |
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -94,13 +98,15 @@ jobs:
|
|||||||
| armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }}
|
| armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }}
|
||||||
| x86 | ${{ env.APK_X86_SHA }} |
|
| x86 | ${{ env.APK_X86_SHA }} |
|
||||||
| x86_64 | ${{ env.APK_X86_64_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: |
|
files: |
|
||||||
tachiyomi-${{ env.VERSION_TAG }}.apk
|
mihon-${{ env.VERSION_TAG }}.apk
|
||||||
tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
mihon-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
||||||
tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
mihon-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
||||||
tachiyomi-x86-${{ env.VERSION_TAG }}.apk
|
mihon-x86-${{ env.VERSION_TAG }}.apk
|
||||||
tachiyomi-x86_64-${{ env.VERSION_TAG }}.apk
|
mihon-x86_64-${{ env.VERSION_TAG }}.apk
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: false
|
prerelease: false
|
||||||
env:
|
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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Moderate issues
|
- name: Moderate issues
|
||||||
uses: tachiyomiorg/issue-moderator-action@v1
|
uses: tachiyomiorg/issue-moderator-action@v2
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
duplicate-label: Duplicate
|
||||||
|
|
||||||
auto-close-rules: |
|
auto-close-rules: |
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -31,5 +33,13 @@ jobs:
|
|||||||
"regex": "^(?!.*myanimelist.*).*(aniyomi|anime).*$",
|
"regex": "^(?!.*myanimelist.*).*(aniyomi|anime).*$",
|
||||||
"ignoreCase": true,
|
"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"
|
"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:
|
lock:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v4
|
- uses: dessant/lock-threads@v5
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
issue-inactive-days: '2'
|
issue-inactive-days: '2'
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,7 +2,8 @@
|
|||||||
/local.properties
|
/local.properties
|
||||||
/.idea/workspace.xml
|
/.idea/workspace.xml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.idea/
|
.idea/*
|
||||||
|
!.idea/icon.png
|
||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
|
Before ![]() (image error) Size: 14 KiB After ![]() (image error) Size: 14 KiB ![]() ![]() |
@ -60,7 +60,7 @@ representative at an online or offline event.
|
|||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community moderators responsible for enforcement at
|
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 complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community moderators are obligated to respect the privacy and security of the
|
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
|
# Code contributions
|
||||||
|
|
||||||
Pull requests are welcome!
|
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.
|
You do not need to ask for permission nor an assignment.
|
||||||
|
|
||||||
## Prerequisites
|
## 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)
|
- [Android Studio](https://developer.android.com/studio)
|
||||||
- Emulator or phone with developer options enabled to test changes.
|
- Emulator or phone with developer options enabled to test changes.
|
||||||
|
|
||||||
|
## Linting
|
||||||
|
|
||||||
|
To auto-fix some linting errors, run the `ktlintFormat` Gradle task.
|
||||||
|
|
||||||
## Getting help
|
## 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
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
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:
|
When creating a fork, remember to:
|
||||||
|
|
||||||
- To avoid confusion with the main app:
|
- To avoid confusion with the main app:
|
||||||
- Change the app name
|
- Change the app name
|
||||||
- Change the app icon
|
- 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:
|
- 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:
|
- 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 Firebase analytics, replace [`google-services.json`](https://github.com/mihonapp/mihon/blob/main/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
|
|
||||||
|
31
README.md
31
README.md
@ -1,10 +1,9 @@
|
|||||||
| Build | Stable | Weekly Preview | Contribute | Support Server |
|
| Build | Stable | Weekly Preview | 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) |
|
| [](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) |
|
||||||
|
|
||||||
|
# Mihon
|
||||||
# Tachiyomi
|
Mihon is a free and open source manga reader for Android 8.0 and above.
|
||||||
Tachiyomi is a free and open source manga reader for Android 6.0 and above.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -19,9 +18,9 @@ Features include:
|
|||||||
* Create backups locally to read offline or to your desired cloud service
|
* Create backups locally to read offline or to your desired cloud service
|
||||||
|
|
||||||
## Download
|
## 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 preview version [here](https://github.com/mihonapp/mihon-preview/releases).
|
||||||
|
|
||||||
## Issues, Feature Requests and Contributing
|
## 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>
|
<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).**
|
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/tachiyomi)
|
2. If you are unsure, ask here: [](https://discord.gg/mihon)
|
||||||
|
|
||||||
</details>
|
</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)
|
* Include version (More → About → Version)
|
||||||
* If not latest, try updating, it may have already been solved
|
* 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
|
* Preview version is equal to the number of commits as seen on the main page
|
||||||
* Include steps to reproduce (if not obvious from description)
|
* Include steps to reproduce (if not obvious from description)
|
||||||
* Include screenshot (if needed)
|
* Include screenshot (if needed)
|
||||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||||
* Don't group unrelated requests into one issue
|
* 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>
|
||||||
|
|
||||||
<details><summary>Feature Requests</summary>
|
<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"
|
* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does"
|
||||||
* Include screenshot (if needed)
|
* 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>
|
||||||
|
|
||||||
<details><summary>Contributing</summary>
|
<details><summary>Contributing</summary>
|
||||||
@ -70,8 +65,8 @@ See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
|||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
[See our website.](https://tachiyomi.org/)
|
[See our website.](https://mihon.app/)
|
||||||
You can also reach out to us on [Discord](https://discord.gg/tachiyomi).
|
You can also reach out to us on [Discord](https://discord.gg/mihon).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
1
app/.gitignore
vendored
1
app/.gitignore
vendored
@ -1,4 +1,3 @@
|
|||||||
/build
|
/build
|
||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
custom.gradle
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
@ -21,9 +20,10 @@ android {
|
|||||||
namespace = "eu.kanade.tachiyomi"
|
namespace = "eu.kanade.tachiyomi"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "eu.kanade.tachiyomi"
|
applicationId = "app.mihon"
|
||||||
versionCode = 101
|
|
||||||
versionName = "0.14.6"
|
versionCode = 1
|
||||||
|
versionName = "0.16.0"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
@ -31,9 +31,6 @@ android {
|
|||||||
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
||||||
buildConfigField("boolean", "PREVIEW", "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 {
|
ndk {
|
||||||
abiFilters += SUPPORTED_ABIS
|
abiFilters += SUPPORTED_ABIS
|
||||||
}
|
}
|
||||||
@ -65,11 +62,11 @@ android {
|
|||||||
initWith(getByName("release"))
|
initWith(getByName("release"))
|
||||||
buildConfigField("boolean", "PREVIEW", "true")
|
buildConfigField("boolean", "PREVIEW", "true")
|
||||||
|
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
matchingFallbacks.add("release")
|
||||||
val debugType = getByName("debug")
|
val debugType = getByName("debug")
|
||||||
signingConfig = debugType.signingConfig
|
|
||||||
versionNameSuffix = debugType.versionNameSuffix
|
versionNameSuffix = debugType.versionNameSuffix
|
||||||
applicationIdSuffix = debugType.applicationIdSuffix
|
applicationIdSuffix = debugType.applicationIdSuffix
|
||||||
matchingFallbacks.add("release")
|
|
||||||
}
|
}
|
||||||
create("benchmark") {
|
create("benchmark") {
|
||||||
initWith(getByName("release"))
|
initWith(getByName("release"))
|
||||||
@ -77,6 +74,7 @@ android {
|
|||||||
signingConfig = signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
matchingFallbacks.add("release")
|
matchingFallbacks.add("release")
|
||||||
isDebuggable = false
|
isDebuggable = false
|
||||||
|
isProfileable = true
|
||||||
versionNameSuffix = "-benchmark"
|
versionNameSuffix = "-benchmark"
|
||||||
applicationIdSuffix = ".benchmark"
|
applicationIdSuffix = ".benchmark"
|
||||||
}
|
}
|
||||||
@ -101,16 +99,18 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packaging {
|
||||||
resources.excludes.addAll(listOf(
|
resources.excludes.addAll(
|
||||||
"META-INF/DEPENDENCIES",
|
listOf(
|
||||||
"LICENSE.txt",
|
"META-INF/DEPENDENCIES",
|
||||||
"META-INF/LICENSE",
|
"LICENSE.txt",
|
||||||
"META-INF/LICENSE.txt",
|
"META-INF/LICENSE",
|
||||||
"META-INF/README.md",
|
"META-INF/LICENSE.txt",
|
||||||
"META-INF/NOTICE",
|
"META-INF/README.md",
|
||||||
"META-INF/*.kotlin_module",
|
"META-INF/NOTICE",
|
||||||
))
|
"META-INF/*.kotlin_module",
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependenciesInfo {
|
dependenciesInfo {
|
||||||
@ -120,6 +120,7 @@ android {
|
|||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
compose = true
|
compose = true
|
||||||
|
buildConfig = true
|
||||||
|
|
||||||
// Disable some unused things
|
// Disable some unused things
|
||||||
aidl = false
|
aidl = false
|
||||||
@ -140,7 +141,9 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":i18n"))
|
implementation(project(":i18n"))
|
||||||
implementation(project(":core"))
|
implementation(project(":core"))
|
||||||
|
implementation(project(":core-metadata"))
|
||||||
implementation(project(":source-api"))
|
implementation(project(":source-api"))
|
||||||
|
implementation(project(":source-local"))
|
||||||
implementation(project(":data"))
|
implementation(project(":data"))
|
||||||
implementation(project(":domain"))
|
implementation(project(":domain"))
|
||||||
implementation(project(":presentation-core"))
|
implementation(project(":presentation-core"))
|
||||||
@ -155,13 +158,12 @@ dependencies {
|
|||||||
implementation(compose.material.icons)
|
implementation(compose.material.icons)
|
||||||
implementation(compose.animation)
|
implementation(compose.animation)
|
||||||
implementation(compose.animation.graphics)
|
implementation(compose.animation.graphics)
|
||||||
implementation(compose.ui.tooling)
|
debugImplementation(compose.ui.tooling)
|
||||||
|
implementation(compose.ui.tooling.preview)
|
||||||
implementation(compose.ui.util)
|
implementation(compose.ui.util)
|
||||||
implementation(compose.accompanist.webview)
|
implementation(compose.accompanist.webview)
|
||||||
implementation(compose.accompanist.flowlayout)
|
|
||||||
implementation(compose.accompanist.permissions)
|
|
||||||
implementation(compose.accompanist.themeadapter)
|
|
||||||
implementation(compose.accompanist.systemuicontroller)
|
implementation(compose.accompanist.systemuicontroller)
|
||||||
|
lintChecks(compose.lintchecks)
|
||||||
|
|
||||||
implementation(androidx.paging.runtime)
|
implementation(androidx.paging.runtime)
|
||||||
implementation(androidx.paging.compose)
|
implementation(androidx.paging.compose)
|
||||||
@ -169,6 +171,7 @@ dependencies {
|
|||||||
implementation(libs.bundles.sqlite)
|
implementation(libs.bundles.sqlite)
|
||||||
|
|
||||||
implementation(kotlinx.reflect)
|
implementation(kotlinx.reflect)
|
||||||
|
implementation(kotlinx.immutables)
|
||||||
|
|
||||||
implementation(platform(kotlinx.coroutines.bom))
|
implementation(platform(kotlinx.coroutines.bom))
|
||||||
implementation(kotlinx.bundles.coroutines)
|
implementation(kotlinx.bundles.coroutines)
|
||||||
@ -178,7 +181,6 @@ dependencies {
|
|||||||
implementation(androidx.appcompat)
|
implementation(androidx.appcompat)
|
||||||
implementation(androidx.biometricktx)
|
implementation(androidx.biometricktx)
|
||||||
implementation(androidx.constraintlayout)
|
implementation(androidx.constraintlayout)
|
||||||
implementation(androidx.coordinatorlayout)
|
|
||||||
implementation(androidx.corektx)
|
implementation(androidx.corektx)
|
||||||
implementation(androidx.splashscreen)
|
implementation(androidx.splashscreen)
|
||||||
implementation(androidx.recyclerview)
|
implementation(androidx.recyclerview)
|
||||||
@ -188,20 +190,17 @@ dependencies {
|
|||||||
implementation(androidx.bundles.lifecycle)
|
implementation(androidx.bundles.lifecycle)
|
||||||
|
|
||||||
// Job scheduling
|
// Job scheduling
|
||||||
implementation(androidx.bundles.workmanager)
|
implementation(androidx.workmanager)
|
||||||
|
|
||||||
// RX
|
// RxJava
|
||||||
implementation(libs.bundles.reactivex)
|
implementation(libs.rxjava)
|
||||||
implementation(libs.flowreactivenetwork)
|
|
||||||
|
|
||||||
// Network client
|
// Networking
|
||||||
implementation(libs.bundles.okhttp)
|
implementation(libs.bundles.okhttp)
|
||||||
implementation(libs.okio)
|
implementation(libs.okio)
|
||||||
|
implementation(libs.conscrypt.android) // TLS 1.3 support for Android < 10
|
||||||
|
|
||||||
// TLS 1.3 support for Android < 10
|
// Data serialization (JSON, protobuf, xml)
|
||||||
implementation(libs.conscrypt.android)
|
|
||||||
|
|
||||||
// Data serialization (JSON, protobuf)
|
|
||||||
implementation(kotlinx.bundles.serialization)
|
implementation(kotlinx.bundles.serialization)
|
||||||
|
|
||||||
// HTML parser
|
// HTML parser
|
||||||
@ -219,19 +218,16 @@ dependencies {
|
|||||||
implementation(libs.injekt.core)
|
implementation(libs.injekt.core)
|
||||||
|
|
||||||
// Image loading
|
// Image loading
|
||||||
|
implementation(platform(libs.coil.bom))
|
||||||
implementation(libs.bundles.coil)
|
implementation(libs.bundles.coil)
|
||||||
implementation(libs.subsamplingscaleimageview) {
|
implementation(libs.subsamplingscaleimageview) {
|
||||||
exclude(module = "image-decoder")
|
exclude(module = "image-decoder")
|
||||||
}
|
}
|
||||||
implementation(libs.image.decoder)
|
implementation(libs.image.decoder)
|
||||||
|
|
||||||
// Sort
|
|
||||||
implementation(libs.natural.comparator)
|
|
||||||
|
|
||||||
// UI libraries
|
// UI libraries
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
implementation(libs.flexible.adapter.core)
|
implementation(libs.flexible.adapter.core)
|
||||||
implementation(libs.flexible.adapter.ui)
|
|
||||||
implementation(libs.photoview)
|
implementation(libs.photoview)
|
||||||
implementation(libs.directionalviewpager) {
|
implementation(libs.directionalviewpager) {
|
||||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||||
@ -239,23 +235,21 @@ dependencies {
|
|||||||
implementation(libs.insetter)
|
implementation(libs.insetter)
|
||||||
implementation(libs.bundles.richtext)
|
implementation(libs.bundles.richtext)
|
||||||
implementation(libs.aboutLibraries.compose)
|
implementation(libs.aboutLibraries.compose)
|
||||||
implementation(libs.cascade)
|
|
||||||
implementation(libs.bundles.voyager)
|
implementation(libs.bundles.voyager)
|
||||||
implementation(libs.wheelpicker)
|
implementation(libs.compose.materialmotion)
|
||||||
implementation(libs.materialmotion.core)
|
implementation(libs.swipe)
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation(libs.logcat)
|
implementation(libs.logcat)
|
||||||
|
|
||||||
// Crash reports/analytics
|
// Crash reports/analytics
|
||||||
implementation(libs.acra.http)
|
|
||||||
"standardImplementation"(libs.firebase.analytics)
|
"standardImplementation"(libs.firebase.analytics)
|
||||||
|
|
||||||
// Shizuku
|
// Shizuku
|
||||||
implementation(libs.bundles.shizuku)
|
implementation(libs.bundles.shizuku)
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.bundles.test)
|
||||||
|
|
||||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||||
// debugImplementation(libs.leakcanary.android)
|
// debugImplementation(libs.leakcanary.android)
|
||||||
@ -266,7 +260,9 @@ androidComponents {
|
|||||||
beforeVariants { variantBuilder ->
|
beforeVariants { variantBuilder ->
|
||||||
// Disables standardBenchmark
|
// Disables standardBenchmark
|
||||||
if (variantBuilder.buildType == "benchmark") {
|
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")) {
|
onVariants(selector().withFlavor("default" to "standard")) {
|
||||||
@ -277,16 +273,10 @@ androidComponents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
|
||||||
withType<LintTask>().configureEach {
|
|
||||||
exclude { it.file.path.contains("generated[\\\\/]".toRegex()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
||||||
withType<KotlinCompile> {
|
withType<KotlinCompile> {
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
"-Xcontext-receivers",
|
||||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
|
||||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||||
@ -295,6 +285,8 @@ tasks {
|
|||||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||||
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
"-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.ExperimentalCoroutinesApi",
|
||||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||||
@ -305,12 +297,12 @@ tasks {
|
|||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-P",
|
"-P",
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||||
project.buildDir.absolutePath + "/compose_metrics"
|
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||||
)
|
)
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-P",
|
"-P",
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||||
project.buildDir.absolutePath + "/compose_metrics"
|
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
-dontusemixedcaseclassnames
|
-dontusemixedcaseclassnames
|
||||||
|
-ignorewarnings
|
||||||
-verbose
|
-verbose
|
||||||
|
|
||||||
-keepattributes *Annotation*
|
-keepattributes *Annotation*
|
||||||
@ -13,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
-keepclassmembers class * implements android.os.Parcelable {
|
-keepclassmembers class * implements android.os.Parcelable {
|
||||||
public static final ** CREATOR;
|
public static final ** CREATOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
-keep class androidx.annotation.Keep
|
-keep class androidx.annotation.Keep
|
||||||
|
14
app/proguard-rules.pro
vendored
14
app/proguard-rules.pro
vendored
@ -1,14 +1,18 @@
|
|||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
|
|
||||||
|
-keep,allowoptimization class eu.kanade.**
|
||||||
|
-keep,allowoptimization class tachiyomi.**
|
||||||
|
|
||||||
# Keep common dependencies used in extensions
|
# Keep common dependencies used in extensions
|
||||||
-keep,allowoptimization class androidx.preference.** { public protected *; }
|
-keep,allowoptimization class androidx.preference.** { public protected *; }
|
||||||
-keep,allowoptimization class kotlin.** { public protected *; }
|
-keep,allowoptimization class kotlin.** { public protected *; }
|
||||||
-keep,allowoptimization class kotlinx.coroutines.** { public protected *; }
|
-keep,allowoptimization class kotlinx.coroutines.** { public protected *; }
|
||||||
-keep,allowoptimization class kotlinx.serialization.** { 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 okhttp3.** { public protected *; }
|
||||||
-keep,allowoptimization class okio.** { public protected *; }
|
-keep,allowoptimization class okio.** { public protected *; }
|
||||||
-keep,allowoptimization class rx.** { public protected *; }
|
|
||||||
-keep,allowoptimization class org.jsoup.** { 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 app.cash.quickjs.** { public protected *; }
|
||||||
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
|
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
|
||||||
|
|
||||||
@ -41,7 +45,7 @@
|
|||||||
|
|
||||||
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
||||||
-keepattributes *Annotation*, InnerClasses
|
-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
|
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
|
||||||
-keepclassmembers class kotlinx.serialization.json.** {
|
-keepclassmembers class kotlinx.serialization.json.** {
|
||||||
@ -66,4 +70,8 @@
|
|||||||
##---------------End: proguard configuration for kotlinx.serialization ----------
|
##---------------End: proguard configuration for kotlinx.serialization ----------
|
||||||
|
|
||||||
# XmlUtil
|
# XmlUtil
|
||||||
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
-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">
|
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<shortcut
|
<shortcut
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:icon="@drawable/sc_collections_bookmark_48dp"
|
android:icon="@drawable/sc_collections_bookmark_48dp"
|
||||||
|
21
app/src/debug/res/drawable/ic_launcher_background.xml
Normal file
21
app/src/debug/res/drawable/ic_launcher_background.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="432"
|
||||||
|
android:viewportHeight="432">
|
||||||
|
<group android:scaleX="0.67"
|
||||||
|
android:scaleY="0.67"
|
||||||
|
android:translateX="71.28"
|
||||||
|
android:translateY="71.28">
|
||||||
|
<path
|
||||||
|
android:pathData="M0,0h432v432h-432z"
|
||||||
|
android:fillColor="#2E3943"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M377,216C377,302.16 304.92,372 216,372C127.08,372 55,302.16 55,216C55,129.84 127.08,60 216,60C304.92,60 377,129.84 377,216Z"
|
||||||
|
android:fillColor="#F2FAFF"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M216,342.75C288.25,342.75 346.81,286 346.81,216C346.81,146 288.25,89.25 216,89.25C143.75,89.25 85.19,146 85.19,216C85.19,286 143.75,342.75 216,342.75ZM216,372C304.92,372 377,302.16 377,216C377,129.84 304.92,60 216,60C127.08,60 55,129.84 55,216C55,302.16 127.08,372 216,372Z"
|
||||||
|
android:fillColor="#7EBBED"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
@ -1,27 +1,14 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="108.0"
|
android:viewportWidth="432"
|
||||||
android:viewportHeight="108.0">
|
android:viewportHeight="432">
|
||||||
|
<group android:scaleX="0.67"
|
||||||
|
android:scaleY="0.67"
|
||||||
|
android:translateX="71.28"
|
||||||
|
android:translateY="71.28">
|
||||||
<path
|
<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:pathData="M162.3,173.59L161.22,148.63C164.47,149.25 168.35,149.41 177.18,149.41C187.87,149.41 201.98,148.79 209.26,147.86C212.36,147.55 213.6,147.09 215.61,146L232.35,160.26C230.8,162.43 230.34,163.36 228.63,167.7C227.24,171.11 220.88,190.79 218.4,199.16C229.87,201.48 236.22,203.18 244.9,206.75C245.99,199.16 246.14,195.13 246.14,181.33C246.14,177.77 245.99,175.76 245.52,172.5L272.49,173.43C271.71,177.15 271.56,178.7 271.4,184.74C270.78,199.31 270.16,206.29 268.61,216.82C279.31,222.25 279.31,222.25 284.73,225.19C287.52,226.74 288.14,227.05 290,227.67L281.01,256.65C276.67,252.78 270.63,248.59 261.8,243.63C254.05,262.08 241.18,275.56 221.66,286.25C215.15,277.57 210.19,272.3 202.29,266.11C213.75,260.68 219.02,257.27 225.07,251.54C230.96,245.8 234.83,240.22 238.55,231.85C228.63,227.36 222.28,225.35 211.27,223.02C204.92,241.93 199.8,254.02 195.31,261.3C189.27,271.06 181.05,276.18 171.6,276.18C164.32,276.18 156.88,272.92 151.45,267.35C145.25,260.99 142,252.16 142,241.93C142,226.74 149.28,213.57 161.99,205.35C170.21,200.09 178.88,197.76 192.68,196.99C195.47,187.84 197.79,179.94 199.96,171.11C193.14,171.73 184.62,172.19 174.24,172.65C168.66,172.81 166.8,172.96 162.3,173.59ZM185.86,220.7C178.57,221.94 174.24,224.26 170.36,229.22C167.42,232.63 166.02,236.66 166.02,241C166.02,245.8 168.35,249.37 171.29,249.37C174.85,249.37 178.88,241.31 185.86,220.7Z"
|
||||||
android:fillColor="#000"/>
|
android:fillColor="#031019"/>
|
||||||
<path
|
</group>
|
||||||
android:pathData="M14.5,7L86.5,7A7,7 0,0 1,93.5 14L93.5,95A7,7 0,0 1,86.5 102L14.5,102A7,7 0,0 1,7.5 95L7.5,14A7,7 0,0 1,14.5 7z"
|
|
||||||
android:fillColor="#455A64"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M7.5,12.01C7.5,9.24 9.74,7 12.5,7L17.5,7L17.5,102L12.5,102C9.74,102 7.5,99.77 7.5,96.99L7.5,12.01Z"
|
|
||||||
android:fillColor="#607D8B"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M54,54.5m-25.5,0a25.5,25.5 0,1 1,51 0a25.5,25.5 0,1 1,-51 0"
|
|
||||||
android:fillColor="#000"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M54,54.5m-25.5,0a25.5,25.5 0,1 1,51 0a25.5,25.5 0,1 1,-51 0"
|
|
||||||
android:fillColor="#CE2828"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M54,54.5m-19.94,0a19.94,19.94 0,1 1,39.87 0a19.94,19.94 0,1 1,-39.87 0"
|
|
||||||
android:fillColor="#FFF"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M52.04,46.3L47.42,46.3C46.14,46.3 44.93,46.23 44.2,46.14L44.2,49.76C45,49.65 46.16,49.6 47.42,49.6L60.58,49.6C61.86,49.6 63.02,49.65 63.82,49.76L63.82,46.14C63.09,46.23 61.86,46.3 60.58,46.3L55.69,46.3L55.69,45.07C55.69,44.43 55.73,43.95 55.82,43.45L51.9,43.45C51.99,44 52.04,44.43 52.04,45.07L52.04,46.3ZM46.78,60.68C45.46,60.68 44.29,60.63 43.45,60.52L43.45,64.14C44.34,64.03 45.46,63.98 46.78,63.98L61.29,63.98C62.57,63.98 63.71,64.03 64.57,64.14L64.57,60.52C63.73,60.63 62.57,60.68 61.29,60.68L58.24,60.68C59.33,58.06 59.99,56.23 60.7,53.91C61.34,51.81 61.34,51.81 61.56,51.13L57.58,50.06C57.51,50.93 57.37,51.52 56.89,53.41C56.19,56.14 55.32,58.74 54.5,60.68L46.78,60.68ZM46.48,51.36C47.55,54.02 48.28,56.53 49.03,60.15L52.66,58.9C51.65,54.98 50.92,52.66 49.94,50.11L46.48,51.36Z"
|
|
||||||
android:fillColor="#000"/>
|
|
||||||
</vector>
|
</vector>
|
||||||
|
@ -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>
|
|
@ -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" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
|
||||||
<!-- Storage -->
|
<!-- 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 -->
|
<!-- For background jobs -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
@ -20,43 +22,87 @@
|
|||||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||||
<!-- To view extension packages in API 30+ -->
|
<!-- 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.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
|
<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 -->
|
<!-- 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" />
|
tools:node="remove" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:localeConfig="@xml/locales_config"
|
android:localeConfig="@xml/locales_config"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
|
android:preserveLegacyExternalStorage="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:theme="@style/Theme.Tachiyomi"
|
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
android:theme="@style/Theme.Tachiyomi">
|
||||||
|
|
||||||
<!-- enable profiling by macrobenchmark -->
|
|
||||||
<profileable
|
|
||||||
android:shell="true"
|
|
||||||
tools:targetApi="q" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.MainActivity"
|
android:name=".ui.main.MainActivity"
|
||||||
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/Theme.Tachiyomi.SplashScreen"
|
android:theme="@style/Theme.Tachiyomi.SplashScreen">
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</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 -->
|
<!--suppress AndroidDomInspection -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.shortcuts"
|
android:name="android.app.shortcuts"
|
||||||
@ -64,16 +110,16 @@
|
|||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:process=":error_handler"
|
|
||||||
android:name=".crash.CrashActivity"
|
android:name=".crash.CrashActivity"
|
||||||
android:exported="false" />
|
android:exported="false"
|
||||||
|
android:process=":error_handler" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.DeepLinkActivity"
|
android:name=".ui.deeplink.DeepLinkActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/action_search"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
android:label="@string/action_global_search"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEARCH" />
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
||||||
@ -97,20 +143,21 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.reader.ReaderActivity"
|
android:name=".ui.reader.ReaderActivity"
|
||||||
android:launchMode="singleTask"
|
android:exported="false"
|
||||||
android:exported="false">
|
android:launchMode="singleTask">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
|
<meta-data
|
||||||
android:resource="@xml/s_pen_actions"/>
|
android:name="com.samsung.android.support.REMOTE_ACTION"
|
||||||
|
android:resource="@xml/s_pen_actions" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.security.UnlockActivity"
|
android:name=".ui.security.UnlockActivity"
|
||||||
android:theme="@style/Theme.Tachiyomi"
|
android:exported="false"
|
||||||
android:exported="false" />
|
android:theme="@style/Theme.Tachiyomi" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.webview.WebViewActivity"
|
android:name=".ui.webview.WebViewActivity"
|
||||||
@ -119,67 +166,25 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".extension.util.ExtensionInstallActivity"
|
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
|
<activity
|
||||||
android:name=".ui.setting.track.AnilistLoginActivity"
|
android:name=".ui.setting.track.TrackLoginActivity"
|
||||||
android:label="Anilist"
|
android:exported="true"
|
||||||
android:exported="true">
|
android:label="@string/track_activity_name">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data android:scheme="mihon" />
|
||||||
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" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<data android:host="anilist-auth" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<data android:host="bangumi-auth" />
|
||||||
|
<data android:host="myanimelist-auth" />
|
||||||
<data
|
<data android:host="shikimori-auth" />
|
||||||
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" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
@ -187,38 +192,10 @@
|
|||||||
android:name=".data.notification.NotificationReceiver"
|
android:name=".data.notification.NotificationReceiver"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<receiver
|
<service
|
||||||
android:name="tachiyomi.presentation.widget.UpdatesGridGlanceReceiver"
|
android:name=".extension.util.ExtensionInstallService"
|
||||||
android:enabled="@bool/glance_appwidget_available"
|
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/label_recent_updates">
|
android:foregroundServiceType="shortService" />
|
||||||
<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" />
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||||
@ -229,6 +206,11 @@
|
|||||||
android:value="true" />
|
android:value="true" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
||||||
|
android:foregroundServiceType="dataSync"
|
||||||
|
tools:node="merge" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
@ -242,9 +224,9 @@
|
|||||||
<provider
|
<provider
|
||||||
android:name="rikka.shizuku.ShizukuProvider"
|
android:name="rikka.shizuku.ShizukuProvider"
|
||||||
android:authorities="${applicationId}.shizuku"
|
android:authorities="${applicationId}.shizuku"
|
||||||
android:multiprocess="false"
|
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:multiprocess="false"
|
||||||
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||||
|
|
||||||
<meta-data
|
<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,4 +1,4 @@
|
|||||||
package eu.kanade.core.prefs
|
package eu.kanade.core.preference
|
||||||
|
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -31,7 +31,7 @@ class PreferenceMutableState<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun component2(): (T) -> Unit {
|
override fun component2(): (T) -> Unit {
|
||||||
return { preference.set(it) }
|
return preference::set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.core.util
|
package eu.kanade.core.util
|
||||||
|
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
@ -12,23 +11,14 @@ fun <T : R, R : Any> List<T>.insertSeparators(
|
|||||||
val newList = mutableListOf<R>()
|
val newList = mutableListOf<R>()
|
||||||
for (i in -1..lastIndex) {
|
for (i in -1..lastIndex) {
|
||||||
val before = getOrNull(i)
|
val before = getOrNull(i)
|
||||||
before?.let { newList.add(it) }
|
before?.let(newList::add)
|
||||||
val after = getOrNull(i + 1)
|
val after = getOrNull(i + 1)
|
||||||
val separator = generator.invoke(before, after)
|
val separator = generator.invoke(before, after)
|
||||||
separator?.let { newList.add(it) }
|
separator?.let(newList::add)
|
||||||
}
|
}
|
||||||
return newList
|
return newList
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new map containing only the key entries of [transform] that are not null.
|
|
||||||
*/
|
|
||||||
inline fun <K, V, R> Map<out K, V>.mapNotNullKeys(transform: (Map.Entry<K?, V>) -> R?): ConcurrentHashMap<R, V> {
|
|
||||||
val mutableMap = ConcurrentHashMap<R, V>()
|
|
||||||
forEach { element -> transform(element)?.let { mutableMap[it] = element.value } }
|
|
||||||
return mutableMap
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
||||||
if (shouldAdd) {
|
if (shouldAdd) {
|
||||||
add(value)
|
add(value)
|
||||||
@ -80,7 +70,7 @@ inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
|
|||||||
contract { callsInPlace(transform) }
|
contract { callsInPlace(transform) }
|
||||||
val destination = ArrayList<R>()
|
val destination = ArrayList<R>()
|
||||||
fastForEach { element ->
|
fastForEach { element ->
|
||||||
transform(element)?.let { destination.add(it) }
|
transform(element)?.let(destination::add)
|
||||||
}
|
}
|
||||||
return destination
|
return destination
|
||||||
}
|
}
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
package eu.kanade.core.util
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import kotlin.time.Duration
|
|
||||||
|
|
||||||
fun Duration.toDurationString(context: Context, fallback: String): String {
|
|
||||||
return toComponents { days, hours, minutes, seconds, _ ->
|
|
||||||
buildList(4) {
|
|
||||||
if (days != 0L) add(context.getString(R.string.day_short, days))
|
|
||||||
if (hours != 0) add(context.getString(R.string.hour_short, hours))
|
|
||||||
if (minutes != 0 && (days == 0L || hours == 0)) add(context.getString(R.string.minute_short, minutes))
|
|
||||||
if (seconds != 0 && days == 0L && hours == 0) add(context.getString(R.string.seconds_short, seconds))
|
|
||||||
}.joinToString(" ").ifBlank { fallback }
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,19 +0,0 @@
|
|||||||
package eu.kanade.data.source
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import tachiyomi.domain.source.model.Source
|
|
||||||
|
|
||||||
val sourceMapper: (eu.kanade.tachiyomi.source.Source) -> Source = { source ->
|
|
||||||
Source(
|
|
||||||
source.id,
|
|
||||||
source.lang,
|
|
||||||
source.name,
|
|
||||||
supportsLatest = false,
|
|
||||||
isStub = source is SourceManager.StubSource,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val catalogueSourceMapper: (CatalogueSource) -> Source = { source ->
|
|
||||||
sourceMapper(source).copy(supportsLatest = source.supportsLatest)
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
package eu.kanade.data.source
|
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import tachiyomi.data.DatabaseHandler
|
|
||||||
import tachiyomi.domain.source.model.Source
|
|
||||||
import tachiyomi.domain.source.model.SourceWithCount
|
|
||||||
|
|
||||||
class SourceRepositoryImpl(
|
|
||||||
private val sourceManager: SourceManager,
|
|
||||||
private val handler: DatabaseHandler,
|
|
||||||
) : SourceRepository {
|
|
||||||
|
|
||||||
override fun getSources(): Flow<List<Source>> {
|
|
||||||
return sourceManager.catalogueSources.map { sources ->
|
|
||||||
sources.map(catalogueSourceMapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getOnlineSources(): Flow<List<Source>> {
|
|
||||||
return sourceManager.onlineSources.map { sources ->
|
|
||||||
sources.map(sourceMapper)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>> {
|
|
||||||
val sourceIdWithFavoriteCount = handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() }
|
|
||||||
return sourceIdWithFavoriteCount.map { sourceIdsWithCount ->
|
|
||||||
sourceIdsWithCount
|
|
||||||
.filterNot { it.source == LocalSource.ID }
|
|
||||||
.map { (sourceId, count) ->
|
|
||||||
val source = sourceManager.getOrStub(sourceId).run {
|
|
||||||
sourceMapper(this)
|
|
||||||
}
|
|
||||||
source to count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSourcesWithNonLibraryManga(): Flow<List<SourceWithCount>> {
|
|
||||||
val sourceIdWithNonLibraryManga = handler.subscribeToList { mangasQueries.getSourceIdsWithNonLibraryManga() }
|
|
||||||
return sourceIdWithNonLibraryManga.map { sourceId ->
|
|
||||||
sourceId.map { (sourceId, count) ->
|
|
||||||
val source = sourceManager.getOrStub(sourceId)
|
|
||||||
SourceWithCount(sourceMapper(source), count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun search(
|
|
||||||
sourceId: Long,
|
|
||||||
query: String,
|
|
||||||
filterList: FilterList,
|
|
||||||
): SourcePagingSourceType {
|
|
||||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
|
||||||
return SourceSearchPagingSource(source, query, filterList)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPopular(sourceId: Long): SourcePagingSourceType {
|
|
||||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
|
||||||
return SourcePopularPagingSource(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLatest(sourceId: Long): SourcePagingSourceType {
|
|
||||||
val source = sourceManager.get(sourceId) as CatalogueSource
|
|
||||||
return SourceLatestPagingSource(source)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +1,85 @@
|
|||||||
package eu.kanade.domain
|
package eu.kanade.domain
|
||||||
|
|
||||||
import eu.kanade.data.source.SourceRepositoryImpl
|
import eu.kanade.domain.chapter.interactor.GetAvailableScanlators
|
||||||
import eu.kanade.domain.category.interactor.CreateCategoryWithName
|
|
||||||
import eu.kanade.domain.category.interactor.DeleteCategory
|
|
||||||
import eu.kanade.domain.category.interactor.RenameCategory
|
|
||||||
import eu.kanade.domain.category.interactor.ReorderCategory
|
|
||||||
import eu.kanade.domain.category.interactor.ResetCategoryFlags
|
|
||||||
import eu.kanade.domain.category.interactor.SetDisplayModeForCategory
|
|
||||||
import eu.kanade.domain.category.interactor.SetMangaCategories
|
|
||||||
import eu.kanade.domain.category.interactor.SetSortModeForCategory
|
|
||||||
import eu.kanade.domain.category.interactor.UpdateCategory
|
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapter
|
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
|
||||||
import eu.kanade.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
|
||||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
|
|
||||||
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
|
||||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
import eu.kanade.domain.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.GetExtensionLanguages
|
||||||
|
import eu.kanade.domain.extension.interactor.GetExtensionRepos
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||||
import eu.kanade.domain.history.interactor.GetNextChapters
|
import eu.kanade.domain.extension.interactor.TrustExtension
|
||||||
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
|
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
|
||||||
import eu.kanade.domain.manga.interactor.GetFavorites
|
import eu.kanade.domain.manga.interactor.SetExcludedScanlators
|
||||||
import eu.kanade.domain.manga.interactor.GetLibraryManga
|
|
||||||
import eu.kanade.domain.manga.interactor.GetManga
|
|
||||||
import eu.kanade.domain.manga.interactor.GetMangaWithChapters
|
|
||||||
import eu.kanade.domain.manga.interactor.NetworkToLocalManga
|
|
||||||
import eu.kanade.domain.manga.interactor.ResetViewerFlags
|
|
||||||
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
|
|
||||||
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||||
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
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.GetSourcesWithFavoriteCount
|
||||||
import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga
|
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.track.interactor.AddTracks
|
||||||
import eu.kanade.domain.track.interactor.DeleteTrack
|
import eu.kanade.domain.track.interactor.RefreshTracks
|
||||||
import eu.kanade.domain.track.interactor.GetTracks
|
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
|
||||||
import eu.kanade.domain.track.interactor.GetTracksPerManga
|
import eu.kanade.domain.track.interactor.TrackChapter
|
||||||
import eu.kanade.domain.track.interactor.InsertTrack
|
|
||||||
import tachiyomi.data.category.CategoryRepositoryImpl
|
import tachiyomi.data.category.CategoryRepositoryImpl
|
||||||
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
||||||
import tachiyomi.data.history.HistoryRepositoryImpl
|
import tachiyomi.data.history.HistoryRepositoryImpl
|
||||||
import tachiyomi.data.manga.MangaRepositoryImpl
|
import tachiyomi.data.manga.MangaRepositoryImpl
|
||||||
import tachiyomi.data.source.SourceDataRepositoryImpl
|
import tachiyomi.data.release.ReleaseServiceImpl
|
||||||
|
import tachiyomi.data.source.SourceRepositoryImpl
|
||||||
|
import tachiyomi.data.source.StubSourceRepositoryImpl
|
||||||
import tachiyomi.data.track.TrackRepositoryImpl
|
import tachiyomi.data.track.TrackRepositoryImpl
|
||||||
import tachiyomi.data.updates.UpdatesRepositoryImpl
|
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.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.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.ShouldUpdateDbChapter
|
||||||
|
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
import tachiyomi.domain.history.interactor.GetHistory
|
import tachiyomi.domain.history.interactor.GetHistory
|
||||||
|
import tachiyomi.domain.history.interactor.GetNextChapters
|
||||||
import tachiyomi.domain.history.interactor.GetTotalReadDuration
|
import tachiyomi.domain.history.interactor.GetTotalReadDuration
|
||||||
import tachiyomi.domain.history.interactor.RemoveHistory
|
import tachiyomi.domain.history.interactor.RemoveHistory
|
||||||
import tachiyomi.domain.history.interactor.UpsertHistory
|
import tachiyomi.domain.history.interactor.UpsertHistory
|
||||||
import tachiyomi.domain.history.repository.HistoryRepository
|
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.manga.repository.MangaRepository
|
||||||
import tachiyomi.domain.source.repository.SourceDataRepository
|
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.track.repository.TrackRepository
|
||||||
import tachiyomi.domain.updates.interactor.GetUpdates
|
import tachiyomi.domain.updates.interactor.GetUpdates
|
||||||
import tachiyomi.domain.updates.repository.UpdatesRepository
|
import tachiyomi.domain.updates.repository.UpdatesRepository
|
||||||
@ -79,7 +95,7 @@ class DomainModule : InjektModule {
|
|||||||
addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) }
|
addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) }
|
||||||
addFactory { GetCategories(get()) }
|
addFactory { GetCategories(get()) }
|
||||||
addFactory { ResetCategoryFlags(get(), get()) }
|
addFactory { ResetCategoryFlags(get(), get()) }
|
||||||
addFactory { SetDisplayModeForCategory(get(), get()) }
|
addFactory { SetDisplayMode(get()) }
|
||||||
addFactory { SetSortModeForCategory(get(), get()) }
|
addFactory { SetSortModeForCategory(get(), get()) }
|
||||||
addFactory { CreateCategoryWithName(get(), get()) }
|
addFactory { CreateCategoryWithName(get(), get()) }
|
||||||
addFactory { RenameCategory(get()) }
|
addFactory { RenameCategory(get()) }
|
||||||
@ -92,30 +108,42 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { GetFavorites(get()) }
|
addFactory { GetFavorites(get()) }
|
||||||
addFactory { GetLibraryManga(get()) }
|
addFactory { GetLibraryManga(get()) }
|
||||||
addFactory { GetMangaWithChapters(get(), get()) }
|
addFactory { GetMangaWithChapters(get(), get()) }
|
||||||
|
addFactory { GetMangaByUrlAndSourceId(get()) }
|
||||||
addFactory { GetManga(get()) }
|
addFactory { GetManga(get()) }
|
||||||
addFactory { GetNextChapters(get(), get(), get()) }
|
addFactory { GetNextChapters(get(), get(), get()) }
|
||||||
addFactory { ResetViewerFlags(get()) }
|
addFactory { ResetViewerFlags(get()) }
|
||||||
addFactory { SetMangaChapterFlags(get()) }
|
addFactory { SetMangaChapterFlags(get()) }
|
||||||
|
addFactory { FetchInterval(get()) }
|
||||||
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
||||||
addFactory { SetMangaViewerFlags(get()) }
|
addFactory { SetMangaViewerFlags(get()) }
|
||||||
addFactory { NetworkToLocalManga(get()) }
|
addFactory { NetworkToLocalManga(get()) }
|
||||||
addFactory { UpdateManga(get()) }
|
addFactory { UpdateManga(get(), get()) }
|
||||||
addFactory { SetMangaCategories(get()) }
|
addFactory { SetMangaCategories(get()) }
|
||||||
|
addFactory { GetExcludedScanlators(get()) }
|
||||||
|
addFactory { SetExcludedScanlators(get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||||
|
addFactory { GetApplicationRelease(get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(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 { DeleteTrack(get()) }
|
||||||
addFactory { GetTracksPerManga(get()) }
|
addFactory { GetTracksPerManga(get()) }
|
||||||
addFactory { GetTracks(get()) }
|
addFactory { GetTracks(get()) }
|
||||||
addFactory { InsertTrack(get()) }
|
addFactory { InsertTrack(get()) }
|
||||||
|
addFactory { SyncChapterProgressWithTrack(get(), get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||||
addFactory { GetChapter(get()) }
|
addFactory { GetChapter(get()) }
|
||||||
addFactory { GetChapterByMangaId(get()) }
|
addFactory { GetChaptersByMangaId(get()) }
|
||||||
|
addFactory { GetChapterByUrlAndMangaId(get()) }
|
||||||
addFactory { UpdateChapter(get()) }
|
addFactory { UpdateChapter(get()) }
|
||||||
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
||||||
addFactory { ShouldUpdateDbChapter() }
|
addFactory { ShouldUpdateDbChapter() }
|
||||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get()) }
|
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||||
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
|
addFactory { GetAvailableScanlators(get()) }
|
||||||
|
|
||||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||||
addFactory { GetHistory(get()) }
|
addFactory { GetHistory(get()) }
|
||||||
@ -133,7 +161,7 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { GetUpdates(get()) }
|
addFactory { GetUpdates(get()) }
|
||||||
|
|
||||||
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
|
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
|
||||||
addSingletonFactory<SourceDataRepository> { SourceDataRepositoryImpl(get()) }
|
addSingletonFactory<StubSourceRepository> { StubSourceRepositoryImpl(get()) }
|
||||||
addFactory { GetEnabledSources(get(), get()) }
|
addFactory { GetEnabledSources(get(), get()) }
|
||||||
addFactory { GetLanguagesWithSources(get(), get()) }
|
addFactory { GetLanguagesWithSources(get(), get()) }
|
||||||
addFactory { GetRemoteManga(get()) }
|
addFactory { GetRemoteManga(get()) }
|
||||||
@ -143,5 +171,10 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { ToggleLanguage(get()) }
|
addFactory { ToggleLanguage(get()) }
|
||||||
addFactory { ToggleSource(get()) }
|
addFactory { ToggleSource(get()) }
|
||||||
addFactory { ToggleSourcePin(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 tachiyomi.core.preference.PreferenceStore
|
|
||||||
import tachiyomi.core.provider.FolderProvider
|
|
||||||
|
|
||||||
class BackupPreferences(
|
|
||||||
private val folderProvider: FolderProvider,
|
|
||||||
private val preferenceStore: PreferenceStore,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun backupsDirectory() = preferenceStore.getString("backup_directory", folderProvider.path())
|
|
||||||
|
|
||||||
fun numberOfBackups() = preferenceStore.getInt("backup_slots", 2)
|
|
||||||
|
|
||||||
fun backupInterval() = preferenceStore.getInt("backup_interval", 12)
|
|
||||||
}
|
|
@ -1,24 +1,31 @@
|
|||||||
package eu.kanade.domain.base
|
package eu.kanade.domain.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
|
||||||
class BasePreferences(
|
class BasePreferences(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun confirmExit() = preferenceStore.getBoolean("pref_confirm_exit", false)
|
fun downloadedOnly() = preferenceStore.getBoolean(
|
||||||
|
Preference.appStateKey("pref_downloaded_only"),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
fun downloadedOnly() = preferenceStore.getBoolean("pref_downloaded_only", false)
|
fun incognitoMode() = preferenceStore.getBoolean(Preference.appStateKey("incognito_mode"), false)
|
||||||
|
|
||||||
fun incognitoMode() = preferenceStore.getBoolean("incognito_mode", false)
|
|
||||||
|
|
||||||
fun automaticExtUpdates() = preferenceStore.getBoolean("automatic_ext_updates", true)
|
|
||||||
|
|
||||||
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
||||||
|
|
||||||
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.domain.base
|
package eu.kanade.domain.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.ExtensionInstaller
|
import eu.kanade.domain.base.BasePreferences.ExtensionInstaller
|
||||||
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
|
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
|
||||||
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -18,7 +18,7 @@ class ExtensionInstallerPreference(
|
|||||||
|
|
||||||
override fun key() = "extension_installer"
|
override fun key() = "extension_installer"
|
||||||
|
|
||||||
val entries get() = ExtensionInstaller.values().run {
|
val entries get() = ExtensionInstaller.entries.run {
|
||||||
if (context.hasMiuiPackageInstaller) {
|
if (context.hasMiuiPackageInstaller) {
|
||||||
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
|
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
|
||||||
import tachiyomi.domain.category.repository.CategoryRepository
|
|
||||||
import tachiyomi.domain.library.model.plus
|
|
||||||
|
|
||||||
class ResetCategoryFlags(
|
|
||||||
private val preferences: LibraryPreferences,
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await() {
|
|
||||||
val display = preferences.libraryDisplayMode().get()
|
|
||||||
val sort = preferences.librarySortingMode().get()
|
|
||||||
categoryRepository.updateAllFlags(display + sort.type + sort.direction)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package eu.kanade.domain.category.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
|
||||||
import tachiyomi.domain.category.model.Category
|
|
||||||
import tachiyomi.domain.category.model.CategoryUpdate
|
|
||||||
import tachiyomi.domain.category.repository.CategoryRepository
|
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
|
||||||
import tachiyomi.domain.library.model.plus
|
|
||||||
|
|
||||||
class SetDisplayModeForCategory(
|
|
||||||
private val preferences: LibraryPreferences,
|
|
||||||
private val categoryRepository: CategoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(categoryId: Long, display: LibraryDisplayMode) {
|
|
||||||
val category = categoryRepository.get(categoryId) ?: return
|
|
||||||
val flags = category.flags + display
|
|
||||||
if (preferences.categorizedDisplaySettings().get()) {
|
|
||||||
categoryRepository.updatePartial(
|
|
||||||
CategoryUpdate(
|
|
||||||
id = category.id,
|
|
||||||
flags = flags,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
preferences.libraryDisplayMode().set(display)
|
|
||||||
categoryRepository.updateAllFlags(flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(category: Category, display: LibraryDisplayMode) {
|
|
||||||
await(category.id, display)
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,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,13 +1,13 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
package eu.kanade.domain.chapter.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||||
import eu.kanade.domain.download.service.DownloadPreferences
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.chapter.model.ChapterUpdate
|
import tachiyomi.domain.chapter.model.ChapterUpdate
|
||||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
import tachiyomi.domain.download.service.DownloadPreferences
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
@ -72,9 +72,9 @@ class SetReadStatus(
|
|||||||
suspend fun await(manga: Manga, read: Boolean) =
|
suspend fun await(manga: Manga, read: Boolean) =
|
||||||
await(manga.id, read)
|
await(manga.id, read)
|
||||||
|
|
||||||
sealed class Result {
|
sealed interface Result {
|
||||||
object Success : Result()
|
data object Success : Result
|
||||||
object NoChapters : Result()
|
data object NoChapters : Result
|
||||||
data class InternalError(val error: Throwable) : Result()
|
data class InternalError(val error: Throwable) : Result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,36 +2,38 @@ package eu.kanade.domain.chapter.interactor
|
|||||||
|
|
||||||
import eu.kanade.domain.chapter.model.copyFromSChapter
|
import eu.kanade.domain.chapter.model.copyFromSChapter
|
||||||
import eu.kanade.domain.chapter.model.toSChapter
|
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.interactor.UpdateManga
|
||||||
import eu.kanade.domain.manga.model.toSManga
|
import eu.kanade.domain.manga.model.toSManga
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.isLocal
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
|
|
||||||
import tachiyomi.data.chapter.ChapterSanitizer
|
import tachiyomi.data.chapter.ChapterSanitizer
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||||
|
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.chapter.model.NoChaptersException
|
import tachiyomi.domain.chapter.model.NoChaptersException
|
||||||
import tachiyomi.domain.chapter.model.toChapterUpdate
|
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
import tachiyomi.domain.chapter.service.ChapterRecognition
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import uy.kohesive.injekt.Injekt
|
import tachiyomi.source.local.isLocal
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.lang.Long.max
|
import java.lang.Long.max
|
||||||
import java.util.Date
|
import java.time.ZonedDateTime
|
||||||
import java.util.TreeSet
|
import java.util.TreeSet
|
||||||
|
|
||||||
class SyncChaptersWithSource(
|
class SyncChaptersWithSource(
|
||||||
private val downloadManager: DownloadManager = Injekt.get(),
|
private val downloadManager: DownloadManager,
|
||||||
private val downloadProvider: DownloadProvider = Injekt.get(),
|
private val downloadProvider: DownloadProvider,
|
||||||
private val chapterRepository: ChapterRepository = Injekt.get(),
|
private val chapterRepository: ChapterRepository,
|
||||||
private val shouldUpdateDbChapter: ShouldUpdateDbChapter = Injekt.get(),
|
private val shouldUpdateDbChapter: ShouldUpdateDbChapter,
|
||||||
private val updateManga: UpdateManga = Injekt.get(),
|
private val updateManga: UpdateManga,
|
||||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
private val updateChapter: UpdateChapter,
|
||||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
|
private val getExcludedScanlators: GetExcludedScanlators,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,11 +48,16 @@ class SyncChaptersWithSource(
|
|||||||
rawSourceChapters: List<SChapter>,
|
rawSourceChapters: List<SChapter>,
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
source: Source,
|
source: Source,
|
||||||
|
manualFetch: Boolean = false,
|
||||||
|
fetchWindow: Pair<Long, Long> = Pair(0, 0),
|
||||||
): List<Chapter> {
|
): List<Chapter> {
|
||||||
if (rawSourceChapters.isEmpty() && !source.isLocal()) {
|
if (rawSourceChapters.isEmpty() && !source.isLocal()) {
|
||||||
throw NoChaptersException()
|
throw NoChaptersException()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val now = ZonedDateTime.now()
|
||||||
|
val nowMillis = now.toInstant().toEpochMilli()
|
||||||
|
|
||||||
val sourceChapters = rawSourceChapters
|
val sourceChapters = rawSourceChapters
|
||||||
.distinctBy { it.url }
|
.distinctBy { it.url }
|
||||||
.mapIndexed { i, sChapter ->
|
.mapIndexed { i, sChapter ->
|
||||||
@ -60,36 +67,27 @@ class SyncChaptersWithSource(
|
|||||||
.copy(mangaId = manga.id, sourceOrder = i.toLong())
|
.copy(mangaId = manga.id, sourceOrder = i.toLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chapters from db.
|
val dbChapters = getChaptersByMangaId.await(manga.id)
|
||||||
val dbChapters = getChapterByMangaId.await(manga.id)
|
|
||||||
|
|
||||||
// Chapters from the source not in db.
|
val newChapters = mutableListOf<Chapter>()
|
||||||
val toAdd = mutableListOf<Chapter>()
|
val updatedChapters = mutableListOf<Chapter>()
|
||||||
|
val removedChapters = dbChapters.filterNot { dbChapter ->
|
||||||
// Chapters whose metadata have changed.
|
|
||||||
val toChange = mutableListOf<Chapter>()
|
|
||||||
|
|
||||||
// Chapters from the db not in source.
|
|
||||||
val toDelete = dbChapters.filterNot { dbChapter ->
|
|
||||||
sourceChapters.any { sourceChapter ->
|
sourceChapters.any { sourceChapter ->
|
||||||
dbChapter.url == sourceChapter.url
|
dbChapter.url == sourceChapter.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val rightNow = Date().time
|
|
||||||
|
|
||||||
// Used to not set upload date of older chapters
|
// Used to not set upload date of older chapters
|
||||||
// to a higher value than newer chapters
|
// to a higher value than newer chapters
|
||||||
var maxSeenUploadDate = 0L
|
var maxSeenUploadDate = 0L
|
||||||
|
|
||||||
val sManga = manga.toSManga()
|
|
||||||
for (sourceChapter in sourceChapters) {
|
for (sourceChapter in sourceChapters) {
|
||||||
var chapter = sourceChapter
|
var chapter = sourceChapter
|
||||||
|
|
||||||
// Update metadata from source if necessary.
|
// Update metadata from source if necessary.
|
||||||
if (source is HttpSource) {
|
if (source is HttpSource) {
|
||||||
val sChapter = chapter.toSChapter()
|
val sChapter = chapter.toSChapter()
|
||||||
source.prepareNewChapter(sChapter, sManga)
|
source.prepareNewChapter(sChapter, manga.toSManga())
|
||||||
chapter = chapter.copyFromSChapter(sChapter)
|
chapter = chapter.copyFromSChapter(sChapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,17 +99,19 @@ class SyncChaptersWithSource(
|
|||||||
|
|
||||||
if (dbChapter == null) {
|
if (dbChapter == null) {
|
||||||
val toAddChapter = if (chapter.dateUpload == 0L) {
|
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)
|
chapter.copy(dateUpload = altDateUpload)
|
||||||
} else {
|
} else {
|
||||||
maxSeenUploadDate = max(maxSeenUploadDate, sourceChapter.dateUpload)
|
maxSeenUploadDate = max(maxSeenUploadDate, sourceChapter.dateUpload)
|
||||||
chapter
|
chapter
|
||||||
}
|
}
|
||||||
toAdd.add(toAddChapter)
|
newChapters.add(toAddChapter)
|
||||||
} else {
|
} else {
|
||||||
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
||||||
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(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) {
|
if (shouldRenameChapter) {
|
||||||
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
||||||
@ -125,36 +125,43 @@ class SyncChaptersWithSource(
|
|||||||
if (chapter.dateUpload != 0L) {
|
if (chapter.dateUpload != 0L) {
|
||||||
toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload)
|
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.
|
// Return if there's nothing to add, delete, or update to avoid unnecessary db transactions.
|
||||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
if (newChapters.isEmpty() && removedChapters.isEmpty() && updatedChapters.isEmpty()) {
|
||||||
|
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
|
||||||
|
updateManga.awaitUpdateFetchInterval(
|
||||||
|
manga,
|
||||||
|
now,
|
||||||
|
fetchWindow,
|
||||||
|
)
|
||||||
|
}
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
val reAdded = mutableListOf<Chapter>()
|
val reAdded = mutableListOf<Chapter>()
|
||||||
|
|
||||||
val deletedChapterNumbers = TreeSet<Float>()
|
val deletedChapterNumbers = TreeSet<Double>()
|
||||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
val deletedReadChapterNumbers = TreeSet<Double>()
|
||||||
val deletedBookmarkedChapterNumbers = TreeSet<Float>()
|
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
|
||||||
|
|
||||||
toDelete.forEach { chapter ->
|
removedChapters.forEach { chapter ->
|
||||||
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
||||||
if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber)
|
if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber)
|
||||||
deletedChapterNumbers.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 }
|
.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
|
// 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.
|
// Sources MUST return the chapters from most to less recent, which is common.
|
||||||
var itemCount = toAdd.size
|
var itemCount = newChapters.size
|
||||||
var updatedToAdd = toAdd.map { toAddItem ->
|
var updatedToAdd = newChapters.map { toAddItem ->
|
||||||
var chapter = toAddItem.copy(dateFetch = rightNow + itemCount--)
|
var chapter = toAddItem.copy(dateFetch = nowMillis + itemCount--)
|
||||||
|
|
||||||
if (chapter.isRecognizedNumber.not() || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
if (chapter.isRecognizedNumber.not() || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
||||||
|
|
||||||
@ -173,8 +180,8 @@ class SyncChaptersWithSource(
|
|||||||
chapter
|
chapter
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toDelete.isNotEmpty()) {
|
if (removedChapters.isNotEmpty()) {
|
||||||
val toDeleteIds = toDelete.map { it.id }
|
val toDeleteIds = removedChapters.map { it.id }
|
||||||
chapterRepository.removeChaptersWithIds(toDeleteIds)
|
chapterRepository.removeChaptersWithIds(toDeleteIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,10 +189,11 @@ class SyncChaptersWithSource(
|
|||||||
updatedToAdd = chapterRepository.addAll(updatedToAdd)
|
updatedToAdd = chapterRepository.addAll(updatedToAdd)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toChange.isNotEmpty()) {
|
if (updatedChapters.isNotEmpty()) {
|
||||||
val chapterUpdates = toChange.map { it.toChapterUpdate() }
|
val chapterUpdates = updatedChapters.map { it.toChapterUpdate() }
|
||||||
updateChapter.awaitAll(chapterUpdates)
|
updateChapter.awaitAll(chapterUpdates)
|
||||||
}
|
}
|
||||||
|
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
|
||||||
|
|
||||||
// Set this manga as updated since chapters were changed
|
// Set this manga as updated since chapters were changed
|
||||||
// Note that last_update actually represents last time the chapter list changed at all
|
// Note that last_update actually represents last time the chapter list changed at all
|
||||||
@ -193,6 +201,10 @@ class SyncChaptersWithSource(
|
|||||||
|
|
||||||
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ fun Chapter.toSChapter(): SChapter {
|
|||||||
it.url = url
|
it.url = url
|
||||||
it.name = name
|
it.name = name
|
||||||
it.date_upload = dateUpload
|
it.date_upload = dateUpload
|
||||||
it.chapter_number = chapterNumber
|
it.chapter_number = chapterNumber.toFloat()
|
||||||
it.scanlator = scanlator
|
it.scanlator = scanlator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -21,8 +21,8 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
|
|||||||
name = sChapter.name,
|
name = sChapter.name,
|
||||||
url = sChapter.url,
|
url = sChapter.url,
|
||||||
dateUpload = sChapter.date_upload,
|
dateUpload = sChapter.date_upload,
|
||||||
chapterNumber = sChapter.chapter_number,
|
chapterNumber = sChapter.chapter_number.toDouble(),
|
||||||
scanlator = sChapter.scanlator?.ifBlank { null },
|
scanlator = sChapter.scanlator?.ifBlank { null }?.trim(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,6 +37,6 @@ fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
|||||||
it.last_page_read = lastPageRead.toInt()
|
it.last_page_read = lastPageRead.toInt()
|
||||||
it.date_fetch = dateFetch
|
it.date_fetch = dateFetch
|
||||||
it.date_upload = dateUpload
|
it.date_upload = dateUpload
|
||||||
it.chapter_number = chapterNumber
|
it.chapter_number = chapterNumber.toFloat()
|
||||||
it.source_order = sourceOrder.toInt()
|
it.source_order = sourceOrder.toInt()
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
package eu.kanade.domain.chapter.model
|
package eu.kanade.domain.chapter.model
|
||||||
|
|
||||||
import eu.kanade.domain.manga.model.downloadedFilter
|
import eu.kanade.domain.manga.model.downloadedFilter
|
||||||
import eu.kanade.domain.manga.model.isLocal
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
||||||
import eu.kanade.tachiyomi.ui.manga.ChapterItem
|
|
||||||
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.chapter.service.getChapterSort
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.TriStateFilter
|
import tachiyomi.domain.manga.model.applyFilter
|
||||||
|
import tachiyomi.source.local.isLocal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the view filters to the list of chapters obtained from the database.
|
* Applies the view filters to the list of chapters obtained from the database.
|
||||||
@ -20,30 +19,17 @@ fun List<Chapter>.applyFilters(manga: Manga, downloadManager: DownloadManager):
|
|||||||
val downloadedFilter = manga.downloadedFilter
|
val downloadedFilter = manga.downloadedFilter
|
||||||
val bookmarkedFilter = manga.bookmarkedFilter
|
val bookmarkedFilter = manga.bookmarkedFilter
|
||||||
|
|
||||||
return filter { chapter ->
|
return filter { chapter -> applyFilter(unreadFilter) { !chapter.read } }
|
||||||
when (unreadFilter) {
|
.filter { chapter -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
|
||||||
TriStateFilter.DISABLED -> true
|
|
||||||
TriStateFilter.ENABLED_IS -> !chapter.read
|
|
||||||
TriStateFilter.ENABLED_NOT -> chapter.read
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.filter { chapter ->
|
.filter { chapter ->
|
||||||
when (bookmarkedFilter) {
|
applyFilter(downloadedFilter) {
|
||||||
TriStateFilter.DISABLED -> true
|
val downloaded = downloadManager.isChapterDownloaded(
|
||||||
TriStateFilter.ENABLED_IS -> chapter.bookmark
|
chapter.name,
|
||||||
TriStateFilter.ENABLED_NOT -> !chapter.bookmark
|
chapter.scanlator,
|
||||||
}
|
manga.title,
|
||||||
}
|
manga.source,
|
||||||
.filter { chapter ->
|
)
|
||||||
val downloaded = downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.title, manga.source)
|
downloaded || isLocalManga
|
||||||
val downloadState = when {
|
|
||||||
downloaded -> Download.State.DOWNLOADED
|
|
||||||
else -> Download.State.NOT_DOWNLOADED
|
|
||||||
}
|
|
||||||
when (downloadedFilter) {
|
|
||||||
TriStateFilter.DISABLED -> true
|
|
||||||
TriStateFilter.ENABLED_IS -> downloadState == Download.State.DOWNLOADED || isLocalManga
|
|
||||||
TriStateFilter.ENABLED_NOT -> downloadState != Download.State.DOWNLOADED && !isLocalManga
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sortedWith(getChapterSort(manga))
|
.sortedWith(getChapterSort(manga))
|
||||||
@ -53,32 +39,14 @@ fun List<Chapter>.applyFilters(manga: Manga, downloadManager: DownloadManager):
|
|||||||
* Applies the view filters to the list of chapters obtained from the database.
|
* Applies the view filters to the list of chapters obtained from the database.
|
||||||
* @return an observable of the list of chapters filtered and sorted.
|
* @return an observable of the list of chapters filtered and sorted.
|
||||||
*/
|
*/
|
||||||
fun List<ChapterItem>.applyFilters(manga: Manga): Sequence<ChapterItem> {
|
fun List<ChapterList.Item>.applyFilters(manga: Manga): Sequence<ChapterList.Item> {
|
||||||
val isLocalManga = manga.isLocal()
|
val isLocalManga = manga.isLocal()
|
||||||
val unreadFilter = manga.unreadFilter
|
val unreadFilter = manga.unreadFilter
|
||||||
val downloadedFilter = manga.downloadedFilter
|
val downloadedFilter = manga.downloadedFilter
|
||||||
val bookmarkedFilter = manga.bookmarkedFilter
|
val bookmarkedFilter = manga.bookmarkedFilter
|
||||||
return asSequence()
|
return asSequence()
|
||||||
.filter { (chapter) ->
|
.filter { (chapter) -> applyFilter(unreadFilter) { !chapter.read } }
|
||||||
when (unreadFilter) {
|
.filter { (chapter) -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
|
||||||
TriStateFilter.DISABLED -> true
|
.filter { applyFilter(downloadedFilter) { it.isDownloaded || isLocalManga } }
|
||||||
TriStateFilter.ENABLED_IS -> !chapter.read
|
|
||||||
TriStateFilter.ENABLED_NOT -> chapter.read
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.filter { (chapter) ->
|
|
||||||
when (bookmarkedFilter) {
|
|
||||||
TriStateFilter.DISABLED -> true
|
|
||||||
TriStateFilter.ENABLED_IS -> chapter.bookmark
|
|
||||||
TriStateFilter.ENABLED_NOT -> !chapter.bookmark
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.filter {
|
|
||||||
when (downloadedFilter) {
|
|
||||||
TriStateFilter.DISABLED -> true
|
|
||||||
TriStateFilter.ENABLED_IS -> it.isDownloaded || isLocalManga
|
|
||||||
TriStateFilter.ENABLED_NOT -> !it.isDownloaded && !isLocalManga
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) }
|
.sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) }
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package eu.kanade.domain.download.interactor
|
package eu.kanade.domain.download.interactor
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
|
|
||||||
class DeleteDownload(
|
class DeleteDownload(
|
||||||
private val sourceManager: SourceManager,
|
private val sourceManager: SourceManager,
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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,111 +0,0 @@
|
|||||||
package eu.kanade.domain.library.service
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI
|
|
||||||
import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD
|
|
||||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
|
|
||||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ
|
|
||||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
|
||||||
import tachiyomi.domain.library.model.LibrarySort
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
|
||||||
|
|
||||||
class LibraryPreferences(
|
|
||||||
private val preferenceStore: PreferenceStore,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun libraryDisplayMode() = preferenceStore.getObject("pref_display_mode_library", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
|
|
||||||
|
|
||||||
fun librarySortingMode() = preferenceStore.getObject("library_sorting_mode", LibrarySort.default, LibrarySort.Serializer::serialize, LibrarySort.Serializer::deserialize)
|
|
||||||
|
|
||||||
fun portraitColumns() = preferenceStore.getInt("pref_library_columns_portrait_key", 0)
|
|
||||||
|
|
||||||
fun landscapeColumns() = preferenceStore.getInt("pref_library_columns_landscape_key", 0)
|
|
||||||
|
|
||||||
fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
|
|
||||||
fun libraryUpdateLastTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L)
|
|
||||||
|
|
||||||
fun libraryUpdateDeviceRestriction() = preferenceStore.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI))
|
|
||||||
fun libraryUpdateMangaRestriction() = preferenceStore.getStringSet("library_update_manga_restriction", setOf(MANGA_HAS_UNREAD, MANGA_NON_COMPLETED, MANGA_NON_READ))
|
|
||||||
|
|
||||||
fun autoUpdateMetadata() = preferenceStore.getBoolean("auto_update_metadata", false)
|
|
||||||
|
|
||||||
fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", false)
|
|
||||||
|
|
||||||
fun showContinueReadingButton() = preferenceStore.getBoolean("display_continue_reading_button", false)
|
|
||||||
|
|
||||||
// region Filter
|
|
||||||
|
|
||||||
fun filterDownloaded() = preferenceStore.getInt("pref_filter_library_downloaded", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
|
||||||
|
|
||||||
fun filterUnread() = preferenceStore.getInt("pref_filter_library_unread", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
|
||||||
|
|
||||||
fun filterStarted() = preferenceStore.getInt("pref_filter_library_started", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
|
||||||
|
|
||||||
fun filterBookmarked() = preferenceStore.getInt("pref_filter_library_bookmarked", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
|
||||||
|
|
||||||
fun filterCompleted() = preferenceStore.getInt("pref_filter_library_completed", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
|
||||||
|
|
||||||
fun filterTracking(name: Int) = preferenceStore.getInt("pref_filter_library_tracked_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Badges
|
|
||||||
|
|
||||||
fun downloadBadge() = preferenceStore.getBoolean("display_download_badge", false)
|
|
||||||
|
|
||||||
fun localBadge() = preferenceStore.getBoolean("display_local_badge", true)
|
|
||||||
|
|
||||||
fun languageBadge() = preferenceStore.getBoolean("display_language_badge", false)
|
|
||||||
|
|
||||||
fun newShowUpdatesCount() = preferenceStore.getBoolean("library_show_updates_count", true)
|
|
||||||
fun newUpdatesCount() = preferenceStore.getInt("library_unseen_updates_count", 0)
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Category
|
|
||||||
|
|
||||||
fun defaultCategory() = preferenceStore.getInt("default_category", -1)
|
|
||||||
|
|
||||||
fun lastUsedCategory() = preferenceStore.getInt("last_used_category", 0)
|
|
||||||
|
|
||||||
fun categoryTabs() = preferenceStore.getBoolean("display_category_tabs", true)
|
|
||||||
|
|
||||||
fun categoryNumberOfItems() = preferenceStore.getBoolean("display_number_of_items", false)
|
|
||||||
|
|
||||||
fun categorizedDisplaySettings() = preferenceStore.getBoolean("categorized_display", false)
|
|
||||||
|
|
||||||
fun libraryUpdateCategories() = preferenceStore.getStringSet("library_update_categories", emptySet())
|
|
||||||
|
|
||||||
fun libraryUpdateCategoriesExclude() = preferenceStore.getStringSet("library_update_categories_exclude", emptySet())
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Chapter
|
|
||||||
|
|
||||||
fun filterChapterByRead() = preferenceStore.getLong("default_chapter_filter_by_read", Manga.SHOW_ALL)
|
|
||||||
|
|
||||||
fun filterChapterByDownloaded() = preferenceStore.getLong("default_chapter_filter_by_downloaded", Manga.SHOW_ALL)
|
|
||||||
|
|
||||||
fun filterChapterByBookmarked() = preferenceStore.getLong("default_chapter_filter_by_bookmarked", Manga.SHOW_ALL)
|
|
||||||
|
|
||||||
// and upload date
|
|
||||||
fun sortChapterBySourceOrNumber() = preferenceStore.getLong("default_chapter_sort_by_source_or_number", Manga.CHAPTER_SORTING_SOURCE)
|
|
||||||
|
|
||||||
fun displayChapterByNameOrNumber() = preferenceStore.getLong("default_chapter_display_by_name_or_number", Manga.CHAPTER_DISPLAY_NAME)
|
|
||||||
|
|
||||||
fun sortChapterByAscendingOrDescending() = preferenceStore.getLong("default_chapter_sort_by_ascending_or_descending", Manga.CHAPTER_SORT_DESC)
|
|
||||||
|
|
||||||
fun setChapterSettingsDefault(manga: Manga) {
|
|
||||||
filterChapterByRead().set(manga.unreadFilterRaw)
|
|
||||||
filterChapterByDownloaded().set(manga.downloadedFilterRaw)
|
|
||||||
filterChapterByBookmarked().set(manga.bookmarkedFilterRaw)
|
|
||||||
sortChapterBySourceOrNumber().set(manga.sorting)
|
|
||||||
displayChapterByNameOrNumber().set(manga.displayMode)
|
|
||||||
sortChapterByAscendingOrDescending().set(if (manga.sortDescending()) Manga.CHAPTER_SORT_DESC else Manga.CHAPTER_SORT_ASC)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun autoClearChapterCache() = preferenceStore.getBoolean("auto_clear_chapter_cache", false)
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
}
|
|
@ -0,0 +1,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() }
|
||||||
|
}
|
||||||
|
}
|
@ -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,7 +1,7 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
@ -9,22 +9,22 @@ class SetMangaViewerFlags(
|
|||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun awaitSetMangaReadingMode(id: Long, flag: Long) {
|
suspend fun awaitSetReadingMode(id: Long, flag: Long) {
|
||||||
val manga = mangaRepository.getMangaById(id)
|
val manga = mangaRepository.getMangaById(id)
|
||||||
mangaRepository.update(
|
mangaRepository.update(
|
||||||
MangaUpdate(
|
MangaUpdate(
|
||||||
id = id,
|
id = id,
|
||||||
viewerFlags = manga.viewerFlags.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)
|
val manga = mangaRepository.getMangaById(id)
|
||||||
mangaRepository.update(
|
mangaRepository.update(
|
||||||
MangaUpdate(
|
MangaUpdate(
|
||||||
id = id,
|
id = id,
|
||||||
viewerFlags = manga.viewerFlags.setFlag(flag, OrientationType.MASK.toLong()),
|
viewerFlags = manga.viewerFlags.setFlag(flag, ReaderOrientation.MASK.toLong()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.manga.model.hasCustomCover
|
import eu.kanade.domain.manga.model.hasCustomCover
|
||||||
import eu.kanade.domain.manga.model.isLocal
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
import tachiyomi.source.local.isLocal
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Date
|
import java.time.Instant
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
class UpdateManga(
|
class UpdateManga(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
|
private val fetchInterval: FetchInterval,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
|
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
|
||||||
@ -43,14 +46,14 @@ class UpdateManga(
|
|||||||
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
||||||
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
||||||
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
||||||
localManga.isLocal() -> Date().time
|
localManga.isLocal() -> Instant.now().toEpochMilli()
|
||||||
localManga.hasCustomCover(coverCache) -> {
|
localManga.hasCustomCover(coverCache) -> {
|
||||||
coverCache.deleteFromCache(localManga, false)
|
coverCache.deleteFromCache(localManga, false)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
coverCache.deleteFromCache(localManga, false)
|
coverCache.deleteFromCache(localManga, false)
|
||||||
Date().time
|
Instant.now().toEpochMilli()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,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 {
|
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 {
|
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 {
|
suspend fun awaitUpdateFavorite(mangaId: Long, favorite: Boolean): Boolean {
|
||||||
val dateAdded = when (favorite) {
|
val dateAdded = when (favorite) {
|
||||||
true -> Date().time
|
true -> Instant.now().toEpochMilli()
|
||||||
false -> 0
|
false -> 0
|
||||||
}
|
}
|
||||||
return mangaRepository.update(
|
return mangaRepository.update(
|
||||||
|
@ -2,35 +2,37 @@ package eu.kanade.domain.manga.model
|
|||||||
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
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 tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.TriStateFilter
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
// TODO: move these into the domain model
|
// TODO: move these into the domain model
|
||||||
val Manga.readingModeType: Long
|
val Manga.readingMode: Long
|
||||||
get() = viewerFlags and ReadingModeType.MASK.toLong()
|
get() = viewerFlags and ReadingMode.MASK.toLong()
|
||||||
|
|
||||||
val Manga.orientationType: Long
|
val Manga.readerOrientation: Long
|
||||||
get() = viewerFlags and OrientationType.MASK.toLong()
|
get() = viewerFlags and ReaderOrientation.MASK.toLong()
|
||||||
|
|
||||||
val Manga.downloadedFilter: TriStateFilter
|
val Manga.downloadedFilter: TriState
|
||||||
get() {
|
get() {
|
||||||
if (forceDownloaded()) return TriStateFilter.ENABLED_IS
|
if (forceDownloaded()) return TriState.ENABLED_IS
|
||||||
return when (downloadedFilterRaw) {
|
return when (downloadedFilterRaw) {
|
||||||
Manga.CHAPTER_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS
|
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
||||||
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT
|
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
||||||
else -> TriStateFilter.DISABLED
|
else -> TriState.DISABLED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun Manga.chaptersFiltered(): Boolean {
|
fun Manga.chaptersFiltered(): Boolean {
|
||||||
return unreadFilter != TriStateFilter.DISABLED ||
|
return unreadFilter != TriState.DISABLED ||
|
||||||
downloadedFilter != TriStateFilter.DISABLED ||
|
downloadedFilter != TriState.DISABLED ||
|
||||||
bookmarkedFilter != TriStateFilter.DISABLED
|
bookmarkedFilter != TriState.DISABLED
|
||||||
}
|
}
|
||||||
fun Manga.forceDownloaded(): Boolean {
|
fun Manga.forceDownloaded(): Boolean {
|
||||||
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
||||||
@ -86,8 +88,36 @@ fun SManga.toDomainManga(sourceId: Long): Manga {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Manga.isLocal(): Boolean = source == LocalSource.ID
|
|
||||||
|
|
||||||
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
||||||
return coverCache.getCustomCoverFile(id).exists()
|
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,14 +1,14 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import tachiyomi.domain.source.model.Pin
|
import tachiyomi.domain.source.model.Pin
|
||||||
import tachiyomi.domain.source.model.Pins
|
import tachiyomi.domain.source.model.Pins
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.domain.source.repository.SourceRepository
|
||||||
|
import tachiyomi.source.local.isLocal
|
||||||
|
|
||||||
class GetEnabledSources(
|
class GetEnabledSources(
|
||||||
private val repository: SourceRepository,
|
private val repository: SourceRepository,
|
||||||
@ -24,7 +24,7 @@ class GetEnabledSources(
|
|||||||
repository.getSources(),
|
repository.getSources(),
|
||||||
) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources ->
|
) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources ->
|
||||||
sources
|
sources
|
||||||
.filter { it.lang in enabledLanguages || it.id == LocalSource.ID }
|
.filter { it.lang in enabledLanguages || it.isLocal() }
|
||||||
.filterNot { it.id.toString() in disabledSources }
|
.filterNot { it.id.toString() in disabledSources }
|
||||||
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||||
.flatMap {
|
.flatMap {
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.domain.source.repository.SourceRepository
|
||||||
|
import java.util.SortedMap
|
||||||
|
|
||||||
class GetLanguagesWithSources(
|
class GetLanguagesWithSources(
|
||||||
private val repository: SourceRepository,
|
private val repository: SourceRepository,
|
||||||
private val preferences: SourcePreferences,
|
private val preferences: SourcePreferences,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun subscribe(): Flow<Map<String, List<Source>>> {
|
fun subscribe(): Flow<SortedMap<String, List<Source>>> {
|
||||||
return combine(
|
return combine(
|
||||||
preferences.enabledLanguages().changes(),
|
preferences.enabledLanguages().changes(),
|
||||||
preferences.disabledSources().changes(),
|
preferences.disabledSources().changes(),
|
||||||
@ -23,7 +24,8 @@ class GetLanguagesWithSources(
|
|||||||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
||||||
)
|
)
|
||||||
|
|
||||||
sortedSources.groupBy { it.lang }
|
sortedSources
|
||||||
|
.groupBy { it.lang }
|
||||||
.toSortedMap(
|
.toSortedMap(
|
||||||
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
||||||
)
|
)
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import tachiyomi.core.util.lang.compareToWithCollator
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
import java.text.Collator
|
import tachiyomi.domain.source.repository.SourceRepository
|
||||||
|
import tachiyomi.source.local.isLocal
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class GetSourcesWithFavoriteCount(
|
class GetSourcesWithFavoriteCount(
|
||||||
private val repository: SourceRepository,
|
private val repository: SourceRepository,
|
||||||
@ -20,7 +20,9 @@ class GetSourcesWithFavoriteCount(
|
|||||||
preferences.migrationSortingMode().changes(),
|
preferences.migrationSortingMode().changes(),
|
||||||
repository.getSourcesWithFavoriteCount(),
|
repository.getSourcesWithFavoriteCount(),
|
||||||
) { direction, mode, list ->
|
) { 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,
|
direction: SetMigrateSorting.Direction,
|
||||||
sorting: SetMigrateSorting.Mode,
|
sorting: SetMigrateSorting.Mode,
|
||||||
): java.util.Comparator<Pair<Source, Long>> {
|
): 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 ->
|
val sortFn: (Pair<Source, Long>, Pair<Source, Long>) -> Int = { a, b ->
|
||||||
when (sorting) {
|
when (sorting) {
|
||||||
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
||||||
when {
|
when {
|
||||||
a.first.isStub && b.first.isStub.not() -> -1
|
a.first.isStub && b.first.isStub.not() -> -1
|
||||||
b.first.isStub && a.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 -> {
|
SetMigrateSorting.Mode.TOTAL -> {
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
package eu.kanade.domain.source.model
|
|
||||||
|
|
||||||
import androidx.paging.PagingSource
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
|
|
||||||
typealias SourcePagingSourceType = PagingSource<Long, SManga>
|
|
@ -2,6 +2,7 @@ package eu.kanade.domain.source.service
|
|||||||
|
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.core.preference.getEnum
|
import tachiyomi.core.preference.getEnum
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
@ -10,7 +11,12 @@ class SourcePreferences(
|
|||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun sourceDisplayMode() = preferenceStore.getObject("pref_display_mode_catalogue", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
|
fun sourceDisplayMode() = preferenceStore.getObject(
|
||||||
|
"pref_display_mode_catalogue",
|
||||||
|
LibraryDisplayMode.default,
|
||||||
|
LibraryDisplayMode.Serializer::serialize,
|
||||||
|
LibraryDisplayMode.Serializer::deserialize,
|
||||||
|
)
|
||||||
|
|
||||||
fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
|
fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
|
||||||
|
|
||||||
@ -18,17 +24,28 @@ class SourcePreferences(
|
|||||||
|
|
||||||
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
||||||
|
|
||||||
fun lastUsedSource() = preferenceStore.getLong("last_catalogue_source", -1)
|
fun lastUsedSource() = preferenceStore.getLong(
|
||||||
|
Preference.appStateKey("last_catalogue_source"),
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
|
||||||
fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true)
|
fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true)
|
||||||
|
|
||||||
fun migrationSortingMode() = preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL)
|
fun migrationSortingMode() = preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL)
|
||||||
|
|
||||||
fun migrationSortingDirection() = preferenceStore.getEnum("pref_migration_direction", SetMigrateSorting.Direction.ASCENDING)
|
fun migrationSortingDirection() = preferenceStore.getEnum(
|
||||||
|
"pref_migration_direction",
|
||||||
|
SetMigrateSorting.Direction.ASCENDING,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
||||||
|
|
||||||
|
fun extensionRepos() = preferenceStore.getStringSet("extension_repos", emptySet())
|
||||||
|
|
||||||
fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
||||||
|
|
||||||
fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet())
|
fun trustedExtensions() = preferenceStore.getStringSet(
|
||||||
|
Preference.appStateKey("trusted_extensions"),
|
||||||
fun searchPinnedSourcesOnly() = preferenceStore.getBoolean("search_pinned_sources_only", false)
|
emptySet(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
107
app/src/main/java/eu/kanade/domain/track/interactor/AddTracks.kt
Normal file
107
app/src/main/java/eu/kanade/domain/track/interactor/AddTracks.kt
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package eu.kanade.domain.track.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
|
import eu.kanade.domain.track.model.toDomainTrack
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||||
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
|
import tachiyomi.domain.history.interactor.GetHistory
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
|
class AddTracks(
|
||||||
|
private val getTracks: GetTracks,
|
||||||
|
private val insertTrack: InsertTrack,
|
||||||
|
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
|
||||||
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
|
) {
|
||||||
|
|
||||||
|
// TODO: update all trackers based on common data
|
||||||
|
suspend fun bind(tracker: Tracker, item: Track, mangaId: Long) = withNonCancellableContext {
|
||||||
|
withIOContext {
|
||||||
|
val allChapters = getChaptersByMangaId.await(mangaId)
|
||||||
|
val hasReadChapters = allChapters.any { it.read }
|
||||||
|
tracker.bind(item, hasReadChapters)
|
||||||
|
|
||||||
|
var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext
|
||||||
|
|
||||||
|
insertTrack.await(track)
|
||||||
|
|
||||||
|
// TODO: merge into [SyncChapterProgressWithTrack]?
|
||||||
|
// Update chapter progress if newer chapters marked read locally
|
||||||
|
if (hasReadChapters) {
|
||||||
|
val latestLocalReadChapterNumber = allChapters
|
||||||
|
.sortedBy { it.chapterNumber }
|
||||||
|
.takeWhile { it.read }
|
||||||
|
.lastOrNull()
|
||||||
|
?.chapterNumber ?: -1.0
|
||||||
|
|
||||||
|
if (latestLocalReadChapterNumber > track.lastChapterRead) {
|
||||||
|
track = track.copy(
|
||||||
|
lastChapterRead = latestLocalReadChapterNumber,
|
||||||
|
)
|
||||||
|
tracker.setRemoteLastChapterRead(track.toDbTrack(), latestLocalReadChapterNumber.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (track.startDate <= 0) {
|
||||||
|
val firstReadChapterDate = Injekt.get<GetHistory>().await(mangaId)
|
||||||
|
.sortedBy { it.readAt }
|
||||||
|
.firstOrNull()
|
||||||
|
?.readAt
|
||||||
|
|
||||||
|
firstReadChapterDate?.let {
|
||||||
|
val startDate = firstReadChapterDate.time.convertEpochMillisZone(
|
||||||
|
ZoneOffset.systemDefault(),
|
||||||
|
ZoneOffset.UTC,
|
||||||
|
)
|
||||||
|
track = track.copy(
|
||||||
|
startDate = startDate,
|
||||||
|
)
|
||||||
|
tracker.setRemoteStartDate(track.toDbTrack(), startDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
syncChapterProgressWithTrack.await(mangaId, track, tracker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun bindEnhancedTrackers(manga: Manga, source: Source) = withNonCancellableContext {
|
||||||
|
withIOContext {
|
||||||
|
getTracks.await(manga.id)
|
||||||
|
.filterIsInstance<EnhancedTracker>()
|
||||||
|
.filter { it.accept(source) }
|
||||||
|
.forEach { service ->
|
||||||
|
try {
|
||||||
|
service.match(manga)?.let { track ->
|
||||||
|
track.manga_id = manga.id
|
||||||
|
(service as Tracker).bind(track)
|
||||||
|
insertTrack.await(track.toDomainTrack()!!)
|
||||||
|
|
||||||
|
syncChapterProgressWithTrack.await(
|
||||||
|
manga.id,
|
||||||
|
track.toDomainTrack()!!,
|
||||||
|
service,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(
|
||||||
|
LogPriority.WARN,
|
||||||
|
e,
|
||||||
|
) { "Could not match manga: ${manga.title} with service $service" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package eu.kanade.domain.track.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
|
import eu.kanade.domain.track.model.toDomainTrack
|
||||||
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.supervisorScope
|
||||||
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
|
|
||||||
|
class RefreshTracks(
|
||||||
|
private val getTracks: GetTracks,
|
||||||
|
private val trackerManager: TrackerManager,
|
||||||
|
private val insertTrack: InsertTrack,
|
||||||
|
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches updated tracking data from all logged in trackers.
|
||||||
|
*
|
||||||
|
* @return Failed updates.
|
||||||
|
*/
|
||||||
|
suspend fun await(mangaId: Long): List<Pair<Tracker?, Throwable>> {
|
||||||
|
return supervisorScope {
|
||||||
|
return@supervisorScope getTracks.await(mangaId)
|
||||||
|
.map { it to trackerManager.get(it.trackerId) }
|
||||||
|
.filter { (_, service) -> service?.isLoggedIn == true }
|
||||||
|
.map { (track, service) ->
|
||||||
|
async {
|
||||||
|
return@async try {
|
||||||
|
val updatedTrack = service!!.refresh(track.toDbTrack())
|
||||||
|
insertTrack.await(updatedTrack.toDomainTrack()!!)
|
||||||
|
syncChapterProgressWithTrack.await(mangaId, track, service)
|
||||||
|
null
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
service to e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.awaitAll()
|
||||||
|
.filterNotNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +1,35 @@
|
|||||||
package eu.kanade.domain.chapter.interactor
|
package eu.kanade.domain.track.interactor
|
||||||
|
|
||||||
import eu.kanade.domain.track.interactor.InsertTrack
|
|
||||||
import eu.kanade.domain.track.model.toDbTrack
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
|
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.chapter.model.toChapterUpdate
|
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||||
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
import tachiyomi.domain.track.model.Track
|
import tachiyomi.domain.track.model.Track
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class SyncChaptersWithTrackServiceTwoWay(
|
class SyncChapterProgressWithTrack(
|
||||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
private val updateChapter: UpdateChapter,
|
||||||
private val insertTrack: InsertTrack = Injekt.get(),
|
private val insertTrack: InsertTrack,
|
||||||
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(
|
suspend fun await(
|
||||||
chapters: List<Chapter>,
|
mangaId: Long,
|
||||||
remoteTrack: Track,
|
remoteTrack: Track,
|
||||||
service: TrackService,
|
tracker: Tracker,
|
||||||
) {
|
) {
|
||||||
val sortedChapters = chapters.sortedBy { it.chapterNumber }
|
if (tracker !is EnhancedTracker) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val sortedChapters = getChaptersByMangaId.await(mangaId)
|
||||||
|
.sortedBy { it.chapterNumber }
|
||||||
|
.filter { it.isRecognizedNumber }
|
||||||
|
|
||||||
val chapterUpdates = sortedChapters
|
val chapterUpdates = sortedChapters
|
||||||
.filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read }
|
.filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read }
|
||||||
.map { it.copy(read = true).toChapterUpdate() }
|
.map { it.copy(read = true).toChapterUpdate() }
|
||||||
@ -31,7 +39,7 @@ class SyncChaptersWithTrackServiceTwoWay(
|
|||||||
val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble())
|
val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble())
|
||||||
|
|
||||||
try {
|
try {
|
||||||
service.update(updatedTrack.toDbTrack())
|
tracker.update(updatedTrack.toDbTrack())
|
||||||
updateChapter.awaitAll(chapterUpdates)
|
updateChapter.awaitAll(chapterUpdates)
|
||||||
insertTrack.await(updatedTrack)
|
insertTrack.await(updatedTrack)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
@ -0,0 +1,57 @@
|
|||||||
|
package eu.kanade.domain.track.interactor
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
|
import eu.kanade.domain.track.model.toDomainTrack
|
||||||
|
import eu.kanade.domain.track.service.DelayedTrackingUpdateJob
|
||||||
|
import eu.kanade.domain.track.store.DelayedTrackingStore
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
|
|
||||||
|
class TrackChapter(
|
||||||
|
private val getTracks: GetTracks,
|
||||||
|
private val trackerManager: TrackerManager,
|
||||||
|
private val insertTrack: InsertTrack,
|
||||||
|
private val delayedTrackingStore: DelayedTrackingStore,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) {
|
||||||
|
withNonCancellableContext {
|
||||||
|
val tracks = getTracks.await(mangaId)
|
||||||
|
if (tracks.isEmpty()) return@withNonCancellableContext
|
||||||
|
|
||||||
|
tracks.mapNotNull { track ->
|
||||||
|
val service = trackerManager.get(track.trackerId)
|
||||||
|
if (service == null || !service.isLoggedIn || chapterNumber <= track.lastChapterRead) {
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
|
||||||
|
async {
|
||||||
|
runCatching {
|
||||||
|
try {
|
||||||
|
val updatedTrack = service.refresh(track.toDbTrack())
|
||||||
|
.toDomainTrack(idRequired = true)!!
|
||||||
|
.copy(lastChapterRead = chapterNumber)
|
||||||
|
service.update(updatedTrack.toDbTrack(), true)
|
||||||
|
insertTrack.await(updatedTrack)
|
||||||
|
delayedTrackingStore.remove(track.id)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
delayedTrackingStore.add(track.id, chapterNumber)
|
||||||
|
DelayedTrackingUpdateJob.setupTask(context)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.awaitAll()
|
||||||
|
.mapNotNull { it.exceptionOrNull() }
|
||||||
|
.forEach { logcat(LogPriority.WARN, it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,16 +13,16 @@ fun Track.copyPersonalFrom(other: Track): Track {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Track.toDbTrack(): DbTrack = DbTrack.create(syncId).also {
|
fun Track.toDbTrack(): DbTrack = DbTrack.create(trackerId).also {
|
||||||
it.id = id
|
it.id = id
|
||||||
it.manga_id = mangaId
|
it.manga_id = mangaId
|
||||||
it.media_id = remoteId
|
it.remote_id = remoteId
|
||||||
it.library_id = libraryId
|
it.library_id = libraryId
|
||||||
it.title = title
|
it.title = title
|
||||||
it.last_chapter_read = lastChapterRead.toFloat()
|
it.last_chapter_read = lastChapterRead.toFloat()
|
||||||
it.total_chapters = totalChapters.toInt()
|
it.total_chapters = totalChapters.toInt()
|
||||||
it.status = status.toInt()
|
it.status = status.toInt()
|
||||||
it.score = score
|
it.score = score.toFloat()
|
||||||
it.tracking_url = remoteUrl
|
it.tracking_url = remoteUrl
|
||||||
it.started_reading_date = startDate
|
it.started_reading_date = startDate
|
||||||
it.finished_reading_date = finishDate
|
it.finished_reading_date = finishDate
|
||||||
@ -33,14 +33,16 @@ fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
|||||||
return Track(
|
return Track(
|
||||||
id = trackId,
|
id = trackId,
|
||||||
mangaId = manga_id,
|
mangaId = manga_id,
|
||||||
syncId = sync_id.toLong(),
|
trackerId = tracker_id.toLong(),
|
||||||
remoteId = media_id,
|
remoteId = remote_id,
|
||||||
libraryId = library_id,
|
libraryId = library_id,
|
||||||
title = title,
|
title = title,
|
||||||
lastChapterRead = last_chapter_read.toDouble(),
|
lastChapterRead = last_chapter_read.toDouble(),
|
||||||
totalChapters = total_chapters.toLong(),
|
totalChapters = total_chapters.toLong(),
|
||||||
status = status.toLong(),
|
status = status.toLong(),
|
||||||
score = score,
|
// Jank workaround due to precision issues while converting
|
||||||
|
// See https://github.com/tachiyomiorg/tachiyomi/issues/10343
|
||||||
|
score = score.toString().toDouble(),
|
||||||
remoteUrl = tracking_url,
|
remoteUrl = tracking_url,
|
||||||
startDate = started_reading_date,
|
startDate = started_reading_date,
|
||||||
finishDate = finished_reading_date,
|
finishDate = finished_reading_date,
|
||||||
|
@ -7,31 +7,32 @@ import androidx.work.CoroutineWorker
|
|||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.NetworkType
|
import androidx.work.NetworkType
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkManager
|
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import eu.kanade.domain.track.interactor.GetTracks
|
import eu.kanade.domain.track.interactor.TrackChapter
|
||||||
import eu.kanade.domain.track.interactor.InsertTrack
|
|
||||||
import eu.kanade.domain.track.model.toDbTrack
|
|
||||||
import eu.kanade.domain.track.store.DelayedTrackingStore
|
import eu.kanade.domain.track.store.DelayedTrackingStore
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.util.system.workManager
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters) :
|
class DelayedTrackingUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||||
CoroutineWorker(context, workerParams) {
|
CoroutineWorker(context, workerParams) {
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val getTracks = Injekt.get<GetTracks>()
|
if (runAttemptCount > 3) {
|
||||||
val insertTrack = Injekt.get<InsertTrack>()
|
return Result.failure()
|
||||||
|
}
|
||||||
|
|
||||||
|
val getTracks = Injekt.get<GetTracks>()
|
||||||
|
val trackChapter = Injekt.get<TrackChapter>()
|
||||||
|
|
||||||
val trackManager = Injekt.get<TrackManager>()
|
|
||||||
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
|
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
|
||||||
|
|
||||||
val results = withIOContext {
|
withIOContext {
|
||||||
delayedTrackingStore.getItems()
|
delayedTrackingStore.getItems()
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
val track = getTracks.awaitOne(it.trackId)
|
val track = getTracks.awaitOne(it.trackId)
|
||||||
@ -40,42 +41,32 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
|
|||||||
}
|
}
|
||||||
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
||||||
}
|
}
|
||||||
.mapNotNull { track ->
|
.forEach { track ->
|
||||||
try {
|
logcat(LogPriority.DEBUG) {
|
||||||
val service = trackManager.getService(track.syncId)
|
"Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}"
|
||||||
if (service != null && service.isLogged) {
|
|
||||||
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" }
|
|
||||||
service.update(track.toDbTrack(), true)
|
|
||||||
insertTrack.await(track)
|
|
||||||
}
|
|
||||||
delayedTrackingStore.remove(track.id)
|
|
||||||
null
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
trackChapter.await(context, track.mangaId, track.lastChapterRead)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (results.isNotEmpty()) Result.failure() else Result.success()
|
return if (delayedTrackingStore.getItems().isEmpty()) Result.success() else Result.retry()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "DelayedTrackingUpdate"
|
private const val TAG = "DelayedTrackingUpdate"
|
||||||
|
|
||||||
fun setupTask(context: Context) {
|
fun setupTask(context: Context) {
|
||||||
val constraints = Constraints.Builder()
|
val constraints = Constraints(
|
||||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
requiredNetworkType = NetworkType.CONNECTED,
|
||||||
.build()
|
)
|
||||||
|
|
||||||
val request = OneTimeWorkRequestBuilder<DelayedTrackingUpdateJob>()
|
val request = OneTimeWorkRequestBuilder<DelayedTrackingUpdateJob>()
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 20, TimeUnit.SECONDS)
|
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5, TimeUnit.MINUTES)
|
||||||
.addTag(TAG)
|
.addTag(TAG)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
WorkManager.getInstance(context)
|
context.workManager.enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request)
|
||||||
.enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,32 @@
|
|||||||
package eu.kanade.domain.track.service
|
package eu.kanade.domain.track.service
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
|
||||||
class TrackPreferences(
|
class TrackPreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun trackUsername(sync: TrackService) = preferenceStore.getString(trackUsername(sync.id), "")
|
fun trackUsername(tracker: Tracker) = preferenceStore.getString(
|
||||||
|
Preference.privateKey("pref_mangasync_username_${tracker.id}"),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
fun trackPassword(sync: TrackService) = preferenceStore.getString(trackPassword(sync.id), "")
|
fun trackPassword(tracker: Tracker) = preferenceStore.getString(
|
||||||
|
Preference.privateKey("pref_mangasync_password_${tracker.id}"),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
fun setTrackCredentials(sync: TrackService, username: String, password: String) {
|
fun setCredentials(tracker: Tracker, username: String, password: String) {
|
||||||
trackUsername(sync).set(username)
|
trackUsername(tracker).set(username)
|
||||||
trackPassword(sync).set(password)
|
trackPassword(tracker).set(password)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun trackToken(sync: TrackService) = preferenceStore.getString(trackToken(sync.id), "")
|
fun trackToken(tracker: Tracker) = preferenceStore.getString(Preference.privateKey("track_token_${tracker.id}"), "")
|
||||||
|
|
||||||
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
||||||
|
|
||||||
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun trackUsername(syncId: Long) = "pref_mangasync_username_$syncId"
|
|
||||||
|
|
||||||
private fun trackPassword(syncId: Long) = "pref_mangasync_password_$syncId"
|
|
||||||
|
|
||||||
private fun trackToken(syncId: Long) = "track_token_$syncId"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import android.content.Context
|
|||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.track.model.Track
|
|
||||||
|
|
||||||
class DelayedTrackingStore(context: Context) {
|
class DelayedTrackingStore(context: Context) {
|
||||||
|
|
||||||
@ -13,13 +12,12 @@ class DelayedTrackingStore(context: Context) {
|
|||||||
*/
|
*/
|
||||||
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
fun addItem(track: Track) {
|
fun add(trackId: Long, lastChapterRead: Double) {
|
||||||
val trackId = track.id.toString()
|
val previousLastChapterRead = preferences.getFloat(trackId.toString(), 0f)
|
||||||
val lastChapterRead = preferences.getFloat(trackId, 0f)
|
if (lastChapterRead > previousLastChapterRead) {
|
||||||
if (track.lastChapterRead > lastChapterRead) {
|
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last chapter read: $lastChapterRead" }
|
||||||
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last chapter read: ${track.lastChapterRead}" }
|
|
||||||
preferences.edit {
|
preferences.edit {
|
||||||
putFloat(trackId, track.lastChapterRead.toFloat())
|
putFloat(trackId.toString(), lastChapterRead.toFloat())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.domain.ui
|
package eu.kanade.domain.ui
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import eu.kanade.domain.ui.model.AppTheme
|
import eu.kanade.domain.ui.model.AppTheme
|
||||||
import eu.kanade.domain.ui.model.TabletUiMode
|
import eu.kanade.domain.ui.model.TabletUiMode
|
||||||
import eu.kanade.domain.ui.model.ThemeMode
|
import eu.kanade.domain.ui.model.ThemeMode
|
||||||
@ -16,10 +15,7 @@ class UiPreferences(
|
|||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun themeMode() = preferenceStore.getEnum(
|
fun themeMode() = preferenceStore.getEnum("pref_theme_mode_key", ThemeMode.SYSTEM)
|
||||||
"pref_theme_mode_key",
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ThemeMode.SYSTEM } else { ThemeMode.LIGHT },
|
|
||||||
)
|
|
||||||
|
|
||||||
fun appTheme() = preferenceStore.getEnum(
|
fun appTheme() = preferenceStore.getEnum(
|
||||||
"pref_app_theme",
|
"pref_app_theme",
|
||||||
@ -28,7 +24,7 @@ class UiPreferences(
|
|||||||
|
|
||||||
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
|
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
|
||||||
|
|
||||||
fun relativeTime() = preferenceStore.getInt("relative_time", 7)
|
fun relativeTime() = preferenceStore.getBoolean("relative_time_v2", true)
|
||||||
|
|
||||||
fun dateFormat() = preferenceStore.getString("app_date_format", "")
|
fun dateFormat() = preferenceStore.getString("app_date_format", "")
|
||||||
|
|
||||||
|
@ -1,19 +1,25 @@
|
|||||||
package eu.kanade.domain.ui.model
|
package eu.kanade.domain.ui.model
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.R
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
||||||
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
|
||||||
enum class AppTheme(val titleResId: Int?) {
|
enum class AppTheme(val titleRes: StringResource?) {
|
||||||
DEFAULT(R.string.label_default),
|
DEFAULT(MR.strings.label_default),
|
||||||
MONET(R.string.theme_monet),
|
MONET(MR.strings.theme_monet),
|
||||||
GREEN_APPLE(R.string.theme_greenapple),
|
GREEN_APPLE(MR.strings.theme_greenapple),
|
||||||
LAVENDER(R.string.theme_lavender),
|
LAVENDER(MR.strings.theme_lavender),
|
||||||
MIDNIGHT_DUSK(R.string.theme_midnightdusk),
|
MIDNIGHT_DUSK(MR.strings.theme_midnightdusk),
|
||||||
STRAWBERRY_DAIQUIRI(R.string.theme_strawberrydaiquiri),
|
|
||||||
TAKO(R.string.theme_tako),
|
// TODO: re-enable for preview
|
||||||
TEALTURQUOISE(R.string.theme_tealturquoise),
|
NORD(MR.strings.theme_nord.takeIf { isDevFlavor || isPreviewBuildType }),
|
||||||
TIDAL_WAVE(R.string.theme_tidalwave),
|
STRAWBERRY_DAIQUIRI(MR.strings.theme_strawberrydaiquiri),
|
||||||
YINYANG(R.string.theme_yinyang),
|
TAKO(MR.strings.theme_tako),
|
||||||
YOTSUBA(R.string.theme_yotsuba),
|
TEALTURQUOISE(MR.strings.theme_tealturquoise),
|
||||||
|
TIDAL_WAVE(MR.strings.theme_tidalwave),
|
||||||
|
YINYANG(MR.strings.theme_yinyang),
|
||||||
|
YOTSUBA(MR.strings.theme_yotsuba),
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
DARK_BLUE(null),
|
DARK_BLUE(null),
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package eu.kanade.domain.ui.model
|
package eu.kanade.domain.ui.model
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.R
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
|
||||||
enum class TabletUiMode(val titleResId: Int) {
|
enum class TabletUiMode(val titleRes: StringResource) {
|
||||||
AUTOMATIC(R.string.automatic_background),
|
AUTOMATIC(MR.strings.automatic_background),
|
||||||
ALWAYS(R.string.lock_always),
|
ALWAYS(MR.strings.lock_always),
|
||||||
LANDSCAPE(R.string.landscape),
|
LANDSCAPE(MR.strings.landscape),
|
||||||
NEVER(R.string.lock_never),
|
NEVER(MR.strings.lock_never),
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package eu.kanade.presentation.browse
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.components.Badge
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun InLibraryBadge(enabled: Boolean) {
|
|
||||||
if (enabled) {
|
|
||||||
Badge(text = stringResource(R.string.in_library))
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||||
import androidx.compose.material.icons.outlined.Public
|
import androidx.compose.material.icons.outlined.Public
|
||||||
import androidx.compose.material.icons.outlined.Refresh
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
import androidx.compose.material3.SnackbarDuration
|
import androidx.compose.material3.SnackbarDuration
|
||||||
@ -16,22 +16,25 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import eu.kanade.data.source.NoResultsException
|
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
|
import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
|
import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceList
|
import eu.kanade.presentation.browse.components.BrowseSourceList
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.util.formattedMessage
|
||||||
import eu.kanade.presentation.components.EmptyScreenAction
|
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import tachiyomi.core.i18n.stringResource
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.source.model.StubSource
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
||||||
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
|
import tachiyomi.source.local.LocalSource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceContent(
|
fun BrowseSourceContent(
|
||||||
@ -53,54 +56,50 @@ fun BrowseSourceContent(
|
|||||||
?: mangaList.loadState.append.takeIf { it is LoadState.Error }
|
?: mangaList.loadState.append.takeIf { it is LoadState.Error }
|
||||||
|
|
||||||
val getErrorMessage: (LoadState.Error) -> String = { state ->
|
val getErrorMessage: (LoadState.Error) -> String = { state ->
|
||||||
when {
|
with(context) { state.error.formattedMessage }
|
||||||
state.error is NoResultsException -> context.getString(R.string.no_results_found)
|
|
||||||
state.error.message.isNullOrEmpty() -> ""
|
|
||||||
state.error.message.orEmpty().startsWith("HTTP error") -> "${state.error.message}: ${context.getString(R.string.http_error_hint)}"
|
|
||||||
else -> state.error.message.orEmpty()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(errorState) {
|
LaunchedEffect(errorState) {
|
||||||
if (mangaList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
if (mangaList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
||||||
val result = snackbarHostState.showSnackbar(
|
val result = snackbarHostState.showSnackbar(
|
||||||
message = getErrorMessage(errorState),
|
message = getErrorMessage(errorState),
|
||||||
actionLabel = context.getString(R.string.action_webview_refresh),
|
actionLabel = context.stringResource(MR.strings.action_retry),
|
||||||
duration = SnackbarDuration.Indefinite,
|
duration = SnackbarDuration.Indefinite,
|
||||||
)
|
)
|
||||||
when (result) {
|
when (result) {
|
||||||
SnackbarResult.Dismissed -> snackbarHostState.currentSnackbarData?.dismiss()
|
SnackbarResult.Dismissed -> snackbarHostState.currentSnackbarData?.dismiss()
|
||||||
SnackbarResult.ActionPerformed -> mangaList.refresh()
|
SnackbarResult.ActionPerformed -> mangaList.retry()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mangaList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) {
|
if (mangaList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
message = getErrorMessage(errorState),
|
message = getErrorMessage(errorState),
|
||||||
actions = if (source is LocalSource) {
|
actions = if (source is LocalSource) {
|
||||||
listOf(
|
persistentListOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.local_source_help_guide,
|
stringRes = MR.strings.local_source_help_guide,
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = onLocalSourceHelpClick,
|
onClick = onLocalSourceHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
listOf(
|
persistentListOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.action_retry,
|
stringRes = MR.strings.action_retry,
|
||||||
icon = Icons.Outlined.Refresh,
|
icon = Icons.Outlined.Refresh,
|
||||||
onClick = mangaList::refresh,
|
onClick = mangaList::refresh,
|
||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.action_open_in_web_view,
|
stringRes = MR.strings.action_open_in_web_view,
|
||||||
icon = Icons.Outlined.Public,
|
icon = Icons.Outlined.Public,
|
||||||
onClick = onWebViewClick,
|
onClick = onWebViewClick,
|
||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.label_help,
|
stringRes = MR.strings.label_help,
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = onHelpClick,
|
onClick = onHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -111,7 +110,9 @@ fun BrowseSourceContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mangaList.itemCount == 0 && mangaList.loadState.refresh is LoadState.Loading) {
|
if (mangaList.itemCount == 0 && mangaList.loadState.refresh is LoadState.Loading) {
|
||||||
LoadingScreen()
|
LoadingScreen(
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,8 +147,8 @@ fun BrowseSourceContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MissingSourceScreen(
|
internal fun MissingSourceScreen(
|
||||||
source: SourceManager.StubSource,
|
source: StubSource,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@ -160,7 +161,7 @@ fun MissingSourceScreen(
|
|||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
message = source.getSourceNotInstalledException().message!!,
|
message = stringResource(MR.strings.source_not_installed, source.toString()),
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,19 +10,17 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||||
import androidx.compose.material.icons.outlined.History
|
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -30,6 +28,7 @@ import androidx.compose.material3.OutlinedButton
|
|||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -38,7 +37,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@ -47,75 +46,82 @@ import eu.kanade.domain.extension.interactor.ExtensionSourceItem
|
|||||||
import eu.kanade.presentation.browse.components.ExtensionIcon
|
import eu.kanade.presentation.browse.components.ExtensionIcon
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.components.DIVIDER_ALPHA
|
|
||||||
import eu.kanade.presentation.components.Divider
|
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
|
||||||
import eu.kanade.presentation.components.WarningBanner
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
||||||
import eu.kanade.presentation.util.padding
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsState
|
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionDetailsScreen(
|
fun ExtensionDetailsScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: ExtensionDetailsState,
|
state: ExtensionDetailsScreenModel.State,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickWhatsNew: () -> Unit,
|
|
||||||
onClickReadme: () -> Unit,
|
|
||||||
onClickEnableAll: () -> Unit,
|
onClickEnableAll: () -> Unit,
|
||||||
onClickDisableAll: () -> Unit,
|
onClickDisableAll: () -> Unit,
|
||||||
onClickClearCookies: () -> Unit,
|
onClickClearCookies: () -> Unit,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickSource: (sourceId: Long) -> Unit,
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
val url = remember(state.extension) {
|
||||||
|
val regex = """https://raw.githubusercontent.com/(.+?)/(.+?)/.+""".toRegex()
|
||||||
|
regex.find(state.extension?.repoUrl.orEmpty())
|
||||||
|
?.let {
|
||||||
|
val (user, repo) = it.destructured
|
||||||
|
"https://github.com/$user/$repo"
|
||||||
|
}
|
||||||
|
?: state.extension?.repoUrl
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
title = stringResource(R.string.label_extension_info),
|
title = stringResource(MR.strings.label_extension_info),
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
actions = buildList {
|
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||||
if (state.extension?.isUnofficial == false) {
|
.apply {
|
||||||
add(
|
if (url != null) {
|
||||||
AppBar.Action(
|
add(
|
||||||
title = stringResource(R.string.whats_new),
|
AppBar.Action(
|
||||||
icon = Icons.Outlined.History,
|
title = stringResource(MR.strings.action_open_repo),
|
||||||
onClick = onClickWhatsNew,
|
icon = Icons.AutoMirrored.Outlined.Launch,
|
||||||
),
|
onClick = {
|
||||||
)
|
uriHandler.openUri(url)
|
||||||
add(
|
},
|
||||||
AppBar.Action(
|
),
|
||||||
title = stringResource(R.string.action_faq_and_guides),
|
)
|
||||||
icon = Icons.Outlined.HelpOutline,
|
}
|
||||||
onClick = onClickReadme,
|
addAll(
|
||||||
|
listOf(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_enable_all),
|
||||||
|
onClick = onClickEnableAll,
|
||||||
|
),
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_disable_all),
|
||||||
|
onClick = onClickDisableAll,
|
||||||
|
),
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.pref_clear_cookies),
|
||||||
|
onClick = onClickClearCookies,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
addAll(
|
.build(),
|
||||||
listOf(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.action_enable_all),
|
|
||||||
onClick = onClickEnableAll,
|
|
||||||
),
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.action_disable_all),
|
|
||||||
onClick = onClickDisableAll,
|
|
||||||
),
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.pref_clear_cookies),
|
|
||||||
onClick = onClickClearCookies,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
@ -124,7 +130,7 @@ fun ExtensionDetailsScreen(
|
|||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
if (state.extension == null) {
|
if (state.extension == null) {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = R.string.empty_screen,
|
MR.strings.empty_screen,
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
)
|
)
|
||||||
return@Scaffold
|
return@Scaffold
|
||||||
@ -145,7 +151,7 @@ fun ExtensionDetailsScreen(
|
|||||||
private fun ExtensionDetails(
|
private fun ExtensionDetails(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
extension: Extension.Installed,
|
extension: Extension.Installed,
|
||||||
sources: List<ExtensionSourceItem>,
|
sources: ImmutableList<ExtensionSourceItem>,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickSource: (sourceId: Long) -> Unit,
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
@ -156,15 +162,10 @@ private fun ExtensionDetails(
|
|||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
when {
|
if (extension.isObsolete) {
|
||||||
extension.isUnofficial ->
|
item {
|
||||||
item {
|
WarningBanner(MR.strings.obsolete_extension_message)
|
||||||
WarningBanner(R.string.unofficial_extension_message)
|
}
|
||||||
}
|
|
||||||
extension.isObsolete ->
|
|
||||||
item {
|
|
||||||
WarningBanner(R.string.obsolete_extension_message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
@ -176,7 +177,8 @@ private fun ExtensionDetails(
|
|||||||
data = Uri.fromParts("package", extension.pkgName, null)
|
data = Uri.fromParts("package", extension.pkgName, null)
|
||||||
context.startActivity(this)
|
context.startActivity(this)
|
||||||
}
|
}
|
||||||
},
|
Unit
|
||||||
|
}.takeIf { extension.isShared },
|
||||||
onClickAgeRating = {
|
onClickAgeRating = {
|
||||||
showNsfwWarning = true
|
showNsfwWarning = true
|
||||||
},
|
},
|
||||||
@ -209,7 +211,7 @@ private fun DetailsHeader(
|
|||||||
extension: Extension,
|
extension: Extension,
|
||||||
onClickAgeRating: () -> Unit,
|
onClickAgeRating: () -> Unit,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickAppInfo: () -> Unit,
|
onClickAppInfo: (() -> Unit)?,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@ -259,7 +261,7 @@ private fun DetailsHeader(
|
|||||||
InfoText(
|
InfoText(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
primaryText = extension.versionName,
|
primaryText = extension.versionName,
|
||||||
secondaryText = stringResource(R.string.ext_info_version),
|
secondaryText = stringResource(MR.strings.ext_info_version),
|
||||||
)
|
)
|
||||||
|
|
||||||
InfoDivider()
|
InfoDivider()
|
||||||
@ -267,7 +269,7 @@ private fun DetailsHeader(
|
|||||||
InfoText(
|
InfoText(
|
||||||
modifier = Modifier.weight(if (extension.isNsfw) 1.5f else 1f),
|
modifier = Modifier.weight(if (extension.isNsfw) 1.5f else 1f),
|
||||||
primaryText = LocaleHelper.getSourceDisplayName(extension.lang, context),
|
primaryText = LocaleHelper.getSourceDisplayName(extension.lang, context),
|
||||||
secondaryText = stringResource(R.string.ext_info_language),
|
secondaryText = stringResource(MR.strings.ext_info_language),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (extension.isNsfw) {
|
if (extension.isNsfw) {
|
||||||
@ -275,12 +277,12 @@ private fun DetailsHeader(
|
|||||||
|
|
||||||
InfoText(
|
InfoText(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
primaryText = stringResource(R.string.ext_nsfw_short),
|
primaryText = stringResource(MR.strings.ext_nsfw_short),
|
||||||
primaryTextStyle = MaterialTheme.typography.bodyLarge.copy(
|
primaryTextStyle = MaterialTheme.typography.bodyLarge.copy(
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
),
|
),
|
||||||
secondaryText = stringResource(R.string.ext_info_age_rating),
|
secondaryText = stringResource(MR.strings.ext_info_age_rating),
|
||||||
onClick = onClickAgeRating,
|
onClick = onClickAgeRating,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -293,37 +295,38 @@ private fun DetailsHeader(
|
|||||||
top = MaterialTheme.padding.small,
|
top = MaterialTheme.padding.small,
|
||||||
bottom = MaterialTheme.padding.medium,
|
bottom = MaterialTheme.padding.medium,
|
||||||
),
|
),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
||||||
) {
|
) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
onClick = onClickUninstall,
|
onClick = onClickUninstall,
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.ext_uninstall))
|
Text(stringResource(MR.strings.ext_uninstall))
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(Modifier.width(16.dp))
|
if (onClickAppInfo != null) {
|
||||||
|
Button(
|
||||||
Button(
|
modifier = Modifier.weight(1f),
|
||||||
modifier = Modifier.weight(1f),
|
onClick = onClickAppInfo,
|
||||||
onClick = onClickAppInfo,
|
) {
|
||||||
) {
|
Text(
|
||||||
Text(
|
text = stringResource(MR.strings.ext_app_info),
|
||||||
text = stringResource(R.string.ext_app_info),
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InfoText(
|
private fun InfoText(
|
||||||
modifier: Modifier,
|
|
||||||
primaryText: String,
|
primaryText: String,
|
||||||
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
|
||||||
secondaryText: String,
|
secondaryText: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
@ -356,20 +359,17 @@ private fun InfoText(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InfoDivider() {
|
private fun InfoDivider() {
|
||||||
Divider(
|
VerticalDivider(
|
||||||
modifier = Modifier
|
modifier = Modifier.height(20.dp),
|
||||||
.height(20.dp)
|
|
||||||
.width(1.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourceSwitchPreference(
|
private fun SourceSwitchPreference(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
source: ExtensionSourceItem,
|
source: ExtensionSourceItem,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickSource: (sourceId: Long) -> Unit,
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@ -388,7 +388,7 @@ private fun SourceSwitchPreference(
|
|||||||
IconButton(onClick = { onClickSourcePreferences(source.source.id) }) {
|
IconButton(onClick = { onClickSourcePreferences(source.source.id) }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Settings,
|
imageVector = Icons.Outlined.Settings,
|
||||||
contentDescription = stringResource(R.string.label_settings),
|
contentDescription = stringResource(MR.strings.label_settings),
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -411,11 +411,11 @@ private fun NsfwWarningDialog(
|
|||||||
) {
|
) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringResource(R.string.ext_nsfw_warning))
|
Text(text = stringResource(MR.strings.ext_nsfw_warning))
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = onClickConfirm) {
|
TextButton(onClick = onClickConfirm) {
|
||||||
Text(text = stringResource(android.R.string.ok))
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDismissRequest = onClickConfirm,
|
onDismissRequest = onClickConfirm,
|
||||||
|
@ -2,19 +2,19 @@ package eu.kanade.presentation.browse
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
|
||||||
import eu.kanade.presentation.components.FastScrollLazyColumn
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
|
||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionFilterScreen(
|
fun ExtensionFilterScreen(
|
||||||
@ -25,7 +25,7 @@ fun ExtensionFilterScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
title = stringResource(R.string.label_extensions),
|
title = stringResource(MR.strings.label_extensions),
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
@ -33,7 +33,7 @@ fun ExtensionFilterScreen(
|
|||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
if (state.isEmpty) {
|
if (state.isEmpty) {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = R.string.empty_screen,
|
stringRes = MR.strings.empty_screen,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
return@Scaffold
|
return@Scaffold
|
||||||
@ -53,7 +53,7 @@ private fun ExtensionFilterContent(
|
|||||||
onClickLang: (String) -> Unit,
|
onClickLang: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
FastScrollLazyColumn(
|
LazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
items(state.languages) { language ->
|
items(state.languages) { language ->
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
@ -13,6 +15,11 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
|
import androidx.compose.material.icons.outlined.GetApp
|
||||||
|
import androidx.compose.material.icons.outlined.Public
|
||||||
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
|
import androidx.compose.material.icons.outlined.VerifiedUser
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
@ -31,36 +38,45 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.presentation.browse.components.BaseBrowseItem
|
import eu.kanade.presentation.browse.components.BaseBrowseItem
|
||||||
import eu.kanade.presentation.browse.components.ExtensionIcon
|
import eu.kanade.presentation.browse.components.ExtensionIcon
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
import eu.kanade.presentation.components.FastScrollLazyColumn
|
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
|
||||||
import eu.kanade.presentation.components.PullRefresh
|
|
||||||
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
||||||
import eu.kanade.presentation.theme.header
|
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
||||||
import eu.kanade.presentation.util.padding
|
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
||||||
import eu.kanade.presentation.util.plus
|
|
||||||
import eu.kanade.presentation.util.secondaryItemAlpha
|
|
||||||
import eu.kanade.presentation.util.topSmallPaddingValues
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsState
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
|
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
||||||
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
|
import tachiyomi.presentation.core.theme.header
|
||||||
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionScreen(
|
fun ExtensionScreen(
|
||||||
state: ExtensionsState,
|
state: ExtensionsScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
onLongClickItem: (Extension) -> Unit,
|
onLongClickItem: (Extension) -> Unit,
|
||||||
onClickItemCancel: (Extension) -> Unit,
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
|
onOpenWebView: (Extension.Available) -> Unit,
|
||||||
onInstallExtension: (Extension.Available) -> Unit,
|
onInstallExtension: (Extension.Available) -> Unit,
|
||||||
onUninstallExtension: (Extension) -> Unit,
|
onUninstallExtension: (Extension) -> Unit,
|
||||||
onUpdateExtension: (Extension.Installed) -> Unit,
|
onUpdateExtension: (Extension.Installed) -> Unit,
|
||||||
@ -69,22 +85,31 @@ fun ExtensionScreen(
|
|||||||
onClickUpdateAll: () -> Unit,
|
onClickUpdateAll: () -> Unit,
|
||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = state.isRefreshing,
|
refreshing = state.isRefreshing,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = !state.isLoading,
|
enabled = { !state.isLoading },
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.isEmpty -> {
|
state.isEmpty -> {
|
||||||
val msg = if (!searchQuery.isNullOrEmpty()) {
|
val msg = if (!searchQuery.isNullOrEmpty()) {
|
||||||
R.string.no_results_found
|
MR.strings.no_results_found
|
||||||
} else {
|
} else {
|
||||||
R.string.empty_screen
|
MR.strings.empty_screen
|
||||||
}
|
}
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = msg,
|
stringRes = msg,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
actions = persistentListOf(
|
||||||
|
EmptyScreenAction(
|
||||||
|
stringRes = MR.strings.label_extension_repos,
|
||||||
|
icon = Icons.Outlined.Settings,
|
||||||
|
onClick = { navigator.push(ExtensionReposScreen()) },
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@ -93,6 +118,7 @@ fun ExtensionScreen(
|
|||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
onClickItemCancel = onClickItemCancel,
|
onClickItemCancel = onClickItemCancel,
|
||||||
|
onOpenWebView = onOpenWebView,
|
||||||
onInstallExtension = onInstallExtension,
|
onInstallExtension = onInstallExtension,
|
||||||
onUninstallExtension = onUninstallExtension,
|
onUninstallExtension = onUninstallExtension,
|
||||||
onUpdateExtension = onUpdateExtension,
|
onUpdateExtension = onUpdateExtension,
|
||||||
@ -107,10 +133,11 @@ fun ExtensionScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExtensionContent(
|
private fun ExtensionContent(
|
||||||
state: ExtensionsState,
|
state: ExtensionsScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onLongClickItem: (Extension) -> Unit,
|
onLongClickItem: (Extension) -> Unit,
|
||||||
onClickItemCancel: (Extension) -> Unit,
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
|
onOpenWebView: (Extension.Available) -> Unit,
|
||||||
onInstallExtension: (Extension.Available) -> Unit,
|
onInstallExtension: (Extension.Available) -> Unit,
|
||||||
onUninstallExtension: (Extension) -> Unit,
|
onUninstallExtension: (Extension) -> Unit,
|
||||||
onUpdateExtension: (Extension.Installed) -> Unit,
|
onUpdateExtension: (Extension.Installed) -> Unit,
|
||||||
@ -118,11 +145,24 @@ private fun ExtensionContent(
|
|||||||
onOpenExtension: (Extension.Installed) -> Unit,
|
onOpenExtension: (Extension.Installed) -> Unit,
|
||||||
onClickUpdateAll: () -> Unit,
|
onClickUpdateAll: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
|
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
|
||||||
|
val installGranted = rememberRequestPackageInstallsPermissionState(initialValue = true)
|
||||||
|
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding + topSmallPaddingValues,
|
contentPadding = contentPadding + topSmallPaddingValues,
|
||||||
) {
|
) {
|
||||||
|
if (!installGranted && state.installer?.requiresSystemPermission == true) {
|
||||||
|
item(key = "extension-permissions-warning") {
|
||||||
|
WarningBanner(
|
||||||
|
textRes = MR.strings.ext_permission_install_apps_warning,
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
context.launchRequestPackageInstallsPermission()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
state.items.forEach { (header, items) ->
|
state.items.forEach { (header, items) ->
|
||||||
item(
|
item(
|
||||||
contentType = "header",
|
contentType = "header",
|
||||||
@ -131,11 +171,11 @@ private fun ExtensionContent(
|
|||||||
when (header) {
|
when (header) {
|
||||||
is ExtensionUiModel.Header.Resource -> {
|
is ExtensionUiModel.Header.Resource -> {
|
||||||
val action: @Composable RowScope.() -> Unit =
|
val action: @Composable RowScope.() -> Unit =
|
||||||
if (header.textRes == R.string.ext_updates_pending) {
|
if (header.textRes == MR.strings.ext_updates_pending) {
|
||||||
{
|
{
|
||||||
Button(onClick = { onClickUpdateAll() }) {
|
Button(onClick = { onClickUpdateAll() }) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.ext_update_all),
|
text = stringResource(MR.strings.ext_update_all),
|
||||||
style = LocalTextStyle.current.copy(
|
style = LocalTextStyle.current.copy(
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
@ -176,6 +216,13 @@ private fun ExtensionContent(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
|
onClickItemSecondaryAction = {
|
||||||
|
when (it) {
|
||||||
|
is Extension.Available -> onOpenWebView(it)
|
||||||
|
is Extension.Installed -> onOpenExtension(it)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
},
|
||||||
onClickItemCancel = onClickItemCancel,
|
onClickItemCancel = onClickItemCancel,
|
||||||
onClickItemAction = {
|
onClickItemAction = {
|
||||||
when (it) {
|
when (it) {
|
||||||
@ -213,12 +260,13 @@ private fun ExtensionContent(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExtensionItem(
|
private fun ExtensionItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
item: ExtensionUiModel.Item,
|
item: ExtensionUiModel.Item,
|
||||||
onClickItem: (Extension) -> Unit,
|
onClickItem: (Extension) -> Unit,
|
||||||
onLongClickItem: (Extension) -> Unit,
|
onLongClickItem: (Extension) -> Unit,
|
||||||
onClickItemCancel: (Extension) -> Unit,
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
onClickItemAction: (Extension) -> Unit,
|
onClickItemAction: (Extension) -> Unit,
|
||||||
|
onClickItemSecondaryAction: (Extension) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val (extension, installStep) = item
|
val (extension, installStep) = item
|
||||||
BaseBrowseItem(
|
BaseBrowseItem(
|
||||||
@ -243,7 +291,10 @@ private fun ExtensionItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val padding by animateDpAsState(targetValue = if (idle) 0.dp else 8.dp)
|
val padding by animateDpAsState(
|
||||||
|
targetValue = if (idle) 0.dp else 8.dp,
|
||||||
|
label = "iconPadding",
|
||||||
|
)
|
||||||
ExtensionIcon(
|
ExtensionIcon(
|
||||||
extension = extension,
|
extension = extension,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -258,6 +309,7 @@ private fun ExtensionItem(
|
|||||||
installStep = installStep,
|
installStep = installStep,
|
||||||
onClickItemCancel = onClickItemCancel,
|
onClickItemCancel = onClickItemCancel,
|
||||||
onClickItemAction = onClickItemAction,
|
onClickItemAction = onClickItemAction,
|
||||||
|
onClickItemSecondaryAction = onClickItemSecondaryAction,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@ -287,7 +339,7 @@ private fun ExtensionItemContent(
|
|||||||
// Won't look good but it's not like we can ellipsize overflowing content
|
// Won't look good but it's not like we can ellipsize overflowing content
|
||||||
FlowRow(
|
FlowRow(
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
mainAxisSpacing = 4.dp,
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||||
) {
|
) {
|
||||||
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||||
if (extension is Extension.Installed && extension.lang.isNotEmpty()) {
|
if (extension is Extension.Installed && extension.lang.isNotEmpty()) {
|
||||||
@ -303,10 +355,9 @@ private fun ExtensionItemContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val warning = when {
|
val warning = when {
|
||||||
extension is Extension.Untrusted -> R.string.ext_untrusted
|
extension is Extension.Untrusted -> MR.strings.ext_untrusted
|
||||||
extension is Extension.Installed && extension.isUnofficial -> R.string.ext_unofficial
|
extension is Extension.Installed && extension.isObsolete -> MR.strings.ext_obsolete
|
||||||
extension is Extension.Installed && extension.isObsolete -> R.string.ext_obsolete
|
extension.isNsfw -> MR.strings.ext_nsfw_short
|
||||||
extension.isNsfw -> R.string.ext_nsfw_short
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
if (warning != null) {
|
if (warning != null) {
|
||||||
@ -322,9 +373,9 @@ private fun ExtensionItemContent(
|
|||||||
DotSeparatorNoSpaceText()
|
DotSeparatorNoSpaceText()
|
||||||
Text(
|
Text(
|
||||||
text = when (installStep) {
|
text = when (installStep) {
|
||||||
InstallStep.Pending -> stringResource(R.string.ext_pending)
|
InstallStep.Pending -> stringResource(MR.strings.ext_pending)
|
||||||
InstallStep.Downloading -> stringResource(R.string.ext_downloading)
|
InstallStep.Downloading -> stringResource(MR.strings.ext_downloading)
|
||||||
InstallStep.Installing -> stringResource(R.string.ext_installing)
|
InstallStep.Installing -> stringResource(MR.strings.ext_installing)
|
||||||
else -> error("Must not show non-install process text")
|
else -> error("Must not show non-install process text")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -341,40 +392,78 @@ private fun ExtensionItemActions(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClickItemCancel: (Extension) -> Unit = {},
|
onClickItemCancel: (Extension) -> Unit = {},
|
||||||
onClickItemAction: (Extension) -> Unit = {},
|
onClickItemAction: (Extension) -> Unit = {},
|
||||||
|
onClickItemSecondaryAction: (Extension) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val isIdle = installStep.isCompleted()
|
val isIdle = installStep.isCompleted()
|
||||||
Row(modifier = modifier) {
|
|
||||||
if (isIdle) {
|
Row(
|
||||||
TextButton(
|
modifier = modifier,
|
||||||
onClick = { onClickItemAction(extension) },
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
) {
|
) {
|
||||||
Text(
|
when {
|
||||||
text = when (installStep) {
|
!isIdle -> {
|
||||||
InstallStep.Installed -> stringResource(R.string.ext_installed)
|
IconButton(onClick = { onClickItemCancel(extension) }) {
|
||||||
InstallStep.Error -> stringResource(R.string.action_retry)
|
Icon(
|
||||||
InstallStep.Idle -> {
|
imageVector = Icons.Outlined.Close,
|
||||||
when (extension) {
|
contentDescription = stringResource(MR.strings.action_cancel),
|
||||||
is Extension.Installed -> {
|
)
|
||||||
if (extension.hasUpdate) {
|
}
|
||||||
stringResource(R.string.ext_update)
|
}
|
||||||
} else {
|
installStep == InstallStep.Error -> {
|
||||||
stringResource(R.string.action_settings)
|
IconButton(onClick = { onClickItemAction(extension) }) {
|
||||||
}
|
Icon(
|
||||||
}
|
imageVector = Icons.Outlined.Refresh,
|
||||||
is Extension.Untrusted -> stringResource(R.string.ext_trust)
|
contentDescription = stringResource(MR.strings.action_retry),
|
||||||
is Extension.Available -> stringResource(R.string.ext_install)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
installStep == InstallStep.Idle -> {
|
||||||
|
when (extension) {
|
||||||
|
is Extension.Installed -> {
|
||||||
|
IconButton(onClick = { onClickItemSecondaryAction(extension) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Settings,
|
||||||
|
contentDescription = stringResource(MR.strings.action_settings),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension.hasUpdate) {
|
||||||
|
IconButton(onClick = { onClickItemAction(extension) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.GetApp,
|
||||||
|
contentDescription = stringResource(MR.strings.ext_update),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> error("Must not show install process text")
|
}
|
||||||
},
|
is Extension.Untrusted -> {
|
||||||
)
|
IconButton(onClick = { onClickItemAction(extension) }) {
|
||||||
}
|
Icon(
|
||||||
} else {
|
imageVector = Icons.Outlined.VerifiedUser,
|
||||||
IconButton(onClick = { onClickItemCancel(extension) }) {
|
contentDescription = stringResource(MR.strings.ext_trust),
|
||||||
Icon(
|
)
|
||||||
imageVector = Icons.Outlined.Close,
|
}
|
||||||
contentDescription = stringResource(R.string.action_cancel),
|
}
|
||||||
)
|
is Extension.Available -> {
|
||||||
|
if (extension.sources.isNotEmpty()) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { onClickItemSecondaryAction(extension) },
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Public,
|
||||||
|
contentDescription = stringResource(MR.strings.action_open_in_web_view),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = { onClickItemAction(extension) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.GetApp,
|
||||||
|
contentDescription = stringResource(MR.strings.ext_install),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -382,7 +471,7 @@ private fun ExtensionItemActions(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExtensionHeader(
|
private fun ExtensionHeader(
|
||||||
@StringRes textRes: Int,
|
textRes: StringResource,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
action: @Composable RowScope.() -> Unit = {},
|
action: @Composable RowScope.() -> Unit = {},
|
||||||
) {
|
) {
|
||||||
@ -422,19 +511,19 @@ private fun ExtensionTrustDialog(
|
|||||||
) {
|
) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.untrusted_extension))
|
Text(text = stringResource(MR.strings.untrusted_extension))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringResource(R.string.untrusted_extension_message))
|
Text(text = stringResource(MR.strings.untrusted_extension_message))
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = onClickConfirm) {
|
TextButton(onClick = onClickConfirm) {
|
||||||
Text(text = stringResource(R.string.ext_trust))
|
Text(text = stringResource(MR.strings.ext_trust))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onClickDismiss) {
|
TextButton(onClick = onClickDismiss) {
|
||||||
Text(text = stringResource(R.string.ext_uninstall))
|
Text(text = stringResource(MR.strings.ext_uninstall))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
|
@ -1,35 +1,31 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||||
import eu.kanade.presentation.components.LazyColumn
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
|
||||||
import eu.kanade.presentation.util.padding
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchState
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalSearchScreen(
|
fun GlobalSearchScreen(
|
||||||
state: GlobalSearchState,
|
state: SearchScreenModel.State,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
|
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||||
|
onToggleResults: () -> Unit,
|
||||||
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
onClickSource: (CatalogueSource) -> Unit,
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onLongClickItem: (Manga) -> Unit,
|
onLongClickItem: (Manga) -> Unit,
|
||||||
@ -43,12 +39,16 @@ fun GlobalSearchScreen(
|
|||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
onChangeSearchQuery = onChangeSearchQuery,
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
onSearch = onSearch,
|
onSearch = onSearch,
|
||||||
|
sourceFilter = state.sourceFilter,
|
||||||
|
onChangeSearchFilter = onChangeSearchFilter,
|
||||||
|
onlyShowHasResults = state.onlyShowHasResults,
|
||||||
|
onToggleResults = onToggleResults,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
GlobalSearchContent(
|
GlobalSearchContent(
|
||||||
items = state.items,
|
items = state.filteredItems,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
getManga = getManga,
|
getManga = getManga,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
@ -59,13 +59,14 @@ fun GlobalSearchScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalSearchContent(
|
internal fun GlobalSearchContent(
|
||||||
items: Map<CatalogueSource, SearchItemResult>,
|
items: Map<CatalogueSource, SearchItemResult>,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
onClickSource: (CatalogueSource) -> Unit,
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onLongClickItem: (Manga) -> Unit,
|
onLongClickItem: (Manga) -> Unit,
|
||||||
|
fromSourceId: Long? = null,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
@ -73,8 +74,10 @@ fun GlobalSearchContent(
|
|||||||
items.forEach { (source, result) ->
|
items.forEach { (source, result) ->
|
||||||
item(key = source.id) {
|
item(key = source.id) {
|
||||||
GlobalSearchResultItem(
|
GlobalSearchResultItem(
|
||||||
title = source.name,
|
title = fromSourceId?.let {
|
||||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
"▶ ${source.name}".takeIf { source.id == fromSourceId }
|
||||||
|
} ?: source.name,
|
||||||
|
subtitle = LocaleHelper.getLocalizedDisplayName(source.lang),
|
||||||
onClick = { onClickSource(source) },
|
onClick = { onClickSource(source) },
|
||||||
) {
|
) {
|
||||||
when (result) {
|
when (result) {
|
||||||
@ -82,21 +85,9 @@ fun GlobalSearchContent(
|
|||||||
GlobalSearchLoadingResultItem()
|
GlobalSearchLoadingResultItem()
|
||||||
}
|
}
|
||||||
is SearchItemResult.Success -> {
|
is SearchItemResult.Success -> {
|
||||||
if (result.isEmpty) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.no_results_found),
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(
|
|
||||||
horizontal = MaterialTheme.padding.medium,
|
|
||||||
vertical = MaterialTheme.padding.small,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return@GlobalSearchResultItem
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalSearchCardRow(
|
GlobalSearchCardRow(
|
||||||
titles = result.result,
|
titles = result.result,
|
||||||
getManga = { getManga(source, it) },
|
getManga = getManga,
|
||||||
onClick = onClickItem,
|
onClick = onClickItem,
|
||||||
onLongClick = onLongClickItem,
|
onLongClick = onLongClickItem,
|
||||||
)
|
)
|
||||||
|
@ -6,19 +6,19 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
|
||||||
import eu.kanade.presentation.components.FastScrollLazyColumn
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
|
||||||
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaState
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateMangaScreen(
|
fun MigrateMangaScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
title: String?,
|
title: String?,
|
||||||
state: MigrateMangaState,
|
state: MigrateMangaScreenModel.State,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -33,7 +33,7 @@ fun MigrateMangaScreen(
|
|||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
if (state.isEmpty) {
|
if (state.isEmpty) {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = R.string.empty_screen,
|
stringRes = MR.strings.empty_screen,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
return@Scaffold
|
return@Scaffold
|
||||||
@ -51,7 +51,7 @@ fun MigrateMangaScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun MigrateMangaContent(
|
private fun MigrateMangaContent(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
state: MigrateMangaState,
|
state: MigrateMangaScreenModel.State,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -70,10 +70,10 @@ private fun MigrateMangaContent(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MigrateMangaItem(
|
private fun MigrateMangaItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseMangaListItem(
|
BaseMangaListItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
@ -1,29 +1,24 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchEmptyResultItem
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||||
import eu.kanade.presentation.components.LazyColumn
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchState
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateSearchScreen(
|
fun MigrateSearchScreen(
|
||||||
|
state: SearchScreenModel.State,
|
||||||
|
fromSourceId: Long?,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: MigrateSearchState,
|
|
||||||
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
|
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
|
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||||
|
onToggleResults: () -> Unit,
|
||||||
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
onClickSource: (CatalogueSource) -> Unit,
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onLongClickItem: (Manga) -> Unit,
|
onLongClickItem: (Manga) -> Unit,
|
||||||
@ -37,13 +32,17 @@ fun MigrateSearchScreen(
|
|||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
onChangeSearchQuery = onChangeSearchQuery,
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
onSearch = onSearch,
|
onSearch = onSearch,
|
||||||
|
sourceFilter = state.sourceFilter,
|
||||||
|
onChangeSearchFilter = onChangeSearchFilter,
|
||||||
|
onlyShowHasResults = state.onlyShowHasResults,
|
||||||
|
onToggleResults = onToggleResults,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
MigrateSearchContent(
|
GlobalSearchContent(
|
||||||
sourceId = state.manga?.source ?: -1,
|
fromSourceId = fromSourceId,
|
||||||
items = state.items,
|
items = state.filteredItems,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
getManga = getManga,
|
getManga = getManga,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
@ -52,50 +51,3 @@ fun MigrateSearchScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MigrateSearchContent(
|
|
||||||
sourceId: Long,
|
|
||||||
items: Map<CatalogueSource, SearchItemResult>,
|
|
||||||
contentPadding: PaddingValues,
|
|
||||||
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
|
|
||||||
onClickSource: (CatalogueSource) -> Unit,
|
|
||||||
onClickItem: (Manga) -> Unit,
|
|
||||||
onLongClickItem: (Manga) -> Unit,
|
|
||||||
) {
|
|
||||||
LazyColumn(
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
) {
|
|
||||||
items.forEach { (source, result) ->
|
|
||||||
item(key = source.id) {
|
|
||||||
GlobalSearchResultItem(
|
|
||||||
title = if (source.id == sourceId) "▶ ${source.name}" else source.name,
|
|
||||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
|
||||||
onClick = { onClickSource(source) },
|
|
||||||
) {
|
|
||||||
when (result) {
|
|
||||||
SearchItemResult.Loading -> {
|
|
||||||
GlobalSearchLoadingResultItem()
|
|
||||||
}
|
|
||||||
is SearchItemResult.Success -> {
|
|
||||||
if (result.isEmpty) {
|
|
||||||
GlobalSearchEmptyResultItem()
|
|
||||||
return@GlobalSearchResultItem
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalSearchCardRow(
|
|
||||||
titles = result.result,
|
|
||||||
getManga = { getManga(source, it) },
|
|
||||||
onClick = onClickItem,
|
|
||||||
onLongClick = onLongClickItem,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is SearchItemResult.Error -> {
|
|
||||||
GlobalSearchErrorResultItem(message = result.throwable.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -20,30 +20,31 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.presentation.browse.components.SourceIcon
|
import eu.kanade.presentation.browse.components.SourceIcon
|
||||||
import eu.kanade.presentation.components.Badge
|
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel
|
||||||
import eu.kanade.presentation.components.BadgeGroup
|
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
|
||||||
import eu.kanade.presentation.components.Scroller.STICKY_HEADER_KEY_PREFIX
|
|
||||||
import eu.kanade.presentation.theme.header
|
|
||||||
import eu.kanade.presentation.util.padding
|
|
||||||
import eu.kanade.presentation.util.plus
|
|
||||||
import eu.kanade.presentation.util.secondaryItemAlpha
|
|
||||||
import eu.kanade.presentation.util.topSmallPaddingValues
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceState
|
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.Badge
|
||||||
|
import tachiyomi.presentation.core.components.BadgeGroup
|
||||||
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
|
import tachiyomi.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
|
import tachiyomi.presentation.core.theme.header
|
||||||
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateSourceScreen(
|
fun MigrateSourceScreen(
|
||||||
state: MigrateSourceState,
|
state: MigrateSourceScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
onToggleSortingDirection: () -> Unit,
|
onToggleSortingDirection: () -> Unit,
|
||||||
@ -51,9 +52,9 @@ fun MigrateSourceScreen(
|
|||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.isEmpty -> EmptyScreen(
|
state.isEmpty -> EmptyScreen(
|
||||||
textResource = R.string.information_empty_library,
|
stringRes = MR.strings.information_empty_library,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
else ->
|
else ->
|
||||||
@ -75,7 +76,7 @@ fun MigrateSourceScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MigrateSourceList(
|
private fun MigrateSourceList(
|
||||||
list: List<Pair<Source, Long>>,
|
list: ImmutableList<Pair<Source, Long>>,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
onLongClickItem: (Source) -> Unit,
|
onLongClickItem: (Source) -> Unit,
|
||||||
@ -95,21 +96,33 @@ private fun MigrateSourceList(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.migration_selection_prompt),
|
text = stringResource(MR.strings.migration_selection_prompt),
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
style = MaterialTheme.typography.header,
|
style = MaterialTheme.typography.header,
|
||||||
)
|
)
|
||||||
|
|
||||||
IconButton(onClick = onToggleSortingMode) {
|
IconButton(onClick = onToggleSortingMode) {
|
||||||
when (sortingMode) {
|
when (sortingMode) {
|
||||||
SetMigrateSorting.Mode.ALPHABETICAL -> Icon(Icons.Outlined.SortByAlpha, contentDescription = stringResource(R.string.action_sort_alpha))
|
SetMigrateSorting.Mode.ALPHABETICAL -> Icon(
|
||||||
SetMigrateSorting.Mode.TOTAL -> Icon(Icons.Outlined.Numbers, contentDescription = stringResource(R.string.action_sort_count))
|
Icons.Outlined.SortByAlpha,
|
||||||
|
contentDescription = stringResource(MR.strings.action_sort_alpha),
|
||||||
|
)
|
||||||
|
SetMigrateSorting.Mode.TOTAL -> Icon(
|
||||||
|
Icons.Outlined.Numbers,
|
||||||
|
contentDescription = stringResource(MR.strings.action_sort_count),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IconButton(onClick = onToggleSortingDirection) {
|
IconButton(onClick = onToggleSortingDirection) {
|
||||||
when (sortingDirection) {
|
when (sortingDirection) {
|
||||||
SetMigrateSorting.Direction.ASCENDING -> Icon(Icons.Outlined.ArrowUpward, contentDescription = stringResource(R.string.action_asc))
|
SetMigrateSorting.Direction.ASCENDING -> Icon(
|
||||||
SetMigrateSorting.Direction.DESCENDING -> Icon(Icons.Outlined.ArrowDownward, contentDescription = stringResource(R.string.action_desc))
|
Icons.Outlined.ArrowUpward,
|
||||||
|
contentDescription = stringResource(MR.strings.action_asc),
|
||||||
|
)
|
||||||
|
SetMigrateSorting.Direction.DESCENDING -> Icon(
|
||||||
|
Icons.Outlined.ArrowDownward,
|
||||||
|
contentDescription = stringResource(MR.strings.action_desc),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,11 +145,11 @@ private fun MigrateSourceList(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MigrateSourceItem(
|
private fun MigrateSourceItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
source: Source,
|
source: Source,
|
||||||
count: Long,
|
count: Long,
|
||||||
onClickItem: () -> Unit,
|
onClickItem: () -> Unit,
|
||||||
onLongClickItem: () -> Unit,
|
onLongClickItem: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseSourceItem(
|
BaseSourceItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@ -178,7 +191,7 @@ private fun MigrateSourceItem(
|
|||||||
if (source.isStub) {
|
if (source.isStub) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
text = stringResource(R.string.not_installed),
|
text = stringResource(MR.strings.not_installed),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
@ -7,29 +7,29 @@ import androidx.compose.material3.Checkbox
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
|
||||||
import eu.kanade.presentation.components.FastScrollLazyColumn
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
|
||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterState
|
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourcesFilterScreen(
|
fun SourcesFilterScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: SourcesFilterState.Success,
|
state: SourcesFilterScreenModel.State.Success,
|
||||||
onClickLanguage: (String) -> Unit,
|
onClickLanguage: (String) -> Unit,
|
||||||
onClickSource: (Source) -> Unit,
|
onClickSource: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
title = stringResource(R.string.label_sources),
|
title = stringResource(MR.strings.label_sources),
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
@ -37,7 +37,7 @@ fun SourcesFilterScreen(
|
|||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
if (state.isEmpty) {
|
if (state.isEmpty) {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = R.string.source_filter_empty_screen,
|
stringRes = MR.strings.source_filter_empty_screen,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
return@Scaffold
|
return@Scaffold
|
||||||
@ -54,7 +54,7 @@ fun SourcesFilterScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun SourcesFilterContent(
|
private fun SourcesFilterContent(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
state: SourcesFilterState.Success,
|
state: SourcesFilterScreenModel.State.Success,
|
||||||
onClickLanguage: (String) -> Unit,
|
onClickLanguage: (String) -> Unit,
|
||||||
onClickSource: (Source) -> Unit,
|
onClickSource: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -64,7 +64,7 @@ private fun SourcesFilterContent(
|
|||||||
state.items.forEach { (language, sources) ->
|
state.items.forEach { (language, sources) ->
|
||||||
val enabled = language in state.enabledLanguages
|
val enabled = language in state.enabledLanguages
|
||||||
item(
|
item(
|
||||||
key = language.hashCode(),
|
key = language,
|
||||||
contentType = "source-filter-header",
|
contentType = "source-filter-header",
|
||||||
) {
|
) {
|
||||||
SourcesFilterHeader(
|
SourcesFilterHeader(
|
||||||
@ -74,18 +74,19 @@ private fun SourcesFilterContent(
|
|||||||
onClickItem = onClickLanguage,
|
onClickItem = onClickLanguage,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!enabled) return@forEach
|
if (enabled) {
|
||||||
items(
|
items(
|
||||||
items = sources,
|
items = sources,
|
||||||
key = { "source-filter-${it.key()}" },
|
key = { "source-filter-${it.key()}" },
|
||||||
contentType = { "source-filter-item" },
|
contentType = { "source-filter-item" },
|
||||||
) { source ->
|
) { source ->
|
||||||
SourcesFilterItem(
|
SourcesFilterItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
source = source,
|
source = source,
|
||||||
enabled = "${source.id}" !in state.disabledSources,
|
enabled = "${source.id}" !in state.disabledSources,
|
||||||
onClickItem = onClickSource,
|
onClickItem = onClickSource,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,10 +94,10 @@ private fun SourcesFilterContent(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourcesFilterHeader(
|
private fun SourcesFilterHeader(
|
||||||
modifier: Modifier,
|
|
||||||
language: String,
|
language: String,
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
onClickItem: (String) -> Unit,
|
onClickItem: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
SwitchPreferenceWidget(
|
SwitchPreferenceWidget(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@ -108,10 +109,10 @@ private fun SourcesFilterHeader(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourcesFilterItem(
|
private fun SourcesFilterItem(
|
||||||
modifier: Modifier,
|
|
||||||
source: Source,
|
source: Source,
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseSourceItem(
|
BaseSourceItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
@ -19,36 +19,37 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
|
||||||
import eu.kanade.presentation.theme.header
|
|
||||||
import eu.kanade.presentation.util.padding
|
|
||||||
import eu.kanade.presentation.util.plus
|
|
||||||
import eu.kanade.presentation.util.topSmallPaddingValues
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesState
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.source.model.Pin
|
import tachiyomi.domain.source.model.Pin
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
|
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
|
import tachiyomi.presentation.core.theme.header
|
||||||
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
import tachiyomi.source.local.isLocal
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourcesScreen(
|
fun SourcesScreen(
|
||||||
state: SourcesState,
|
state: SourcesScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onClickItem: (Source, Listing) -> Unit,
|
onClickItem: (Source, Listing) -> Unit,
|
||||||
onClickPin: (Source) -> Unit,
|
onClickPin: (Source) -> Unit,
|
||||||
onLongClickItem: (Source) -> Unit,
|
onLongClickItem: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.isEmpty -> EmptyScreen(
|
state.isEmpty -> EmptyScreen(
|
||||||
textResource = R.string.source_empty_screen,
|
stringRes = MR.strings.source_empty_screen,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
else -> {
|
else -> {
|
||||||
@ -93,8 +94,8 @@ fun SourcesScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourceHeader(
|
private fun SourceHeader(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
language: String,
|
language: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
Text(
|
Text(
|
||||||
@ -107,11 +108,11 @@ private fun SourceHeader(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourceItem(
|
private fun SourceItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
source: Source,
|
source: Source,
|
||||||
onClickItem: (Source, Listing) -> Unit,
|
onClickItem: (Source, Listing) -> Unit,
|
||||||
onLongClickItem: (Source) -> Unit,
|
onLongClickItem: (Source) -> Unit,
|
||||||
onClickPin: (Source) -> Unit,
|
onClickPin: (Source) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseSourceItem(
|
BaseSourceItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@ -122,7 +123,7 @@ private fun SourceItem(
|
|||||||
if (source.supportsLatest) {
|
if (source.supportsLatest) {
|
||||||
TextButton(onClick = { onClickItem(source, Listing.Latest) }) {
|
TextButton(onClick = { onClickItem(source, Listing.Latest) }) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.latest),
|
text = stringResource(MR.strings.latest),
|
||||||
style = LocalTextStyle.current.copy(
|
style = LocalTextStyle.current.copy(
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
@ -143,8 +144,14 @@ private fun SourcePinButton(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val icon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin
|
val icon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin
|
||||||
val tint = if (isPinned) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground
|
val tint = if (isPinned) {
|
||||||
val description = if (isPinned) R.string.action_unpin else R.string.action_pin
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = SecondaryItemAlpha,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin
|
||||||
IconButton(onClick = onClick) {
|
IconButton(onClick = onClick) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
@ -167,7 +174,7 @@ fun SourceOptionsDialog(
|
|||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column {
|
||||||
val textId = if (Pin.Pinned in source.pin) R.string.action_unpin else R.string.action_pin
|
val textId = if (Pin.Pinned in source.pin) MR.strings.action_unpin else MR.strings.action_pin
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(textId),
|
text = stringResource(textId),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -175,9 +182,9 @@ fun SourceOptionsDialog(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 16.dp),
|
.padding(vertical = 16.dp),
|
||||||
)
|
)
|
||||||
if (source.id != LocalSource.ID) {
|
if (!source.isLocal()) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.action_disable),
|
text = stringResource(MR.strings.action_disable),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(onClick = onClickDisable)
|
.clickable(onClick = onClickDisable)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -191,7 +198,7 @@ fun SourceOptionsDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class SourceUiModel {
|
sealed interface SourceUiModel {
|
||||||
data class Item(val source: Source) : SourceUiModel()
|
data class Item(val source: Source) : SourceUiModel
|
||||||
data class Header(val language: String) : SourceUiModel()
|
data class Header(val language: String) : SourceUiModel
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import eu.kanade.presentation.util.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BaseBrowseItem(
|
fun BaseBrowseItem(
|
||||||
|
@ -9,15 +9,15 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import eu.kanade.presentation.util.padding
|
|
||||||
import eu.kanade.presentation.util.secondaryItemAlpha
|
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BaseSourceItem(
|
fun BaseSourceItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
source: Source,
|
source: Source,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
showLanguageInContent: Boolean = true,
|
showLanguageInContent: Boolean = true,
|
||||||
onClickItem: () -> Unit = {},
|
onClickItem: () -> Unit = {},
|
||||||
onLongClickItem: () -> Unit = {},
|
onLongClickItem: () -> Unit = {},
|
||||||
@ -25,7 +25,9 @@ fun BaseSourceItem(
|
|||||||
action: @Composable RowScope.(Source) -> Unit = {},
|
action: @Composable RowScope.(Source) -> Unit = {},
|
||||||
content: @Composable RowScope.(Source, String?) -> Unit = defaultContent,
|
content: @Composable RowScope.(Source, String?) -> Unit = defaultContent,
|
||||||
) {
|
) {
|
||||||
val sourceLangString = LocaleHelper.getSourceDisplayName(source.lang, LocalContext.current).takeIf { showLanguageInContent }
|
val sourceLangString = LocaleHelper.getSourceDisplayName(source.lang, LocalContext.current).takeIf {
|
||||||
|
showLanguageInContent
|
||||||
|
}
|
||||||
BaseBrowseItem(
|
BaseBrowseItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.CollectionsBookmark
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import tachiyomi.presentation.core.components.Badge
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun InLibraryBadge(enabled: Boolean) {
|
||||||
|
if (enabled) {
|
||||||
|
Badge(
|
||||||
|
imageVector = Icons.Outlined.CollectionsBookmark,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.presentation.browse.components
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@ -31,9 +30,10 @@ import eu.kanade.domain.source.model.icon
|
|||||||
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.source.local.isLocal
|
||||||
|
|
||||||
private val defaultModifier = Modifier
|
private val defaultModifier = Modifier
|
||||||
.height(40.dp)
|
.height(40.dp)
|
||||||
@ -62,7 +62,7 @@ fun SourceIcon(
|
|||||||
modifier = modifier.then(defaultModifier),
|
modifier = modifier.then(defaultModifier),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
source.id == LocalSource.ID -> {
|
source.isLocal() -> {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(R.mipmap.ic_local_source),
|
painter = painterResource(R.mipmap.ic_local_source),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
@ -127,7 +127,7 @@ private fun Extension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT): St
|
|||||||
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
|
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
|
||||||
withIOContext {
|
withIOContext {
|
||||||
value = try {
|
value = try {
|
||||||
val appInfo = context.packageManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA)
|
val appInfo = ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
|
||||||
val appResources = context.packageManager.getResourcesForApplication(appInfo)
|
val appResources = context.packageManager.getResourcesForApplication(appInfo)
|
||||||
Result.Success(
|
Result.Success(
|
||||||
appResources.getDrawableForDensity(appInfo.icon, density, null)!!
|
appResources.getDrawableForDensity(appInfo.icon, density, null)!!
|
||||||
@ -142,7 +142,7 @@ private fun Extension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT): St
|
|||||||
}
|
}
|
||||||
|
|
||||||
sealed class Result<out T> {
|
sealed class Result<out T> {
|
||||||
object Loading : Result<Nothing>()
|
data object Loading : Result<Nothing>()
|
||||||
object Error : Result<Nothing>()
|
data object Error : Result<Nothing>()
|
||||||
data class Success<out T>(val value: T) : Result<T>()
|
data class Success<out T>(val value: T) : Result<T>()
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,12 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import eu.kanade.presentation.browse.InLibraryBadge
|
import eu.kanade.presentation.library.components.CommonMangaItemDefaults
|
||||||
import eu.kanade.presentation.components.CommonMangaItemDefaults
|
import eu.kanade.presentation.library.components.MangaComfortableGridItem
|
||||||
import eu.kanade.presentation.components.MangaComfortableGridItem
|
|
||||||
import eu.kanade.presentation.util.plus
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceComfortableGrid(
|
fun BrowseSourceComfortableGrid(
|
||||||
@ -39,7 +38,7 @@ fun BrowseSourceComfortableGrid(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items(mangaList.itemCount) { index ->
|
items(count = mangaList.itemCount) { index ->
|
||||||
val manga by mangaList[index]?.collectAsState() ?: return@items
|
val manga by mangaList[index]?.collectAsState() ?: return@items
|
||||||
BrowseSourceComfortableGridItem(
|
BrowseSourceComfortableGridItem(
|
||||||
manga = manga,
|
manga = manga,
|
||||||
@ -57,7 +56,7 @@ fun BrowseSourceComfortableGrid(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceComfortableGridItem(
|
private fun BrowseSourceComfortableGridItem(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
onLongClick: () -> Unit = onClick,
|
onLongClick: () -> Unit = onClick,
|
||||||
|
@ -11,13 +11,12 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import eu.kanade.presentation.browse.InLibraryBadge
|
import eu.kanade.presentation.library.components.CommonMangaItemDefaults
|
||||||
import eu.kanade.presentation.components.CommonMangaItemDefaults
|
import eu.kanade.presentation.library.components.MangaCompactGridItem
|
||||||
import eu.kanade.presentation.components.MangaCompactGridItem
|
|
||||||
import eu.kanade.presentation.util.plus
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceCompactGrid(
|
fun BrowseSourceCompactGrid(
|
||||||
@ -39,7 +38,7 @@ fun BrowseSourceCompactGrid(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items(mangaList.itemCount) { index ->
|
items(count = mangaList.itemCount) { index ->
|
||||||
val manga by mangaList[index]?.collectAsState() ?: return@items
|
val manga by mangaList[index]?.collectAsState() ?: return@items
|
||||||
BrowseSourceCompactGridItem(
|
BrowseSourceCompactGridItem(
|
||||||
manga = manga,
|
manga = manga,
|
||||||
|
@ -4,11 +4,9 @@ import androidx.compose.material3.AlertDialog
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RemoveMangaDialog(
|
fun RemoveMangaDialog(
|
||||||
@ -20,7 +18,7 @@ fun RemoveMangaDialog(
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
@ -30,14 +28,14 @@ fun RemoveMangaDialog(
|
|||||||
onConfirm()
|
onConfirm()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.action_remove))
|
Text(text = stringResource(MR.strings.action_remove))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = stringResource(R.string.are_you_sure))
|
Text(text = stringResource(MR.strings.are_you_sure))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringResource(R.string.remove_manga, mangaToRemove.title))
|
Text(text = stringResource(MR.strings.remove_manga, mangaToRemove.title))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user