mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-27 11:37:51 +02:00
Compare commits
6608 Commits
v0.1.1
...
c550a81598
Author | SHA1 | Date | |
---|---|---|---|
c550a81598 | |||
0be36a10c3 | |||
e16c3953c7 | |||
f3a2f566c8 | |||
15e3f28aa3 | |||
3bf70b230f | |||
eb3bea8150 | |||
5612ae0149 | |||
dbf6ad2ca7 | |||
d2afbfe4ed | |||
337806d9e1 | |||
443f6e0ae5 | |||
572ee2f02a | |||
ba1343bed8 | |||
9f3d5d13d4 | |||
48166b9b52 | |||
2e2c8d36c1 | |||
788235feec | |||
afa5002988 | |||
9503082d44 | |||
de36357da8 | |||
eb6092bd0c | |||
32d2c2ac1b | |||
4051f180a2 | |||
3ed8a91c7b | |||
87db3f90de | |||
0a4ad89b99 | |||
a72db41bf1 | |||
6b2bba4e54 | |||
7c7af72f8c | |||
c8bb78d91a | |||
2ba3f0612c | |||
f84d9a08b4 | |||
37419cdc26 | |||
481cfedf08 | |||
9b8ab6acc2 | |||
3bddb55385 | |||
2beb89d531 | |||
016f627fb0 | |||
44aab7a243 | |||
a2dc88965b | |||
aa998071a1 | |||
8113b77f1e | |||
6adfa4fd0f | |||
76e0aba70c | |||
f7fbc93833 | |||
85ee9c6686 | |||
c72c07f355 | |||
6984e0465b | |||
3ca989eae8 | |||
cca33481dd | |||
f7c8f1801e | |||
112b68b782 | |||
2dd02b73d6 | |||
369df527b2 | |||
d04eeface9 | |||
dde942df4e | |||
380787a310 | |||
418ba30265 | |||
b3867dd63c | |||
6dd93d70cc | |||
2276abbb23 | |||
be671b42ce | |||
0042cb6582 | |||
1e570bc965 | |||
9cc7d42dd9 | |||
f5c6d2e1a6 | |||
339dc33f58 | |||
223af5508f | |||
d42f776c5c | |||
5c0dc3e05a | |||
bebf80dfae | |||
86dd809f4d | |||
1ff88dd927 | |||
be5d467955 | |||
fcb01b5bcf | |||
844dae1a4d | |||
83fd4746ed | |||
c8ad6cdf31 | |||
fbcc48fefc | |||
6f422745ba | |||
bec549cc44 | |||
c4f235ae07 | |||
8fd1239bea | |||
b56a97bb8e | |||
52036e5664 | |||
29a74509a4 | |||
0e956cbb51 | |||
2baffa62ca | |||
bd7b354198 | |||
70c1a842b2 | |||
001249a89d | |||
c4d2fffb12 | |||
f22767d863 | |||
02af9b1acf | |||
3c611b95fb | |||
fc1c804bfd | |||
abfb72c89c | |||
9c1905ede7 | |||
9f99f038f3 | |||
6c6ea84509 | |||
4ee31bfea5 | |||
03eb756ecb | |||
a45eb5e528 | |||
8f9a325895 | |||
f74071ab0a | |||
7fb3ef48e4 | |||
1837faa573 | |||
518abf032c | |||
7ca64a67c5 | |||
d26c010e57 | |||
607e56a4ec | |||
952a98c180 | |||
45628b14db | |||
5dc6569a68 | |||
fba9bacdc1 | |||
ca968f162e | |||
379d587826 | |||
034ec4cb12 | |||
2481767532 | |||
ab2b734d49 | |||
aac4d6e548 | |||
08ae51ea8c | |||
4387ae5ff3 | |||
d6252ab770 | |||
5ae8095ef1 | |||
ac41bffdc9 | |||
777ae2461e | |||
b2f1719c50 | |||
3f050a83dd | |||
6f4e3f776f | |||
1c47a6b9b3 | |||
f4348df870 | |||
9a34ace09c | |||
124a787cda | |||
b404a71e26 | |||
be124ebe86 | |||
fdb96179c6 | |||
c5994e057b | |||
3f1d28c383 | |||
84b2164787 | |||
200d39e023 | |||
b1b15a93ee | |||
97c81fadb4 | |||
9240eceedc | |||
14ae57d78b | |||
4828c54245 | |||
dca9bf1057 | |||
e8b7c3e24b | |||
af77083660 | |||
36b9caeea8 | |||
fdc1423f3d | |||
8e40146f96 | |||
1c16fc79c2 | |||
31263084ec | |||
2858ef835f | |||
edb8201f74 | |||
854474f85f | |||
04db46fe75 | |||
3f6bd5f010 | |||
a3dfd2efe6 | |||
de8ef6dad7 | |||
8160b47ff5 | |||
c201b341a7 | |||
56fb4f62a1 | |||
0af90999c8 | |||
913ff22132 | |||
04aa5b36a5 | |||
41e2dc7ae8 | |||
88efde8796 | |||
b7849d7146 | |||
602b58f364 | |||
e48dbdbf23 | |||
6ace423e18 | |||
51b68cd25f | |||
ca784cbe32 | |||
4f61b2e4e8 | |||
8c9d12a840 | |||
f63e950910 | |||
f3f2bd41c3 | |||
14d687c5cd | |||
e94c8dac94 | |||
7a2ca4bf4d | |||
4a7613d515 | |||
e65634cb42 | |||
daa47e0493 | |||
cbcd8bd668 | |||
2092c81bad | |||
5a61ca5535 | |||
ddba71df37 | |||
75b5d96601 | |||
77db8873f6 | |||
bff6183cf3 | |||
e620665dda | |||
c0f9de88e7 | |||
80cdebcdf4 | |||
9e2f97eeb8 | |||
e132cc405f | |||
2674b84974 | |||
f34702d4fc | |||
7823966ddf | |||
2d41bf5589 | |||
d8fe7d32ca | |||
2f86f25d5b | |||
239c38982c | |||
36e40c0997 | |||
40754659a9 | |||
a41ea8a61d | |||
5c249dd790 | |||
e17f70f722 | |||
e57638a49c | |||
0ce1cf22cd | |||
4ed2062cab | |||
f6ec53cdde | |||
b37357f909 | |||
f58a05e918 | |||
cf02119da5 | |||
5e2a3ee927 | |||
3b8ed3059a | |||
4182ae89a0 | |||
f3226fb278 | |||
30a6e3a6a1 | |||
2e78bceb30 | |||
a5838387b1 | |||
aa1714b2ac | |||
f696f209c6 | |||
9fa22f0b37 | |||
6d8cfd5f30 | |||
af57e124f2 | |||
8e8ee69bba | |||
e9d69a83fe | |||
6a80305d6c | |||
119bcbf8ed | |||
87fe64468c | |||
bdce3c39f1 | |||
15d999229f | |||
1edd55c981 | |||
777a071f4a | |||
71b558cb34 | |||
46003ec251 | |||
e8fdfaad64 | |||
1f7574bd4f | |||
da62c7a21a | |||
0870cffba1 | |||
8632ba85ee | |||
116579d38c | |||
098f925519 | |||
9f5db70572 | |||
1f286f1a35 | |||
7ab7f5ac37 | |||
e567250b17 | |||
095da924b9 | |||
b9da98b527 | |||
af8696cb90 | |||
de5a64aa73 | |||
9b944092c7 | |||
0cb1794a44 | |||
2f243fae11 | |||
d2e5c78074 | |||
5912d6b08f | |||
99b550ae0d | |||
8d187f7865 | |||
653d5d3e25 | |||
1dca9363a4 | |||
fffa6a462d | |||
ce497003e3 | |||
84ea5166de | |||
0392ef0d18 | |||
16392adcbb | |||
f603db3f3f | |||
ab546e0884 | |||
d3306e8cfe | |||
aebb86794a | |||
9a62e4fba3 | |||
5955c9c311 | |||
fb9423028e | |||
8e9396a9cf | |||
1df87eabf2 | |||
ca7391bbf3 | |||
550f1197e8 | |||
dbcc4a7d71 | |||
e473c7f09f | |||
263e467cde | |||
7ec2108812 | |||
e55e5f6f64 | |||
28dca3b7b8 | |||
70cd688ac2 | |||
21145144cd | |||
134e4648a9 | |||
fa6dba6cc7 | |||
8a51d56c59 | |||
47ee2b45a8 | |||
a2f7d47a0a | |||
4e5cbbc96b | |||
b720f34267 | |||
c6a1412f18 | |||
6290cf222d | |||
a3d438e2f5 | |||
80461d883f | |||
c3e7bb12f4 | |||
2ad98520aa | |||
f1ce205d00 | |||
5289830a84 | |||
dd932e1362 | |||
e85ce6456a | |||
9a3ffe2ea6 | |||
213effa169 | |||
25570147a1 | |||
e82a2f5f9f | |||
935c0c7e2e | |||
2ad462b4d8 | |||
7fd8f65352 | |||
b152e3881b | |||
f27ca3b1b2 | |||
843daa5304 | |||
f080a4937e | |||
8bba926891 | |||
5378c4c5d6 | |||
c94d212ef4 | |||
4c43a0ef66 | |||
ea0fe2414e | |||
015620711d | |||
6d4267b3bb | |||
2a01a2ac6b | |||
9a6559b013 | |||
a7509b3a3c | |||
2755d1f35e | |||
6a8a9c6bbf | |||
7862088b94 | |||
35f8eda8c5 | |||
fa6fa1f53a | |||
c348fac78f | |||
ab06720966 | |||
42ebf017e4 | |||
3910ffdd9e | |||
0bfacf5570 | |||
1e28999e13 | |||
1ee54d74a4 | |||
a1a52ae81a | |||
56e66e041d | |||
13656959ae | |||
20e4cb26d6 | |||
e448e40406 | |||
aed53d3bdc | |||
d77f2f429d | |||
c3fd2df6f5 | |||
f5a41e9693 | |||
34bf5c6f87 | |||
6abaa47f5b | |||
c9fddf9e38 | |||
555d2f834f | |||
6b3423a12b | |||
7c6fd026a3 | |||
86fbd20665 | |||
8c8d65d3c7 | |||
f1660beafc | |||
72222ad86d | |||
0265c16eb2 | |||
666d6aa117 | |||
6965e59a64 | |||
e020ae5ed5 | |||
05071b4205 | |||
da20d00481 | |||
8c437ceecf | |||
9672ea8b1b | |||
ba9cfd867c | |||
4b4e468510 | |||
e75488f5d9 | |||
3838dbcf08 | |||
b3ca097e5a | |||
70c2443e82 | |||
c0a888807b | |||
34930920a5 | |||
6682b5dd39 | |||
3c5f4a317a | |||
6a2bfd5e87 | |||
ef6cad58fe | |||
7e9340aa7f | |||
3f2c8e9ef6 | |||
a29870c01e | |||
583aa430ba | |||
0ea0138a73 | |||
59bedb33ff | |||
ebee275110 | |||
015d9b3bd0 | |||
f2ccfb0817 | |||
1b60c5f0f4 | |||
0a91b57f67 | |||
bcdf17fe27 | |||
a08e03f5cb | |||
f087135876 | |||
f66f52c244 | |||
0d6f426dbd | |||
edd7d0522c | |||
4ae9dbe524 | |||
402e579a69 | |||
d0e64d3a66 | |||
154f4d327c | |||
d8b9a9f593 | |||
b7e091d5d0 | |||
31e052ac15 | |||
60480686da | |||
d6ba3c8249 | |||
c56f4665ef | |||
b51a0a38bd | |||
f72b6e4d7c | |||
84984ef7e1 | |||
9f48def1e2 | |||
e83bfb0d35 | |||
0301362430 | |||
9d5978aca0 | |||
4bfc5e7b51 | |||
5859a8bbf6 | |||
802a2c5c1e | |||
c1c1746985 | |||
4fcbd80a8e | |||
16969193c7 | |||
55637ddfe1 | |||
e50358dc4b | |||
1e40199b7d | |||
410b918b77 | |||
a4f5dfab1a | |||
085147b15b | |||
085ad8d446 | |||
9254079957 | |||
7974a1fc0c | |||
1521c35941 | |||
2247f6004a | |||
c15f3f2fd5 | |||
21020e1797 | |||
7edecae57f | |||
07f963d5ae | |||
ab02568ac6 | |||
617bf491ee | |||
840b647b4b | |||
1b0bbb8440 | |||
95d4df9ca8 | |||
fb86c470f6 | |||
5aec8f8018 | |||
e183cbb231 | |||
6bdb37be65 | |||
7ff95e21ba | |||
96c236e5c3 | |||
72f3756a3b | |||
0780385d2e | |||
31e9273b1f | |||
088e37b2d8 | |||
5b88f1bd94 | |||
18beb20aac | |||
d5d7065e75 | |||
78271a54a4 | |||
9bff20cb1a | |||
2ccff8cdde | |||
0da7ad6f1a | |||
170daf9fb2 | |||
139663acfc | |||
1581b876cf | |||
0f4de03d7a | |||
ddbe8efbc5 | |||
63146e717b | |||
b2d22f86c6 | |||
79f46b25f6 | |||
aa498360db | |||
f03f998b21 | |||
4811cf07cd | |||
b71c793fad | |||
47e5421527 | |||
5dc6501688 | |||
cc09230e26 | |||
9c4d2b087f | |||
4bcba0503a | |||
cf33f250cc | |||
915a967151 | |||
9cc0c4e035 | |||
222e111806 | |||
8489b0dd8b | |||
88ed634978 | |||
32188f9f65 | |||
05efc4ebeb | |||
65bfa083f2 | |||
b8a9998bbd | |||
d736bec003 | |||
348b23a9fd | |||
121b2ec829 | |||
1dd130df9e | |||
e17d87f357 | |||
de75561402 | |||
58085336a5 | |||
89ea0a271b | |||
e3f33e24f5 | |||
9fd1419142 | |||
cb06898430 | |||
39407407f2 | |||
a024218410 | |||
26815c7356 | |||
e0deeb8008 | |||
38d6ab80ce | |||
78e66fd8d3 | |||
26aa126ecb | |||
e4a65656e7 | |||
6018aa99e2 | |||
99fd2731f5 | |||
e34043f1fe | |||
3c3a1cd448 | |||
0a2df21c5b | |||
277be02682 | |||
1849715418 | |||
dc6d4f9917 | |||
a9d98e5048 | |||
653940613d | |||
23a2d816e4 | |||
8a3a9146db | |||
a605a4ec75 | |||
c83037eeab | |||
25c76f5612 | |||
0d449a9b1d | |||
62cb12a3f1 | |||
9ec4dc5758 | |||
f594f1994b | |||
96b85962e3 | |||
f77e0e2d00 | |||
ce60ac150b | |||
19afd8c9ca | |||
5067160132 | |||
c9906491fb | |||
e51013d2a4 | |||
1aa75f22d0 | |||
8c910f2a2c | |||
dfb3091e38 | |||
98bdef230a | |||
4b594fc11f | |||
71931cf697 | |||
87e3525f88 | |||
a9c7cbf2c4 | |||
e63a52b8e3 | |||
49991d38d9 | |||
33c62ab711 | |||
899bd26956 | |||
a37f3eb709 | |||
9ae71dfe93 | |||
c65a9aecf5 | |||
02e50411de | |||
6e822dfd5b | |||
7292dadd5f | |||
b1067b942e | |||
d6c4af89c4 | |||
cf6f7c521c | |||
c6601c1f94 | |||
68899aea61 | |||
c3edf9b5d0 | |||
97e04392d3 | |||
3d178737b1 | |||
bf737cf95c | |||
c91ec9a33b | |||
a8040cb21a | |||
f60782f11f | |||
7d6e1bdafc | |||
5854ad97e0 | |||
4b8fa059d5 | |||
3dc2f9a711 | |||
8033a94ee2 | |||
028da099dd | |||
e6c6c32d81 | |||
bce6af62fc | |||
6510a9617a | |||
14510f1d26 | |||
f115edf2ea | |||
8a8362203f | |||
f3336fc5c3 | |||
727289c8eb | |||
9c91ddd4e3 | |||
3ea026e311 | |||
36f307e3bb | |||
89678ebb17 | |||
80b7d14af1 | |||
bbd8098a61 | |||
f8ef0f143b | |||
a3ef3604ee | |||
c4ceda59df | |||
7e053b5862 | |||
ac8ed3c028 | |||
8321ff6000 | |||
9c899e97a9 | |||
556f5a42a7 | |||
850813820c | |||
dba5e6fbfd | |||
c17ada2c98 | |||
32bed9b041 | |||
e0a0942015 | |||
8409ebe4eb | |||
493da5c3f4 | |||
4e221397ce | |||
8a7d6a328a | |||
22589a9c30 | |||
ec478cbb1b | |||
b5e3f429fc | |||
83130f9bf9 | |||
6f34c5e894 | |||
74931fad86 | |||
6ab8e1e73d | |||
1cdaa761b7 | |||
901b77f55c | |||
54f4711f7b | |||
3d0d5c0472 | |||
f0a0ecfd4a | |||
f3b7eaf4a3 | |||
5bba7af24a | |||
32c3269291 | |||
a1e84911be | |||
6bb77bcf1a | |||
ccec5c3efe | |||
8735836498 | |||
8b65fd5751 | |||
f0710df356 | |||
3afcee81f4 | |||
9c120e6231 | |||
4b208fc7ce | |||
a9b0ac43c4 | |||
fca4f25122 | |||
bfb0d31ff6 | |||
8939274b5c | |||
087da2b2f3 | |||
4571dc6b56 | |||
f31bc47757 | |||
950b4a6c90 | |||
2d7650537d | |||
80d6d412f3 | |||
446b146f95 | |||
6887d98f15 | |||
6d74a86711 | |||
5908bd1930 | |||
1a559124eb | |||
54ba1d719e | |||
93cbeca5c0 | |||
19f0175a56 | |||
bf3899d04a | |||
dcf0379496 | |||
9f90ee358b | |||
565317d99c | |||
83a67feb48 | |||
a51108cbe8 | |||
b9fd416fc6 | |||
c10cd6c808 | |||
c62cd6e997 | |||
7ae17e6aac | |||
f20980b4c9 | |||
02cd2d2ca3 | |||
3847d4f4cf | |||
f9b57800b1 | |||
387159b5af | |||
09531e7f5a | |||
c6356fe4b2 | |||
ff3bc66055 | |||
13b3bec8ad | |||
8aaf8df708 | |||
c00f05a1c1 | |||
db3ddf07ee | |||
cd16522805 | |||
5fec881387 | |||
3ac68e810d | |||
e6fe5c827c | |||
65e1e2cf4f | |||
e36a2c68f1 | |||
add9357257 | |||
ad3d915fc5 | |||
36f400d542 | |||
dd1a19745a | |||
58daedc89e | |||
d20a8fcf13 | |||
e56bf82c31 | |||
0f9895eec8 | |||
f776c36e70 | |||
1ef01b53f2 | |||
720169dce3 | |||
0d09039e5f | |||
cc56fde9fe | |||
3a0b3de175 | |||
47e544b710 | |||
44d6c4fe44 | |||
e5693ed668 | |||
8c21aa86e9 | |||
f7c5b42435 | |||
e3404cd3d3 | |||
8b57169e92 | |||
ab9a26f6bd | |||
8779b263ab | |||
3135db4bb2 | |||
734cb0be6e | |||
1f259f9298 | |||
427fbfdf5e | |||
0c860c0fe9 | |||
5b2a099203 | |||
ccadfc8fe5 | |||
3aead3a2a9 | |||
6a48fed170 | |||
ea1684133b | |||
e5263d0345 | |||
24e1b4034e | |||
dfa5c229b3 | |||
87be54aa4a | |||
82d9ae31bd | |||
e5518b7615 | |||
e5a22eafe7 | |||
7a52afd223 | |||
296201d6b7 | |||
162b639705 | |||
5dda32bb81 | |||
8ce8b60092 | |||
8ff2c01bf2 | |||
e22eebfd02 | |||
4fcdde4913 | |||
e41668862f | |||
a74a689c90 | |||
d85a76484c | |||
82bdf63419 | |||
9ce0bc6b5f | |||
bf524595e2 | |||
27c4db752c | |||
ca54984344 | |||
46aeab9a7a | |||
f365b53a0f | |||
d4dfa9a2c2 | |||
cf9e60fd92 | |||
21ae04d25d | |||
f1778ac5b4 | |||
ba10093ddc | |||
a5c9469698 | |||
75314c78e0 | |||
53edae1b6b | |||
356fc5b524 | |||
60150423d7 | |||
bcc42dd259 | |||
d59cb9c1e3 | |||
3006604922 | |||
1fbf8ca079 | |||
695813ef7d | |||
e3b70ca08d | |||
8857b7e0c1 | |||
4a7c20f5a0 | |||
29368fc953 | |||
0696e4bce0 | |||
255ed50685 | |||
00afee83b8 | |||
0d1bced122 | |||
46e734fc8e | |||
c39ae21f4a | |||
69aa13bc56 | |||
2c032ff70d | |||
0af4703b78 | |||
ea15bc782a | |||
9ec0f73e87 | |||
f9fb034330 | |||
6eb5a25ea1 | |||
45d8411f98 | |||
d9e2317e62 | |||
336221a972 | |||
dd998be1e7 | |||
3c3b09209c | |||
4a6571d310 | |||
cb67f1de52 | |||
402e2c47fb | |||
58b2895ec9 | |||
00b2853d3d | |||
634ceeec50 | |||
d7442d771b | |||
8f22480ec9 | |||
9d974273af | |||
3a8aa3e8cd | |||
9e67abcc8a | |||
d0bcd30909 | |||
b97aa23548 | |||
e6ca54fd04 | |||
4502902fb0 | |||
5f34539525 | |||
953f5fb025 | |||
4f3a0b3523 | |||
1d144e6767 | |||
056dbaefda | |||
3a15c6b843 | |||
db20d04c4b | |||
c5e8c9f01f | |||
64c50c1283 | |||
4146c4c31d | |||
69223df27c | |||
4b225a4ff1 | |||
4a2ee0b596 | |||
8644d90bd4 | |||
f30ab56fd0 | |||
5d91b77c93 | |||
aca36f9625 | |||
d5e8c38075 | |||
6d538db5f2 | |||
b3d7c92475 | |||
d862d83511 | |||
8a1625ec79 | |||
2ee895ee3c | |||
7cf2ce2994 | |||
cb8ea5eab0 | |||
ce7bf396eb | |||
1aa5222c99 | |||
298c49f3ab | |||
ce5e10be95 | |||
64ad25d1b5 | |||
4868dd2d03 | |||
443d56f69b | |||
7457a18aee | |||
d80ba2e807 | |||
118d3b7fcc | |||
eed57b80be | |||
c46c39d4ae | |||
d7d7a6d2fc | |||
17b90d2491 | |||
9ecec5d468 | |||
0bdd3f79d4 | |||
7dccde0930 | |||
c8d68590db | |||
94448faf97 | |||
28028c789c | |||
f8834ee764 | |||
7c703b17d3 | |||
f77ade7dda | |||
91712daee8 | |||
8057f067b9 | |||
548f7f415a | |||
d9c0b1ce7d | |||
0a0b686119 | |||
092d930175 | |||
3b7ed9bc6d | |||
012854dd1e | |||
6d1e520c6c | |||
dcc3141080 | |||
7326598475 | |||
fcba2306e9 | |||
f84868a264 | |||
15423bfc84 | |||
8e4cedf173 | |||
19965e0bdb | |||
3a35c13575 | |||
489d22720a | |||
e1b3345b94 | |||
c53172265b | |||
8626a55fe4 | |||
1302461518 | |||
8f3681d79f | |||
c4ce3dd46f | |||
22df12a680 | |||
e572abb041 | |||
ea99d77fda | |||
2bf77f1d81 | |||
f79f0a7e97 | |||
82a9d36df7 | |||
447bcb28ef | |||
0be7ac5871 | |||
d18022c259 | |||
5619a4c0d9 | |||
8a7bbfddda | |||
0026f96fad | |||
c492efcb31 | |||
c386d375de | |||
540fb1bb7c | |||
81448f5d01 | |||
7c01201055 | |||
90d3dd2242 | |||
97b4d1f13d | |||
79b37df647 | |||
b7d282235d | |||
77ebc362f6 | |||
8568d5d6c3 | |||
7ed99fbbd6 | |||
94cba9324c | |||
6dab94a937 | |||
0f42b9f154 | |||
730f3a6e52 | |||
72024aa44a | |||
9c688b08c0 | |||
c66a4fa7a7 | |||
e47f4cc177 | |||
6462472d16 | |||
78aa50bb35 | |||
7f0f67d752 | |||
df332860b8 | |||
8a8afa46e9 | |||
509bee0563 | |||
afb1ee2200 | |||
66a938779d | |||
ed506f8495 | |||
c8e226acb2 | |||
86edce0d87 | |||
4e69bf993a | |||
56d2464870 | |||
5de72b7d32 | |||
de92b1351f | |||
77a8a4229c | |||
d4290f6f59 | |||
b08d604d2a | |||
9e04f14a7b | |||
6663abebaf | |||
e5f83d0c6e | |||
3ad7add3b5 | |||
66aacade9a | |||
fe3a710ed0 | |||
f9754f4f58 | |||
8824c7dbe3 | |||
ccc9a5a052 | |||
f5e0cee36c | |||
36f1e0e476 | |||
2dd2db7225 | |||
3d0e750519 | |||
26c5d761da | |||
1668be8587 | |||
86a3fc77c6 | |||
cc018cee18 | |||
d9d143e6be | |||
3f0db60a99 | |||
87f3d4bd05 | |||
5c3d655d9e | |||
66b175a3c8 | |||
3cd3f45c8a | |||
816d7815e9 | |||
dbc7fe4d54 | |||
d29b7c4e57 | |||
772db51593 | |||
87530f506e | |||
98d6ce2eaf | |||
7644d7c31e | |||
dde2f42138 | |||
6922792ad1 | |||
f32243899d | |||
13dc54df70 | |||
6d9a8a30e9 | |||
2bf263e301 | |||
c06beac660 | |||
74f74eef56 | |||
3aafec482c | |||
ed80ac3154 | |||
eeeaae4570 | |||
d1c956401c | |||
1be7949275 | |||
5572b28d01 | |||
4e68b62881 | |||
4e31e6a2fa | |||
bc692ebfc6 | |||
96f6a5abc2 | |||
3411ac40c0 | |||
8a6a104987 | |||
efa7a3a167 | |||
67bc81ebde | |||
0a3ce8ebe4 | |||
3ebf39bd55 | |||
8f395d98e7 | |||
26b3eb696c | |||
627f07408e | |||
7146913c71 | |||
39c6bcccd8 | |||
6259bbaa5e | |||
cb4b8ac0dc | |||
4b7acdb022 | |||
af0fdfa3b7 | |||
d874f20362 | |||
8680accd8e | |||
7308090288 | |||
400ca48456 | |||
9b6567f5e4 | |||
7798186c32 | |||
9dc66c7c8d | |||
10b0ef9b6d | |||
81cd765543 | |||
c9a1bd86b5 | |||
dfbbbadfac | |||
0f21d16263 | |||
d65f9c2916 | |||
5718983f41 | |||
f7b335e4fb | |||
aa6937baf2 | |||
59f7d2273f | |||
cd91ea9b77 | |||
6a558ad119 | |||
f5936e9456 | |||
9df351da0a | |||
90325d48aa | |||
e2abf283fe | |||
db788d519d | |||
f3e9d5f346 | |||
fd30c0adcd | |||
3ad4f1114a | |||
fe90546821 | |||
3892c4caac | |||
cb639f4e90 | |||
6d69caf59e | |||
cdc1c5efa3 | |||
77bfd0c099 | |||
8ff0c9d61a | |||
b6620434b3 | |||
abae9bf37d | |||
2556e9f08c | |||
7aa172c512 | |||
81cf232bcb | |||
ee26d6dffd | |||
7b2764e8f7 | |||
46e3b9e40d | |||
8d00ff1b40 | |||
cf14831fbe | |||
7a4680603d | |||
99f12b1fbf | |||
5c73045aa4 | |||
ac306547a0 | |||
3f868c0435 | |||
262ce3473f | |||
43b9b104f5 | |||
c7f0a54a37 | |||
ca789dca0e | |||
ef7b285151 | |||
dd3ca0c131 | |||
8b46e8edad | |||
30f845139d | |||
818471b7e1 | |||
a3a3f44056 | |||
22c6dbda3f | |||
34f7caa0fc | |||
01553b1ed8 | |||
a24afa9a76 | |||
ec08ba05fc | |||
30bea8b753 | |||
09e4b5a9cd | |||
5467104b95 | |||
e0733c1a4c | |||
1cf7f9be54 | |||
fb99577836 | |||
28131ac135 | |||
e40b8d537c | |||
12e7ee9d0c | |||
54733e6ceb | |||
22e8050fff | |||
a629db2884 | |||
cbcec8c4d9 | |||
2f05f7b91f | |||
a3a9699e8a | |||
f01a312c23 | |||
0fffde50ff | |||
8775596a82 | |||
2f0133986a | |||
3bd2cad45f | |||
48f7a2de41 | |||
3aa6e7ae0e | |||
813d7e49cd | |||
710ebfb7a5 | |||
87bdee5990 | |||
efabe801be | |||
9a817e49be | |||
a577f5534f | |||
d0f52ea93d | |||
6063efd101 | |||
0759936226 | |||
1e3d9a00f2 | |||
7c62453280 | |||
226272f686 | |||
16cbcecd99 | |||
b008223661 | |||
f8cf3db4a4 | |||
a585d46e7a | |||
8cc42bce5a | |||
67c6dbea0d | |||
db33437577 | |||
8287c9d193 | |||
d32409bd6e | |||
cf3f2d0380 | |||
53c6230afe | |||
4882896f4d | |||
4d67066de3 | |||
6fe5e6e21b | |||
8c5496b53f | |||
235a587e42 | |||
3125d78706 | |||
bb8f3c63f1 | |||
20faaaa908 | |||
44cc6f11c7 | |||
bae391c2c1 | |||
0ac5f3b93c | |||
b79ef5dc79 | |||
7d26ca046f | |||
d99f4697e8 | |||
bb3fdef40b | |||
2a7cca6ea4 | |||
6ed2748846 | |||
7c90fe0f7d | |||
8a5e443ca5 | |||
a07e0df815 | |||
88e9fefa59 | |||
c0fd47b066 | |||
ee684cbef5 | |||
1f618d6634 | |||
7d4af1f8cc | |||
fe82cdb9c8 | |||
b354e37cc3 | |||
f344831d58 | |||
2eca8511cb | |||
f2b0d74b4c | |||
42bc2b07ce | |||
e2d6269a38 | |||
fcfa62f220 | |||
25b0458930 | |||
6808fbbb21 | |||
b36b3bfcab | |||
7f0ed58b54 | |||
b4393ff741 | |||
b8af1621b5 | |||
4a75f82a6f | |||
740e370465 | |||
245985bf42 | |||
344f5afd50 | |||
1c6e5605f9 | |||
0871208023 | |||
ee95c1439f | |||
e323f3c25a | |||
9766399539 | |||
fc4fd487f9 | |||
dddba7bb6f | |||
9ec8d770ea | |||
cf777d9893 | |||
0d9f8e8743 | |||
841f80f935 | |||
39a7356ed1 | |||
438054a0ec | |||
34b9c82cd0 | |||
405a75438a | |||
39e4568460 | |||
0d96791a84 | |||
531e1c62bb | |||
1a1f16f44a | |||
431f8772f8 | |||
8a5382042c | |||
8f4bc71cf7 | |||
0ac38297f4 | |||
4c65c2311e | |||
f48f212001 | |||
c90f344910 | |||
a458bd9fdb | |||
ed5a56be60 | |||
899fe57f15 | |||
bac42edabb | |||
8735f3566f | |||
46efd4c134 | |||
dfd38db7e3 | |||
0189fc1f66 | |||
929a881943 | |||
152fdec855 | |||
9c07451d95 | |||
e3b2720924 | |||
3ae1e37c40 | |||
d8998aacb4 | |||
efdff9a21a | |||
1824adb2ed | |||
38445673f3 | |||
5a9889b562 | |||
5ca7c39751 | |||
44609c494c | |||
0810d3db69 | |||
d4fb9995ef | |||
22a4372583 | |||
a4d86a2e1e | |||
b8716ff6fe | |||
73118d4af7 | |||
c27bf4e866 | |||
f50f5c4b54 | |||
fb38d30775 | |||
a3a9c8ac8e | |||
b4bb855675 | |||
6263a52777 | |||
96defd6b05 | |||
8df9bce1b4 | |||
bcd90be525 | |||
22afae4449 | |||
8fae92034e | |||
ce81b76150 | |||
f70d5ea976 | |||
dbbf6c5de0 | |||
2379df7e60 | |||
e3ce3ff418 | |||
4395202703 | |||
84acae27b7 | |||
71f6e07e71 | |||
6f59c6c6bb | |||
d36cf5ce15 | |||
332d9ff61b | |||
7bb1ccf6f7 | |||
05a7d5174a | |||
b051e37ab7 | |||
44383ff950 | |||
1b25290d39 | |||
2f5eb73d29 | |||
f0dd33ee4c | |||
e15b945e16 | |||
5c7d88c2ed | |||
bbe0ab1dd0 | |||
cb2d43c0d1 | |||
fce9cb820c | |||
08e4863d94 | |||
9a10656bf0 | |||
3c79777e66 | |||
f5ad95d78a | |||
14c465d36f | |||
921a988c4a | |||
99378ddf20 | |||
c623258e8c | |||
6ce42dc167 | |||
f63573f25f | |||
b328f0e344 | |||
02864ebd60 | |||
eed91f6360 | |||
f317193bec | |||
f459515dd7 | |||
9339ea4196 | |||
6bdc1b676e | |||
7451c13edd | |||
ccd4143d9d | |||
c590f55030 | |||
c21813a8b5 | |||
2cb08e6bb1 | |||
058ee4c86b | |||
ea6e5eebac | |||
9cc25ff345 | |||
c9805b8612 | |||
41c89eb61d | |||
392c3492b3 | |||
f7cd3929a3 | |||
20bec66a9d | |||
3ce9a9ff97 | |||
a8f17a3fab | |||
ef3d2c14b4 | |||
44619febd3 | |||
c48accb357 | |||
418e6a8b3a | |||
67b4e53a58 | |||
d62d94f587 | |||
265934d77a | |||
2a218cca90 | |||
e23cc8f83a | |||
0b125b7106 | |||
320587e36e | |||
26f3995595 | |||
94c94b2d88 | |||
41cc1fe723 | |||
03a344e9c1 | |||
add228407f | |||
2c6e025063 | |||
ba30dfe7e2 | |||
97e6f1ea9a | |||
5c1a81d8ca | |||
c615f4d458 | |||
9e09a20e65 | |||
7115a9b9fe | |||
fd8b97fc87 | |||
4dd67e4348 | |||
10973bf3cd | |||
934ed0551a | |||
38428c6ebe | |||
bf85e147e7 | |||
d2dd34c2e5 | |||
c4ab2b4675 | |||
aa2ec5940f | |||
79323de326 | |||
08e6487a9a | |||
4498b10a10 | |||
6f2bb18d72 | |||
7e56cba060 | |||
dc569fb20a | |||
c6ac992798 | |||
8a18e10cc2 | |||
d6b9711e45 | |||
8ab7e63293 | |||
4bcd623829 | |||
18acf66cb8 | |||
4816b4b53a | |||
60d8650860 | |||
bfb7b5afd5 | |||
a2627d70af | |||
6662a97b2f | |||
564a0980b9 | |||
e3fbd26880 | |||
c1e23ec18e | |||
b7cd7b8b4e | |||
776d36caf1 | |||
182e642cfc | |||
88bf1a706b | |||
d25ba23079 | |||
75460e01c8 | |||
c9bd3a5314 | |||
7c6a5dc43b | |||
274218cf22 | |||
c7d6509565 | |||
bc0b9e536a | |||
7a1b599462 | |||
1dd62af188 | |||
6f1099b710 | |||
be8e2f119f | |||
18f9e5ba6b | |||
d1bf857079 | |||
1814b3b22c | |||
be54b8862e | |||
1a61130f0b | |||
1de4bc9586 | |||
1986042277 | |||
e932983494 | |||
35d381144d | |||
0bcc22822d | |||
1ff78173f7 | |||
ee45f46193 | |||
290efb0283 | |||
8d7a7919a9 | |||
953720472f | |||
f94d902bb6 | |||
da25322572 | |||
cb4699a5bb | |||
2e5efadf42 | |||
e5e18c2030 | |||
ac0596a53d | |||
7ec5a51eb8 | |||
3cca460282 | |||
d703fb7946 | |||
859601a46e | |||
cdc160afc2 | |||
14d1bcacc9 | |||
abd23b6826 | |||
7d8a865cac | |||
dfdb688b43 | |||
c955ac6a66 | |||
f3ca4e76a8 | |||
2769525b2c | |||
843e748de3 | |||
d160cfaa0e | |||
81af97df77 | |||
18e55aa25f | |||
4d3e13b0d1 | |||
a335b4ee9e | |||
47a2d06682 | |||
ce66ed0389 | |||
c0f94ae8af | |||
ed32a511e7 | |||
17ed4873e8 | |||
09acc53483 | |||
bebd4be43d | |||
9b77759f24 | |||
e458de5e9c | |||
737a303df7 | |||
477dd37981 | |||
e917349bb7 | |||
ad4912803b | |||
f96f0c5889 | |||
2b9acadc5b | |||
9caa0d147b | |||
c6e5f8abd9 | |||
1abf01c4a0 | |||
b41565f879 | |||
f03a834136 | |||
f7f2072621 | |||
5b2e937d5f | |||
f27dc19b37 | |||
2368c50ebb | |||
0505906e7a | |||
4efca04765 | |||
b12c7cf963 | |||
487622c592 | |||
26d422b0ae | |||
79a7b68837 | |||
63048d2f0b | |||
79662a5866 | |||
ed6809fa28 | |||
86b9262a7e | |||
7ec87e76db | |||
a0e76d2fd9 | |||
ec3ce74af8 | |||
83a4e34095 | |||
84a0044d51 | |||
92132c59f5 | |||
36ae388332 | |||
bd47eafeec | |||
9432d2d06a | |||
fa61c8fe6f | |||
92bd98e45f | |||
fd7c993b0b | |||
779df32e98 | |||
f4e843f114 | |||
c0e2eb211d | |||
0bd56ab77c | |||
6b03dca5f4 | |||
bd7b21337c | |||
60a3ba5a5c | |||
7c2eb0b881 | |||
93523ef50b | |||
10d7349506 | |||
3d7c136320 | |||
a6d6a5ed87 | |||
b690de55e5 | |||
83fda20078 | |||
f656a37045 | |||
c58b495433 | |||
242aeb6a68 | |||
d9969cea8a | |||
d61db5931e | |||
0ea3ac9807 | |||
f9e43f574f | |||
5ef11e61d0 | |||
48546c3db4 | |||
4d87ed496c | |||
06d12e6562 | |||
ec49411bee | |||
3f7911235c | |||
727399611d | |||
94232a4937 | |||
07fdb74fbc | |||
d400ac2a49 | |||
dd71c76a8f | |||
58a0add4f6 | |||
bfe143015a | |||
e3cf863230 | |||
ee818bc7c5 | |||
f816196df2 | |||
753bf7de5d | |||
3634b52e3a | |||
ef863335e6 | |||
ceaf579cb0 | |||
b49280e347 | |||
d3dadf71e8 | |||
ffa8c8fd07 | |||
0ef7650c1a | |||
4635e58405 | |||
dc2eaf0788 | |||
d02b0ca2db | |||
4d607c4aed | |||
be4072c86b | |||
2970eca9e4 | |||
42954609b9 | |||
6348cbaeb7 | |||
a7cb33d8c9 | |||
ec46b2281b | |||
3a2dc46ff0 | |||
e052bdef96 | |||
d522d6d545 | |||
7b118eba22 | |||
f6e6a7ddf1 | |||
5ce64ac7ff | |||
1671a56f42 | |||
ab6dfe9e25 | |||
bff98ca768 | |||
32b9b261f0 | |||
23432e4405 | |||
34a586ce48 | |||
ad762f8303 | |||
389b039679 | |||
ef9dacde79 | |||
13bb45b4be | |||
bd2cb97179 | |||
0d8f1c8560 | |||
477e3d9b94 | |||
3c16082636 | |||
29aee68ec7 | |||
75e23299b4 | |||
935ff1ee98 | |||
c672cb81ec | |||
7559c133c0 | |||
589bdba0b1 | |||
aca65f13bb | |||
7bf30a094a | |||
5454279a8e | |||
006bcdf934 | |||
b00f00730d | |||
f2c48480b6 | |||
1730dd6af1 | |||
2501fef9e4 | |||
12e41b6e6f | |||
c892c793a8 | |||
3a82b4d924 | |||
b4b3a4d286 | |||
448702e5be | |||
2ef1f07aae | |||
1a319601de | |||
cdf242e8c8 | |||
aee785a8bb | |||
d45fc1e245 | |||
14500ba4f8 | |||
345e9c2a9a | |||
b53e24e0db | |||
d3a73fc228 | |||
c2812fca24 | |||
856847a60a | |||
748e2480d3 | |||
2ebc8d9ae5 | |||
e28b015580 | |||
e4bc8990fb | |||
a179327d9d | |||
823749fc1e | |||
2b5d9fd76b | |||
7a972dfdb7 | |||
c31e75f02f | |||
b56b8b55b4 | |||
2695a4d8c7 | |||
1a4dad72a9 | |||
b7e6b4c28a | |||
dc2d470413 | |||
293b967858 | |||
c637172ee0 | |||
e468554fd9 | |||
5b5eb92184 | |||
58ebf14691 | |||
992bab4f79 | |||
6fe650319d | |||
f301dc64f0 | |||
33a2219716 | |||
62480f090b | |||
e7937fe562 | |||
287489d7d0 | |||
2df0236669 | |||
c54d77333f | |||
8c494f314c | |||
8cea78de83 | |||
b6468c7e31 | |||
1967923a94 | |||
91004ad514 | |||
a2ee4e63ae | |||
4d8289cd36 | |||
289264878e | |||
768bb7b503 | |||
db4ae134aa | |||
7329f03bc5 | |||
82ea643c7d | |||
741c10e0b9 | |||
34bb90f3c2 | |||
f04cf72c0c | |||
157438e0c1 | |||
75b23c99ec | |||
6bb3070c57 | |||
7df10b076c | |||
2245658363 | |||
46774771ec | |||
6263817bb4 | |||
60456fe0e9 | |||
a0f47d3f1b | |||
6efcb8ccfa | |||
0d128b75e2 | |||
0067d474c8 | |||
cf393b217b | |||
e265b929a1 | |||
4cd01428ed | |||
3be05fbf9b | |||
5d90ba8aa0 | |||
48cab708ce | |||
5d9753d6a7 | |||
425e48bec6 | |||
a42be4a833 | |||
30e030bb8e | |||
2a3c3d8d6a | |||
7b026cec8d | |||
d8b528a4e0 | |||
0f45907144 | |||
c4c9931ae2 | |||
68345e636e | |||
0861c5618c | |||
817418f7c9 | |||
4eb2cd85b2 | |||
086eac5975 | |||
addd6bffbd | |||
1e65313fa7 | |||
c4c6e41c46 | |||
920ca405a2 | |||
6d3a3b3f39 | |||
50d46fe7f6 | |||
91e282d7e5 | |||
a0f10f868e | |||
6a423f0650 | |||
5cc84403e1 | |||
ab61a65b4a | |||
01ec26842d | |||
bbf5817805 | |||
50981cb102 | |||
611ec8103c | |||
12c672667c | |||
db3c98fe72 | |||
f401574f5a | |||
3251fb36c8 | |||
94a410f50f | |||
a14c01c1de | |||
ca3b948628 | |||
a8230ad574 | |||
8e1b5b4803 | |||
8552838bda | |||
46417fe427 | |||
dac04f2929 | |||
63da463e02 | |||
817e144ff6 | |||
9d2d78ae5b | |||
c44db54d9f | |||
376bbeb724 | |||
0e2bdb7863 | |||
235bc77457 | |||
593172f891 | |||
e20c66b156 | |||
5f4825465e | |||
bc6a12a4f7 | |||
90db3acefd | |||
2f2f59279d | |||
4992f87cb1 | |||
7608cb0da3 | |||
9dd9e741f3 | |||
171db639ff | |||
3ede42252c | |||
a94ca175e2 | |||
3749cee28f | |||
ca500da4d8 | |||
820ed6a468 | |||
7cbe18d325 | |||
8937e22ce4 | |||
82a3a98a5a | |||
d97eab0328 | |||
a61e2799db | |||
1009e15aa6 | |||
01c6e46a71 | |||
ed5e013874 | |||
f8e4153dbf | |||
f7a92cf6ac | |||
e748d91d4a | |||
2c4ddca38e | |||
6ca32710be | |||
f05e251991 | |||
a3f3f9d562 | |||
410fcb73c5 | |||
b6d6de6b9f | |||
09cebf20f3 | |||
a8c732d67b | |||
843c9c7e57 | |||
c88b79fa17 | |||
3f9820ac79 | |||
c288e6b8fa | |||
8945ef8880 | |||
99a717f849 | |||
4622b18c99 | |||
4f5270cb7d | |||
719d427956 | |||
d7a21771a5 | |||
be854b3e90 | |||
47f079891f | |||
696dc59ea5 | |||
5f6666a438 | |||
f284a656d7 | |||
1c3d566f8d | |||
373463e995 | |||
7be9b49143 | |||
059a79debb | |||
1a70ebe7ea | |||
beda99bbe0 | |||
bb1e7816e1 | |||
b0dc20e00c | |||
3d66eaea83 | |||
5313a5d5d2 | |||
5b189a909b | |||
75a687138d | |||
ba91b483a0 | |||
3a8b5e1b5e | |||
94d1b68598 | |||
8eda4df71f | |||
8ad9337863 | |||
cd13e187cf | |||
bcc21e55bd | |||
5fbecfd7b7 | |||
3480b45098 | |||
5076ab3049 | |||
44366ac058 | |||
4f2a794fba | |||
fe6aa4358f | |||
f99b62a069 | |||
ac1bed38f9 | |||
217b03a292 | |||
28bceffc6f | |||
09266a155c | |||
3d7591feca | |||
e14909fff4 | |||
fe579c4865 | |||
37118088d4 | |||
5c9e9bd2c4 | |||
db35ba53b1 | |||
758d223776 | |||
21a9bf2463 | |||
a54d9912d0 | |||
7e74949d38 | |||
a8c5780963 | |||
f4ac754d02 | |||
0347d3970a | |||
acc2312384 | |||
7d34ff214c | |||
e2179a6669 | |||
5c37347cec | |||
ef3a6c80a7 | |||
2a2c6cee5f | |||
7dff3cc6cb | |||
8c1171a722 | |||
2c850d0e33 | |||
f1b85ff39d | |||
b7fa25777d | |||
2d86f69caa | |||
e22896a956 | |||
be5802e473 | |||
434c90d378 | |||
eb6ba96b57 | |||
5325e590ec | |||
6ad6dae191 | |||
3f34fa1f58 | |||
d12ea86b55 | |||
a8e45beb51 | |||
ba2a528886 | |||
d60367768b | |||
db6528d3fa | |||
f5873d70c6 | |||
10e349f76e | |||
b1ccebf329 | |||
3407eb84c5 | |||
6017229d1b | |||
4f00af3173 | |||
9da232dcd8 | |||
acd43005df | |||
c31cf2a03a | |||
51c964de3a | |||
dad24e785b | |||
a908283e86 | |||
d8725c7b7f | |||
262f8449b4 | |||
bdf035d60a | |||
0270878748 | |||
6ada3c90ff | |||
4e628fe6de | |||
a8eebd824a | |||
afa0a0a0e2 | |||
92b039fac7 | |||
acc65529a0 | |||
3061f198e9 | |||
6fc1f4fc21 | |||
a0f49b16c5 | |||
c6c4c1c393 | |||
811931ccc0 | |||
08d5633d81 | |||
c76d5dd30c | |||
340357d158 | |||
11ed47397d | |||
6ce54eb845 | |||
d0236aaecf | |||
00059848b4 | |||
e45f6d0c92 | |||
18ccde082d | |||
21bc0f1952 | |||
a37be747e9 | |||
bc3bb82651 | |||
ba00d9e5d2 | |||
bf9edda04c | |||
9c9357639a | |||
3733871d2f | |||
54471a014f | |||
8749be518f | |||
6d880c938a | |||
34aa4eb291 | |||
280b0f42db | |||
65387d0089 | |||
d41c103a72 | |||
0b93b9e059 | |||
ea3f933e95 | |||
b006fe3a22 | |||
37ff3b4920 | |||
1e93d785e5 | |||
999bd4efee | |||
3222247969 | |||
dd6c9ce2fe | |||
7446b28ff1 | |||
38c6702b8f | |||
afcf4b2988 | |||
ebb96a6ff4 | |||
8b0affe9bd | |||
1a25cea0d6 | |||
2ecbcdf4bd | |||
642b392d44 | |||
8dce7b3e9e | |||
33e90d6449 | |||
50b17d5d34 | |||
7818885406 | |||
26af7ccc77 | |||
5d1f79012e | |||
cac80daa71 | |||
fc184f1cfa | |||
725fcbba0e | |||
bdeb209d43 | |||
a078f1ab1b | |||
86c3d8c064 | |||
156191af44 | |||
57bba9e5ab | |||
dd1923fe88 | |||
df773ee15c | |||
f5451a6881 | |||
fcec1581b7 | |||
11cc789e36 | |||
16f9fb2f40 | |||
6bfaa85e84 | |||
8f43fb9530 | |||
04d2a3399b | |||
054bf8ec5d | |||
8417f5a63c | |||
26b46cace0 | |||
0849111247 | |||
f9c25b350e | |||
5b12c144da | |||
f38130d086 | |||
4b60138d41 | |||
fde7bfa3d1 | |||
69635ee66a | |||
224f29077d | |||
e1ab1fdb65 | |||
3e86cb094b | |||
9fbd3fe33f | |||
073e9f94ff | |||
64c0d9506d | |||
f638092ab9 | |||
d0c4463ab3 | |||
ad107860b9 | |||
5efb31bd71 | |||
e4a2f35907 | |||
e49781de7a | |||
37cb4ec0c2 | |||
401134fa8e | |||
87391832ba | |||
e36d31bf0f | |||
37b7efbc87 | |||
6e4a30e593 | |||
ebddb96373 | |||
0288abb66e | |||
d869a13ef9 | |||
ccdfc37c97 | |||
37c55abc2a | |||
c50b1a5c66 | |||
187e9f94aa | |||
1704dc062d | |||
0657a52924 | |||
ccc4144f3c | |||
d5b4bb49b1 | |||
5b3f9e082e | |||
ca06516900 | |||
3fb42b6ce9 | |||
2cbe946e7e | |||
3b5b9a1ae5 | |||
a834ff3a44 | |||
82b552ac9a | |||
15f7e53e4f | |||
9792a6cb78 | |||
f30150c0f0 | |||
5c868d7846 | |||
39e41510d0 | |||
78b76a186c | |||
6e04822f5e | |||
4ff5c1148e | |||
bd285920cd | |||
fb04401460 | |||
42bf91779d | |||
2ab744c525 | |||
4a244a598b | |||
d0bff298b7 | |||
152eb5b951 | |||
d558f9e1d6 | |||
b3557e844c | |||
9c8ccb8e0e | |||
4138a17e29 | |||
fbda243c0d | |||
eb742b29f8 | |||
d2e62ffb19 | |||
2921be620a | |||
c61a51438d | |||
7e40680af0 | |||
93925a7286 | |||
b04807e53a | |||
01e13e59e5 | |||
2cf1009f70 | |||
93827aba34 | |||
3318314c4a | |||
44cabf2f0b | |||
a8ca7b690f | |||
824d5e22bc | |||
7a360779b3 | |||
4b5f965cea | |||
d03cbbe0cd | |||
84bcd8d1d2 | |||
6756bfab75 | |||
8d97b980e3 | |||
2d19729869 | |||
f5bde3726a | |||
ea092fa175 | |||
9c4051a5ba | |||
fed914827a | |||
ea33f8dba5 | |||
4f91d80765 | |||
4178f945c9 | |||
558aad1a71 | |||
d6cbff2837 | |||
aea0cadbfb | |||
e4292719d3 | |||
69cdba71eb | |||
5c5468f9af | |||
6635dd2990 | |||
27e5256305 | |||
b6dbf63633 | |||
551e6a8b62 | |||
570fec6ea6 | |||
7da32750b2 | |||
a2b21e5ad6 | |||
dbd93cf5d1 | |||
c2eaf1c86b | |||
890f1a3c7b | |||
3fdcd636d7 | |||
3d7e44726d | |||
147455f99c | |||
b25ca7617d | |||
bc1fbfac9d | |||
7e92921f84 | |||
e1adb89ff8 | |||
4e544005fe | |||
31bc2c4420 | |||
02b3718aa1 | |||
26a42ba9c0 | |||
b1e104319f | |||
a3afb35539 | |||
fba244423f | |||
8500add09f | |||
23bfa1f18f | |||
b4f2da12ea | |||
b84a31ba92 | |||
d0950cb026 | |||
404f53b16b | |||
737d0fb8f3 | |||
b95a30e424 | |||
0d9c1e6e9c | |||
3bfbd58402 | |||
bd9a08c73d | |||
41dc41f285 | |||
50f959e5f4 | |||
4b4be58d0d | |||
4bba7a8bab | |||
60bcebe4d1 | |||
cf6407c4d4 | |||
5f8252447f | |||
dcd5541e96 | |||
7be6863910 | |||
caf9219d99 | |||
3b62396442 | |||
bbe1608006 | |||
b8fa326c21 | |||
1cf1b34e7f | |||
ff4fb83bff | |||
e24501da09 | |||
0ca14c61c2 | |||
6be9cccc7a | |||
a5a70defc8 | |||
db3cbac310 | |||
de23226591 | |||
ea8383978b | |||
b04d1e5f50 | |||
98c459a6b6 | |||
00f442b77e | |||
42b0e3e438 | |||
8d1f99a480 | |||
bef8342aa5 | |||
2131294b22 | |||
5c22cbf28e | |||
488276d498 | |||
6ac17363ed | |||
58c47c4c50 | |||
80b2ebc45b | |||
ef2c9460b5 | |||
ad84a8c3e9 | |||
8b9a06e298 | |||
6b1d597d34 | |||
5a37f2398a | |||
98a4f6cccb | |||
633bd6eb46 | |||
f19c288bec | |||
e2ce3f68bf | |||
56722140c9 | |||
e90b39b29d | |||
f4c684b4b8 | |||
869396b1a4 | |||
7f9222f7b7 | |||
a35f947892 | |||
ec272f6c4e | |||
f0af3858e8 | |||
db91d04e82 | |||
9859b38f32 | |||
0190c36d20 | |||
ba533f30ce | |||
29fa93e829 | |||
0fabe4bd01 | |||
f98b4f4e39 | |||
b8c1257645 | |||
467ceacb17 | |||
2d22baba62 | |||
750f90614d | |||
d28ded4525 | |||
4b4a138eee | |||
b5dca2eb09 | |||
747cbd24cb | |||
d3520419d4 | |||
acb8ab15b2 | |||
5cdcc1679f | |||
b37b3767f3 | |||
2d56ad1ad9 | |||
5d3bc7245e | |||
e82963c9ef | |||
ec34977a64 | |||
2ced56e490 | |||
e568951396 | |||
e275897bf9 | |||
2b089648a3 | |||
c2a831dded | |||
c740558327 | |||
0e3176a77c | |||
f85cbb1582 | |||
20bbda78e6 | |||
0225711f6f | |||
7ec822503a | |||
83871fc013 | |||
b668364afb | |||
877ae041a4 | |||
1395343f11 | |||
30b3b2d3ff | |||
f3cecd3cde | |||
0086743a53 | |||
bc8c45832e | |||
4a3070265a | |||
f54adb49a1 | |||
ec30026333 | |||
4ea512f6c2 | |||
829aadd0bd | |||
9d28def387 | |||
86fe850794 | |||
30ac94181b | |||
48d3d454c0 | |||
6865c21c75 | |||
82cd316493 | |||
7270c48f26 | |||
9e5d79aec3 | |||
c51e83c048 | |||
52fa28c16a | |||
935c8e7d82 | |||
19be0d68b6 | |||
f9bbbce466 | |||
eb5ef72747 | |||
0215b66098 | |||
3dea10bcb9 | |||
cd3cb72b65 | |||
5b474e96b7 | |||
9ce1d71a45 | |||
b8cdf7fbff | |||
28594bba2c | |||
d5c207d8a3 | |||
56826fb477 | |||
171d7f2b8c | |||
5ec5829e77 | |||
448978ac8a | |||
fb9791f597 | |||
07d1b9f3ba | |||
6b91f65457 | |||
0c7b1bda7f | |||
032b377de7 | |||
26d8e47bb9 | |||
970ff7841e | |||
3f62837260 | |||
d55c854ebf | |||
6b2b21edfa | |||
99270e370e | |||
c7d09d098a | |||
21804bfc45 | |||
38950f7bc8 | |||
bbf5c86b46 | |||
3fa68ed217 | |||
cc6aef693e | |||
5a320d87e8 | |||
da95ecb686 | |||
774a87a42a | |||
ff4a217730 | |||
a43754e1a6 | |||
8ef200861c | |||
ddd180e56a | |||
30b86e530b | |||
2f26982e34 | |||
504844a892 | |||
4c1da1bd1d | |||
dc62d0ea8b | |||
fddca15182 | |||
81f49f34ef | |||
c39a1b7867 | |||
d4b764fa31 | |||
bb54a81ef0 | |||
d85af2fec6 | |||
90c08303fa | |||
92e83f702c | |||
084e6a964e | |||
532f662b05 | |||
53f5ea7fe9 | |||
fc6946ed61 | |||
f5c7aa1142 | |||
761635b572 | |||
488d8ab8cf | |||
8efb20439a | |||
43c195e14a | |||
8a3a7418d0 | |||
32190b6cac | |||
880407442c | |||
3b34a878a7 | |||
b79340989f | |||
0e526c36be | |||
a83d29f058 | |||
be7108a2ee | |||
8e9b1124cd | |||
1948d55d5d | |||
9c49a5ed22 | |||
0bb20a92af | |||
cd82c88b9a | |||
8d40e20b7d | |||
31b62b2779 | |||
88b56121a3 | |||
d6c0a5ef8b | |||
5732fc61e8 | |||
655fa25b51 | |||
aab5f083db | |||
03b9950fa1 | |||
2453d1a886 | |||
4b9a6541d1 | |||
a70b848646 | |||
ce44c0615b | |||
f207e87722 | |||
2e81e1b7d8 | |||
605c3de150 | |||
7aa073ddca | |||
4b0f549666 | |||
40749dc767 | |||
3599d53c61 | |||
2156844b87 | |||
763288ab13 | |||
58e6479438 | |||
6d6c38ecaf | |||
3760b310df | |||
47b56644de | |||
301cae13f0 | |||
1fe9b7bda7 | |||
324ae3fcfb | |||
e36e9d9d5c | |||
4228bbb88e | |||
09abfc7843 | |||
1f34f5277c | |||
80b4b7bee6 | |||
1f9f9662bc | |||
97656935a2 | |||
2d690a09b3 | |||
29348677b8 | |||
1f79444a53 | |||
daaa23e8e0 | |||
1d6aa9a277 | |||
7497e02979 | |||
f34dc3be90 | |||
65261356eb | |||
4291cc8eb1 | |||
8811d951d0 | |||
9dbc1aa7a3 | |||
b0520df1dd | |||
a89651810d | |||
431c04e54f | |||
f461c71625 | |||
b635789740 | |||
f00e03e5ea | |||
6db2becd30 | |||
5f378e28b6 | |||
4ebceac07f | |||
aab5a56892 | |||
e58945a209 | |||
03e4eb1061 | |||
09a3509d79 | |||
b3a11eca0f | |||
650c2dc6e7 | |||
d4adb664cc | |||
5194bdb229 | |||
87ec71142b | |||
85f2996ae9 | |||
e296d56e09 | |||
dd676b6d14 | |||
7c7bd72c8e | |||
c7e44aa22f | |||
ac4f98e152 | |||
e0d23cd688 | |||
3966a917ee | |||
be33a57d43 | |||
4a71022a60 | |||
83129385e2 | |||
34ac39e7e5 | |||
1474c8ffb3 | |||
441e7bf8b1 | |||
71fc5d6d35 | |||
ff996d282a | |||
11f640cfee | |||
1cbe225a94 | |||
d6f1534ee8 | |||
24e64f52e2 | |||
b0da0753d9 | |||
e511f24979 | |||
22e83f408b | |||
ec96a81735 | |||
7892cc1519 | |||
f7b11f2ce9 | |||
b4e15263db | |||
96c3116af6 | |||
7845f9430e | |||
16abfeeff0 | |||
3bc6b1e202 | |||
3c2e237d63 | |||
7701672d7a | |||
2993e3f0f2 | |||
688cc64dff | |||
9f0052eceb | |||
19eb4aaac9 | |||
a2bb81b7db | |||
5e68fe4fe9 | |||
914831d51f | |||
5315467908 | |||
807987f0d3 | |||
b3426f37e7 | |||
afceac15c8 | |||
3d4e56948d | |||
737cf9898d | |||
322f3a07e8 | |||
6c7b3d7811 | |||
bfd22f8f2d | |||
2ca62c4eda | |||
a2d53c439e | |||
29e1976b90 | |||
4efb736e56 | |||
58acf0a8aa | |||
9f5f101858 | |||
2a875fe9b8 | |||
bb5a5ea25f | |||
039fe4a618 | |||
0c9c4c0347 | |||
819577a15d | |||
b563e85c3b | |||
99ac30e59f | |||
4774deb1ef | |||
d49ec41f3a | |||
f90e1b935c | |||
db93d1da76 | |||
7d74b174e0 | |||
e513487caa | |||
483b204fb5 | |||
56028aff55 | |||
7336714306 | |||
8bde35298f | |||
3fe5e53b25 | |||
dcafdac036 | |||
f8d8cf9f6a | |||
5bb1133f0f | |||
2b96709799 | |||
1c8da5fa97 | |||
73901f50c0 | |||
76057b84b2 | |||
164de67a56 | |||
aeffb5eeb8 | |||
6f94777530 | |||
2e15be59af | |||
bc1f6ba517 | |||
59f8c1a288 | |||
cd9487f94c | |||
978489fade | |||
07c9af4901 | |||
d6977e5676 | |||
a843054388 | |||
098a7d1deb | |||
9ef0af0069 | |||
c751851941 | |||
9f2ddaadde | |||
fc328e141c | |||
0e19c245e9 | |||
27bac4fffb | |||
4bf4b167a5 | |||
2b8d1bcc02 | |||
e8b7743826 | |||
8ea05e852e | |||
3547d0142f | |||
4d9d587366 | |||
e2510c144a | |||
00519e3b93 | |||
473dc688f0 | |||
b635f02d93 | |||
d8fb6b893f | |||
bdc5d557d1 | |||
cbfe9c30bb | |||
459b369feb | |||
3192d47837 | |||
f6f5b6aeab | |||
1b2c12385f | |||
80c7a45328 | |||
2096df301d | |||
0b78028cf6 | |||
46ac9fe970 | |||
b034f503f8 | |||
9ebeff04e6 | |||
fa73e2403b | |||
35ec593658 | |||
905c96922b | |||
018ca71336 | |||
383f7089c4 | |||
a21aa8125e | |||
83e193f1ab | |||
bdbe1c4d0f | |||
e5eadb0261 | |||
4ee1d72b6f | |||
902bb35ba7 | |||
4684797dfb | |||
386b8945c8 | |||
86a018ebad | |||
ba93060e59 | |||
788583e66f | |||
cbcab5a545 | |||
634ee86bbd | |||
64f60c36e6 | |||
0b4f3f5532 | |||
d977b89af1 | |||
487ce37d91 | |||
1551891c15 | |||
b15073fd61 | |||
e56f6c1017 | |||
34906a7425 | |||
86bacbe586 | |||
14a08f0668 | |||
9385b86ecb | |||
da7a64b40d | |||
ab1a44e108 | |||
26ddc6e3aa | |||
1dc4a52f61 | |||
473a4fec70 | |||
1919c2d925 | |||
71e31e6c03 | |||
c01df7f0a1 | |||
6024f6175b | |||
33500e5b69 | |||
17899a6d6d | |||
4c3eb68d3a | |||
29ced9642d | |||
af82591d85 | |||
5bc4a446ec | |||
83e93b254e | |||
49c7dd0cac | |||
96d2fb62e4 | |||
c76a136d3f | |||
940409a4c3 | |||
071dd88ef8 | |||
a58a4634e2 | |||
5979e72662 | |||
010436e797 | |||
980709cccb | |||
fe80356756 | |||
cecf532ffd | |||
6cb255e60a | |||
b46fb7d1e1 | |||
8874193927 | |||
a4515ad251 | |||
55b0b57699 | |||
aab7795b4c | |||
196a8e6829 | |||
972cd98d7b | |||
a16b5d241b | |||
bfa918140f | |||
0721de5b81 | |||
a409fde519 | |||
8e34a30dce | |||
ba43462041 | |||
c8ae936ce9 | |||
853f949140 | |||
615b01a006 | |||
0eb5a3176b | |||
867a5a3ea0 | |||
5159eabc5d | |||
9357af2bcf | |||
038532897b | |||
325a5e37aa | |||
7d3fe0ed43 | |||
eef95cef33 | |||
591df8abcc | |||
46734c525f | |||
a3378e6080 | |||
3791d82540 | |||
a3ab8746bf | |||
069bd90c0f | |||
68697e59d7 | |||
b3dd8b7355 | |||
eb2a904b61 | |||
17951cfd68 | |||
6d6237e370 | |||
851a5ab7e4 | |||
d3ce46a367 | |||
74c5b29484 | |||
20453dc08f | |||
9e3b454b1b | |||
29633b64aa | |||
76c0ead1db | |||
2674570792 | |||
21771e62aa | |||
5d77ee37d2 | |||
2dfbfd0958 | |||
05085fe57f | |||
ff32ab09fb | |||
deaded5af2 | |||
f3c50ee9a3 | |||
3072296919 | |||
1f10b79ee8 | |||
b9e108eb4d | |||
f26cfa58e4 | |||
e1525a5125 | |||
388dc2f103 | |||
7e4c45858f | |||
d476431707 | |||
284445c364 | |||
08d1ecfba7 | |||
0969226fd3 | |||
0c856438fa | |||
e44bb30996 | |||
7440086ef1 | |||
ef3acb8c43 | |||
ee38671400 | |||
5b8cd68cf3 | |||
53decfd47b | |||
65264e3ef5 | |||
4ca0fc7a4d | |||
7b294478e4 | |||
04f0ca7846 | |||
61a44101a2 | |||
924dfa19cf | |||
9ff6ae81bd | |||
c33e5c8a17 | |||
6129bbc9ab | |||
b34b10c6b8 | |||
ad106bd884 | |||
37fe25ac06 | |||
0e0c1dcdc5 | |||
c9770eea2f | |||
80d2d9d258 | |||
3ca1ce4636 | |||
8ec91cddab | |||
470a576441 | |||
33a778873a | |||
cf7ca5bd28 | |||
a77bce7b37 | |||
8e985eb0db | |||
915e38f636 | |||
e3b1053c03 | |||
c2520bff12 | |||
cd5bcc3673 | |||
254f021903 | |||
8fedd2d5f1 | |||
cb1830d747 | |||
68c47a3238 | |||
e644772731 | |||
11f1482818 | |||
a7decdb62d | |||
005b9b595c | |||
e6a9d0b090 | |||
82879a129e | |||
9f66c85281 | |||
0f5731360b | |||
3fd9e021fa | |||
4c3af7bf36 | |||
28e7009b49 | |||
dd983c803b | |||
1b804e61cb | |||
02eb3cb6b5 | |||
c5d84b4f24 | |||
ae88252cb1 | |||
3c3d787a2b | |||
6aee4fc464 | |||
4ef337f1e9 | |||
017f6b22f0 | |||
602168bc48 | |||
fdf384b809 | |||
284880d096 | |||
a446b37c1f | |||
ad75d137b0 | |||
d2f4c43526 | |||
6bc484617e | |||
9d5b7de1d8 | |||
a01c370d63 | |||
fd5da2de3a | |||
3c9f96d621 | |||
693cc103ea | |||
5ccde61ae1 | |||
b96686e6ad | |||
e7695aef78 | |||
5bb78eb77f | |||
5fbf454652 | |||
d098eca69d | |||
120943a8b3 | |||
1e64542f14 | |||
e15a867106 | |||
349e6ca98f | |||
da8669c826 | |||
59837bbb90 | |||
20c14a0a00 | |||
06fdfcdb23 | |||
cf48bbc176 | |||
40f5d26945 | |||
18ea6c4f65 | |||
7a661747c5 | |||
177a642afc | |||
161c8bcf9e | |||
e3f8aedd5a | |||
7fdbf40cd2 | |||
5ea03fad87 | |||
dd5da56695 | |||
0e1e57c1c3 | |||
6ddd6ed0e3 | |||
b80a992fdb | |||
20de02dffb | |||
a3a85ea49f | |||
4560033e66 | |||
11c61d42dc | |||
d1be221d7a | |||
cd0294b1b6 | |||
0dbe82c781 | |||
ad9ef81a77 | |||
b36ca92dd9 | |||
c8468c29f1 | |||
3c40010aff | |||
63238b388d | |||
809da49301 | |||
6b14f38cfa | |||
e1e1c20dbe | |||
b0360b83d4 | |||
f7881651c5 | |||
d71224b40b | |||
241c4ad857 | |||
87661eb85a | |||
ad17eb1386 | |||
0c631a4990 | |||
9b0d85bf6c | |||
3b2362c784 | |||
aa2370b381 | |||
3e07100dc2 | |||
1949fb1abe | |||
28be423e65 | |||
9a75232ca4 | |||
64da16f58f | |||
fd9510e18f | |||
4be9b03ac6 | |||
2761d27aaa | |||
6d154b1e4f | |||
bbb69482e1 | |||
f4e344f686 | |||
395a840fc4 | |||
6580f5771f | |||
b21bcc2d45 | |||
4481c54376 | |||
7b242bf118 | |||
f2a478288a | |||
01e04e31bf | |||
cbc114608b | |||
63987f952e | |||
9f42306f79 | |||
d61bfd7caf | |||
13943f77f7 | |||
1c94ecdcdf | |||
fb83a07f84 | |||
3e2d7d76b9 | |||
7a0915964a | |||
aef97c5563 | |||
4c9331c4e9 | |||
6fb5552d57 | |||
bdb55ef881 | |||
628a3bc16c | |||
c77396dbdb | |||
5002692bda | |||
71bb8ed975 | |||
6d011ebe32 | |||
f1ab34e27c | |||
6d655ff757 | |||
63627c81eb | |||
5dc688dc2e | |||
08fb2fe467 | |||
f1afeac0bc | |||
f75d632740 | |||
b26daf8824 | |||
393fc14630 | |||
c7707dc50e | |||
37199a10bf | |||
b950370f12 | |||
598e4516b3 | |||
cd8392bae2 | |||
ae7df4fb7f | |||
8bee5accb7 | |||
cf024b0e61 | |||
d3f9232a3f | |||
9f655e0d41 | |||
e7ed130f2a | |||
e421eb61bc | |||
bc053580ad | |||
11c01235ac | |||
c49d862fc5 | |||
6993e88265 | |||
681e9396b3 | |||
85ef40d0ff | |||
39c0b74250 | |||
a9e629aea6 | |||
aa11902aa1 | |||
c4088bad12 | |||
49d3ddb830 | |||
6d802063b4 | |||
55a1cdb1c7 | |||
ed8a54bd2a | |||
5bd5b21543 | |||
aec980662f | |||
aef1dc6eaf | |||
bd45bf7407 | |||
e3f6cfa2df | |||
609f552c8d | |||
5763201307 | |||
dee7830793 | |||
23f8f35354 | |||
cccd09fb5c | |||
bf6d59cd21 | |||
6ef6eab994 | |||
ccff333123 | |||
891406cc7f | |||
a5d767042c | |||
9fdc803c14 | |||
8798c295e6 | |||
7abb407897 | |||
78207d48ba | |||
70698e6494 | |||
adf02e53fd | |||
259c370eb9 | |||
7261fcccda | |||
a4a4503311 | |||
2752540330 | |||
0b77b78f6a | |||
06bec0ad54 | |||
f1126c55ca | |||
2caf220b18 | |||
4d23f35b9d | |||
f6fdb12db2 | |||
7773deabc0 | |||
91ed3a4a5f | |||
20145f7a12 | |||
883945e3e8 | |||
3feea71146 | |||
5e32b8e49f | |||
08e63e5fab | |||
0ec9496d26 | |||
29a0989f28 | |||
558b18899c | |||
6e95fde4ec | |||
e691e17efc | |||
80ea14bf7f | |||
c25cffafc6 | |||
8933b41937 | |||
7e2f1d729f | |||
d6c87ec10e | |||
070abd79ce | |||
2d01933c28 | |||
bf0bb5aa88 | |||
1b4d9fc4e9 | |||
2b79295240 | |||
42eaaa497f | |||
96c894ce5b | |||
c0214103a9 | |||
2b76a97989 | |||
9d77052d9c | |||
b4981058a2 | |||
032aa64195 | |||
7c8e8317a8 | |||
eb1cfc4cd4 | |||
f1e5cccee7 | |||
bc2ed763bd | |||
a35995b898 | |||
b1f46ed830 | |||
6c1565a7d4 | |||
2ca6b655ad | |||
a83a481ac8 | |||
65a8b63b3b | |||
b20ca36db9 | |||
189f92d7e8 | |||
cdd4ec6233 | |||
ef1bb4e800 | |||
c475acd1ea | |||
7d50d7ff52 | |||
28522f4f90 | |||
ec3a227a02 | |||
89decf3474 | |||
0b2794e843 | |||
554dfb5874 | |||
9c30fa1da3 | |||
e81bd61e24 | |||
7a0b54bb38 | |||
f060daf8c4 | |||
c1976ef599 | |||
f16fb4e1e4 | |||
5da2c82f47 | |||
d443245d66 | |||
9be3eea5fd | |||
07a9fd061d | |||
2a070c0b1e | |||
7b5106d206 | |||
821d9cdb02 | |||
28575936d3 | |||
83a04da4a0 | |||
0894b1394f | |||
eb33d3c991 | |||
d7f01abf3a | |||
80635343ae | |||
2b38b4e022 | |||
4ecde9fc39 | |||
445ee274c5 | |||
f2bdc514e8 | |||
5afff31f72 | |||
2dfafa387b | |||
7318f4f5dd | |||
175b77fe6f | |||
346652e508 | |||
f0eb42e72d | |||
37100f0937 | |||
ac980a4dbf | |||
a8b53499af | |||
a8aeae329e | |||
52911539b8 | |||
3026ff241b | |||
2466a079d5 | |||
ed9fdf49e2 | |||
668d962233 | |||
996f770935 | |||
041a6dd919 | |||
dbad60d03b | |||
27a60423dc | |||
5a37d38a84 | |||
6f566e67d5 | |||
dd490f2ac9 | |||
5409af0a6c | |||
0ed0d903cc | |||
85be4c492d | |||
c06ad8b87e | |||
b89acb5853 | |||
7890511a53 | |||
3aa4e6eb93 | |||
f8eb9f94f4 | |||
c581b9eeb9 | |||
ffd9c6995a | |||
ef600c0956 | |||
5c0a43e8d6 | |||
8e332dba30 | |||
cd07027192 | |||
da2b30268a | |||
1163aa4e4e | |||
ddb856edc7 | |||
9c426bc216 | |||
382852d0bd | |||
87ae86e1be | |||
9547311d7d | |||
1613d561c1 | |||
538478cac8 | |||
267ecce958 | |||
fae43fedfa | |||
c447022092 | |||
56042ad0b6 | |||
45da036789 | |||
b47b702a52 | |||
869424cd16 | |||
b9fd01315b | |||
a72098b862 | |||
86016de6cb | |||
592b9fedb9 | |||
d06984e3a3 | |||
6b55ee250d | |||
10eef282fa | |||
f312936629 | |||
d53bb4c337 | |||
1a605e27bc | |||
08ee858f64 | |||
af70fe3e7e | |||
29c5c0af50 | |||
9420b750d2 | |||
6f5328f663 | |||
90214d02d7 | |||
2f07f226b8 | |||
a8ad19a89d | |||
57c07250fd | |||
4a3e4a7c5c | |||
c284a23afb | |||
fad1449de3 | |||
f18d161eaf | |||
88054b453a | |||
c560373596 | |||
d698d03521 | |||
d8c8d7c588 | |||
9120e82517 | |||
e214746536 | |||
142396400c | |||
51d48bdde6 | |||
44b055c019 | |||
790d7b9170 | |||
d8719ceee9 | |||
71ddb16574 | |||
2932ed670f | |||
ae2a6a3d4f | |||
30061ada58 | |||
a131e28b60 | |||
8c1662cfdb | |||
299e52e877 | |||
95b253db09 | |||
067cb2452e | |||
45e4092335 | |||
7659a997cf | |||
aa5e428222 | |||
319e4360c8 | |||
f5c6e80dbb | |||
7108993936 | |||
b6553bdc34 | |||
19fe689969 | |||
d6386cef41 | |||
b88f8ae9d2 | |||
408c7b2ca6 | |||
271253fd0b | |||
5348154c42 | |||
e1b1f4f3fc | |||
75a2110626 | |||
9857d3d6ea | |||
836a2649d3 | |||
59cba2533c | |||
a6ac2fbc9a | |||
3da8677e32 | |||
4d0d7d5ad6 | |||
8c4ece4b2d | |||
bf3bb8a378 | |||
cf5e60f8eb | |||
7de707c60a | |||
5cd11ad8c3 | |||
6bba52a2b6 | |||
54b476df4e | |||
a68f123594 | |||
08ad4f96b9 | |||
77a3acf5cc | |||
dea585e69b | |||
879dacfba6 | |||
b459234ddc | |||
76d2c676fd | |||
d5015d37e1 | |||
1b71e4cee7 | |||
18ef5c6ff9 | |||
35e0561950 | |||
adab8e3ed8 | |||
89dbb4d300 | |||
e3f3686b8a | |||
9984e983b4 | |||
4ebe67ef53 | |||
1a11d4153e | |||
cd7cf3583e | |||
66a180bc36 | |||
eb06667455 | |||
0ff8966a27 | |||
2cc6794db5 | |||
0cb4094dd9 | |||
edd213343b | |||
46ec655db5 | |||
769efd9d06 | |||
49cb3b6aa7 | |||
8ad98b67d2 | |||
8a8f1d3205 | |||
4a27f0546c | |||
727a7e4b2d | |||
2b5e8241ab | |||
3dc4fd8dd1 | |||
375a27a93d | |||
544387d1a0 | |||
cb8120d38f | |||
78a261f5d3 | |||
b8f7653fb2 | |||
e0d2a01bc8 | |||
560be9f553 | |||
47723042c5 | |||
d04d676d2f | |||
3435636ca0 | |||
2e1572d7cc | |||
938339690e | |||
dbb2c523c1 | |||
0b9d436753 | |||
2d03f3ce1e | |||
c4a476d0d2 | |||
5122aed332 | |||
5336c5b46e | |||
22615f5981 | |||
bdf4b4b679 | |||
548e300c4b | |||
8a5d8c96ef | |||
78c2631b6f | |||
7c246ffc71 | |||
8bb85753cc | |||
abfdde28ef | |||
9801f1edfa | |||
fc3a200a63 | |||
6a00658119 | |||
353485054e | |||
800583b5e2 | |||
2db2b7348d | |||
f3718257f5 | |||
5500762acd | |||
4c8f5e1f7a | |||
733cf99bb4 | |||
58c2f22120 | |||
42accebeca | |||
1c5c370c12 | |||
448645d83a | |||
09b6a3b41e | |||
74206d60ce | |||
c3a0de7fab | |||
7edf7a434f | |||
b701821550 | |||
d022bf2673 | |||
7eed8c440c | |||
1ab12e380a | |||
728e14e8e4 | |||
8aa402526a | |||
4793ee4786 | |||
a09d6c0470 | |||
9e83130bd8 | |||
2ed01af723 | |||
afc80d6a7c | |||
532a1b1aba | |||
65062b4bcb | |||
c16206d816 | |||
185283f864 | |||
7d1f5c7383 | |||
945afc71ef | |||
818fe50f77 | |||
6fddad7a77 | |||
38d131be37 | |||
aeff846e1f | |||
6b52fc1e2d | |||
0671b530ba | |||
207f9c26ae | |||
6367ce5e5e | |||
ba1a2e9942 | |||
7f998ecdbd | |||
ecd5414287 | |||
6107f5f3d2 | |||
13afa9f476 | |||
cd87c7e88e | |||
ed4dea8686 | |||
808177f8c9 | |||
aed51251b3 | |||
1c2730163d | |||
0de86dfe6f | |||
7a1b99be46 | |||
9b64b0139c | |||
0a6160d7cf | |||
e51a6d332e | |||
a9d2741e6a | |||
12bd7268d2 | |||
be0a23d9ad | |||
458a0e608a | |||
32f3a50def | |||
7de4226d80 | |||
6a39c8fc13 | |||
dc39669321 | |||
be4f27028c | |||
60e73e2d1f | |||
e8f284d377 | |||
3ea3b0bf2e | |||
e1a43d2e7d | |||
2e918fe1d6 | |||
601309c7cc | |||
10ddeeb799 | |||
3463d6c752 | |||
8acce011b5 | |||
fe9ea50356 | |||
e6f29ae57f | |||
6cfd2c510b | |||
430ff80198 | |||
230fa76d57 | |||
46a4b0e0b6 | |||
5b3cadb7a8 | |||
3153071a8a | |||
bba7372556 | |||
9fe1a7e2ae | |||
98822a39d9 | |||
a2c830b908 | |||
bdef2cfdfb | |||
f229a5e2ec | |||
845e061382 | |||
e7d4eb1ae3 | |||
b4ba56bfb4 | |||
25784d1fe5 | |||
619eca7a51 | |||
f3d85655a0 | |||
9600675677 | |||
ce8a759192 | |||
88bc0bf613 | |||
b508e4208a | |||
c74d8cf499 | |||
a34c2b082f | |||
ad49a02879 | |||
e985ffc690 | |||
6cbb02f02d | |||
c0d0ff66b6 | |||
1e4d7f8c6e | |||
a8a761aa5f | |||
41952f0215 | |||
bfcc883f01 | |||
39722055f5 | |||
f85dfa90b8 | |||
0a4163d236 | |||
78de11a9e3 | |||
d2fc6d9f44 | |||
abf31f4a79 | |||
f28dd4f4de | |||
55b64899f5 | |||
d4aeeadb26 | |||
7ce0110158 | |||
7c1e55eb7f | |||
27542bc81d | |||
9ebbfb2d90 | |||
701b1ee744 | |||
0edc981cd2 | |||
da5942b398 | |||
709de81814 | |||
90b312a56e | |||
459759bfe5 | |||
00817aacfe | |||
e306eb0874 | |||
33a02b47d5 | |||
f0a5557e60 | |||
58a871c8cc | |||
4f56071786 | |||
f8b2c79aef | |||
8f00d34b0b | |||
6129519e5a | |||
593091a5e3 | |||
22ed163c8f | |||
93e2b88d41 | |||
7cd54dc8f0 | |||
ccd7c8df53 | |||
5b3bd3f470 | |||
bf1b7f44b6 | |||
538dd60580 | |||
f453236840 | |||
bfe7aa1ed2 | |||
9e2ef82902 | |||
9352e249ee | |||
3800065230 | |||
ebc2c4f73a | |||
f057440cc1 | |||
506f9cfca8 | |||
8a70c3353f | |||
3d8f123e05 | |||
a8c8f15e07 | |||
21e647017b | |||
2a1bb3dc27 | |||
55a3094a65 | |||
b4490e209b | |||
9aa676333c | |||
71b23e57ff | |||
2c76bc99fc | |||
bb06895145 | |||
684965f3e5 | |||
e621f4e2fa | |||
028ea57232 | |||
718fa25c10 | |||
90c9f28818 | |||
cb9c5a35cb | |||
fadaefeaef | |||
b17b882a3b | |||
f0f3afd5f1 | |||
42026b49bf | |||
151193c4c3 | |||
3448751e0e | |||
aae011ed83 | |||
c95a269460 | |||
98c0e5271f | |||
f343131802 | |||
ea34ba53b9 | |||
b8d8cf19d9 | |||
c9be4093e7 | |||
082eef708f | |||
9106fc5b94 | |||
918502742d | |||
f32f1eeaa5 | |||
2d1404d155 | |||
a56997e98c | |||
ef918078d1 | |||
7e61900cf5 | |||
e98f90b099 | |||
2e127dff1f | |||
828db19e02 | |||
99aa3f5713 | |||
1a568e2961 | |||
e863e8c64b | |||
f5b591430c | |||
8cfaf8eb51 | |||
675c0cefc3 | |||
1a52385b78 | |||
372e500590 | |||
cc1a317439 | |||
6d650518a1 | |||
7940117577 | |||
b0f87fdd21 | |||
dc92ffed87 | |||
4af578e310 | |||
e22825d818 | |||
e2da6259e7 | |||
d149017c60 | |||
afc400121b | |||
ef993515c6 | |||
edb1d21ddc | |||
ba8abd94a8 | |||
c6d4e4c15f | |||
09f0ac866f | |||
7ed25704d6 | |||
2196dac63e | |||
c8f70efded | |||
ea97488670 | |||
c2255b0a0f | |||
f754b081ce | |||
07771cb5e4 | |||
690d8e43ae | |||
82f14a7d59 | |||
b284384f0a | |||
1ae0d1b5d0 | |||
9de08c8166 | |||
a2d007f2a9 | |||
774f818bbb | |||
0ec7121b8f | |||
d7d46f4447 | |||
45fad147bf | |||
3664195c71 | |||
fce3cd00a1 | |||
33b3be0d0e | |||
cfd1b4a6c6 | |||
d45fefd6f0 | |||
f125ab01ee | |||
be001d090c | |||
971d8a7e40 | |||
a2cf210a52 | |||
3eec207166 | |||
b5d83bdb56 | |||
2c495c4119 | |||
7c72d6cb7c | |||
8362bf0886 | |||
1a8155c45b | |||
3f2f946019 | |||
2c14a8dee1 | |||
917a283bd1 | |||
3e403d5ab3 | |||
746d35b52b | |||
9a7a03e327 | |||
a051079c6a | |||
7b3c18bb97 | |||
52daf3d58c | |||
f41bde5ee1 | |||
6151318ac1 | |||
b45c322729 | |||
b00e8768dc | |||
156feb6e8e | |||
e942b8a402 | |||
abdb67a123 | |||
ee20787c5e | |||
ec4e631760 | |||
02b430a5bf | |||
7878053df2 | |||
12a593c3c6 | |||
6b1f130750 | |||
bde4c0a648 | |||
5ae4621da1 | |||
5ea8d0546e | |||
8a064c118f | |||
2f91c27df2 | |||
763bd54707 | |||
0ea3cc7ce4 | |||
0de3558ab3 | |||
069f4e12d8 | |||
ae4dfc9956 | |||
ee711dc0fb | |||
c316e7faab | |||
7083b3d912 | |||
2d3a1b6a9e | |||
0df23ab878 | |||
7ed8de2ef4 | |||
d935e22f0d | |||
0e26abf7a6 | |||
59aef13200 | |||
9d1f6c4416 | |||
b9f7660a91 | |||
18b5250ed1 | |||
f683f21ee2 | |||
bd033db84c | |||
ab036312a4 | |||
634da15191 | |||
cea1720ea0 | |||
3f2f542265 | |||
b77edb2b5b | |||
1b699bb814 | |||
333c035fed | |||
ce29914c56 | |||
70e5361146 | |||
e7d6dfff53 | |||
eebfad5a95 | |||
77c0a93ac6 | |||
63a3e126b3 | |||
3ea84cf0ce | |||
7fa80ae556 | |||
925f71af15 | |||
c666dd623d | |||
2cd8733212 | |||
4b2a9bc621 | |||
12a9d0575d | |||
edcfa28b0b | |||
3155829994 | |||
d25707554e | |||
38df44ef4b | |||
df683375b1 | |||
cc3cbbc4bb | |||
6922394b8e | |||
24fd82d773 | |||
57aefcd917 | |||
b3854ad382 | |||
5f5fc77877 | |||
0493e77cff | |||
6240fe1dfc | |||
beb7f90908 | |||
a3917972b4 | |||
7094fef37f | |||
0f41e56a24 | |||
52b283283f | |||
ebb15bf96c | |||
6c527d52fb | |||
b8ea57e097 | |||
909aed4262 | |||
4d2fff9538 | |||
9a45983f17 | |||
11926014da | |||
72002c13d6 | |||
6ed767ae84 | |||
3826b307f7 | |||
887b157056 | |||
d36dd39743 | |||
dd008bc13a | |||
50b282f58b | |||
f8a7efbce7 | |||
7d2caeb270 | |||
708e71a35a | |||
4eaccc966e | |||
3670d649b8 | |||
90ab04e81d | |||
26b8df5354 | |||
11a8046c5f | |||
da16110e1c | |||
914b686c8e | |||
27133520fc | |||
24b967ad5c | |||
ca4b4a3f1e | |||
faef35ec47 | |||
326d4c2641 | |||
83436c9550 | |||
2084822731 | |||
071bad1232 | |||
ae1a76da2b | |||
fbc6965c4e | |||
57a5862840 | |||
91fbccdbaa | |||
0ab0dd95ae | |||
bc41040fd3 | |||
4c8dfd0c0c | |||
2b9dbfb390 | |||
84d546b724 | |||
63053b9940 | |||
2256030a2a | |||
79da33b597 | |||
7d67450e58 | |||
8aa11951bf | |||
f23f22ab01 | |||
96a64c7bd2 | |||
d1bb0fdf1d | |||
feca30d7ed | |||
b650151693 | |||
bb3afd0dc9 | |||
5e77ae208d | |||
24e5a4d7ec | |||
1d10d29fa9 | |||
9b00e91773 | |||
cd73c30d6f | |||
7bbba0c7d9 | |||
7907a4fc24 | |||
2f94f62a56 | |||
85791a9336 | |||
a4eba50cfd | |||
03980b2f27 | |||
664e5cfb59 | |||
b9736df7e0 | |||
f48b2681e3 | |||
ab46bd56b0 | |||
c23506e887 | |||
9ad67a7b7d | |||
7a1b6142df | |||
478256d766 | |||
4d92caacef | |||
fd45de5c58 | |||
bcaa9674fe | |||
40aa3b7e18 | |||
5aea21a194 | |||
b5e118e2b4 | |||
dfec0e45ed | |||
ff2a4e6952 | |||
7660751f7f | |||
78b9ac4766 | |||
d5c75571dc | |||
16b9c459ab | |||
41c060e28b | |||
a3090e62f5 | |||
39b7024be0 | |||
d019c5999b | |||
20264eecb9 | |||
cc55453076 | |||
6cab2427f5 | |||
511bcc9197 | |||
00ac632d8f | |||
649209890d | |||
f2fca0f13d | |||
4084d5e69a | |||
e8beb7103c | |||
0e4ce0f1ae | |||
c42d517f6b | |||
356cd4ef52 | |||
88619145d8 | |||
6ba779fb7a | |||
8bd965267c | |||
7f76ffa5cb | |||
4acc7cee3d | |||
be28e0b559 | |||
116fec208b | |||
fece92e15a | |||
dce3049446 | |||
fcd6fe5d8a | |||
a69a833716 | |||
697b082591 | |||
b2d58e04d2 | |||
8bfc5f0450 | |||
a252a8acee | |||
447ee4bd09 | |||
3cd6382795 | |||
5d1134dfa8 | |||
05e7b0dc22 | |||
c0647c3110 | |||
ef84ed4982 | |||
a1e83b9f19 | |||
4ce4ee3c00 | |||
0d62aedfbb | |||
b7c2890250 | |||
ae97bb0445 | |||
117fd4bd0f | |||
bd424ce460 | |||
1dddba7f25 | |||
7fd75b7501 | |||
423f07033e | |||
ef9c457681 | |||
a6d4a3b785 | |||
2e487f8a3f | |||
2423a70abd | |||
13d39fc942 | |||
b7547a8458 | |||
8931dbb657 | |||
52416ff3a8 | |||
3dbfee91f6 | |||
09d4901781 | |||
62955e7385 | |||
1ef7722504 | |||
24bb2f02dc | |||
627698d81f | |||
d4c8480dee | |||
015e8deb79 | |||
714aa4b4ba | |||
8d5f798591 | |||
e65f59b3df | |||
341c3d179e | |||
67128937ca | |||
d9ea621e54 | |||
fb35d7af59 | |||
c254aa6fcc | |||
37d30eb887 | |||
49cdcc644c | |||
07e5525c74 | |||
776194f5b2 | |||
ed80ee98a7 | |||
040bac3da2 | |||
9df721d158 | |||
c50ede8b2c | |||
ba0907ae59 | |||
e9dce32a98 | |||
535cc0d81e | |||
5801297d78 | |||
51a33a47cd | |||
01a1a9ebab | |||
438bad9649 | |||
fe3b36caeb | |||
83588e14d9 | |||
64b1c9636b | |||
db0c1b2634 | |||
568c4d8c8e | |||
d645507eeb | |||
3548112ab2 | |||
0cb042cd93 | |||
0eadc028b6 | |||
82f3677168 | |||
70ed49e478 | |||
3c67a36b60 | |||
e5621246ec | |||
cb71d44024 | |||
7e3ea9074c | |||
e2cf157857 | |||
60890147c3 | |||
64c95305b9 | |||
feddd9285d | |||
d1b393965f | |||
e31a39b9d5 | |||
98fc028d39 | |||
88fd799a30 | |||
ef937f277e | |||
c3fb5af3fc | |||
859e8deb02 | |||
932c92412c | |||
05771ddf6d | |||
848d387ec4 | |||
ac6b4235b9 | |||
ab73e98075 | |||
aecdd04e04 | |||
e5cdf74587 | |||
8d25ce7323 | |||
8deca3b63a | |||
9b967177c5 | |||
4dfb3cc972 | |||
73e5e9ecd9 | |||
653b7ffcd0 | |||
8791b72cb1 | |||
d961492380 | |||
07de367476 | |||
31d96c2bf0 | |||
fb8aafb69f | |||
3d58b78062 | |||
ec5e6958ef | |||
71bd5fe367 | |||
6385c71c72 | |||
d43255e688 | |||
3527dedc99 | |||
de50f53be4 | |||
f2e4b2fc99 | |||
e6f3cd03bb | |||
a1e31549a2 | |||
71d225c562 | |||
7c23212850 | |||
fdf178d4df | |||
04ebca8413 | |||
edeee54fb2 | |||
a906e9b302 | |||
fff72b61df | |||
74381ef59e | |||
64f95af3e5 | |||
85a1eb75c9 | |||
597cec3064 | |||
b03ebc1fa4 | |||
6c53bb4d51 | |||
fb7a458747 | |||
db25a9ae4f | |||
c69420373a | |||
2b8347f899 | |||
281a3911f6 | |||
9b77dd9a2b | |||
cb8cff3179 | |||
3db85c7274 | |||
b41ac355a0 | |||
88d9ffe92e | |||
5113c78ab6 | |||
3854995ef2 | |||
36e14b951a | |||
9299a4beff | |||
d681bea395 | |||
0f3f1e9226 | |||
79ab492a5b | |||
62db4bb09d | |||
7be2cbb75b | |||
5b1fe3460f | |||
31997fe50a | |||
5e5ceef122 | |||
40edbac7f0 | |||
5bb1f72c28 | |||
8622e6492c | |||
1feac9c559 | |||
fce81dd6d9 | |||
aa50554f06 | |||
034506f56b | |||
2d8858edb4 | |||
b2601ad696 | |||
8099f561c5 | |||
8a014ddb0c | |||
3d9383ce67 | |||
9de07c11a6 | |||
9f744bc445 | |||
aed6e12119 | |||
c57d0046bc | |||
07b9fc9b31 | |||
2c6bcb85a0 | |||
fefa519486 | |||
11a232a2df | |||
8dcd919ff0 | |||
d9c27e7109 | |||
8af8c57bb4 | |||
a1a4916abf | |||
9be8f675ac | |||
a271c3726e | |||
8c18a14dfd | |||
9a801cfdfb | |||
4af13e3536 | |||
e76e903060 | |||
3d89a317c1 | |||
d8251224cb | |||
acd927a937 | |||
a498f940c6 | |||
948cb31d1a | |||
179cb8eb50 | |||
47f865aa72 | |||
b47face2f8 | |||
69869115f6 | |||
0fb9ca3e8b | |||
eaf9c9b2d8 | |||
70d9b0c390 | |||
e57a999c9c | |||
3b49289cfb | |||
176e984b56 | |||
b5a700276a | |||
3c186a3c8d | |||
a462ce3626 | |||
065cf42aea | |||
986b709f2c | |||
fed6f44995 | |||
1b52acdad7 | |||
10a638c6b8 | |||
7875f363a8 | |||
685736b9ec | |||
aefd2bf6f8 | |||
ce9fb2f1fe | |||
974275a429 | |||
98461f9bca | |||
094f78fb41 | |||
33dcdc1599 | |||
8870ccb18c | |||
2a7ed1375a | |||
107727eea9 | |||
54b50cca71 | |||
1c10ba7925 | |||
2b8df691ff | |||
15da856303 | |||
cef5343a24 | |||
f96b85fcb2 | |||
a62628423f | |||
ef8a87a30f | |||
89fb943733 | |||
147978b932 | |||
c741920ec0 | |||
bbbcb18b91 | |||
d6b3b0baf7 | |||
dbe8931cf0 | |||
d2eb5d7f45 | |||
562dce60ee | |||
569df39fb8 | |||
2f7f00c7a2 | |||
afd59eabbb | |||
cf99446a12 | |||
68286b2acc | |||
a410184e0a | |||
d3ceecf620 | |||
940c5b3838 | |||
17c321286d | |||
0dbb79359b | |||
19f39fcdb0 | |||
ab021c1302 | |||
3b11ad8de8 | |||
cf4b870846 | |||
5e37f72d74 | |||
6843dbf7e1 | |||
09c07faafd | |||
8e7c235ff0 | |||
7fb4cbb8a0 | |||
fa872f6cf7 | |||
ef53d4ec07 | |||
c68e7c8da7 | |||
de35a4c62a | |||
fcde6c2b84 | |||
9cbe053e79 | |||
818468c58f | |||
7ba43ae5c2 | |||
5700c7a0c7 | |||
4bfd395d9f | |||
5069d8dee6 | |||
47c120e58c | |||
8d7ab13f5c | |||
122cdae5bc | |||
157d8db68c | |||
998da965cd | |||
8d58a8d548 | |||
b453be081e | |||
3c947f323f | |||
cb203ef02c | |||
908c9bc624 | |||
fe373a95a2 | |||
60f18f3b5a | |||
284c019b32 | |||
32434471e5 | |||
6a4c280235 | |||
f0eacf4218 | |||
0afe3011bc | |||
0fef546a0d | |||
93e6136795 | |||
7d23fd8ef5 | |||
71c9df5279 | |||
224fcada17 | |||
9278407b85 | |||
dad3292bdd | |||
cfdf319972 | |||
89619b7836 | |||
6aff438a16 | |||
13324dd1a1 | |||
ae9bf06b46 | |||
5236834911 | |||
bf80dd622c | |||
662b71436e | |||
f608cb55eb | |||
6ba82da029 | |||
f407e30b6e | |||
4e7b8c98f9 | |||
5f9574541f | |||
08a6db7d6e | |||
b485e1d657 | |||
e8d8621f06 | |||
4cefbce7c3 | |||
fa31369f99 | |||
d0bf93ebb7 | |||
41a747c7e7 | |||
8882cd4787 | |||
6676490e09 | |||
68bea8a196 | |||
25995c09a0 | |||
0eb8d7d081 | |||
554f890ae3 | |||
dd1743698f | |||
b092e98ac9 | |||
9ee6262aed | |||
24a2d86f41 | |||
b5c5c66336 | |||
7654feb6a8 | |||
a598ac3993 | |||
cab919d74c | |||
60a929b92c | |||
356b7c346a | |||
ad57fde1c5 | |||
17f7dea21b | |||
b40af7c3c6 | |||
9065362fde | |||
d264b03ca1 | |||
ad9bad3d17 | |||
dfd858034f | |||
58ad8fa8c0 | |||
38610d8a24 | |||
27cec697bf | |||
024f9a8c76 | |||
f7cc36f2f0 | |||
ef5148ebb4 | |||
6dbc0a6fd5 | |||
fba3f9d501 | |||
d9f8137362 | |||
28416489b2 | |||
54a23ddd1f | |||
3287ca9cf2 | |||
a59e134862 | |||
1f8c5b0120 | |||
c7f839ea4a | |||
d981245723 | |||
1f729f1cb3 | |||
b4577d6676 | |||
544adb9940 | |||
1875c4a752 | |||
5f0493f1e5 | |||
c749e50bec | |||
a4e5e3ece5 | |||
2a69d1b051 | |||
126e1e2d9d | |||
0586e1d3ad | |||
07cb1c237e | |||
f4f1efe5fa | |||
37fdf4d434 | |||
99b46096a4 | |||
12e90ae35e | |||
023311a874 | |||
155a4dd463 | |||
15bed1ac4c | |||
27f55f8098 | |||
00598879e2 | |||
df274a0a78 | |||
0dc4862d79 | |||
a3f1b72126 | |||
5ff10799e4 | |||
a82e5f5452 | |||
e10cb0e632 | |||
c7e07a6df0 | |||
2e0c778090 | |||
592050c668 | |||
02c9191525 | |||
d421401626 | |||
b2d4e5ab84 | |||
84e023607c | |||
f145fd0dec | |||
42a9f911d8 | |||
9567d55312 | |||
531cd99247 | |||
f3660d88dd | |||
3accb9a08b | |||
63ce7371bb | |||
01c3498dbf | |||
b3471234ad | |||
b2d697131c | |||
ef49fc91d8 | |||
6222b47a4f | |||
f58e3c390a | |||
7504621a24 | |||
88e49a9b8b | |||
5b23f29d06 | |||
c1bdebee78 | |||
ddd4cc10ff | |||
0ca62a4acc | |||
4f1275ac01 | |||
b2fee7035f | |||
e15d7cb548 | |||
3257cbe21f | |||
1237af1ff3 | |||
68600b337e | |||
dac2072eaa | |||
1b921f9845 | |||
a3992d9fbe | |||
efd2a0cb7b | |||
fba428257b | |||
ff36901007 | |||
940d8389b5 | |||
f7a6cbe5e2 | |||
7aa379a857 | |||
443024cebb | |||
1657f04d55 | |||
407e798fdb | |||
4054f2a6a0 | |||
468cdf603c | |||
988ec6a224 | |||
bdbdf211e2 | |||
0437703cbf | |||
71aa592111 | |||
d501c02f8b | |||
9daf0e78b8 | |||
dfa07a5f35 | |||
437c995d12 | |||
cc6ae9d1a8 | |||
c58e4f4dee | |||
c87b0e77de | |||
355d5af8ae | |||
3d99a8ebdb | |||
c4b975b777 | |||
2911fe7a1a | |||
14c114756d | |||
e7a8107279 | |||
bff73b1b40 | |||
c255f57d95 | |||
64c47bbaed | |||
e0b7698d40 | |||
a01792ac9a | |||
3ba078f64c | |||
a16240f123 | |||
e5a120e778 | |||
2ba60e9114 | |||
472ce5a5e4 | |||
99ba84c810 | |||
78285bdf37 | |||
5a7f2684b3 | |||
d912a42249 | |||
6d8c4fb8b1 | |||
a63cecbfcb | |||
4a5bceb4e4 | |||
86541445b7 | |||
4e826aa8e7 | |||
b6e6f490e9 | |||
2145e878a4 | |||
355f6db255 | |||
bc7632bf02 | |||
609d8c9685 | |||
2f08515455 | |||
7f450e185d | |||
747879b4ec | |||
4193870fa6 | |||
cdc5de3f1b | |||
bc34d4fa88 | |||
6fd4af8736 | |||
b5c2934270 | |||
94f5117941 | |||
112e233498 | |||
18b1326f3a | |||
1e58b05ead | |||
938919bd9b | |||
b6b78994d8 | |||
fddd8ce305 | |||
ccff337975 | |||
fde6b7af4f | |||
0657db7dcb | |||
d1c2eaf6d5 | |||
91bb6b9016 | |||
90351c6e9e | |||
dd4740e54f | |||
48e7cbd76c | |||
f51e32f39b | |||
ae42f59102 | |||
5c8006f9b7 | |||
aa5861d3ca | |||
7a64bf55cb | |||
d4c9ab793f | |||
48d2849d97 | |||
776610d0e6 | |||
3a790f3d66 | |||
7382042288 | |||
33992d80bf | |||
a92b0e567b | |||
829a65e515 | |||
03ad48c055 | |||
89837e4ced | |||
ace1db21d1 | |||
8bb69c455b | |||
2dae706198 | |||
3eda2a220a | |||
61e5440b7c | |||
2e2663bad9 | |||
f4dd150b70 | |||
2b35d22e25 | |||
f590378761 | |||
f5f592be91 | |||
7a373fb43a | |||
aded11e599 | |||
41d7cee020 | |||
f2ef6a20e6 | |||
a398c3fb81 | |||
2a454b44cc | |||
7b66ece895 | |||
b5017eebbf | |||
aa67229daf | |||
5af68186d6 | |||
545bc0e605 | |||
291168f4de | |||
9facb51f22 | |||
5b7d8c5e37 | |||
5945937e4b | |||
9f9f9872eb | |||
3566072f4a | |||
b85cd86b24 | |||
79c3767fff | |||
cf1609a429 | |||
3aeac7e7b5 | |||
1557f713f4 | |||
b63d24ac1a | |||
348c1ff29d | |||
717e55497f | |||
d84b5e8b46 | |||
5f9ddf9ff5 | |||
bbee093c63 | |||
e8c35ae4e1 | |||
1607658c30 | |||
2e9ef373f3 | |||
ec6eef6d37 | |||
45a19d15ec | |||
7191552126 | |||
cfa07490e5 | |||
ae40990eb9 | |||
9f2fe33ce0 | |||
33660de6b1 | |||
13d25e0849 | |||
6662e2002f | |||
d4081dc899 | |||
62dffb8226 | |||
cb6aa18480 | |||
d5cfbef42b | |||
535abcbb8b | |||
c34b548a3e | |||
9bf452856c | |||
17109ab760 | |||
6bc6e1a1d1 | |||
7eef4f7fbf | |||
75bec6a8e3 | |||
0a10f66053 | |||
58860b51a2 | |||
3ee652b61a | |||
426ed7308b | |||
0ecfef3f70 | |||
5f7e34b6a1 | |||
34cb24fe34 | |||
1490112135 | |||
c4716a3f4c | |||
0a54901eb0 | |||
fea2e0a265 | |||
d3c087375b | |||
a93c0577ac | |||
e4dc35674d | |||
8a668ba7b9 | |||
ee9a68b040 | |||
78e8d40649 | |||
660e13b701 | |||
0685382083 | |||
04a993c997 | |||
7cae3095c4 | |||
e288bf902b | |||
a083e1f71a | |||
86b9d7e843 | |||
628bd5d6b4 | |||
00285a782c | |||
16be469ecb | |||
fdcbc4cffa | |||
fc548304cf | |||
7c7ff8165e | |||
496a476c13 | |||
441fc6e45b | |||
cf7ec6aa76 | |||
db2dd4b6c6 | |||
a68417a0b0 | |||
2a5102a457 | |||
837d8f5f30 | |||
1a5858e99b | |||
4044427d93 | |||
f667f85fa5 | |||
5cddc0c387 | |||
cbc01dd6f1 | |||
b820c7debf | |||
2bee072cba | |||
80710b0b94 | |||
3319ccfd41 | |||
878008e93b | |||
0cd551d4fd | |||
f85194ec46 | |||
271489bdfd | |||
bd5f22a049 | |||
189f18b112 | |||
df166184ea | |||
ce42cba096 | |||
9670863a41 | |||
1ae52bd33f | |||
c9cf9cfff0 | |||
2ffbee3db2 | |||
96b8beb9cd | |||
365b849046 | |||
8e613d03e3 | |||
b18a794eca | |||
c620c924f9 | |||
9db81a5a49 | |||
6fb7a85e8a | |||
36f81b4a62 | |||
2caecc01b2 | |||
dedb8d2d68 | |||
7192b26402 | |||
762f5bdc33 | |||
bebb52b4e8 | |||
2c9f8bb9ce | |||
efbefabb01 | |||
990fb22d3e | |||
9b2c22b2d9 | |||
df7e0d2f2f | |||
5cfda1b1bf | |||
ac9bf1f3ff | |||
7eb0868791 | |||
8a792e6d76 | |||
d8a3692d92 | |||
95ce0e39ef | |||
17b70ab38c | |||
07e76f35fa | |||
a4cab9876a | |||
c06a932c95 | |||
7d713b87b1 | |||
b1167146c5 | |||
2d0a5eb02c | |||
8d68859c2a | |||
444cefc9a2 | |||
d0deceabbd | |||
175c1df0b8 | |||
9cc6491c2a | |||
710179f4b4 | |||
d11c72fd48 | |||
0af505828e | |||
135cf9960f | |||
3bf7c74f93 | |||
cea4911c4d | |||
54dc01253d | |||
4db9a90da2 | |||
d69e9034ab | |||
71ece73d99 | |||
3bb2102eb4 | |||
b7914909d0 | |||
63398fe491 | |||
bf32bf28da | |||
dcb6bfb18d | |||
8f605dc0f6 | |||
47e770948b | |||
9ab29f5b7f | |||
10bf430ce6 | |||
67eb4e8180 | |||
141f9b7730 | |||
139a589ad6 | |||
591873a185 | |||
97a308b114 | |||
430714e67f | |||
a49adbd09c | |||
3df98d576e | |||
8135136c86 | |||
cef1c4b8a1 | |||
2e8791a101 | |||
0e2b8b10d1 | |||
3cb64669e4 | |||
bc0d32f330 | |||
0db17beacc | |||
931efed784 | |||
6378a41b6d | |||
23bf7faf9f | |||
01ff3af63f | |||
8f98055e9e | |||
84ae61f72c | |||
6dd280205b | |||
1365d553a4 | |||
61a594493c | |||
62ab70f889 | |||
eaccfdde59 | |||
a8e536478c | |||
e94d5626dd | |||
be3e31ddc4 | |||
b92b6520cb | |||
ea33179a95 | |||
6fcf6ae1f5 | |||
f2a9247b68 | |||
dc3ed7fffc | |||
271de31d51 | |||
1268caf3e0 | |||
c0cef58e39 | |||
d363d205c3 | |||
2fd5a9e883 | |||
e7ef974a39 | |||
0b62fa8b76 | |||
2d28750782 | |||
e2054a0ab7 | |||
7ae5c3b2e7 | |||
6e7fefb8b2 | |||
450bef278b | |||
0affc0d58b | |||
3d153b6c8e | |||
04fff91e23 | |||
28a23452f2 | |||
6d403851cf | |||
395a749bce | |||
2cc2a90941 | |||
c87ba6231d | |||
c5ca739b49 | |||
00fe4cdf2d | |||
69be3e1e87 | |||
2cb3984d68 | |||
5901978889 | |||
8bf1cf3cc5 | |||
f6af1184bc | |||
4880741b8b | |||
e8627800fe | |||
907fbb94a2 | |||
fd2028557e | |||
91fa1ec6b2 | |||
628c525599 | |||
bbc00768f0 | |||
5b09461ccf | |||
1a439ecece | |||
836aec4396 | |||
0b5dec9bab | |||
fd56123267 | |||
45ca470789 | |||
a3b1690d38 | |||
a3bad75899 | |||
d1aaafbfff | |||
93d4af99bf | |||
c950595fe3 | |||
8ffd3a8ed2 | |||
b6e246c6b2 | |||
59859e124f | |||
2bb7a33bc3 | |||
c2b8fea291 | |||
08ab7f6aa0 | |||
560f0bba5c | |||
722437a022 | |||
8a44b1dabe | |||
b39191ff50 | |||
9814d20404 | |||
6664dfb048 | |||
3133a63cf8 | |||
c9c0f3d014 | |||
ff66f307dd | |||
e048d66f74 | |||
66e3fa7df8 | |||
019a0f31c7 | |||
749c2071af | |||
322d66d282 | |||
aa98cd0da0 | |||
c8316c7254 | |||
6b9180844d | |||
c0e4863229 | |||
2be9871d05 | |||
776f6a9a16 | |||
10163aab21 | |||
60b2a4ea9d | |||
56e1e3e205 | |||
0f805cd45e | |||
1d7c692e89 | |||
38bc8ec6b4 | |||
2154e3aa2d | |||
56c19e57a9 | |||
d548c690d6 | |||
3fa70dade3 | |||
368c30a2cc | |||
5539e4591f | |||
781971ee81 | |||
1140316d1b | |||
cf6c48744a | |||
eed6db8e92 | |||
858664bfd7 | |||
eceac4d6e3 | |||
47a172df1f | |||
f2c0732c40 | |||
682fae12b6 | |||
a150762c63 | |||
2695bdddf8 | |||
c9b1a425a7 | |||
122b2b1a8e | |||
2351c1b426 | |||
558c4ada06 | |||
779fd9c61a | |||
0b5128dfd1 | |||
c0519e8670 | |||
fd545db1bd | |||
6991c224b2 | |||
7dc70c9eab | |||
e32445f2cf | |||
8aa6486bf7 | |||
d21c147203 | |||
9b10e851d1 | |||
6675508b24 | |||
7310ec4fe4 | |||
b1ce3693ed | |||
deb1ed5623 | |||
0902d7cca9 | |||
95ec903862 | |||
2ab6af6471 | |||
9493577de2 | |||
837ce62844 | |||
2860bbfb12 | |||
a2b1acd70f | |||
f1350bc33e | |||
6af0eb4068 | |||
538c168641 | |||
832a4fa68e | |||
ccb727529d | |||
ed41604f56 | |||
9f05d563f9 | |||
72d114d46a | |||
99b96d80d0 | |||
67418ba853 | |||
60755d0c26 | |||
a689e4e041 | |||
f5aa36c787 | |||
f8d82cb052 | |||
980feb6c96 | |||
e7d6605490 | |||
7a476abb53 | |||
19581792fc | |||
aadc6a56cd | |||
b88e444cbc | |||
6c792d2821 | |||
9afb445620 | |||
efc951191d | |||
a249373bf5 | |||
e1eb030b18 | |||
6aea0f48ed | |||
e637f22540 | |||
842295348e | |||
2992a0f4d8 | |||
e8f5963a57 | |||
4cbe497770 | |||
76a53097b1 | |||
0904692f15 | |||
65bacd288b | |||
11ab3b2c2e | |||
812368e332 | |||
cf39ae0000 | |||
7194f65203 | |||
4b78ff324d | |||
79ccfcd553 | |||
2df6a4dde8 | |||
e88cbc2769 | |||
25d1c40cda | |||
969b57ade9 | |||
bddeb86223 | |||
b5986b509e | |||
9d2adcd512 | |||
fb3756420b | |||
2eab43a669 | |||
caeab0a63b | |||
7c69b1b649 | |||
3784d1a8f2 | |||
458e761b45 | |||
371b0b2132 | |||
5d1ca64768 | |||
972a595c74 | |||
a3c598a3e1 | |||
9ce8c5c160 | |||
79bbc99882 | |||
31867362dc | |||
3bce07e873 | |||
766f9e37b5 | |||
a9bed90d02 | |||
01ad405dd2 | |||
8cef35f4a0 | |||
274f0edd76 | |||
88aea311f8 | |||
477aedbffa | |||
b898442fe3 | |||
528c1b90c2 | |||
205c1e5170 | |||
004e1c98ee | |||
7641bb4d0d | |||
687f3d48ea | |||
da5f10a2f1 | |||
64050e8266 | |||
791a7d5a01 | |||
13930d3706 | |||
2769e27a2a | |||
76c795d0d0 | |||
b20bced3ca | |||
4f2da9a78f | |||
8e0ba3650b | |||
76f6fe4601 | |||
ca1373f36b | |||
fc6c2e083d | |||
c0789cd6ba | |||
af47103707 | |||
c466baaa25 | |||
670294a427 | |||
21ddae6a86 | |||
9f260c3513 | |||
18061d1077 | |||
381c061ebc | |||
d37341d7d0 | |||
a5098e5b5b | |||
b55d394a1f | |||
5e2e177aa9 | |||
86e59977de | |||
66baf01e43 | |||
7a33e198dc | |||
4b493ebbaf | |||
565e8cf00b | |||
738a3999b4 | |||
6f4b84c8fb | |||
29ab99aa1f | |||
d53719b79e | |||
f10fe8bf02 | |||
d7cfe1990c | |||
8bedc8f456 | |||
d9000f6fd1 | |||
50c7b32b00 | |||
78072ad285 | |||
437a34b5dc | |||
3ebea4c305 | |||
8fe315c354 | |||
b10b13a339 | |||
5b5ea5ab8a | |||
1a230b3900 | |||
e90b0aaf8b | |||
fe7c7e72f5 | |||
9ba11a585f | |||
9920ff617b | |||
3f1355c413 | |||
4929e66ecc | |||
02e370c2d8 | |||
4c31e3fc5f | |||
15f49b39b8 | |||
8c82b766e3 | |||
4c8665c9f0 | |||
ba67781431 | |||
4ef25c75b7 | |||
3aafc671f8 | |||
967df6f7a3 | |||
22518f173f | |||
64bdfabbd8 | |||
c8c65ab7b1 | |||
19cd28b66b | |||
4a136ef2aa | |||
9e7a53cb90 | |||
19a7f37efa | |||
c3084ac43a | |||
159146e197 | |||
67ddf4a5b8 | |||
4b9b53a9b8 | |||
e6f025a9fb | |||
aa607e0ecb | |||
65b32ddeb2 | |||
5e9bdc2690 | |||
9ce994168a | |||
748a720199 | |||
8db34eb3dd | |||
b657bba96e | |||
dbaac69fad | |||
b6a1e89535 | |||
cce919750a | |||
9376b223bb | |||
6f047fb5aa | |||
3e6b0117fd | |||
421dfb4a2d | |||
abaca6e676 | |||
8bab1d9798 | |||
13d31669ac | |||
c1dfdeb500 | |||
dda7e677a5 | |||
b1fb401f63 | |||
885ace111e | |||
885552b792 | |||
4f02872a84 | |||
ecec1bd102 | |||
0c07e05a2b | |||
060f0682f4 | |||
88032e11df | |||
493c8b0943 | |||
af2ef0621a | |||
095461e31b | |||
3ddd1033c3 | |||
912687ac78 | |||
40a9595012 | |||
12ff37d052 | |||
4857073f30 | |||
cdbefd9191 | |||
2e9d89574d | |||
569c99496b | |||
ea3b8767de | |||
8e8c30c1eb | |||
d921ba81c8 | |||
ad9f646102 | |||
2ef277bcef | |||
9e396e1624 | |||
9708d84e60 | |||
4efc195548 | |||
0d15cbe334 | |||
e381d9fc8e | |||
85ed7a7457 | |||
b8a98ef5e4 | |||
c8fa90f473 | |||
b47ee8857b | |||
131dfa62c4 | |||
6a5af438dd | |||
ccc0a61158 | |||
e990ad25eb | |||
98a4d1e763 | |||
f762598c5c | |||
ec56c27071 | |||
eb0e0a1952 | |||
01a837fde6 | |||
b9488645d4 | |||
7a94b477cb | |||
99710b45d1 | |||
3813743e3d | |||
9bb2334b69 | |||
7e73ede47a | |||
b0106aa420 | |||
33e5fea96c | |||
f0a1dcd120 | |||
26d5a87bef | |||
52ae208df3 | |||
34aaa7fb0a | |||
a8c784355c | |||
0aed93becf | |||
1ea0804209 | |||
a52fbb012a | |||
2dc47352f8 | |||
e95a5be21d | |||
abd69d4f91 | |||
d749e309f8 | |||
e4d075fb91 | |||
71c6c71081 | |||
d2b14bcfc4 | |||
2c04c81bd1 | |||
dd66c83c50 | |||
9e51d82154 | |||
bdc441a5be | |||
2dcb73700b | |||
ee01686ae4 | |||
9a55cf880e | |||
6742cdeb8b | |||
c37377bffa | |||
76147a9be7 | |||
bf22e69250 | |||
49693934cf | |||
e6a63ee5b2 | |||
08dc57fd02 | |||
cede590696 | |||
c401915fb5 | |||
2a202bd510 | |||
dcd8ed08fc | |||
d3ebedeef2 | |||
d2e2ebbe45 | |||
0cef05dd89 | |||
a443dc3040 | |||
4e6cc013e5 | |||
0c65d54d89 | |||
ccd0e0cdfe | |||
9278ca3f5e | |||
d7a70b962b | |||
fff0f841fa | |||
8ba426350f | |||
8ef548032f | |||
5452e29840 | |||
148f8e6d11 | |||
13a5662a84 | |||
6713a7ae3c | |||
226ad13061 | |||
88ee86b7ef | |||
4bc2288806 | |||
a928d9fa0b | |||
d8f4e6b45f | |||
5ef5087406 | |||
135c371d88 | |||
966c196f4a | |||
dc43e41896 | |||
4809d06d04 | |||
9f7fda0bc5 | |||
1f67695713 | |||
66ef1a8206 | |||
beaffc3870 | |||
8536ecb611 | |||
d7a89b0f8c | |||
943081e80d | |||
3f007a1edd | |||
e33cacf6a4 | |||
23fe848a35 | |||
fa5d2276c0 | |||
d353a3457d | |||
b363b9fc1a | |||
1920568057 | |||
763da19c9d | |||
1813dbbf59 | |||
339169b624 | |||
93960315d9 | |||
479eb1ba71 | |||
962d8e5fd2 | |||
fefd4c0b26 | |||
40639c0933 | |||
7401673ac1 | |||
41f759dafe | |||
73dcc7bcb1 | |||
d8b1f60581 | |||
16fc58bd16 | |||
68df2f4ce7 | |||
367932de69 | |||
3da08cbcbf | |||
68efc0c42f | |||
f5b01b0ca2 | |||
5733429682 | |||
c6d29fc19b | |||
d9a12d79b0 | |||
963cf4c996 | |||
0ef073669a | |||
ed1123feb0 | |||
c2114bbd4f | |||
fedb1d2590 | |||
eef39b75a6 | |||
eca593ac36 | |||
a1917b8c81 | |||
ec6dba12bd | |||
da0671ad62 | |||
04d83e9a6a | |||
2cb7624953 | |||
e6ace844b6 | |||
f2f6628693 | |||
1c33032721 | |||
b3f5f13c39 | |||
a5339969c9 | |||
1681437206 | |||
2eaf083eee | |||
3645d19135 | |||
3b4b1185e2 | |||
406c5bde11 | |||
75d1913aaf | |||
eb254d9c56 | |||
4e633b8936 | |||
e99a27e382 | |||
c8a6a2653f | |||
0ea0eba4f0 | |||
7f88b56d8b | |||
789421c7a0 | |||
d44503cb19 | |||
0258422527 | |||
47327d840d | |||
d0f1a33744 | |||
c54b8e62d7 | |||
3dc738f28c | |||
ca75400467 | |||
65091c05c9 | |||
52e846f3b6 | |||
ce22b2c29a | |||
361b0284fa | |||
b8947a1c50 | |||
8d1effa0e8 | |||
096a9f4cbf | |||
18712b166f | |||
4605e14729 | |||
a768280d82 | |||
cbb8f25645 | |||
24ff7ff67c | |||
eee0bd6cf4 | |||
f176a5179a | |||
381ba86e3c | |||
f4be8e28ca | |||
e17605f8d9 | |||
e06a488af8 | |||
6dc8bfed8d | |||
f642f23366 | |||
c041410f61 | |||
ff5f13eafe | |||
749b240897 | |||
9207762ade | |||
0a22950ad3 | |||
0fcd404b4f | |||
150ea29a70 | |||
f9baff0e90 | |||
6ad3fcb91d | |||
395ca3630c | |||
e9fbdb660b | |||
526e029ebb | |||
763c4522b7 | |||
32e3ac63ed | |||
90f31aab38 | |||
3d44feaf2c | |||
f5a44245e9 | |||
7e7eb9f39f | |||
6c9b982104 | |||
a106104027 | |||
7753161332 | |||
ec9d592cf1 | |||
31015504f4 | |||
bd40ec527d | |||
3899938b25 | |||
ca7373c28b | |||
390bdfa93d | |||
ac2df87954 | |||
0b73f8b1ef | |||
812fcec9f0 | |||
ec7297f8c2 | |||
0fdb19c07d | |||
3bc07b4753 | |||
0b8b13d0bb | |||
4c5bf9bc8e | |||
9f53109414 | |||
bccb1229c8 | |||
40ab3fe0a6 | |||
4a99118cce | |||
58ba29fa16 | |||
54cfb2acdf | |||
0bf14fd31c | |||
7dd9a0211b | |||
3d43473bf8 | |||
2194c4ba28 | |||
744a4f8f47 | |||
67d91f7b69 | |||
bf5065d16b | |||
3e837f8781 | |||
77d378ccd1 | |||
1a542bae71 | |||
e3ed12b5d2 | |||
759795940b | |||
a23d5ab734 | |||
6ee69ef430 | |||
3edf17d322 | |||
8c2b2f99bc | |||
73dc51b3f6 | |||
9a082d4df1 | |||
f430b6f853 | |||
78a352541a | |||
a0f5633094 | |||
0af81c7d05 | |||
52e82b3548 | |||
f05b99ec1f | |||
194897bf3c | |||
7cf26363c8 | |||
3d1dec4c05 | |||
af1935d2e4 | |||
3c4bc17065 | |||
333d1c1ad9 | |||
4e027cec71 | |||
39ae84301a | |||
3bf14623ad | |||
ac8f2923e5 | |||
e9d3b75e2b | |||
e6bc181e7a | |||
a2ece82197 | |||
259946cf0a | |||
4fdb4f14a8 | |||
914f5e569b | |||
9b4ffd1cd5 | |||
ed87dd89a1 | |||
3b41a78e76 | |||
0fccbbc0ca | |||
067627b51a | |||
09816ed5b6 | |||
b457cdb0c2 | |||
5fd1dec347 | |||
647391ef73 | |||
ed029c52ae | |||
102a372df9 | |||
d4ffb09a8b | |||
6ba052d2af | |||
57b63f43f5 | |||
2fb0969c75 | |||
3357e878a5 | |||
471d5d62d5 | |||
e810b343cf | |||
620be2617a | |||
035038a0b6 | |||
b8ffb87f01 | |||
39e1e11f99 | |||
5f9df78ab0 | |||
a00d11701f | |||
1cf74a5396 | |||
8cd27a199d | |||
772929b5c6 | |||
c4ca3606ad | |||
9e830f1c55 | |||
ee8c71c14a | |||
39bd823651 | |||
a9d16fad34 | |||
1da169319d | |||
2bb1eea2be | |||
9cb45b92e1 | |||
4a8d5098da | |||
d875d5ef74 | |||
fc4e290c49 | |||
ccc198e081 | |||
9f0ed77423 | |||
bb3e616890 | |||
573f3a392a | |||
830a834ea6 | |||
e4ea5d0344 | |||
97aed045e6 | |||
46b01c6134 | |||
e208fa4020 | |||
6b71264482 | |||
5723c184b1 | |||
530daeaa3a | |||
dd1b5c7ea7 | |||
3cffc6890d | |||
04d44f19f5 | |||
d46a742a43 | |||
a94fd24fa9 | |||
8a4c4c346a | |||
dc54299e24 | |||
436253dd63 | |||
29feee0095 | |||
63f3180dff | |||
6b3b98cf57 | |||
1442e2b53e | |||
521ebf0678 | |||
a20874f6a1 | |||
40776bdc8d | |||
f853158e6b | |||
c9035b5df9 | |||
150132f4dd | |||
fb97ac47bc | |||
5b53b90495 | |||
c6513d4450 | |||
ce6848b3c0 | |||
d86d861e4b | |||
3b45fcdb21 | |||
3d1250f2f8 | |||
eaf1ef831a | |||
b4c7992726 | |||
03baa21185 | |||
694de99a3f | |||
8383f4fb7b | |||
eb3fff6c51 | |||
4ff3f0bcba | |||
788ea052fc | |||
08ba805bbd | |||
dbd14c6dac | |||
5977fca6b9 | |||
fcf596d36b | |||
dabca5f09e | |||
018dbce57e | |||
bd45cc2024 | |||
abf47deee3 | |||
ce0090f0ca | |||
6cd34614f6 | |||
264e04368b | |||
43c11c6ac5 | |||
ede42cd6aa | |||
33901dd72f | |||
ac4ae8103e | |||
2c93704b4a | |||
e2158af592 | |||
b06189ff95 | |||
d72c51c8dd | |||
e29e94a5a1 | |||
04d7eccc63 | |||
e58896bdfb | |||
d2bb4487e1 | |||
e8623fbd7d | |||
0a6de2b3ea | |||
23a8e31791 | |||
286d2b4cf6 | |||
1ab07d169d | |||
abf2d4b718 | |||
37045c77cb | |||
fd5d52a709 | |||
abcb21491c | |||
33d2b0984f | |||
fc7a040509 | |||
be09dded20 | |||
6493b9a6b3 | |||
f07fbcc196 | |||
cae90ddd08 | |||
7bd9b21e5a | |||
e29fb68375 | |||
33e0a34916 | |||
fccb2e762d | |||
956b3284d3 | |||
937d0852b3 | |||
cf2967cf34 | |||
c1e64a839d | |||
c06928a7a5 | |||
8c0cafc793 | |||
543ca43e24 | |||
31b86f539b | |||
dff1f4dd52 | |||
276cfed832 | |||
bd61c33097 | |||
e2a9c09597 | |||
5059c6295f | |||
916c1c9de2 | |||
2661700d9a | |||
02502c4ea5 | |||
382e85a8a0 | |||
05d830eb58 | |||
7b149425fa | |||
6f0e1965b6 | |||
970f1466b5 | |||
e422b1d6c1 | |||
8992f47915 | |||
fe65f4d6f8 | |||
8077e421e6 | |||
4f806a921f | |||
8efe0502f3 | |||
0eb61f34f6 | |||
cc7735e284 | |||
e24ddb9106 | |||
67d284fc35 | |||
a9d32fea37 | |||
3374481912 | |||
1972cc25a9 | |||
6a3cd0054b | |||
548dbf4b78 | |||
74af40a352 | |||
8f26c4bd07 | |||
1d51b06d3e | |||
305ee3c12e | |||
90ce89193d | |||
2ad3fa1593 | |||
4bb9949768 | |||
c4f1d2f153 | |||
ed6f82af0f | |||
3ba8cff60f | |||
7307ddd1b8 | |||
0601bedb0b | |||
0c988d95ee | |||
d593362ba8 | |||
f97f4c68ba | |||
6d60d14f4a | |||
d13bc9d420 | |||
2a41e4ce68 | |||
850654dccc | |||
2f9dfed073 | |||
57911c42d1 | |||
6064eda68f | |||
fe803a4588 | |||
2787116171 | |||
0a509cb382 | |||
b25ab941ba | |||
6ef59a5949 | |||
17fc8deb19 | |||
45b5c1c262 | |||
7fd547a2d6 | |||
d19d787f6e | |||
52474e39d9 | |||
d243ee4b4c | |||
2c2f8f5853 | |||
64ba127e7d | |||
9d22a9e664 | |||
96c55db7ca | |||
0f48563e29 | |||
7c014292d2 | |||
19507d1837 | |||
81b2dfd9d0 | |||
3f63b320c4 | |||
4da760d614 | |||
a95ac8ad83 | |||
f841d3d259 | |||
902700e1f4 | |||
bbf456a4aa | |||
134dbd2f1d | |||
d371b093d8 | |||
979c49b99a | |||
dad010a891 | |||
9f974c9401 | |||
fba3ed2244 | |||
aa1d927da6 | |||
292655cbdc | |||
a088c9ca7c | |||
8c1ec43500 | |||
1ac1c6dc9e | |||
87ffd6eef2 | |||
41139cea76 | |||
d98b7275d1 | |||
77cca1ce84 | |||
c0bbbdb7ee | |||
8e6b7aaec0 | |||
aae6820fdc | |||
2a4f35959b | |||
f71708e5c5 | |||
6de00b1f21 | |||
2b27b40142 | |||
55101b1a4a | |||
c014fd7f6d | |||
53a3be0703 | |||
04a178e7da | |||
6d5b6b2ff7 | |||
ab97de2763 | |||
34f7e4d448 | |||
f7c139030f | |||
c967308859 | |||
02207f6cfd | |||
badddcf0de | |||
b6f1c516e2 | |||
932a47a8a7 | |||
f69f78db34 | |||
fc2c465bac | |||
f2a7f8efda | |||
e6c172ac22 | |||
d8e7481118 | |||
2485ef8547 | |||
b17762f8d9 | |||
b3611eef9d | |||
a68c1adba6 | |||
42b536e40b | |||
d6e49be268 | |||
3d10dad780 | |||
c9a727594e | |||
cd25e1283a | |||
6675de1a26 | |||
96618e9517 | |||
dff239141d | |||
abbb329ba6 | |||
5bc77fc6e8 | |||
9c820fcca1 | |||
bb064a1ba7 | |||
a5d9fb518a | |||
01dd46c5ed | |||
ed51989796 | |||
bd20977ebc | |||
11e10f6eff | |||
c88265ac04 | |||
3f655ca50a | |||
359d4dc1b2 | |||
ca47446b46 | |||
cffb2db7ae | |||
0272907596 | |||
321a4b24b9 | |||
8a243ffb57 | |||
c2330fe3af | |||
9876732875 | |||
29e453c201 | |||
6c14402992 | |||
bdee525336 | |||
fbf13efe74 | |||
415df2357c | |||
4fc8800a37 | |||
6a532b836d | |||
31b94fd3ff | |||
fd733e819d | |||
7b6d52c613 | |||
df69559b39 | |||
e388f0d563 | |||
85e7b78b21 | |||
42a97f8c40 | |||
5cff247aa4 | |||
f115691365 | |||
deb66436cb | |||
b8152dd7f9 | |||
678c004a64 | |||
d9f44c1f7d | |||
088160ed32 | |||
8de004d281 | |||
bcde4337ac | |||
401210da44 | |||
beb81b657e | |||
f7b3450d65 | |||
29776c739a | |||
bdf322ceb0 | |||
fae763dbb0 | |||
c0792522a4 | |||
c67e62bac3 | |||
9dc184adff | |||
7118817df7 | |||
287b83b6c6 | |||
5d03eef051 | |||
48f7b06549 | |||
4e111cebbe | |||
fe32332e2a | |||
0bb6e1cdc2 | |||
e67bb64311 | |||
9058536406 | |||
5bc9e1632a | |||
a0a8899801 | |||
aedb4749a2 | |||
f52d49ad00 | |||
a1e7592bd8 | |||
c784d24fa6 | |||
0375c0b2c5 | |||
135e55fe27 | |||
1ed291086a | |||
f6e25627de | |||
8fe79a1fb5 | |||
a1df78517f | |||
92fa8d683a | |||
df27138401 | |||
7c7d40ea44 | |||
aa70be525d | |||
f7ac969a4a | |||
4f5e52fdd4 | |||
5183848250 | |||
69af1baf7a | |||
9a28cbc1e4 | |||
c38b457ba0 | |||
1a50f7062a | |||
8cfd80ba84 | |||
e5e14e1f9e | |||
6611464f73 | |||
51d93f0217 | |||
fdc7981d18 | |||
a63d165dd3 | |||
145a744ce0 | |||
df625a998f | |||
21c1a499ac | |||
9a81cabece | |||
b50dc206eb | |||
9044760a10 | |||
0b811773e1 | |||
627a720d4b | |||
89d45e7775 | |||
c0e6e03dc6 | |||
05fd8e2a38 | |||
dd59748bf0 | |||
38ceaf5253 | |||
b2fba5083b | |||
be6a209fe9 | |||
0f0305c602 | |||
d5350fd719 | |||
ea75f63dfb | |||
de8e530b37 | |||
bff927c6eb | |||
985bb44559 | |||
9c44cae5b8 | |||
6872455922 | |||
ce13a5152b | |||
d7c13cc291 | |||
022c0746c0 | |||
06c3f57f62 | |||
9da27cc56e | |||
92c5497eab | |||
f115fe47fe | |||
224f08279b | |||
a3b660a2c9 | |||
89df50da4e | |||
cce3b3a559 | |||
f53cc10338 | |||
61fb4d584c | |||
b93e2ca4cf | |||
325b3c6271 | |||
859e9ca653 | |||
441e2a69d8 | |||
af937f2e31 | |||
912629c2dc | |||
879fa484f6 | |||
e86103fdcc | |||
920ffa8c24 | |||
488f81ef74 | |||
0a5461cbea | |||
40c934c544 | |||
bb43e2aa03 | |||
ed277357cf | |||
bf1fb8b7bd | |||
e615cba9a5 | |||
8adabe1f74 | |||
896b34d1a2 | |||
5fd1865504 | |||
0b318a19c6 | |||
622d6c0cab | |||
3f4140900d | |||
b434bc93a3 | |||
11529f2795 | |||
d86b030796 | |||
a7e4657752 | |||
2f28fcba05 | |||
6da350aee6 | |||
9585f9a1a6 | |||
fd4876be24 | |||
31e2fe6a4c | |||
aa51968603 | |||
a3336368e5 | |||
33c0c6ff3b | |||
eaf37c828b | |||
d65a8e84f0 | |||
b000d96dd6 | |||
cd867f800e | |||
124f0e7093 | |||
fb897e37d1 | |||
3e5a48e5e4 | |||
3f88a67865 | |||
ef0b546d4c | |||
088f8b8b54 | |||
1ebcafb25d | |||
90396153f4 | |||
0fa93cf615 | |||
15a7a2b0ea | |||
446c254bc8 | |||
e41edc1fb7 | |||
738f776d36 | |||
e77db309b8 | |||
a6c1de1cb2 | |||
ace54f8175 | |||
d7043bcaeb | |||
04bbc764a0 | |||
91f7056767 | |||
8299093bf8 | |||
a0dffcf51f | |||
b3daf7d760 | |||
724e1d33b6 | |||
9deaff9181 | |||
b9ea6e8d96 | |||
231771e16c | |||
5b8308b3d2 | |||
f91f9c4862 | |||
24b848faac | |||
7d0ea614da | |||
2615b067e2 | |||
cd1abb60d7 | |||
436ec0ced7 | |||
937fb85376 | |||
a405324907 | |||
fba55711f2 | |||
208552f0b4 | |||
c7cdb950ce | |||
921169b3ad | |||
d7d3731567 | |||
d5ff5fd6f8 | |||
e195257d2a | |||
466ec7b962 | |||
8bfe59c8a8 | |||
de512216c4 | |||
9936b402a6 | |||
bd2dfaad2e | |||
8e539bebea | |||
022cde2c00 | |||
572f58a3a4 | |||
07e2bdac81 | |||
fb00929ee9 | |||
fb5da15746 | |||
48363aa3b0 | |||
090a7794b5 | |||
c63d8e7a30 | |||
d6ea69a115 | |||
4358f9fd2a | |||
af2ef36d68 | |||
316211372c | |||
29a2d41331 | |||
9f8046324d | |||
af05c34da3 | |||
a4410f3a02 | |||
4503199d25 | |||
c275adbb91 | |||
4061232fe3 | |||
507471e318 | |||
f0f613e2cf | |||
0ec8def0d8 | |||
fbaaed1516 | |||
152c196c65 | |||
e4dc84a5d8 | |||
5fb3b0e0e3 | |||
2854fb5f6c | |||
a9adb2f1a2 | |||
51383afd50 | |||
10bdde4459 | |||
4b02ecd6e8 | |||
b84638ac5a | |||
79f2882aaf | |||
1c978f64b1 | |||
5e9496ef36 | |||
8b6268966e | |||
52434819c3 | |||
29eb87b7ef | |||
c38026886a | |||
63e330b83d | |||
164da0fd9f | |||
e30b1de100 | |||
6940ad3fd9 | |||
8ae15141f6 | |||
89007923c7 | |||
4c10b9844b | |||
f3d69599aa | |||
fc8f91baec | |||
853bf3065a | |||
8d712c81d4 | |||
aa0597da2a | |||
a29f33020d | |||
9eb441ac44 | |||
10e7c96379 | |||
6c474daacd | |||
bb7ed73743 | |||
ea749d69a3 | |||
5bc0dfd616 | |||
fef34dfe82 | |||
a3dd5c1e92 | |||
d873d653d0 | |||
d9934ad8db | |||
26e5364aea | |||
91451111a2 | |||
b104bec49d | |||
0ac33b64b1 | |||
82141cec6e | |||
218313428f | |||
44b47b49bc | |||
2f69317f5d | |||
e1eff7b744 | |||
3aa12281c3 | |||
72920130c0 | |||
0fd00331e1 | |||
7a4763ee68 | |||
647a78b791 | |||
82faa91ce3 | |||
ad664dfb9f | |||
005ac9e732 | |||
d8e3fe542d | |||
c40e4f6c5a | |||
9bb3195bca | |||
eee0bc4985 | |||
a24d670f54 | |||
51e049ab78 | |||
01e37dfab8 | |||
74087edebb | |||
db58c9b77f | |||
c4dad1c20b | |||
cae04656b9 | |||
6586c4e387 | |||
4e60a81b36 | |||
464b4b18a4 | |||
e5c0969047 | |||
ab0d144300 | |||
ac3823e10a | |||
d3a4126e27 | |||
3a62acc54d | |||
bf8268adc4 | |||
7d4f25b354 | |||
0f2d480036 | |||
043e3784e8 | |||
58ab06b4f8 | |||
11544fe8ef | |||
8776a45ee9 | |||
a7bb059096 | |||
ba8977b47f | |||
032a6adaab | |||
460fbb18c7 | |||
978ac50015 | |||
b323b9c843 | |||
1afcf34829 | |||
48d9ad00e1 | |||
ca10356fd9 | |||
275bd44e15 | |||
beb2d880ea | |||
61d2107e9c | |||
b06f1c81bc | |||
8bb83782c7 | |||
aa05458f1d | |||
c694160c9c | |||
5b24a8f21d | |||
2c23c42c98 | |||
edcadb7dd1 | |||
3bce3502d2 | |||
9942227c6c | |||
02b5c3da71 | |||
ef533b4c87 | |||
3ecc883944 | |||
a1fadce7c6 | |||
79bc1290ae | |||
10272ef395 | |||
f03c49850b | |||
6677c10173 | |||
3223a3ac54 | |||
497fe1e68a | |||
3b334c4230 | |||
7f115f2e83 | |||
12aa04be93 | |||
a7ece4fdf3 | |||
57d1ed1073 | |||
b04ebb1782 | |||
958dbfdfa5 | |||
eb724336f5 | |||
9cdd4bee97 | |||
74cc77400c | |||
247a39c0a9 | |||
1b0c13a417 | |||
abb2e231f6 | |||
50ef4cc5da | |||
34bf9b729e | |||
0a6f607e22 | |||
9e4c61c139 | |||
144418434b | |||
a20ad68fe3 | |||
a50a3df716 | |||
f29124773b | |||
c1235897df | |||
4a52869d23 | |||
f9098b5379 | |||
6a95ff56df | |||
340829bb71 | |||
8aa48effaa | |||
a5fadcda15 | |||
f515674dff | |||
72ef5d8f8d | |||
a0a077eaaa | |||
8feb4365dd | |||
94b2dd74de | |||
678597cc96 | |||
417d82de58 | |||
f3adff1da1 | |||
0473c36c6f | |||
c9cb75aee1 | |||
36011e124a | |||
b9420040f5 | |||
59b925a028 | |||
7af075633b | |||
09891bb0ad | |||
160ebe01d9 | |||
a096e6b337 | |||
3c41a5e910 | |||
a3e39987d4 | |||
47f5ea881f | |||
faf8f1fbbc | |||
9f9de27a57 | |||
9a3ec56eb4 | |||
4f03ee814a | |||
6f84815801 | |||
9588a582ce | |||
ed8e549ecb | |||
c79ebd4eeb | |||
2a85bb28b9 | |||
13ea1342fb | |||
c707d4bfd8 | |||
aeacdad484 | |||
6d9bec3e0b | |||
1b5554eeda | |||
bf140be75e | |||
eb4c7c6841 | |||
dcd3c709fe | |||
f966187ea4 | |||
a288c0f280 | |||
a746d4cc3a | |||
f1cca207fc | |||
f4bb9b604a | |||
7be6ee9a68 | |||
a22c79c58a | |||
b642e019e8 | |||
578bab5fdd | |||
b48f08e65c | |||
81c14ba610 | |||
4b84fb5ac5 | |||
7f5e650796 | |||
c22e2e8159 | |||
ee8a53188c | |||
98f86a44ef | |||
1b3169e0d0 | |||
8273a396c8 | |||
5bad914411 | |||
879d260202 | |||
ce4d75f62a | |||
f30622424a | |||
09e7d56ff2 | |||
5ca23b5363 | |||
f7e70d25ea | |||
4338c41112 | |||
01f9b25be2 | |||
8b0458cdf6 | |||
bed978a26a | |||
73fbc81067 | |||
0d7f84857c | |||
42d6815ece | |||
d1db9fb659 | |||
b74fb2ef5e | |||
f3e228e8a4 | |||
6b5742c1ff | |||
57595988f5 | |||
81418a7712 | |||
885c7bbb10 | |||
d4c25359bd | |||
44f406b4b9 | |||
427d2fed8c | |||
51d454cded | |||
ab2bdfc508 | |||
3892b93bca | |||
83c2e907c7 | |||
32b7cc68b9 | |||
a61e3cd689 | |||
f922598127 | |||
e11c289150 | |||
1b37c61b5a | |||
2d3bfa9a89 | |||
e414b9edf1 | |||
62d3fc65e0 | |||
262ad45b79 | |||
cd90702fe5 | |||
012b1b56aa | |||
ff999a6dda | |||
797553ce16 | |||
8f82c8ad3d | |||
7a2c132b8e | |||
ba9f6fef99 | |||
6633a96245 | |||
745f8d32b5 | |||
f715478070 | |||
5f2aaeac57 | |||
044a4f7575 | |||
83d5e458ca | |||
f7669b6797 | |||
eb56567812 | |||
489f981e40 | |||
19adbeebd5 | |||
dc93368e03 | |||
8d3166c5fe | |||
07caea8b4e | |||
141b7ac554 | |||
4bc5f1401f | |||
13a2d3dfdd | |||
d62f0de862 | |||
0073ddf237 | |||
e411f54236 | |||
6025b44e5b | |||
f4f427dd2a | |||
ea226a1697 | |||
26c5c9c839 | |||
5cddb269d6 | |||
0d5099f230 | |||
b55814a1c0 | |||
eb5382e0de | |||
39d509a756 | |||
b3f1714ba9 | |||
708525ef9d | |||
df14e6d43e | |||
faedd325be | |||
600fbb2ef8 | |||
b4aedb5f84 | |||
ed7ebf2da1 | |||
dd1e6402c9 | |||
78689e7443 | |||
aa57b1bc77 | |||
491d476cac | |||
f0053a2f78 | |||
10e7a3b35b | |||
4147fd6b19 | |||
2bb903088e | |||
c90f985fcc | |||
2ebaacfc89 | |||
c339bd49d0 | |||
c349fb0e37 | |||
bc825bdefa | |||
ed49ce8e1d | |||
ad2ecd538d | |||
ff8e3f0af4 | |||
698e17178a | |||
ebeee70931 | |||
b8b118bdeb | |||
5ddd7d1b14 | |||
450b23436f | |||
89793ac338 | |||
c456812a46 | |||
6f8f6e9233 | |||
5770d00f81 | |||
a0fb1eff4a | |||
89dc240a22 | |||
ee4f069341 | |||
011bb9f5b1 | |||
08b06e1b4e | |||
0416a2ff15 | |||
3a7cdfcaa4 | |||
c36a47576d | |||
f14af7cf83 | |||
4014c48c62 | |||
6c9135c093 | |||
19993199db | |||
5b9f362925 | |||
80ea9001b3 | |||
24bb94ceac | |||
0f16351f5f | |||
b60b26bbd0 | |||
86e53e08de | |||
d3cb10a74e | |||
7d6cfff719 | |||
cc0fe0a1a9 | |||
25327342fb | |||
e02cf67f85 | |||
bf4bef6d62 | |||
76645bce6e | |||
9276c491bc | |||
fa59b4f8a7 | |||
934a37c36b | |||
5362f62078 | |||
ccd360687e | |||
5a2e8a838c | |||
3abae1cc75 | |||
b68ef8c983 | |||
d5f5ba95bb | |||
e8638cb0b3 | |||
62f9071adc | |||
1d079dd9a4 | |||
cccb56bda1 | |||
5d8dc241d8 | |||
9ba7312caf | |||
8ebda219c4 | |||
47f14e8555 | |||
974a24d03b | |||
15f225537e | |||
a32572fc96 | |||
be3ed9b6af | |||
a0939e1c48 | |||
003dca9d45 | |||
5c1770247c | |||
021dde66eb | |||
5840a3e1e2 | |||
7c6478fe6b | |||
68aca55e6f | |||
ba674935f4 | |||
a053d55fbc | |||
38ba8852a3 | |||
3533359fae | |||
0a988d1c69 | |||
5f9e65cc9b | |||
026188268d | |||
0e3464457c | |||
56195434e7 | |||
ba2194f435 | |||
e7df172da1 | |||
e7606e6dca | |||
8d4c0f505c | |||
8f2878a841 | |||
77296348a0 | |||
a62a7d5330 | |||
bf60aae9d8 | |||
ecc1520100 | |||
f1f6a2b341 | |||
55bf1c31a6 | |||
e47dd3d587 | |||
af0e3a278f | |||
493ad93957 | |||
dbe8f3cfbe | |||
08cdac968d | |||
f12d5ba689 | |||
0afd77d110 | |||
7551941ef2 | |||
9ca0307e1c | |||
9a6f8be28c | |||
9baf3b5a09 | |||
ca3f0873f3 | |||
adb0201449 | |||
cf293642fb | |||
10e1106760 | |||
3f2d375a53 | |||
f8e121ee06 | |||
0ee005579b | |||
6ecd7fced8 | |||
aeaf4d78f8 | |||
7baf0ddcc2 | |||
d79e141fe5 | |||
030071e659 | |||
9cbf226cfd | |||
36aabf23e1 | |||
8b67255186 | |||
3186661420 | |||
46896d9e86 | |||
2c4fd340c8 | |||
ae6d052978 | |||
974891a085 | |||
d44cd16682 | |||
23e99a3ed8 | |||
024a457250 | |||
788cb843fc | |||
790e0908a3 | |||
7a45cd5b56 | |||
f61a8ce51d | |||
fcce29a467 | |||
5f568733f3 | |||
96340de17d | |||
3611f67fb4 | |||
353ccbd444 | |||
3c1179d27b | |||
e502caee9f | |||
da8b870670 | |||
6b26859983 | |||
7afd224aff | |||
62e7bead73 | |||
116f7d1c4a | |||
18f89cc341 | |||
7c99ae1b3b | |||
16dc4d298d | |||
762c378bd6 | |||
515289134e | |||
3d1afe7cf2 | |||
fd825b1049 | |||
136e90638a | |||
9bf071132d | |||
014bb2f426 | |||
56927927c8 | |||
b19a4d2977 | |||
f4b838d8e2 | |||
c6cfd24f19 | |||
10f36f40d6 | |||
9d5cf9163a | |||
9abce0cca3 | |||
c6245f4fa3 | |||
75fc160204 | |||
263198dd89 | |||
345f96055d | |||
51144aa45e | |||
86a599d13f | |||
0cf81e6f7a | |||
8874fe973c | |||
f8a03226ee | |||
32db1e3045 | |||
303e6c0102 | |||
18883f1ba3 | |||
5c31271e91 | |||
00981cf4e8 | |||
968f4a69e8 | |||
e7e1a9bf50 | |||
fe1becb001 | |||
7789171c71 | |||
3fd2222c99 | |||
6de36a88c0 | |||
b37685542d | |||
a2b1b9e746 | |||
8017324033 | |||
7464497c88 | |||
499def3daa | |||
6931b75cc5 | |||
f853610578 | |||
69f51b88bf | |||
e0d680201a | |||
1566b8f8b8 | |||
4bbf78e840 | |||
7ab16a69df | |||
95e60ed775 | |||
d38cd2547a | |||
2159b72e69 | |||
81c23bbf9d | |||
0d5b8edf31 | |||
fcdb80830b | |||
50b48ab25c | |||
31b45666b0 | |||
233e76724a | |||
af637a82c3 | |||
ea32ea11f2 | |||
1b7a0de745 | |||
50e0cb65d9 | |||
ba4807f62c | |||
5efc02a238 | |||
8e50ac67bc | |||
a3c03e8ceb | |||
5a3e30b30a | |||
e3ab90042d | |||
f35c15f7d2 | |||
32387cd034 | |||
cf5c816483 | |||
bf9b9ca54c | |||
0ca2ca33c2 | |||
51f25e96e9 | |||
1875047638 | |||
fa4d61eaf0 | |||
49eb638e15 | |||
fc1f290b85 | |||
9194dc0161 | |||
0d480dbf7c | |||
183e83684a | |||
7b4ac7998a | |||
d75c6b0c36 | |||
40b222f8bc | |||
aa7dfb7bee | |||
6c1453eb54 | |||
c1845aec83 | |||
eb8479ac9a | |||
636c027298 | |||
02e187f066 | |||
854112095b | |||
a71c805959 | |||
c3ced0d089 | |||
80996ea63e | |||
aff51f8af1 | |||
ccbb81e9f5 | |||
f88dd28c51 | |||
a65a71df5d | |||
22f2ecc433 | |||
7f90ad7847 | |||
1292c0ecea | |||
55b7d5025b | |||
6a310bbaa9 | |||
bc8753da85 | |||
7f63e318f1 | |||
6c749319cf | |||
7a4463e104 | |||
e1be4ba925 | |||
34d21c1de3 | |||
fae36aebf4 | |||
75e828923a | |||
b499b87f8c | |||
233dbec4b3 | |||
d56ff9592e | |||
08f6317beb | |||
a75457ad88 | |||
b0482003bd | |||
634356e72f | |||
6d3cc16ab1 | |||
6027671c09 | |||
29d0cb4a15 | |||
fe7001975a | |||
ac88f1c146 | |||
b5b86218c5 | |||
bdcc6e52e6 | |||
0eae817aa6 | |||
8994b42760 | |||
6a63ce992a | |||
9ae6285eef | |||
8f9737f567 | |||
f287d313c3 | |||
e745836404 | |||
08baf798aa | |||
8bcb14c65d | |||
d94dc68830 | |||
297fed6aef | |||
d690d6e0e3 | |||
9ba8d88b07 | |||
34a40b0131 | |||
182bf5f2bd | |||
04638535d8 | |||
d87c8428fe | |||
166fb9a8e4 | |||
28a21d0b8f | |||
d1d1d60c30 | |||
80fd49d60b | |||
34eb1331a3 | |||
bff329a329 | |||
604929d002 | |||
4a9151e4aa | |||
020cc89576 | |||
25898d34ca | |||
6394388714 | |||
d4101c7bdf | |||
c93bf89cbe | |||
88d1f29fe2 | |||
c437a33f2a | |||
e3259f39f1 | |||
cb357b0a16 | |||
a7faf445c4 | |||
82a08f24c0 | |||
afa89ac125 | |||
2060b5cd34 | |||
d69730a333 | |||
9714a30148 | |||
23c0f2c313 | |||
5c4139be45 | |||
6b1a3a20e5 | |||
4ae00c80ca | |||
827792c4f0 | |||
abbe700dac | |||
1347bfe243 | |||
a76ee95b6d | |||
f3689f09cd | |||
d545cfd38c | |||
3631a9fac2 | |||
aee4ad2d3f | |||
f88c86c799 | |||
60ac27e401 | |||
d0567de4e6 | |||
ca30fd6088 | |||
1470e9d5ca | |||
f45efe2aa8 | |||
5b6c475817 | |||
4abd2d709f | |||
d97aff85b3 | |||
deec65446f | |||
5c662b1ae1 | |||
f648940388 | |||
5aae17754f | |||
0ef0f6ece1 | |||
bfd46f28e0 | |||
eaece18afc | |||
67d39b037c | |||
dd3f5a146d | |||
9fdc5b4b9d | |||
1f32d13698 | |||
886b1019ed | |||
3e8ed8a171 | |||
8307daee63 | |||
9b40d10352 | |||
f2a06eab37 | |||
74fd70416f | |||
b85c164195 | |||
75c41b645a | |||
54c8b3ef29 | |||
56bde40035 | |||
ff4a015baa | |||
0db4fcc27e | |||
f3080b6277 | |||
69cbbd5811 | |||
0b85760939 | |||
03f3a4805f | |||
d95adf2631 | |||
e971d40e06 | |||
c65a01a5f0 | |||
8586014e17 | |||
bdfae4ba04 | |||
75cb94b51a | |||
2f6d163a7a | |||
ecfe72bcad | |||
e6ff9e18cc | |||
3c550c1781 | |||
537693f5cf | |||
5ae0589547 | |||
71fc6fc257 | |||
c0d7b16ee6 | |||
f3f7aa9e1d | |||
43355970db | |||
bfa386acba | |||
e8b432485d | |||
a12a34e3bb | |||
b79855c01d | |||
17fe501a6d | |||
8201b367ec | |||
6c242084ca | |||
aefe7b176a | |||
6059b85e58 | |||
aa46c52eee | |||
d3cbfbdb59 | |||
cc9b77b876 | |||
1568ac9e8a | |||
1129dacdfa | |||
fab7967018 | |||
bb40a4d6b8 | |||
90d27147e6 | |||
634247c590 | |||
fd8f7ea693 | |||
5eeb497f2b | |||
505e642691 | |||
74a7e2a17e | |||
5fec956ce6 | |||
1794782323 | |||
0210ee8747 | |||
ca412832ef | |||
1089c25b8f | |||
e85841784c | |||
ca2236958a | |||
c7686323b7 | |||
73d1a1a05e | |||
211f7b591b | |||
72ea256906 | |||
f521622d4d | |||
dc5283ce9a | |||
256a4197c9 | |||
a5a12f8b3a | |||
bbe180ecd1 | |||
67678cd49e | |||
097d4fe34c | |||
5914346ace | |||
062788f222 | |||
55be9b9ca5 | |||
0da2f91771 | |||
ff190e02d4 | |||
29fd5747eb | |||
fa8f5bc0d8 | |||
2118434823 | |||
2eeac0bf8b | |||
89b293fecd | |||
3f758d5981 | |||
e838bb43d2 | |||
b7b83305b2 | |||
8df3080e0d | |||
f88794c752 | |||
cc9e2cee1f | |||
91cb892c74 | |||
a26f908370 | |||
4d14f56fa8 | |||
d9a2255be9 | |||
5e3d71c6c5 | |||
619d94bf36 | |||
6069659e0f | |||
f6a79bde6f | |||
bb9e230b35 | |||
bc9417e16b | |||
a4313d388d | |||
4ebb3a894d | |||
0642889b64 | |||
3094d084d6 | |||
f9fec74ffd | |||
8ef3ab0d49 | |||
e9a6f8ef46 | |||
68724752f8 | |||
de8fa09366 | |||
e619870eec | |||
4be5f0dab3 | |||
abe1929b49 | |||
68c4116327 | |||
3be9881997 | |||
2e44f29882 | |||
a5520c1936 | |||
112cdd54e3 | |||
b512c67b5d | |||
d8fa7bc9d2 | |||
41397ab41d | |||
c437f1473c | |||
6020cd011d | |||
582bb3e2ca | |||
5c67161dce | |||
c00eaae62b | |||
8e4dd030d0 | |||
4d7b188999 | |||
6de260d73f | |||
2230ad59f1 | |||
e22b1661f4 | |||
bb723076ee | |||
fd9c24413d | |||
91c58640a7 | |||
c8e3375248 | |||
aeef8c02d8 | |||
08f2cd2472 | |||
fe413d52d6 | |||
add2ca0b8f | |||
ad6cdc9017 | |||
dd8cab4562 | |||
f7c791d153 | |||
b66f06d9dc | |||
89940677cc | |||
56e7a1e2a0 | |||
810f5ad531 | |||
495f5d03ac | |||
f63d2cebfc | |||
ae60e8cbd5 | |||
772bef05fc | |||
9320d1f7a4 | |||
e8912c5dc9 | |||
2b73a9d2a4 | |||
e25ce768bb | |||
05c628b33c | |||
fda0aa3ce1 | |||
cdf5bbadea | |||
c6b89a826c | |||
6264e56148 | |||
7f8bfd759f | |||
c3c2013944 | |||
1e4a01399d | |||
03967b67cf | |||
7425478a55 | |||
f807447de1 | |||
706163e7a6 | |||
a4c145c1ef | |||
c445ea90ba | |||
52c50398b8 | |||
fb89f77db7 | |||
f7b94179a4 | |||
e045ca8538 | |||
871e17c2f5 | |||
f86c3c81bf | |||
71ab6d38e4 | |||
e76fb7a524 | |||
90a99dde1f | |||
7b9f5d0e9f | |||
e4d4dbbeb6 | |||
6ef94fb59b | |||
a03dceff7d | |||
f717c57648 | |||
ca8fdad422 | |||
faa61923fb | |||
96a39f5c54 | |||
72f8c4d5e2 | |||
07cae4d684 | |||
dd56d7c0bb | |||
77d986f213 | |||
13bcefe5cd | |||
49d0e06704 | |||
2c8790c545 | |||
d0260acd3d | |||
44ec6184c8 | |||
cfa9729831 | |||
c25af3d5ad | |||
d3e9200a7f | |||
2032ba3ba3 | |||
9bcde69ee0 | |||
beca2b429c | |||
3a1699f0b3 | |||
a7192e866f | |||
dc882b4dce | |||
77b4de3941 | |||
006d17aac7 | |||
1a3a1db4ff | |||
97fa659283 | |||
f1d84ccb49 | |||
1f14240251 | |||
ea6fed6ecf | |||
d09eca7833 | |||
2c6f64c5ae | |||
ec87e4359b | |||
2b63bae989 | |||
82f4e3157a | |||
725ceab00b | |||
ba428c401d | |||
510669ee2c | |||
8d749df290 | |||
091c0c0c71 | |||
7fdd2cacd7 | |||
2241a0b2de | |||
d21a93123b | |||
e542a8d8e2 | |||
94ee4e7fb5 | |||
e3d430eb5e | |||
fd76255cf6 | |||
d180631877 | |||
1977e21363 | |||
e1a3ee1b81 | |||
cc43d9daed | |||
79705df499 | |||
36bbb906c1 | |||
816cc17ed3 | |||
97e3b5d2ab | |||
79ab9d80f2 | |||
32511149d1 | |||
cc9fd53abb | |||
4061c7450b | |||
9ad535bde6 | |||
b067096fc7 | |||
2dd58e5f7d | |||
7c42ab885b | |||
26b283d44d | |||
8c1b07c4ba | |||
f98e0858a7 | |||
8b60d5bfcb | |||
30b4c6e755 | |||
3d2a98451b | |||
aba528b227 | |||
d971768056 | |||
2e39be6625 | |||
f514d466a6 | |||
d10bf45283 | |||
a0064a1699 | |||
907472403d | |||
a9b6db9ee9 | |||
3e1dc9f400 | |||
d30c019b89 | |||
86b8712dd1 | |||
44241e03da | |||
12dcc2c31f | |||
bb89b72a81 | |||
ea790faeb3 | |||
4ef7b16925 | |||
93e244b4c4 | |||
87281d34c1 | |||
20041701cd | |||
f9c5379400 | |||
2a531f1a1e | |||
4d4b9c0d6d | |||
dc592e92b5 | |||
0db1a3167d | |||
830f792824 | |||
a13ebc3975 | |||
b28ef61618 | |||
6f297161de | |||
59c626b4a8 | |||
1d014a5a94 | |||
2dc8159d96 | |||
453f742732 | |||
5e6cf9fb02 | |||
83349fc72d | |||
979a5c8c16 | |||
9f625835ec | |||
5fd379e71b | |||
9c5b497751 | |||
4dc5f3e7d9 | |||
13954ffe01 | |||
36d4e1f7ef | |||
b716a2f8ac | |||
f98095e6cb | |||
d183aca810 | |||
52f4bddbce | |||
b837424f29 | |||
ba2a8c82f8 | |||
2856d9d6a3 | |||
71fac76e3d | |||
125f1ae34c | |||
b418169c20 | |||
f4d12ba622 | |||
c64d8c8b6b | |||
10a1ba95d6 | |||
27d3daf918 | |||
dcbd72e64d | |||
52e1e93f9d | |||
7d3d0999f3 | |||
93f90b5a62 | |||
c2b113ac0a | |||
8ff8ab4f27 | |||
414b8c9f21 | |||
4975787afa | |||
1210691fdd | |||
2a4527a8d6 | |||
2991906a85 | |||
5b1f4f189b | |||
d77a1e6925 | |||
19c713ebb2 | |||
90e0e0b72a | |||
22bbcaeed0 | |||
d7b8015df7 | |||
c1ac47e1ce | |||
e375101132 | |||
05b14bae7b | |||
eb15fe3898 | |||
4f5518bdd8 | |||
c9e1e6e020 | |||
ade73e6892 | |||
ee2aae7e3a | |||
b6011d4cf5 | |||
a31c6ff875 | |||
69baaac27e | |||
b16a90e9d9 | |||
f31aa622c0 | |||
4578edf157 | |||
33df35db1b | |||
093ddd776b | |||
da10b27219 | |||
5b4ed6f926 | |||
8fc467652d | |||
7971b64d57 | |||
9c1e2c3c45 | |||
909917e133 | |||
3b6c37a30b | |||
4a6e2a5d99 | |||
6cf84256fe | |||
876831480a | |||
aebc9a3b9e | |||
7b28614c37 | |||
4524c705da | |||
1f70be688a | |||
500eedaab7 | |||
2d2ff0a29d | |||
6d0689fe6c | |||
0b3dda18d3 | |||
09a8a494a0 | |||
11ac4df5d7 | |||
d352405ba6 | |||
a81609fd2c | |||
bf05952582 | |||
596a24fce8 | |||
9f20e40257 | |||
8be67a4431 | |||
58a2f7a874 | |||
cb92143613 | |||
08e26aa30d | |||
8e3ffe87b8 | |||
20e2bf9682 | |||
8512f97386 | |||
3ce880bc62 | |||
72ae243fa2 | |||
91829b0e7d | |||
7c3cd10696 | |||
24bdee626f | |||
6a30a75e3e | |||
ccdc336112 | |||
a4b71f4d11 | |||
c3f61e86b7 | |||
d8d93ee344 | |||
8ffff44454 | |||
568b90d0b4 | |||
46e09d174b | |||
1698a85e99 | |||
c9b62209c2 | |||
b280d6a76b | |||
29993e6412 | |||
2a5edf4547 | |||
d58c517a6c | |||
50136c319f | |||
2fb3b50535 | |||
4171e87b4b | |||
60b3036037 | |||
dfb2487640 | |||
97454ca162 | |||
4200409f79 | |||
b6a06189fb | |||
be521804c8 | |||
e95fcf6172 | |||
fbd2235a51 | |||
31b1b83606 | |||
a5d4f63281 | |||
328f9a70d3 | |||
df2b1dbeb1 | |||
f768393a4b | |||
c0a0d60c87 | |||
9cf5a4cac0 | |||
f21a030cf8 | |||
74e3d387eb | |||
8f83f497d5 | |||
6999fa858e | |||
8c1bedf796 | |||
1090c04fe3 | |||
33b04427d5 | |||
f7bb356abd | |||
e16bf0698e | |||
e6190683dd | |||
e08e41ae0d | |||
5f1a89df63 | |||
f15df40a54 | |||
a32e0e4ec5 | |||
3e8ac6b2d0 | |||
50a773f456 | |||
42484d718a | |||
81887000a8 | |||
987473df44 | |||
3680eb0bf5 | |||
3dbdc495e7 | |||
466515c801 | |||
e198f7e671 | |||
5fe1799dab | |||
ce7118084a | |||
06786322ca | |||
130b7501d1 | |||
864f001c3e | |||
1553ce973f | |||
72811e59f5 | |||
4c1da3575b | |||
05c0516a57 | |||
fe6dff9086 | |||
b6df5e6ee6 | |||
3ee5774870 | |||
c8fbb96f49 | |||
143303f7df | |||
585f7ec17d | |||
9beeca652f | |||
cd92569355 | |||
a82e1d0e45 | |||
5ad06df4ac | |||
5cfd5da338 | |||
b1d7167112 | |||
a475ecec4d | |||
5c98e020f4 | |||
eed295587d | |||
237af4b07d | |||
658860fdff | |||
21ba371a32 | |||
589160242e | |||
4de8b6e9a8 | |||
e79d536f33 | |||
9e90096328 | |||
f0a382c21a | |||
682a2c7546 | |||
2d1e85f280 | |||
dbec4fc15e | |||
95cd77e749 | |||
1f8126e2af | |||
86db7497e9 | |||
172305fc6a | |||
dad9dcd742 | |||
59b90a94d0 | |||
93fc5944f3 | |||
7039216eae | |||
7ba898f701 | |||
11262f86f9 | |||
d1db2d60ec | |||
156e43290e | |||
6687c80b2d | |||
1fbec7bf3d | |||
6196480d1d | |||
2723aeeb5c | |||
728ab18017 | |||
0714fdc7e6 | |||
7bfdfee27b | |||
10ec3a9b0c | |||
eec728f162 | |||
1ac8ef5341 | |||
18cdddf433 | |||
02a697031f | |||
6beff242b0 | |||
46cc078e93 | |||
9aa6da0642 | |||
8bd20c39aa | |||
efd36388b0 | |||
79bb207a8d | |||
0fe350af9a | |||
4e784cd7c3 | |||
12d6919421 | |||
79ec4faddb | |||
6603c0b990 | |||
b5dbdd1774 | |||
a08cea9df8 | |||
5d9c817461 | |||
f95c9a12c9 | |||
015257fe75 | |||
dd5692bb2d | |||
bc3d5dc863 | |||
a4b6003e58 | |||
8086d1db46 | |||
26f4f53ec2 | |||
6af78418a4 | |||
f629db3c10 | |||
af0cf9e52d | |||
e885469504 | |||
ca7e5260f0 | |||
dba64f849b | |||
02e43bafd6 | |||
637dda2e22 | |||
575eaee1d2 | |||
48a1e8f74c | |||
4d65038ad3 | |||
6e8a41f898 | |||
8da11dbdb9 | |||
70fabf6a6b | |||
43fafbc747 | |||
88e64c878b | |||
0ad9e4af0b | |||
a6df745daa | |||
c64bd81339 | |||
c31f0b4bb3 | |||
136136d055 | |||
c20d86e5c0 | |||
7ca99f749b | |||
7cc4405c09 | |||
62d5deaa6f | |||
7f5879ed6f | |||
a0f7761a37 | |||
ed77c60283 | |||
8f144316a6 | |||
e73eed4a9b | |||
9de3da33aa | |||
0de214c3b5 | |||
1226023dc2 | |||
5e24054a0b | |||
af2b886599 | |||
1d1e5f1f99 | |||
49628e9cf5 | |||
47bc1f7a9f | |||
f9783407bd | |||
74ffa14304 | |||
e881488bcc | |||
97ee7b81af | |||
ff6eefe1c4 | |||
9f546d13c2 | |||
2e6fc70353 | |||
270cacb1d7 | |||
e2ecf0ce5f | |||
5d396bfb7c | |||
de6cc8394e | |||
4b7159648a | |||
eb9c5f95db | |||
55e9d2880c | |||
ec9c19ce7d | |||
31731e8f26 | |||
bfb12bc7c1 | |||
4befcf3819 | |||
cb58145361 | |||
b83efd90a8 | |||
9f0da3f1d6 | |||
50ae08ed8d | |||
5385642a5b | |||
0a27d4e185 | |||
bd8b9febd2 | |||
a30705f197 | |||
877032a757 | |||
19bf47b6d2 | |||
a9bfeb058b | |||
9213fc6999 | |||
447dfd1e3c | |||
638d3a32cf | |||
17c59657c3 | |||
81bce8ef76 | |||
78314077bb | |||
a7840bc247 | |||
6d0254c5e5 | |||
06681a3db7 | |||
55de2b7d97 | |||
065ada3d17 | |||
0ee2bf5254 | |||
0fe0088ff0 | |||
492a24ec17 | |||
17a6ea973e | |||
deaba48431 | |||
eb662f1234 | |||
5ecdecea98 | |||
b4277faf90 | |||
09902566ad | |||
dc80a5ffbd | |||
b1b97c19d4 | |||
46a0820e5c | |||
6cbdbb5be3 | |||
e753539c6d | |||
b8d1a88623 | |||
b84635ffec | |||
ed2fd00603 | |||
af20c613a4 | |||
b27669ee32 | |||
81d39ea272 | |||
840437580f | |||
936ede9aba | |||
2fa5d0cbaf | |||
e28f69cddf | |||
11f6c44442 | |||
8c9db2db61 | |||
7e7d27505a | |||
b4211ddc0c | |||
ff906e8ee7 | |||
c1ebccd0f4 | |||
9c223b48a3 | |||
16037dd9bd | |||
742e8c9f50 | |||
194cdf3b5f | |||
5fbeeade94 | |||
72f029b57f | |||
67c4781376 | |||
fe49286d97 | |||
4196a0f585 | |||
a6a9b13545 | |||
fa8d0946e9 | |||
1844b8c5a2 | |||
7c503648ff | |||
a598ebf72f | |||
d8ac35d259 | |||
5029e4a28c | |||
908e60dea4 | |||
d577bf300c | |||
579a606f93 | |||
ac15c0c57e | |||
0c0372dc51 | |||
723c0e99a5 | |||
e04596c668 | |||
a809b05808 | |||
da44dc3fb5 | |||
06c63f1207 | |||
c3425346b7 | |||
fe8e4a4f54 | |||
abab778e2e | |||
03ecb2fe13 | |||
a78f89d4eb | |||
499c2213ee | |||
abbc7b572a | |||
bf1fdda651 | |||
218ea7267d | |||
70cf085df9 | |||
71783657af | |||
137c21e6c9 | |||
bc473055b9 | |||
3d67607768 | |||
ce271649ac | |||
0078cb88c3 | |||
19cb548e18 | |||
b3cf7dbc14 | |||
bbfe0a0cd1 | |||
92b3f90380 | |||
b09345f2e1 | |||
0d41c60a38 | |||
5132f4850f | |||
53fae2939a | |||
14f248546a | |||
0d519b3d16 | |||
8e0a9d6d66 | |||
b8bc3476f4 | |||
6b326cfb79 | |||
aaef738dda | |||
35748fc1f3 | |||
a122d817e8 | |||
4ccce424de | |||
396a79899e | |||
de53681d2b | |||
aacd42b9f6 | |||
5ef5f9b45f | |||
98d420d5aa | |||
5b75818fc5 | |||
f49577bc77 | |||
a87c65872c | |||
ed636d5e2f | |||
2ced70652b | |||
dc742f4b8d | |||
689f2e7fbf | |||
ba1dca1826 | |||
a07e4c69b6 | |||
05adde552d | |||
0ddbfd1036 | |||
8b45df37d2 | |||
70e557575f | |||
bcbd541d48 | |||
6383a745ff | |||
b89d6644d8 | |||
8fbef4b4bb | |||
d9f5a97d56 | |||
e4ee03cb61 | |||
c2a65c71e1 | |||
8c456a2da4 | |||
f856386bf7 | |||
e1448eaeda | |||
3cf61f2f93 | |||
ff61282104 | |||
b2fe9d7d4d | |||
16a5e6c01a | |||
306b1d74bb | |||
a7e652f1f7 | |||
e1a3ab2726 | |||
ae9c412b6d | |||
fad0027e17 | |||
1d7f012fd1 | |||
ee4bf163ef | |||
fabdba4452 | |||
1a14fc5c48 | |||
fa5b64ce2e | |||
4397a44b80 | |||
d4bb092543 | |||
f73f0cc341 | |||
b95d0e2848 | |||
50b97fa28f | |||
61c7feca87 | |||
4dde6d1a31 | |||
9ac2f02885 | |||
19eb77f049 | |||
59276b7160 | |||
9062e40ec5 | |||
9f78c8f6b4 | |||
144d315e27 | |||
34dc85e605 | |||
4ba0f343e3 | |||
4876eaafcc | |||
16f6be3613 | |||
da0c7d7484 | |||
fbd2f86f00 | |||
a309b7a066 | |||
9ad9c275a4 | |||
7a467d43df | |||
db97250db8 | |||
c0452038f7 | |||
110df59197 | |||
75ae4081d8 | |||
2efca050b3 | |||
37e119c4f2 | |||
9786074119 | |||
d4876b426f | |||
8581e4667a | |||
b94f86765d | |||
ba0f3778ce | |||
aac6b242a0 | |||
dec9442a65 | |||
b33da641d9 | |||
96d498e7e5 | |||
eee137a084 | |||
5e834ae3be | |||
dcfda61aba | |||
5ac7f7057a | |||
ff46c61f63 | |||
57b64a412e | |||
1e81f75377 | |||
1dd49a2ab1 | |||
1cd77a97a7 | |||
f820522a69 | |||
3da613dedb | |||
5c329d2314 | |||
4c073e713d | |||
2832f4ae5e | |||
7690e8a53f | |||
3a19f8e40b | |||
a33b525f9e | |||
7d6ce46829 | |||
a90a4bf80c | |||
140bf8caee | |||
56a45f263e | |||
01d6ddfafb | |||
393b4916f6 | |||
cb3c3af865 | |||
5a83976fa5 | |||
a81f6c3ac4 | |||
6846ce5bfb | |||
0c0ebe06e5 | |||
e50c683159 | |||
872af276ea | |||
e6faee9779 | |||
bc1ddd4379 | |||
e348d6c1cf | |||
7835921045 | |||
1611a274b9 | |||
fa4a8204a4 | |||
5977e9f47f | |||
63d0161da5 | |||
d8b46c1969 | |||
f84731c2df | |||
50d71d1395 | |||
4be0b2502e | |||
6c069ad87b | |||
e69011ac5b | |||
ea130a0899 | |||
2566862e0f | |||
16081817c2 | |||
945625d3ad | |||
050b9c9fce | |||
c35184abdc | |||
34c5f0b7ba | |||
6435eeb251 | |||
eec2dcd981 | |||
57ba368ae0 | |||
ed06469885 | |||
79cd8c691e | |||
391550f49a | |||
6aa07dd17e | |||
aada373a0c | |||
3deac86bbe | |||
d7aef2e97a | |||
7953ba6e78 | |||
8aa3c2a260 | |||
c204548df5 | |||
4d47f5a387 | |||
7944bb8479 | |||
c4ae88a8ff | |||
ad953b7bf6 | |||
d799ae5d72 | |||
a3ec057384 | |||
486f129e62 | |||
e6c3864c71 | |||
7461f12066 | |||
e53b05feba | |||
bcefc176c1 | |||
d0580d0df1 | |||
28fd22dfe0 | |||
742924625d | |||
78a2eae719 | |||
38bb0b61d4 | |||
8b52fea602 | |||
c03495be94 | |||
f19889c222 | |||
af0ab5ec86 | |||
ea4fa60e01 | |||
4b60560a9f | |||
733b0da461 | |||
db074a371d | |||
bb110ce353 | |||
74c32f9e16 | |||
d8ab8f297f | |||
ec7df6b1f2 | |||
ef03ca22d1 | |||
82865dd3fd | |||
ba5d13936c | |||
23a6f76c37 | |||
0c9bc97fe8 | |||
c6ecfb2f67 | |||
8ca0814aff | |||
eceb4c3682 | |||
e7ecd5a5c2 | |||
f7c20a5517 | |||
6f409c0e3b | |||
0a31c223e3 | |||
0f42956f3f | |||
ac2485d4a7 | |||
7993ec5074 | |||
4521174138 | |||
27b95e9d73 | |||
a54425f47d | |||
4918e67fda | |||
b174adbab0 | |||
59cc87c583 | |||
0e87dc995a | |||
fad7b75b96 | |||
c99c90fc4c | |||
594219848d | |||
fa301bfbd2 | |||
50306f6ea3 | |||
9b90ad0a3b | |||
5c854984e4 | |||
6c844cfd9c | |||
9e666dcdb3 | |||
e81f98a975 | |||
11dc0d7e9e | |||
07ed2e2ebb | |||
e1aa460106 | |||
75a77566cf | |||
dd0a2d842a | |||
fa71e906c9 | |||
e6a17e25a9 | |||
d88513de56 | |||
ad97d03f1d | |||
7fc23d526b |
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[*.{kt,kts}]
|
||||||
|
max_line_length = 120
|
||||||
|
indent_size = 4
|
||||||
|
insert_final_newline = true
|
||||||
|
ij_kotlin_allow_trailing_comma = true
|
||||||
|
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||||
|
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||||
|
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
24
.gitattributes
vendored
Normal file
24
.gitattributes
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
* text=auto
|
||||||
|
* text eol=lf
|
||||||
|
|
||||||
|
# Windows forced line-endings
|
||||||
|
/.idea/* text eol=crlf
|
||||||
|
|
||||||
|
# Gradle wrapper
|
||||||
|
*.jar binary
|
||||||
|
|
||||||
|
# Images
|
||||||
|
*.webp binary
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.ico binary
|
||||||
|
*.gz binary
|
||||||
|
*.zip binary
|
||||||
|
*.7z binary
|
||||||
|
*.ttf binary
|
||||||
|
*.eot binary
|
||||||
|
*.woff binary
|
||||||
|
*.pyc binary
|
||||||
|
*.swp binary
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: 🖥️ Mihon website
|
||||||
|
url: https://mihon.app/
|
||||||
|
about: Guides, troubleshooting, and answers to common questions
|
104
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
Normal file
104
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
name: 🐞 Issue report
|
||||||
|
description: Report an issue in Mihon
|
||||||
|
labels: [Bug]
|
||||||
|
body:
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reproduce-steps
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce
|
||||||
|
description: Provide an example of the issue.
|
||||||
|
placeholder: |
|
||||||
|
Example:
|
||||||
|
1. First step
|
||||||
|
2. Second step
|
||||||
|
3. Issue here
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
description: Explain what you should expect to happen.
|
||||||
|
placeholder: |
|
||||||
|
Example:
|
||||||
|
"This should happen..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: actual-behavior
|
||||||
|
attributes:
|
||||||
|
label: Actual behavior
|
||||||
|
description: Explain what actually happens.
|
||||||
|
placeholder: |
|
||||||
|
Example:
|
||||||
|
"This happened instead..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: crash-logs
|
||||||
|
attributes:
|
||||||
|
label: Crash logs
|
||||||
|
description: |
|
||||||
|
If you're experiencing crashes, share the crash logs from **More → Settings → Advanced** then press **Dump crash logs**.
|
||||||
|
placeholder: |
|
||||||
|
You can paste the crash logs in plain text or upload it as an attachment.
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: mihon-version
|
||||||
|
attributes:
|
||||||
|
label: Mihon version
|
||||||
|
description: You can find your Mihon version in **More → About**.
|
||||||
|
placeholder: |
|
||||||
|
Example: "0.16.5"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: android-version
|
||||||
|
attributes:
|
||||||
|
label: Android version
|
||||||
|
description: You can find this somewhere in your Android settings.
|
||||||
|
placeholder: |
|
||||||
|
Example: "Android 11"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: device
|
||||||
|
attributes:
|
||||||
|
label: Device
|
||||||
|
description: List your device and model.
|
||||||
|
placeholder: |
|
||||||
|
Example: "Google Pixel 5"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: other-details
|
||||||
|
attributes:
|
||||||
|
label: Other details
|
||||||
|
placeholder: |
|
||||||
|
Additional details and attachments.
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: acknowledgements
|
||||||
|
attributes:
|
||||||
|
label: Acknowledgements
|
||||||
|
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
||||||
|
options:
|
||||||
|
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
|
||||||
|
required: true
|
||||||
|
- label: I have written a short but informative title.
|
||||||
|
required: true
|
||||||
|
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
|
||||||
|
required: true
|
||||||
|
- label: I have updated the app to version **[0.16.5](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||||
|
required: true
|
||||||
|
- label: I have updated all installed extensions.
|
||||||
|
required: true
|
||||||
|
- label: I will fill out all of the requested information in this form.
|
||||||
|
required: true
|
37
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
Normal file
37
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
name: ⭐ Feature request
|
||||||
|
description: Suggest a feature to improve Mihon
|
||||||
|
labels: [Feature request]
|
||||||
|
body:
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: feature-description
|
||||||
|
attributes:
|
||||||
|
label: Describe your suggested feature
|
||||||
|
description: How can Mihon be improved?
|
||||||
|
placeholder: |
|
||||||
|
Example:
|
||||||
|
"It should work like this..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: other-details
|
||||||
|
attributes:
|
||||||
|
label: Other details
|
||||||
|
placeholder: |
|
||||||
|
Additional details and attachments.
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: acknowledgements
|
||||||
|
attributes:
|
||||||
|
label: Acknowledgements
|
||||||
|
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
||||||
|
options:
|
||||||
|
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
|
||||||
|
required: true
|
||||||
|
- label: I have written a short but informative title.
|
||||||
|
required: true
|
||||||
|
- label: I have updated the app to version **[0.16.5](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||||
|
required: true
|
||||||
|
- label: I will fill out all of the requested information in this form.
|
||||||
|
required: true
|
BIN
.github/assets/logo.png
vendored
Normal file
BIN
.github/assets/logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
12
.github/pull_request_template.md
vendored
Normal file
12
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!--
|
||||||
|
Please include a summary of the change and which issue is fixed.
|
||||||
|
Also make sure you've tested your code and also done a self-review of it.
|
||||||
|
Don't forget to check all base themes and tablet mode for relevant changes.
|
||||||
|
|
||||||
|
If your changes are visual, please provide images below:
|
||||||
|
|
||||||
|
### Images
|
||||||
|
| Image 1 | Image 2 |
|
||||||
|
| ------- | ------- |
|
||||||
|
|  |  |
|
||||||
|
-->
|
6
.github/renovate.json5
vendored
Normal file
6
.github/renovate.json5
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": ["config:base"],
|
||||||
|
"labels": ["Dependencies"],
|
||||||
|
"semanticCommits": "disabled"
|
||||||
|
}
|
56
.github/workflows/build_pull_request.yml
vendored
Normal file
56
.github/workflows/build_pull_request.yml
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
name: PR build check
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '**'
|
||||||
|
- '!**.md'
|
||||||
|
- '!i18n/src/commonMain/moko-resources/**/strings.xml'
|
||||||
|
- '!i18n/src/commonMain/moko-resources/**/plurals.xml'
|
||||||
|
- 'i18n/src/commonMain/moko-resources/base/strings.xml'
|
||||||
|
- 'i18n/src/commonMain/moko-resources/base/plurals.xml'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build app
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone repo
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||||
|
|
||||||
|
- name: Validate Gradle Wrapper
|
||||||
|
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||||
|
|
||||||
|
- name: Dependency Review
|
||||||
|
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0
|
||||||
|
with:
|
||||||
|
java-version: 17
|
||||||
|
distribution: adopt
|
||||||
|
|
||||||
|
- name: Set up gradle
|
||||||
|
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||||
|
|
||||||
|
- name: Build app and run unit tests
|
||||||
|
run: ./gradlew spotlessCheck assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest
|
||||||
|
|
||||||
|
- name: Upload APK
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: arm64-v8a-${{ github.sha }}
|
||||||
|
path: app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned.apk
|
||||||
|
|
||||||
|
- name: Upload mapping
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: mapping-${{ github.sha }}
|
||||||
|
path: app/build/outputs/mapping/standardRelease
|
125
.github/workflows/build_push.yml
vendored
Normal file
125
.github/workflows/build_push.yml
vendored
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
name: CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build app
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone repo
|
||||||
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||||
|
|
||||||
|
- name: Validate Gradle Wrapper
|
||||||
|
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
run: |
|
||||||
|
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0
|
||||||
|
with:
|
||||||
|
java-version: 17
|
||||||
|
distribution: adopt
|
||||||
|
|
||||||
|
- name: Set up gradle
|
||||||
|
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||||
|
|
||||||
|
- name: Build app and run unit tests
|
||||||
|
run: ./gradlew spotlessCheck assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest
|
||||||
|
|
||||||
|
- name: Upload APK
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: arm64-v8a-${{ github.sha }}
|
||||||
|
path: app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned.apk
|
||||||
|
|
||||||
|
- name: Upload mapping
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: mapping-${{ github.sha }}
|
||||||
|
path: app/build/outputs/mapping/standardRelease
|
||||||
|
|
||||||
|
# Sign APK and create release for tags
|
||||||
|
|
||||||
|
- name: Get tag name
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
echo "VERSION_TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Sign APK
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||||
|
uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # v1
|
||||||
|
with:
|
||||||
|
releaseDirectory: app/build/outputs/apk/standard/release
|
||||||
|
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||||
|
alias: ${{ secrets.ALIAS }}
|
||||||
|
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||||
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Clean up build artifacts
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
|
||||||
|
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk mihon-${{ env.VERSION_TAG }}.apk
|
||||||
|
sha=`sha256sum mihon-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
|
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
cp app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk mihon-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
||||||
|
sha=`sha256sum mihon-arm64-v8a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
|
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
cp app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk mihon-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
||||||
|
sha=`sha256sum mihon-armeabi-v7a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
|
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
cp app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk mihon-x86-${{ env.VERSION_TAG }}.apk
|
||||||
|
sha=`sha256sum mihon-x86-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
|
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
cp app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk mihon-x86_64-${{ env.VERSION_TAG }}.apk
|
||||||
|
sha=`sha256sum mihon-x86_64-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
|
||||||
|
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||||
|
uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8
|
||||||
|
with:
|
||||||
|
tag_name: ${{ env.VERSION_TAG }}
|
||||||
|
name: Mihon ${{ env.VERSION_TAG }}
|
||||||
|
body: |
|
||||||
|
---
|
||||||
|
|
||||||
|
### Checksums
|
||||||
|
|
||||||
|
| Variant | SHA-256 |
|
||||||
|
| ------- | ------- |
|
||||||
|
| Universal | ${{ env.APK_UNIVERSAL_SHA }}
|
||||||
|
| arm64-v8a | ${{ env.APK_ARM64_V8A_SHA }}
|
||||||
|
| armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }}
|
||||||
|
| x86 | ${{ env.APK_X86_SHA }} |
|
||||||
|
| x86_64 | ${{ env.APK_X86_64_SHA }} |
|
||||||
|
|
||||||
|
## If you are unsure which version to choose then go with mihon-${{ env.VERSION_TAG }}.apk
|
||||||
|
files: |
|
||||||
|
mihon-${{ env.VERSION_TAG }}.apk
|
||||||
|
mihon-arm64-v8a-${{ env.VERSION_TAG }}.apk
|
||||||
|
mihon-armeabi-v7a-${{ env.VERSION_TAG }}.apk
|
||||||
|
mihon-x86-${{ env.VERSION_TAG }}.apk
|
||||||
|
mihon-x86_64-${{ env.VERSION_TAG }}.apk
|
||||||
|
draft: true
|
||||||
|
prerelease: false
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.PAT }}
|
19
.github/workflows/lock.yml
vendored
Normal file
19
.github/workflows/lock.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
name: Lock threads
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Daily
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *'
|
||||||
|
# Manual trigger
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lock:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
issue-inactive-days: '2'
|
||||||
|
pr-inactive-days: '2'
|
21
.gitignore
vendored
21
.gitignore
vendored
@ -1,9 +1,16 @@
|
|||||||
|
# Build files
|
||||||
.gradle
|
.gradle
|
||||||
/local.properties
|
.kotlin
|
||||||
/.idea/workspace.xml
|
build
|
||||||
.DS_Store
|
|
||||||
/build
|
# IDE files
|
||||||
.idea/
|
|
||||||
*iml
|
|
||||||
*.iml
|
*.iml
|
||||||
*/build
|
.idea/*
|
||||||
|
!.idea/icon.png
|
||||||
|
/captures
|
||||||
|
|
||||||
|
# Configuration files
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# macOS specific files
|
||||||
|
.DS_Store
|
||||||
|
BIN
.idea/icon.png
generated
Normal file
BIN
.idea/icon.png
generated
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
19
.travis.yml
19
.travis.yml
@ -1,19 +0,0 @@
|
|||||||
language: android
|
|
||||||
android:
|
|
||||||
components:
|
|
||||||
- platform-tools
|
|
||||||
- tools
|
|
||||||
|
|
||||||
# The BuildTools version used by your project
|
|
||||||
- build-tools-23.0.1
|
|
||||||
- android-23
|
|
||||||
- extra-android-m2repository
|
|
||||||
- extra-google-m2repository
|
|
||||||
- extra-android-support
|
|
||||||
- extra-google-google_play_services
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- chmod +x gradlew
|
|
||||||
#Build, and run tests
|
|
||||||
script: "./gradlew build testDebug"
|
|
||||||
sudo: false
|
|
134
CHANGELOG.md
Normal file
134
CHANGELOG.md
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- Option to disable reader zoom out ([@Splintorien](https://github.com/Splintorien)) ([#302](https://github.com/mihonapp/mihon/pull/302))
|
||||||
|
- Source name and tracker urls to app generated `ComicInfo.xml` file ([@Shamicen](https://github.com/Shamicen)) ([#459](https://github.com/mihonapp/mihon/pull/459))
|
||||||
|
- Option to migrate in Duplicate entry dialog ([@sirlag](https://github.com/sirlag)) ([#492](https://github.com/mihonapp/mihon/pull/492))
|
||||||
|
- Upcoming screen to visualize expected update dates ([@sirlag](https://github.com/sirlag)) ([#420](https://github.com/mihonapp/mihon/pull/420))
|
||||||
|
- Crash screen error message to the top of the crash log generated from that screen ([@FooIbar](https://github.com/FooIbar)) ([#742](https://github.com/mihonapp/mihon/pull/742))
|
||||||
|
- Support for 7Zip and RAR5 archives ([@FooIbar](https://github.com/FooIbar), [@null2264](https://github.com/null2264)) ([#949](https://github.com/mihonapp/mihon/pull/949), [#967](https://github.com/mihonapp/mihon/pull/967))
|
||||||
|
- Extra configuration options to e-ink page flashes ([@sirlag](https://github.com/sirlag)) ([#625](https://github.com/mihonapp/mihon/pull/625))
|
||||||
|
- 8-bit+ AVIF image support ([@WerctFourth](https://github.com/WerctFourth)) ([#971](https://github.com/mihonapp/mihon/pull/971))
|
||||||
|
- Smart update dialog message when no predicted released date exists ([@Animeboynz](https://github.com/Animeboynz)) ([#977](https://github.com/mihonapp/mihon/pull/977))
|
||||||
|
- Save global search "Has result" choice ([@AntsyLich](https://github.com/AntsyLich)) ([`5a61ca5`](https://github.com/mihonapp/mihon/commit/5a61ca5535fe0d9e8e7bcb9e665ba2f9cb0cf649))
|
||||||
|
- Option to copy reader panel to clipboard ([@Animeboynz](https://github.com/Animeboynz)) ([#1003](https://github.com/mihonapp/mihon/pull/1003))
|
||||||
|
- Copy Tracker URL option to tracker sheet ([@mm12](https://github.com/mm12)) ([#1101](https://github.com/mihonapp/mihon/pull/1101))
|
||||||
|
- A button to exclude all scanlators in exclude scanlators dialog ([@AntsyLich](https://github.com/AntsyLich)) ([`84b2164`](https://github.com/mihonapp/mihon/commit/84b2164787a795f3fd757c325cbfb6ef660ac3a3))
|
||||||
|
- Open in browser option to reader menu ([@mm12](https://github.com/mm12)) ([#1110](https://github.com/mihonapp/mihon/pull/1110))
|
||||||
|
- Option to skip downloading duplicate read chapters ([@shabnix](https://github.com/shabnix)) ([#1125](https://github.com/mihonapp/mihon/pull/1125))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Read archive files from memory instead of extracting files to internal storage ([@FooIbar](https://github.com/FooIbar)) ([#326](https://github.com/mihonapp/mihon/pull/326))
|
||||||
|
- Try to get resource from Extension before checking in the app ([@beer-psi](https://github.com/beer-psi)) ([#433](https://github.com/mihonapp/mihon/pull/433))
|
||||||
|
- Default user agent ([@AntsyLich](https://github.com/AntsyLich)) ([`8160b47`](https://github.com/mihonapp/mihon/commit/8160b47ff5fbbd9b32caeb462b5be881fabd3449))
|
||||||
|
- Wait for sources to be initialized before performing source related tasks ([@jobobby04](https://github.com/jobobby04)) ([`a08e03f`](https://github.com/mihonapp/mihon/commit/a08e03f5cbf3f4e6be1de35f97ef8ebb26a1210e))
|
||||||
|
- Duplicate entry dialog UI ([@sirlag](https://github.com/sirlag)) ([#492](https://github.com/mihonapp/mihon/pull/492))
|
||||||
|
- Extension trust system ([@AntsyLich](https://github.com/AntsyLich), [@Animeboynz](https://github.com/Animeboynz) ([#570](https://github.com/mihonapp/mihon/pull/570), [#1057](https://github.com/mihonapp/mihon/pull/1057))
|
||||||
|
- Make category backup/restore not dependant on library backup ([@AntsyLich](https://github.com/AntsyLich)) ([`56fb4f6`](https://github.com/mihonapp/mihon/commit/56fb4f62a152e87a71892aa68c78cac51a2c8596))
|
||||||
|
- Kitsu domain to `kitsu.app` ([@MajorTanya](https://github.com/MajorTanya)) ([#1106](https://github.com/mihonapp/mihon/pull/1106))
|
||||||
|
- Respect privacy settings in extension update notification ([@Animeboynz](https://github.com/Animeboynz)) ([#1156](https://github.com/mihonapp/mihon/pull/1156))
|
||||||
|
|
||||||
|
### Improvement
|
||||||
|
- Long strip reader performance ([@FooIbar](https://github.com/FooIbar), [@wwww-wwww](https://github.com/wwww-wwww)) ([#687](https://github.com/mihonapp/mihon/pull/687))
|
||||||
|
- Performance when looking up specific files ([@raxod502](https://github.com/raxod502)) ([#728](https://github.com/mihonapp/mihon/pull/728))
|
||||||
|
- Chapter number parsing ([@Naputt1](https://github.com/Naputt1)) ([`6a80305`](https://github.com/mihonapp/mihon/commit/6a80305d6c572da6c08c0c69f5c25ff26ecf7383))
|
||||||
|
- Error message on restoring if backup decoding fails ([@vetleledaal](https://github.com/vetleledaal)) ([#1056](https://github.com/mihonapp/mihon/pull/1056))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Creating `ComicInfo.xml` file for local source ([@FooIbar](https://github.com/FooIbar)) ([#325](https://github.com/mihonapp/mihon/pull/325))
|
||||||
|
- Chapter download indicator ([@ivaniskandar](https://github.com/ivaniskandar)) ([`d8b9a9f`](https://github.com/mihonapp/mihon/commit/d8b9a9f593911569ff2bceb49b4f020978d0d2e1))
|
||||||
|
- Issues with shizuku in a multi user setup ([@Redjard](https://github.com/Redjard)) ([#494](https://github.com/mihonapp/mihon/pull/494))
|
||||||
|
- Occasional black bar when scrolling in long strip reader ([@FooIbar](https://github.com/FooIbar)) ([#563](https://github.com/mihonapp/mihon/pull/563))
|
||||||
|
- Extension being marked as not installed instead of untrusted after updating with private installer ([@AntsyLich](https://github.com/AntsyLich)) ([`2114514`](https://github.com/mihonapp/mihon/commit/21145144cdf550aa775047603e06e261951ebc42))
|
||||||
|
- Extension update counter not updating due to extension being marked as untrusted ([@AntsyLich](https://github.com/AntsyLich)) ([`2114514`](https://github.com/mihonapp/mihon/commit/21145144cdf550aa775047603e06e261951ebc42))
|
||||||
|
- `Key "extension-XXX-YYY" was already used` crash ([@AntsyLich](https://github.com/AntsyLich)) ([`2114514`](https://github.com/mihonapp/mihon/commit/21145144cdf550aa775047603e06e261951ebc42))
|
||||||
|
- Navigation layout tap zones shifting after zooming out in webtoon readers ([@FooIbar](https://github.com/FooIbar)) ([#767](https://github.com/mihonapp/mihon/pull/767))
|
||||||
|
- Some extension not loading due to missing classes ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#783](https://github.com/mihonapp/mihon/pull/783))
|
||||||
|
- Theme colors in accordance to upstream changes ([@CrepeTF](https://github.com/CrepeTF), [@AntsyLich](https://github.com/AntsyLich)) ([#766](https://github.com/mihonapp/mihon/pull/766), [#963](https://github.com/mihonapp/mihon/pull/963), [#976](https://github.com/mihonapp/mihon/pull/976))
|
||||||
|
- Crash when requesting folder access on non-conforming devices ([@mainrs](https://github.com/mainrs)) ([#726](https://github.com/mihonapp/mihon/pull/726))
|
||||||
|
- Bugged color for Date/Scanlator in chapter list for read chapters ([@ivaniskandar](https://github.com/ivaniskandar)) ([`15d9992`](https://github.com/mihonapp/mihon/commit/15d999229fcce865001d5fa77d0163e6e80e38db))
|
||||||
|
- Categories having same `order` after restoring backup ([@Cologler](https://github.com/Cologler)) ([`119bcbf`](https://github.com/mihonapp/mihon/commit/119bcbf8ed2415664922ea77fadf0da1165d1732))
|
||||||
|
- Filter by "Tracking" temporarily stuck after signing out of tracker ([@AntsyLich](https://github.com/AntsyLich)) ([#987](https://github.com/mihonapp/mihon/pull/987))
|
||||||
|
- JXL image downloading and loading ([@FooIbar](https://github.com/FooIbar)) ([#993](https://github.com/mihonapp/mihon/pull/993))
|
||||||
|
- Crash when using `%` in category name ([@Animeboynz](https://github.com/Animeboynz), [@FooIbar](https://github.com/FooIbar)) ([#1030](https://github.com/mihonapp/mihon/pull/1030))
|
||||||
|
- Library is backed up while being disabled ([@AntsyLich](https://github.com/AntsyLich)) ([`56fb4f6`](https://github.com/mihonapp/mihon/commit/56fb4f62a152e87a71892aa68c78cac51a2c8596))
|
||||||
|
- Crash on list with 0 item but only sticky header ([@cuong-tran](https://github.com/cuong-tran)) ([#1083](https://github.com/mihonapp/mihon/pull/1083))
|
||||||
|
- Crash when trying to clear cookies of some source ([@FooIbar](https://github.com/FooIbar)) ([#1084](https://github.com/mihonapp/mihon/pull/1084))
|
||||||
|
- MAL search results not showing start dates ([@MajorTanya](https://github.com/MajorTanya)) ([#1098](https://github.com/mihonapp/mihon/pull/1098))
|
||||||
|
- Android SDK 35 API collision ([@AntsyLich](https://github.com/AntsyLich)) ([`fdb9617`](https://github.com/mihonapp/mihon/commit/fdb96179c6373eb0a8e7d6daea671a315d5ce5f0))
|
||||||
|
|
||||||
|
## [v0.16.5] - 2024-04-09
|
||||||
|
### Added
|
||||||
|
- Setting to install custom color profiles to get true colors ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Permanently enable 32-bit color mode ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix wrong dates in Updates and History tab due to time zone issues ([@sirlag](https://github.com/sirlag)) ([#402](https://github.com/mihonapp/mihon/pull/402), [#415](https://github.com/mihonapp/mihon/pull/415))
|
||||||
|
- Fix app infinitely retries tracker update instead of failing after 3 tries ([@MajorTanya](https://github.com/MajorTanya)) ([#411](https://github.com/mihonapp/mihon/pull/411))
|
||||||
|
- Fix crash on Pixel devices ([`ab06720`](https://github.com/mihonapp/mihon/commit/ab067209661eceefc04c65f6bdbfcaa8a1264651))
|
||||||
|
- Fix crash when opening some heif/heic images ([@az4521](https://github.com/az4521)) ([#466](https://github.com/mihonapp/mihon/pull/466))
|
||||||
|
- Fix crash in track date selection dialog ([@ivaniskandar](https://github.com/ivaniskandar)) ([`c348fac`](https://github.com/mihonapp/mihon/commit/c348fac78fac479fb123bd617c01c78b9ca851d5))
|
||||||
|
- Fix dates for saved images on Samsung devices ([@MajorTanya](https://github.com/MajorTanya)) ([#552](https://github.com/mihonapp/mihon/pull/552))
|
||||||
|
- Fix colors getting distorted when opening CMYK jpeg images ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523))
|
||||||
|
|
||||||
|
## [v0.16.4] - 2024-02-26
|
||||||
|
### Fixed
|
||||||
|
- Circumvent MAL block ([@AntsyLich](https://github.com/AntsyLich)) ([`085ad8d`](https://github.com/mihonapp/mihon/commit/085ad8d44637c375a8ed24aba3a6f75f5b0cc9ee))
|
||||||
|
|
||||||
|
## [v0.16.3] - 2024-01-30
|
||||||
|
### Added
|
||||||
|
- Copy extension debug info when clicking logo or name in the extension details screen ([@MajorTanya](https://github.com/MajorTanya)) ([#271](https://github.com/mihonapp/mihon/pull/271))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Rename extension update error file to `mihon_update_errors.txt` ([@mjishnu](https://github.com/mjishnu)) ([#253](https://github.com/mihonapp/mihon/pull/253))
|
||||||
|
- Hide display cutoff setting in reader settings sheet if fullscreen is off ([@Riztard](https://github.com/Riztard)) ([#241](https://github.com/mihonapp/mihon/pull/241))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix bottom sheet display issues on non-Tablet UI ([@theolm](https://github.com/theolm)) ([#182](https://github.com/mihonapp/mihon/pull/182))
|
||||||
|
- Fix crash when switching screen while a list is scrolling ([@theolm](https://github.com/theolm)) ([#272](https://github.com/mihonapp/mihon/pull/272))
|
||||||
|
- Fix newly installed extensions not being recognized by Mihon ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#275](https://github.com/mihonapp/mihon/pull/275))
|
||||||
|
- Fix error handling when refreshing MAL OAuth token ([@AntsyLich](https://github.com/AntsyLich)) ([`0f4de03`](https://github.com/mihonapp/mihon/commit/0f4de03d7a77b52490dc9a95e96a308b93b26e4f))
|
||||||
|
|
||||||
|
## [v0.16.2] - 2024-01-28
|
||||||
|
### Added
|
||||||
|
- Scanlator filter is now part of Backup ([@jobobby04](https://github.com/jobobby04)) ([#166](https://github.com/mihonapp/mihon/pull/166))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Rename crash log filename to `mihon_crash_logs.txt` ([@MajorTanya](https://github.com/MajorTanya)) ([#234](https://github.com/mihonapp/mihon/pull/234))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- "Flash screen on page change" Making the screen goes blank ([@AntsyLich](https://github.com/AntsyLich)) ([`38d6ab8`](https://github.com/mihonapp/mihon/commit/38d6ab80ce868707829dbc81de4170afe3c2f2a5))
|
||||||
|
- App icon scaling ([@AntsyLich](https://github.com/AntsyLich)) ([`26815c7`](https://github.com/mihonapp/mihon/commit/26815c7356111394665467c1e81255ac9ee33c1a))
|
||||||
|
- Updating extension not reflecting correctly ([@AntsyLich](https://github.com/AntsyLich)) ([`cb06898`](https://github.com/mihonapp/mihon/commit/cb068984303f811692531bf6f14902ae118d8ac7))
|
||||||
|
- Inconsistent button height with some languages in "Data and storage" ([@theolm](https://github.com/theolm)) ([#202](https://github.com/mihonapp/mihon/pull/202))
|
||||||
|
- Fix chapter not being marked as read in some cases with Enhanced Trackers ([@Secozzi](https://github.com/Secozzi)) ([#219](https://github.com/mihonapp/mihon/pull/219))
|
||||||
|
- And various tracker related fixes ([@AntsyLich](https://github.com/AntsyLich), [@kitsumed](https://github.com/kitsumed), [@Secozzi](https://github.com/Secozzi)) ([`a024218`](https://github.com/mihonapp/mihon/commit/a024218410953a389b8af4880fa7ae6cc30124a2), [`e3f33e2`](https://github.com/mihonapp/mihon/commit/e3f33e24f5e928ac8a85d1f500fd42d4715fc6b5), [`32188f9`](https://github.com/mihonapp/mihon/commit/32188f9f65009a18250674ef1bd6e57d351c1fba))
|
||||||
|
|
||||||
|
## [v0.16.1] - 2024-01-18
|
||||||
|
### Fixed
|
||||||
|
- App Icon not filled ([@AntsyLich](https://github.com/AntsyLich)) ([`1849715`](https://github.com/mihonapp/mihon/commit/18497154183356bb0d469b27827f9f7d6b7a3130))
|
||||||
|
- MangaUpdates default score being set to -1.0 ([@AntsyLich](https://github.com/AntsyLich)) ([`99fd273`](https://github.com/mihonapp/mihon/commit/99fd2731f5d9d374700e89fa67d4d5bf611bbafa))
|
||||||
|
|
||||||
|
## [v0.16.0] - 2024-01-16
|
||||||
|
|
||||||
|
"The end of 立ち読み (Tachiyomi) is the beginning of みほん (Mihon)"
|
||||||
|
Credit to LinkCable, the icon designer, for this poetic quote.
|
||||||
|
|
||||||
|
What's New?
|
||||||
|
Well, nothing, except you now you need Android 8+ to install the app.
|
||||||
|
|
||||||
|
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.16.5...HEAD
|
||||||
|
[v0.16.5]: https://github.com/mihonapp/mihon/compare/v0.16.4...v0.16.5
|
||||||
|
[v0.16.4]: https://github.com/mihonapp/mihon/compare/v0.16.3...v0.16.4
|
||||||
|
[v0.16.3]: https://github.com/mihonapp/mihon/compare/v0.16.2...v0.16.3
|
||||||
|
[v0.16.2]: https://github.com/mihonapp/mihon/compare/v0.16.1...v0.16.2
|
||||||
|
[v0.16.1]: https://github.com/mihonapp/mihon/compare/v0.16.0...v0.16.1
|
||||||
|
[v0.16.0]: https://github.com/mihonapp/mihon/releases/tag/v0.16.0
|
126
CODE_OF_CONDUCT.md
Normal file
126
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, caste, color, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community moderators are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community moderators have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community moderators responsible for enforcement at
|
||||||
|
the [Mihon Discord server](https://discord.gg/mihon).
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community moderators are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community moderators will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community moderators, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/),
|
||||||
|
version 2.1, available at
|
||||||
|
[v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by
|
||||||
|
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
[FAQ](https://www.contributor-covenant.org/faq). Translations are available
|
||||||
|
at [translations](https://www.contributor-covenant.org/translations).
|
49
CONTRIBUTING.md
Normal file
49
CONTRIBUTING.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
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 Mihon!
|
||||||
|
|
||||||
|
|
||||||
|
# Code contributions
|
||||||
|
|
||||||
|
Pull requests are welcome!
|
||||||
|
|
||||||
|
If you're interested in taking on [an open issue](https://github.com/mihonapp/mihon/issues), please comment on it so others are aware.
|
||||||
|
You do not need to ask for permission nor an assignment.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before you start, please note that the ability to use following technologies is **required** and that existing contributors will not actively teach them to you.
|
||||||
|
|
||||||
|
- Basic [Android development](https://developer.android.com/)
|
||||||
|
- [Kotlin](https://kotlinlang.org/)
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
|
||||||
|
- [Android Studio](https://developer.android.com/studio)
|
||||||
|
- Emulator or phone with developer options enabled to test changes.
|
||||||
|
|
||||||
|
## Getting help
|
||||||
|
|
||||||
|
- Join [the Discord server](https://discord.gg/mihon) for online help and to ask questions while developing.
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
Translations are done externally via Weblate. See [our website](https://mihon.app/docs/contribute#translation) for more details.
|
||||||
|
|
||||||
|
|
||||||
|
# Forks
|
||||||
|
|
||||||
|
Forks are allowed so long as they abide by [the project's LICENSE](https://github.com/mihonapp/mihon/blob/main/LICENSE).
|
||||||
|
|
||||||
|
When creating a fork, remember to:
|
||||||
|
|
||||||
|
- To avoid confusion with the main app:
|
||||||
|
- Change the app name
|
||||||
|
- Change the app icon
|
||||||
|
- Change or disable the [app update checker](https://github.com/mihonapp/mihon/blob/main/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt)
|
||||||
|
- To avoid installation conflicts:
|
||||||
|
- Change the `applicationId` in [`build.gradle.kts`](https://github.com/mihonapp/mihon/blob/main/app/build.gradle.kts)
|
||||||
|
- To avoid having your data polluting the main app's analytics and crash report services:
|
||||||
|
- If you want to use Firebase analytics, replace [`google-services.json`](https://github.com/mihonapp/mihon/blob/main/app/src/standard/google-services.json) with your own
|
26
LICENSE
26
LICENSE
@ -174,29 +174,3 @@
|
|||||||
of your accepting any such warranty or additional liability.
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright {yyyy} {name of copyright owner}
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
|
98
README.md
98
README.md
@ -1,38 +1,86 @@
|
|||||||
Tachiyomi is a free and open source manga reader for Android.
|
<div align="center">
|
||||||
|
|
||||||
Keep in mind it's still a beta, so expect it to crash sometimes.
|
<a href="https://mihon.app">
|
||||||
|
<img src="./.github/assets/logo.png" alt="Mihon logo" title="Mihon logo" width="80"/>
|
||||||
|
</a>
|
||||||
|
|
||||||
Current features:
|
# Mihon [App](#)
|
||||||
|
|
||||||
* Online and offline reading
|
### Full-featured reader
|
||||||
* Configurable reader with multiple viewers and settings
|
Discover and read manga, webtoons, comics, and more – easier than ever on your Android device.
|
||||||
* MyAnimeList support
|
|
||||||
* Resume from the next unread chapter
|
[](https://discord.gg/mihon)
|
||||||
* Chapter filtering
|
[](https://github.com/mihonapp/mihon/releases)
|
||||||
* Schedule searching for updates
|
|
||||||
* Categories to organize your library
|
[](https://github.com/mihonapp/mihon/actions/workflows/build_push.yml)
|
||||||
|
[](/LICENSE)
|
||||||
|
[](https://hosted.weblate.org/engage/mihon/)
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
[](https://github.com/inorichi/tachiyomi/releases/download/v0.1.0/tachiyomi-v0.1.0.apk)
|
[](https://github.com/mihonapp/mihon/releases)
|
||||||
[](http://tachiyomi.kanade.eu/latest/app-debug.apk)
|
[](https://github.com/mihonapp/mihon-preview/releases)
|
||||||
|
|
||||||
## License
|
*Requires Android 8.0 or higher.*
|
||||||
|
|
||||||
Copyright 2015 Javier Tomás
|
## Features
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
<div align="left">
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
* Local reading of content.
|
||||||
|
* A configurable reader with multiple viewers, reading directions and other settings.
|
||||||
|
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.app/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support.
|
||||||
|
* Categories to organize your library.
|
||||||
|
* Light and dark themes.
|
||||||
|
* Schedule updating your library for new chapters.
|
||||||
|
* Create backups locally to read offline or to your desired cloud service.
|
||||||
|
* Plus much more...
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
</div>
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
## Disclaimer
|
## Contributing
|
||||||
|
|
||||||
The developer of this application does not have any affiliation with the content providers available.
|
[Code of conduct](./CODE_OF_CONDUCT.md) · [Contributing guide](./CONTRIBUTING.md)
|
||||||
|
|
||||||
|
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
||||||
|
|
||||||
|
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); if you got any questions, join our [Discord server](https://discord.gg/mihon).
|
||||||
|
|
||||||
|
|
||||||
|
### Repositories
|
||||||
|
|
||||||
|
[](https://github.com/mihonapp/website/)
|
||||||
|
[](https://github.com/mihonapp/bitmap.kt/)
|
||||||
|
|
||||||
|
### Credits
|
||||||
|
|
||||||
|
Thank you to all the people who have contributed!
|
||||||
|
|
||||||
|
<a href="https://github.com/mihonapp/mihon/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=mihonapp/mihon" alt="Mihon app contributors" title="Mihon app contributors" width="800"/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### Disclaimer
|
||||||
|
|
||||||
|
The developer(s) of this application does not have any affiliation with the content providers available, and this application hosts zero content.
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
Copyright © 2015 Javier Tomás
|
||||||
|
Copyright © 2024 The Mihon Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
4
app/.gitignore
vendored
4
app/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
/build
|
|
||||||
*iml
|
|
||||||
*.iml
|
|
||||||
.idea
|
|
143
app/build.gradle
143
app/build.gradle
@ -1,143 +0,0 @@
|
|||||||
import java.text.SimpleDateFormat
|
|
||||||
|
|
||||||
apply plugin: 'android-sdk-manager'
|
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'com.neenbedankt.android-apt'
|
|
||||||
apply plugin: 'me.tatarka.retrolambda'
|
|
||||||
|
|
||||||
retrolambda {
|
|
||||||
jvmArgs '-noverify'
|
|
||||||
}
|
|
||||||
|
|
||||||
ext {
|
|
||||||
// Git is needed in your system PATH for these commands to work.
|
|
||||||
// If it's not installed, you can return a random value as a workaround
|
|
||||||
getCommitCount = {
|
|
||||||
return 'git rev-list --count origin/master'.execute().text.trim()
|
|
||||||
// return "1"
|
|
||||||
}
|
|
||||||
|
|
||||||
getGitSha = {
|
|
||||||
return 'git rev-parse --short HEAD'.execute().text.trim()
|
|
||||||
// return "1"
|
|
||||||
}
|
|
||||||
|
|
||||||
getBuildTime = {
|
|
||||||
def df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")
|
|
||||||
df.setTimeZone(TimeZone.getTimeZone("UTC"))
|
|
||||||
return df.format(new Date())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdkVersion 23
|
|
||||||
buildToolsVersion "23.0.2"
|
|
||||||
publishNonDefault true
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
applicationId "eu.kanade.tachiyomi"
|
|
||||||
minSdkVersion 16
|
|
||||||
targetSdkVersion 23
|
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
|
||||||
versionCode 2
|
|
||||||
versionName "0.1.1"
|
|
||||||
|
|
||||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
|
||||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
|
||||||
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
debug {
|
|
||||||
applicationIdSuffix ".debug"
|
|
||||||
}
|
|
||||||
release {
|
|
||||||
minifyEnabled true
|
|
||||||
shrinkResources true
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
packagingOptions {
|
|
||||||
exclude 'META-INF/DEPENDENCIES'
|
|
||||||
exclude 'LICENSE.txt'
|
|
||||||
exclude 'META-INF/LICENSE'
|
|
||||||
exclude 'META-INF/LICENSE.txt'
|
|
||||||
exclude 'META-INF/NOTICE'
|
|
||||||
}
|
|
||||||
|
|
||||||
lintOptions {
|
|
||||||
abortOnError false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
final SUPPORT_LIBRARY_VERSION = '23.1.1'
|
|
||||||
final DAGGER_VERSION = '2.0.2'
|
|
||||||
final MOCKITO_VERSION = '1.10.19'
|
|
||||||
final STORIO_VERSION = '1.8.0'
|
|
||||||
final ICEPICK_VERSION = '3.1.0'
|
|
||||||
|
|
||||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
|
||||||
compile project(":SubsamplingScaleImageView")
|
|
||||||
|
|
||||||
compile "com.android.support:support-v4:$SUPPORT_LIBRARY_VERSION"
|
|
||||||
compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION"
|
|
||||||
compile "com.android.support:cardview-v7:$SUPPORT_LIBRARY_VERSION"
|
|
||||||
compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION"
|
|
||||||
compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION"
|
|
||||||
compile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION"
|
|
||||||
compile 'com.squareup.okhttp:okhttp-urlconnection:2.7.2'
|
|
||||||
compile 'com.squareup.okhttp:okhttp:2.7.2'
|
|
||||||
compile 'com.squareup.okio:okio:1.6.0'
|
|
||||||
compile 'com.google.code.gson:gson:2.5'
|
|
||||||
compile 'com.jakewharton:disklrucache:2.0.2'
|
|
||||||
compile 'org.jsoup:jsoup:1.8.3'
|
|
||||||
compile 'io.reactivex:rxandroid:1.1.0'
|
|
||||||
compile 'io.reactivex:rxjava:1.1.0'
|
|
||||||
compile 'com.f2prateek.rx.preferences:rx-preferences:1.0.1'
|
|
||||||
compile "com.pushtorefresh.storio:sqlite:$STORIO_VERSION"
|
|
||||||
compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION"
|
|
||||||
compile 'info.android15.nucleus:nucleus:2.0.4'
|
|
||||||
compile 'de.greenrobot:eventbus:2.4.0'
|
|
||||||
compile 'com.github.bumptech.glide:glide:3.6.1'
|
|
||||||
compile 'com.jakewharton:butterknife:7.0.1'
|
|
||||||
compile 'com.jakewharton.timber:timber:4.1.0'
|
|
||||||
compile 'uk.co.ribot:easyadapter:1.5.0@aar'
|
|
||||||
compile 'ch.acra:acra:4.7.0'
|
|
||||||
compile "frankiesardo:icepick:$ICEPICK_VERSION"
|
|
||||||
provided "frankiesardo:icepick-processor:$ICEPICK_VERSION"
|
|
||||||
compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
|
||||||
compile 'eu.davidea:flexible-adapter:4.2.0@aar'
|
|
||||||
compile 'com.nononsenseapps:filepicker:2.5.1'
|
|
||||||
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
|
||||||
compile 'com.github.pwittchen:reactivenetwork:0.1.5'
|
|
||||||
|
|
||||||
compile "com.google.dagger:dagger:$DAGGER_VERSION"
|
|
||||||
apt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
|
||||||
apt "com.pushtorefresh.storio:sqlite-annotations-processor:$STORIO_VERSION"
|
|
||||||
provided 'org.glassfish:javax.annotation:10.0-b28'
|
|
||||||
|
|
||||||
compile('com.mikepenz:materialdrawer:4.6.4@aar') {
|
|
||||||
transitive = true
|
|
||||||
}
|
|
||||||
compile('com.github.afollestad.material-dialogs:core:0.8.5.3@aar') {
|
|
||||||
transitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
testCompile 'junit:junit:4.12'
|
|
||||||
testCompile 'org.assertj:assertj-core:2.3.0'
|
|
||||||
testCompile "org.mockito:mockito-core:$MOCKITO_VERSION"
|
|
||||||
testCompile('org.robolectric:robolectric:3.0') {
|
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
|
||||||
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
|
|
||||||
}
|
|
||||||
|
|
||||||
androidTestApt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
|
||||||
}
|
|
308
app/build.gradle.kts
Normal file
308
app/build.gradle.kts
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
import mihon.buildlogic.getBuildTime
|
||||||
|
import mihon.buildlogic.getCommitCount
|
||||||
|
import mihon.buildlogic.getGitSha
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("mihon.android.application")
|
||||||
|
id("mihon.android.application.compose")
|
||||||
|
id("com.github.zellius.shortcut-helper")
|
||||||
|
kotlin("plugin.serialization")
|
||||||
|
alias(libs.plugins.aboutLibraries)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
||||||
|
pluginManager.apply {
|
||||||
|
apply(libs.plugins.google.services.get().pluginId)
|
||||||
|
apply(libs.plugins.firebase.crashlytics.get().pluginId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shortcutHelper.setFilePath("./shortcuts.xml")
|
||||||
|
|
||||||
|
val supportedAbis = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "eu.kanade.tachiyomi"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "app.mihon"
|
||||||
|
|
||||||
|
versionCode = 7
|
||||||
|
versionName = "0.16.5"
|
||||||
|
|
||||||
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
|
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime()}\"")
|
||||||
|
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
|
||||||
|
buildConfigField("boolean", "PREVIEW", "false")
|
||||||
|
|
||||||
|
ndk {
|
||||||
|
abiFilters += supportedAbis
|
||||||
|
}
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
splits {
|
||||||
|
abi {
|
||||||
|
isEnable = true
|
||||||
|
reset()
|
||||||
|
include(*supportedAbis.toTypedArray())
|
||||||
|
isUniversalApk = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
named("debug") {
|
||||||
|
versionNameSuffix = "-${getCommitCount()}"
|
||||||
|
applicationIdSuffix = ".debug"
|
||||||
|
isPseudoLocalesEnabled = true
|
||||||
|
}
|
||||||
|
named("release") {
|
||||||
|
isShrinkResources = true
|
||||||
|
isMinifyEnabled = true
|
||||||
|
proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro")
|
||||||
|
}
|
||||||
|
create("preview") {
|
||||||
|
initWith(getByName("release"))
|
||||||
|
buildConfigField("boolean", "PREVIEW", "true")
|
||||||
|
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
matchingFallbacks.add("release")
|
||||||
|
val debugType = getByName("debug")
|
||||||
|
versionNameSuffix = debugType.versionNameSuffix
|
||||||
|
applicationIdSuffix = debugType.applicationIdSuffix
|
||||||
|
}
|
||||||
|
create("benchmark") {
|
||||||
|
initWith(getByName("release"))
|
||||||
|
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
matchingFallbacks.add("release")
|
||||||
|
isDebuggable = false
|
||||||
|
isProfileable = true
|
||||||
|
versionNameSuffix = "-benchmark"
|
||||||
|
applicationIdSuffix = ".benchmark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
getByName("preview").res.srcDirs("src/debug/res")
|
||||||
|
getByName("benchmark").res.srcDirs("src/debug/res")
|
||||||
|
}
|
||||||
|
|
||||||
|
flavorDimensions.add("default")
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
create("standard") {
|
||||||
|
buildConfigField("boolean", "INCLUDE_UPDATER", "true")
|
||||||
|
dimension = "default"
|
||||||
|
}
|
||||||
|
create("dev") {
|
||||||
|
// Include pseudolocales: https://developer.android.com/guide/topics/resources/pseudolocales
|
||||||
|
resourceConfigurations.addAll(listOf("en", "en_XA", "ar_XB", "xxhdpi"))
|
||||||
|
dimension = "default"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packaging {
|
||||||
|
resources.excludes.addAll(
|
||||||
|
listOf(
|
||||||
|
"kotlin-tooling-metadata.json",
|
||||||
|
"META-INF/DEPENDENCIES",
|
||||||
|
"LICENSE.txt",
|
||||||
|
"META-INF/LICENSE",
|
||||||
|
"META-INF/**/LICENSE.txt",
|
||||||
|
"META-INF/*.properties",
|
||||||
|
"META-INF/**/*.properties",
|
||||||
|
"META-INF/README.md",
|
||||||
|
"META-INF/NOTICE",
|
||||||
|
"META-INF/*.version",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependenciesInfo {
|
||||||
|
includeInApk = false
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding = true
|
||||||
|
buildConfig = true
|
||||||
|
|
||||||
|
// Disable some unused things
|
||||||
|
aidl = false
|
||||||
|
renderScript = false
|
||||||
|
shaders = false
|
||||||
|
}
|
||||||
|
|
||||||
|
lint {
|
||||||
|
abortOnError = false
|
||||||
|
checkReleaseBuilds = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.i18n)
|
||||||
|
implementation(projects.core.archive)
|
||||||
|
implementation(projects.core.common)
|
||||||
|
implementation(projects.coreMetadata)
|
||||||
|
implementation(projects.sourceApi)
|
||||||
|
implementation(projects.sourceLocal)
|
||||||
|
implementation(projects.data)
|
||||||
|
implementation(projects.domain)
|
||||||
|
implementation(projects.presentationCore)
|
||||||
|
implementation(projects.presentationWidget)
|
||||||
|
|
||||||
|
// Compose
|
||||||
|
implementation(compose.activity)
|
||||||
|
implementation(compose.foundation)
|
||||||
|
implementation(compose.material3.core)
|
||||||
|
implementation(compose.material.icons)
|
||||||
|
implementation(compose.animation)
|
||||||
|
implementation(compose.animation.graphics)
|
||||||
|
debugImplementation(compose.ui.tooling)
|
||||||
|
implementation(compose.ui.tooling.preview)
|
||||||
|
implementation(compose.ui.util)
|
||||||
|
|
||||||
|
implementation(androidx.interpolator)
|
||||||
|
|
||||||
|
implementation(androidx.paging.runtime)
|
||||||
|
implementation(androidx.paging.compose)
|
||||||
|
|
||||||
|
implementation(libs.bundles.sqlite)
|
||||||
|
|
||||||
|
implementation(kotlinx.reflect)
|
||||||
|
implementation(kotlinx.immutables)
|
||||||
|
|
||||||
|
implementation(platform(kotlinx.coroutines.bom))
|
||||||
|
implementation(kotlinx.bundles.coroutines)
|
||||||
|
|
||||||
|
// AndroidX libraries
|
||||||
|
implementation(androidx.annotation)
|
||||||
|
implementation(androidx.appcompat)
|
||||||
|
implementation(androidx.biometricktx)
|
||||||
|
implementation(androidx.constraintlayout)
|
||||||
|
implementation(androidx.corektx)
|
||||||
|
implementation(androidx.splashscreen)
|
||||||
|
implementation(androidx.recyclerview)
|
||||||
|
implementation(androidx.viewpager)
|
||||||
|
implementation(androidx.profileinstaller)
|
||||||
|
|
||||||
|
implementation(androidx.bundles.lifecycle)
|
||||||
|
|
||||||
|
// Job scheduling
|
||||||
|
implementation(androidx.workmanager)
|
||||||
|
|
||||||
|
// RxJava
|
||||||
|
implementation(libs.rxjava)
|
||||||
|
|
||||||
|
// Networking
|
||||||
|
implementation(libs.bundles.okhttp)
|
||||||
|
implementation(libs.okio)
|
||||||
|
implementation(libs.conscrypt.android) // TLS 1.3 support for Android < 10
|
||||||
|
|
||||||
|
// Data serialization (JSON, protobuf, xml)
|
||||||
|
implementation(kotlinx.bundles.serialization)
|
||||||
|
|
||||||
|
// HTML parser
|
||||||
|
implementation(libs.jsoup)
|
||||||
|
|
||||||
|
// Disk
|
||||||
|
implementation(libs.disklrucache)
|
||||||
|
implementation(libs.unifile)
|
||||||
|
|
||||||
|
// Preferences
|
||||||
|
implementation(libs.preferencektx)
|
||||||
|
|
||||||
|
// Dependency injection
|
||||||
|
implementation(libs.injekt)
|
||||||
|
|
||||||
|
// Image loading
|
||||||
|
implementation(platform(libs.coil.bom))
|
||||||
|
implementation(libs.bundles.coil)
|
||||||
|
implementation(libs.subsamplingscaleimageview) {
|
||||||
|
exclude(module = "image-decoder")
|
||||||
|
}
|
||||||
|
implementation(libs.image.decoder)
|
||||||
|
|
||||||
|
// UI libraries
|
||||||
|
implementation(libs.material)
|
||||||
|
implementation(libs.flexible.adapter.core)
|
||||||
|
implementation(libs.photoview)
|
||||||
|
implementation(libs.directionalviewpager) {
|
||||||
|
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||||
|
}
|
||||||
|
implementation(libs.insetter)
|
||||||
|
implementation(libs.bundles.richtext)
|
||||||
|
implementation(libs.aboutLibraries.compose)
|
||||||
|
implementation(libs.bundles.voyager)
|
||||||
|
implementation(libs.compose.materialmotion)
|
||||||
|
implementation(libs.swipe)
|
||||||
|
implementation(libs.compose.webview)
|
||||||
|
implementation(libs.compose.grid)
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
implementation(libs.logcat)
|
||||||
|
|
||||||
|
// Crash reports/analytics
|
||||||
|
"standardImplementation"(platform(libs.firebase.bom))
|
||||||
|
"standardImplementation"(libs.firebase.analytics)
|
||||||
|
"standardImplementation"(libs.firebase.crashlytics)
|
||||||
|
|
||||||
|
// Shizuku
|
||||||
|
implementation(libs.bundles.shizuku)
|
||||||
|
|
||||||
|
// Tests
|
||||||
|
testImplementation(libs.bundles.test)
|
||||||
|
|
||||||
|
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||||
|
// debugImplementation(libs.leakcanary.android)
|
||||||
|
implementation(libs.leakcanary.plumber)
|
||||||
|
|
||||||
|
testImplementation(kotlinx.coroutines.test)
|
||||||
|
}
|
||||||
|
|
||||||
|
androidComponents {
|
||||||
|
beforeVariants { variantBuilder ->
|
||||||
|
// Disables standardBenchmark
|
||||||
|
if (variantBuilder.buildType == "benchmark") {
|
||||||
|
variantBuilder.enable = variantBuilder.productFlavors.containsAll(
|
||||||
|
listOf("default" to "dev"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onVariants(selector().withFlavor("default" to "standard")) {
|
||||||
|
// Only excluding in standard flavor because this breaks
|
||||||
|
// Layout Inspector's Compose tree
|
||||||
|
it.packaging.resources.excludes.add("META-INF/*.version")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
||||||
|
withType<KotlinCompile> {
|
||||||
|
compilerOptions.freeCompilerArgs.addAll(
|
||||||
|
"-Xcontext-receivers",
|
||||||
|
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||||
|
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||||
|
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||||
|
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||||
|
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||||
|
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||||
|
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||||
|
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
||||||
|
"-opt-in=coil3.annotation.ExperimentalCoilApi",
|
||||||
|
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
|
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||||
|
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||||
|
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
dependencies {
|
||||||
|
classpath(kotlinx.gradle)
|
||||||
|
}
|
||||||
|
}
|
34
app/proguard-android-optimize.txt
Normal file
34
app/proguard-android-optimize.txt
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
-dontusemixedcaseclassnames
|
||||||
|
-ignorewarnings
|
||||||
|
-verbose
|
||||||
|
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
|
||||||
|
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||||
|
native <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepclassmembers enum * {
|
||||||
|
public static **[] values();
|
||||||
|
public static ** valueOf(java.lang.String);
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepclassmembers class * implements android.os.Parcelable {
|
||||||
|
public static final ** CREATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keep class androidx.annotation.Keep
|
||||||
|
|
||||||
|
-keep @androidx.annotation.Keep class * {*;}
|
||||||
|
|
||||||
|
-keepclasseswithmembers class * {
|
||||||
|
@androidx.annotation.Keep <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepclasseswithmembers class * {
|
||||||
|
@androidx.annotation.Keep <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keepclasseswithmembers class * {
|
||||||
|
@androidx.annotation.Keep <init>(...);
|
||||||
|
}
|
175
app/proguard-rules.pro
vendored
175
app/proguard-rules.pro
vendored
@ -1,63 +1,31 @@
|
|||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
|
|
||||||
-keep class eu.kanade.tachiyomi.injection.** { *; }
|
-keep,allowoptimization class eu.kanade.**
|
||||||
|
-keep,allowoptimization class tachiyomi.**
|
||||||
|
-keep,allowoptimization class mihon.**
|
||||||
|
|
||||||
# Retrolambda
|
# Keep common dependencies used in extensions
|
||||||
-dontwarn java.lang.invoke.*
|
-keep,allowoptimization class androidx.preference.** { public protected *; }
|
||||||
|
-keep,allowoptimization class kotlin.** { public protected *; }
|
||||||
|
-keep,allowoptimization class kotlinx.coroutines.** { public protected *; }
|
||||||
|
-keep,allowoptimization class kotlinx.serialization.** { public protected *; }
|
||||||
|
-keep,allowoptimization class kotlin.time.** { public protected *; }
|
||||||
|
-keep,allowoptimization class okhttp3.** { public protected *; }
|
||||||
|
-keep,allowoptimization class okio.** { public protected *; }
|
||||||
|
-keep,allowoptimization class org.jsoup.** { public protected *; }
|
||||||
|
-keep,allowoptimization class rx.** { public protected *; }
|
||||||
|
-keep,allowoptimization class app.cash.quickjs.** { public protected *; }
|
||||||
|
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
|
||||||
|
|
||||||
# OkHttp
|
# From extensions-lib
|
||||||
-keepattributes Signature
|
-keep,allowoptimization class eu.kanade.tachiyomi.network.interceptor.RateLimitInterceptorKt { public protected *; }
|
||||||
-keepattributes *Annotation*
|
-keep,allowoptimization class eu.kanade.tachiyomi.network.interceptor.SpecificHostRateLimitInterceptorKt { public protected *; }
|
||||||
-keep class com.squareup.okhttp.** { *; }
|
-keep,allowoptimization class eu.kanade.tachiyomi.network.NetworkHelper { public protected *; }
|
||||||
-keep interface com.squareup.okhttp.** { *; }
|
-keep,allowoptimization class eu.kanade.tachiyomi.network.OkHttpExtensionsKt { public protected *; }
|
||||||
-dontwarn com.squareup.okhttp.**
|
-keep,allowoptimization class eu.kanade.tachiyomi.network.RequestsKt { public protected *; }
|
||||||
-dontwarn okio.**
|
-keep,allowoptimization class eu.kanade.tachiyomi.AppInfo { public protected *; }
|
||||||
|
|
||||||
# Okio
|
|
||||||
-keep class sun.misc.Unsafe { *; }
|
|
||||||
-dontwarn java.nio.file.*
|
|
||||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
|
||||||
-dontwarn okio.**
|
|
||||||
|
|
||||||
# ButterKnife 7
|
|
||||||
-keep class butterknife.** { *; }
|
|
||||||
-dontwarn butterknife.internal.**
|
|
||||||
-keep class **$$ViewBinder { *; }
|
|
||||||
|
|
||||||
-keepclasseswithmembernames class * {
|
|
||||||
@butterknife.* <fields>;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keepclasseswithmembernames class * {
|
|
||||||
@butterknife.* <methods>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#Easy-Adapter v1.5.0
|
|
||||||
-keepattributes *Annotation*
|
|
||||||
-keepclassmembers class * extends uk.co.ribot.easyadapter.ItemViewHolder {
|
|
||||||
public <init>(...);
|
|
||||||
}
|
|
||||||
|
|
||||||
## GreenRobot EventBus specific rules ##
|
|
||||||
# https://github.com/greenrobot/EventBus/blob/master/HOWTO.md#proguard-configuration
|
|
||||||
-keepclassmembers class ** {
|
|
||||||
public void onEvent*(***);
|
|
||||||
}
|
|
||||||
|
|
||||||
# Don't warn for missing support classes
|
|
||||||
-dontwarn de.greenrobot.event.util.*$Support
|
|
||||||
-dontwarn de.greenrobot.event.util.*$SupportManagerFragment
|
|
||||||
|
|
||||||
# Glide specific rules #
|
|
||||||
# https://github.com/bumptech/glide
|
|
||||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
|
||||||
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
|
||||||
**[] $VALUES;
|
|
||||||
public *;
|
|
||||||
}
|
|
||||||
|
|
||||||
# RxJava 1.1.0
|
|
||||||
|
|
||||||
|
##---------------Begin: proguard configuration for RxJava 1.x ----------
|
||||||
-dontwarn sun.misc.**
|
-dontwarn sun.misc.**
|
||||||
|
|
||||||
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
|
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
|
||||||
@ -73,77 +41,42 @@
|
|||||||
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
# AppCombat
|
-dontnote rx.internal.util.PlatformDependent
|
||||||
-keep public class android.support.v7.widget.** { *; }
|
##---------------End: proguard configuration for RxJava 1.x ----------
|
||||||
-keep public class android.support.v7.internal.widget.** { *; }
|
|
||||||
-keep public class android.support.v7.internal.view.menu.** { *; }
|
|
||||||
|
|
||||||
-keep public class * extends android.support.v4.view.ActionProvider {
|
##---------------Begin: proguard configuration for okhttp ----------
|
||||||
public <init>(android.content.Context);
|
-keepclasseswithmembers class okhttp3.MultipartBody$Builder { *; }
|
||||||
|
##---------------End: proguard configuration for okhttp ----------
|
||||||
|
|
||||||
|
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
||||||
|
-keepattributes *Annotation*, InnerClasses
|
||||||
|
-dontnote kotlinx.serialization.** # core serialization annotations
|
||||||
|
|
||||||
|
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
|
||||||
|
-keepclassmembers class kotlinx.serialization.json.** {
|
||||||
|
*** Companion;
|
||||||
|
}
|
||||||
|
-keepclasseswithmembers class kotlinx.serialization.json.** {
|
||||||
|
kotlinx.serialization.KSerializer serializer(...);
|
||||||
}
|
}
|
||||||
|
|
||||||
# Icepick
|
-keep,includedescriptorclasses class eu.kanade.**$$serializer { *; }
|
||||||
-dontwarn icepick.**
|
-keepclassmembers class eu.kanade.** {
|
||||||
-keep class **$$Icepick { *; }
|
*** Companion;
|
||||||
-keepclasseswithmembernames class * {
|
}
|
||||||
@icepick.* <fields>;
|
-keepclasseswithmembers class eu.kanade.** {
|
||||||
|
kotlinx.serialization.KSerializer serializer(...);
|
||||||
}
|
}
|
||||||
|
|
||||||
## GSON 2.2.4 specific rules ##
|
-keep class kotlinx.serialization.**
|
||||||
|
-keepclassmembers class kotlinx.serialization.** {
|
||||||
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
<methods>;
|
||||||
# removes such information by default, so configure it to keep all of it.
|
|
||||||
-keepattributes Signature
|
|
||||||
|
|
||||||
# For using GSON @Expose annotation
|
|
||||||
-keepattributes *Annotation*
|
|
||||||
|
|
||||||
-keepattributes EnclosingMethod
|
|
||||||
|
|
||||||
# Gson specific classes
|
|
||||||
-keep class sun.misc.Unsafe { *; }
|
|
||||||
-keep class com.google.gson.stream.** { *; }
|
|
||||||
|
|
||||||
## ACRA 4.5.0 specific rules ##
|
|
||||||
|
|
||||||
# we need line numbers in our stack traces otherwise they are pretty useless
|
|
||||||
-renamesourcefileattribute SourceFile
|
|
||||||
-keepattributes SourceFile,LineNumberTable
|
|
||||||
|
|
||||||
# ACRA needs "annotations" so add this...
|
|
||||||
-keepattributes *Annotation*
|
|
||||||
|
|
||||||
# keep this class so that logging will show 'ACRA' and not a obfuscated name like 'a'.
|
|
||||||
# Note: if you are removing log messages elsewhere in this file then this isn't necessary
|
|
||||||
-keep class org.acra.ACRA {
|
|
||||||
*;
|
|
||||||
}
|
}
|
||||||
|
##---------------End: proguard configuration for kotlinx.serialization ----------
|
||||||
|
|
||||||
# keep this around for some enums that ACRA needs
|
# XmlUtil
|
||||||
-keep class org.acra.ReportingInteractionMode {
|
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
||||||
*;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keepnames class org.acra.sender.HttpSender$** {
|
# Firebase
|
||||||
*;
|
-keep class com.google.firebase.installations.** { *; }
|
||||||
}
|
-keep interface com.google.firebase.installations.** { *; }
|
||||||
|
|
||||||
-keepnames class org.acra.ReportField {
|
|
||||||
*;
|
|
||||||
}
|
|
||||||
|
|
||||||
# keep this otherwise it is removed by ProGuard
|
|
||||||
-keep public class org.acra.ErrorReporter {
|
|
||||||
public void addCustomData(java.lang.String,java.lang.String);
|
|
||||||
public void putCustomData(java.lang.String,java.lang.String);
|
|
||||||
public void removeCustomData(java.lang.String);
|
|
||||||
}
|
|
||||||
|
|
||||||
# keep this otherwise it is removed by ProGuard
|
|
||||||
-keep public class org.acra.ErrorReporter {
|
|
||||||
public void handleSilentException(java.lang.Throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
# Keep the support library
|
|
||||||
-keep class org.acra.** { *; }
|
|
||||||
-keep interface org.acra.** { *; }
|
|
||||||
|
46
app/shortcuts.xml
Normal file
46
app/shortcuts.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<shortcut
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/sc_collections_bookmark_48dp"
|
||||||
|
android:shortcutDisabledMessage="@string/app_not_available"
|
||||||
|
android:shortcutId="show_library"
|
||||||
|
android:shortcutLongLabel="@string/label_library"
|
||||||
|
android:shortcutShortLabel="@string/label_library">
|
||||||
|
<intent
|
||||||
|
android:action="eu.kanade.tachiyomi.SHOW_LIBRARY"
|
||||||
|
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||||
|
</shortcut>
|
||||||
|
<shortcut
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/sc_new_releases_48dp"
|
||||||
|
android:shortcutDisabledMessage="@string/app_not_available"
|
||||||
|
android:shortcutId="show_recently_updated"
|
||||||
|
android:shortcutLongLabel="@string/label_recent_updates"
|
||||||
|
android:shortcutShortLabel="@string/label_recent_updates">
|
||||||
|
<intent
|
||||||
|
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"
|
||||||
|
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||||
|
</shortcut>
|
||||||
|
<shortcut
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/sc_history_48dp"
|
||||||
|
android:shortcutDisabledMessage="@string/app_not_available"
|
||||||
|
android:shortcutId="show_recently_read"
|
||||||
|
android:shortcutLongLabel="@string/label_recent_manga"
|
||||||
|
android:shortcutShortLabel="@string/label_recent_manga">
|
||||||
|
<intent
|
||||||
|
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_READ"
|
||||||
|
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||||
|
</shortcut>
|
||||||
|
<shortcut
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/sc_explore_48dp"
|
||||||
|
android:shortcutDisabledMessage="@string/app_not_available"
|
||||||
|
android:shortcutId="show_catalogues"
|
||||||
|
android:shortcutLongLabel="@string/browse"
|
||||||
|
android:shortcutShortLabel="@string/browse">
|
||||||
|
<intent
|
||||||
|
android:action="eu.kanade.tachiyomi.SHOW_CATALOGUES"
|
||||||
|
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
|
||||||
|
</shortcut>
|
||||||
|
</shortcuts>
|
23
app/src/debug/res/drawable/ic_launcher_background.xml
Normal file
23
app/src/debug/res/drawable/ic_launcher_background.xml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="432"
|
||||||
|
android:viewportHeight="432">
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M0,0h432v432h-432z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M0,0h432v432h-432z"
|
||||||
|
android:fillColor="#FAFAFA"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M0,0h432v432h-432z"
|
||||||
|
android:fillColor="#2E3943"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M322.13,215.5C322.13,272.66 274.64,319 216.07,319C157.49,319 110,272.66 110,215.5C110,158.34 157.49,112 216.07,112C274.64,112 322.13,158.34 322.13,215.5Z"
|
||||||
|
android:fillColor="#F2FAFF"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M216.07,299.59C263.66,299.59 302.24,261.94 302.24,215.5C302.24,169.06 263.66,131.41 216.07,131.41C168.47,131.41 129.89,169.06 129.89,215.5C129.89,261.94 168.47,299.59 216.07,299.59ZM216.07,319C274.64,319 322.13,272.66 322.13,215.5C322.13,158.34 274.64,112 216.07,112C157.49,112 110,158.34 110,215.5C110,272.66 157.49,319 216.07,319Z"
|
||||||
|
android:fillColor="#7EBBED"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
9
app/src/debug/res/drawable/ic_launcher_foreground.xml
Normal file
9
app/src/debug/res/drawable/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="432"
|
||||||
|
android:viewportHeight="432">
|
||||||
|
<path
|
||||||
|
android:pathData="M182.03,188.7L181.33,172.69C183.42,173.09 185.91,173.19 191.57,173.19C198.44,173.19 207.49,172.79 212.16,172.19C214.15,171.99 214.95,171.7 216.24,171L226.98,180.15C225.98,181.54 225.68,182.14 224.59,184.92C223.7,187.11 219.62,199.74 218.03,205.11C225.39,206.6 229.46,207.7 235.03,209.98C235.73,205.11 235.83,202.52 235.83,193.67C235.83,191.39 235.73,190.09 235.43,188.01L252.74,188.6C252.24,190.99 252.14,191.98 252.04,195.86C251.64,205.21 251.24,209.68 250.25,216.45C257.11,219.93 257.11,219.93 260.59,221.82C262.38,222.81 262.78,223.01 263.97,223.41L258.2,242.01C255.42,239.52 251.54,236.83 245.87,233.65C240.9,245.49 232.65,254.14 220.12,261C215.94,255.43 212.76,252.05 207.68,248.07C215.04,244.59 218.43,242.4 222.3,238.72C226.08,235.04 228.57,231.46 230.96,226.09C224.59,223.21 220.51,221.92 213.45,220.43C209.38,232.56 206.09,240.32 203.21,244.99C199.33,251.25 194.06,254.54 187.99,254.54C183.32,254.54 178.55,252.45 175.07,248.87C171.09,244.79 169,239.12 169,232.56C169,222.81 173.67,214.36 181.83,209.09C187.1,205.71 192.67,204.21 201.52,203.72C203.31,197.85 204.8,192.78 206.19,187.11C201.82,187.51 196.35,187.81 189.68,188.1C186.1,188.2 184.91,188.3 182.03,188.7ZM197.14,218.93C192.47,219.73 189.68,221.22 187.2,224.4C185.31,226.59 184.41,229.18 184.41,231.96C184.41,235.04 185.91,237.33 187.8,237.33C190.08,237.33 192.67,232.16 197.14,218.93Z"
|
||||||
|
android:fillColor="#031019"/>
|
||||||
|
</vector>
|
11
app/src/dev/java/mihon/core/firebase/FirebaseConfig.kt
Normal file
11
app/src/dev/java/mihon/core/firebase/FirebaseConfig.kt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package mihon.core.firebase
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
object FirebaseConfig {
|
||||||
|
fun init(context: Context) = Unit
|
||||||
|
|
||||||
|
fun setAnalyticsEnabled(enabled: Boolean) = Unit
|
||||||
|
|
||||||
|
fun setCrashlyticsEnabled(enabled: Boolean) = Unit
|
||||||
|
}
|
@ -1,84 +1,235 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="eu.kanade.tachiyomi" >
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<!-- Internet -->
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
|
||||||
|
<!-- Storage -->
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
tools:ignore="ScopedStorage" />
|
||||||
|
|
||||||
|
<!-- For background jobs -->
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
|
||||||
|
<!-- For managing extensions -->
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||||
|
<!-- To view extension packages in API 30+ -->
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||||
|
tools:ignore="QueryAllPackagesPermission" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.READ_APP_SPECIFIC_LOCALES"
|
||||||
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="false"
|
||||||
|
android:enableOnBackInvokedCallback="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:hardwareAccelerated="true"
|
android:largeHeap="true"
|
||||||
android:theme="@style/AppTheme" >
|
android:localeConfig="@xml/locales_config"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
|
android:preserveLegacyExternalStorage="true"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.Tachiyomi">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.MainActivity">
|
android:name=".ui.main.MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/Theme.Tachiyomi.SplashScreen">
|
||||||
<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 -->
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.shortcuts"
|
||||||
|
android:resource="@xml/shortcuts" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.manga.MangaActivity"
|
android:name=".crash.CrashActivity"
|
||||||
android:parentActivityName=".ui.main.MainActivity" >
|
android:exported="false"
|
||||||
|
android:process=":error_handler" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.deeplink.DeepLinkActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/action_search"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
|
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="eu.kanade.tachiyomi.SEARCH" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.searchable"
|
||||||
|
android:resource="@xml/searchable" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.reader.ReaderActivity"
|
android:name=".ui.reader.ReaderActivity"
|
||||||
android:parentActivityName=".ui.manga.MangaActivity">
|
android:exported="false"
|
||||||
|
android:launchMode="singleTask">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="com.samsung.android.support.REMOTE_ACTION"
|
||||||
android:value=".ui.manga.MangaActivity" />
|
android:resource="@xml/s_pen_actions" />
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name=".ui.setting.SettingsActivity"
|
|
||||||
android:label="@string/label_settings"
|
|
||||||
android:parentActivityName=".ui.main.MainActivity" >
|
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name=".ui.library.category.CategoryActivity"
|
|
||||||
android:label="@string/label_categories"
|
|
||||||
android:parentActivityName=".ui.main.MainActivity">
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.SettingsDownloadsFragment$CustomLayoutPickerActivity"
|
android:name=".ui.security.UnlockActivity"
|
||||||
android:label="@string/app_name"
|
android:exported="false"
|
||||||
android:theme="@style/FilePickerTheme">
|
android:theme="@style/Theme.Tachiyomi" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.webview.WebViewActivity"
|
||||||
|
android:configChanges="uiMode|orientation|screenSize"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".extension.util.ExtensionInstallActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.setting.track.TrackLoginActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/track_activity_name">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="mihon" />
|
||||||
|
|
||||||
|
<data android:host="anilist-auth" />
|
||||||
|
<data android:host="bangumi-auth" />
|
||||||
|
<data android:host="myanimelist-auth" />
|
||||||
|
<data android:host="shikimori-auth" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service android:name=".data.sync.LibraryUpdateService"
|
|
||||||
android:exported="false"/>
|
|
||||||
|
|
||||||
<service android:name=".data.download.DownloadService"
|
|
||||||
android:exported="false"/>
|
|
||||||
|
|
||||||
<service android:name=".data.sync.UpdateMangaSyncService"
|
|
||||||
android:exported="false"/>
|
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".data.sync.LibraryUpdateService$SyncOnConnectionAvailable"
|
android:name=".data.notification.NotificationReceiver"
|
||||||
android:enabled="false">
|
android:exported="false" />
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver
|
<service
|
||||||
android:name=".data.sync.LibraryUpdateAlarm">
|
android:name=".extension.util.ExtensionInstallService"
|
||||||
<intent-filter>
|
android:exported="false"
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
android:foregroundServiceType="shortService" />
|
||||||
<action android:name="eu.kanade.UPDATE_LIBRARY" />
|
|
||||||
</intent-filter>
|
<service
|
||||||
</receiver>
|
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||||
|
android:enabled="false"
|
||||||
|
android:exported="false">
|
||||||
|
<meta-data
|
||||||
|
android:name="autoStoreLocales"
|
||||||
|
android:value="true" />
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
||||||
|
android:foregroundServiceType="dataSync"
|
||||||
|
tools:node="merge" />
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.provider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/provider_paths" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="rikka.shizuku.ShizukuProvider"
|
||||||
|
android:authorities="${applicationId}.shizuku"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:multiprocess="false"
|
||||||
|
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="eu.kanade.tachiyomi.data.cache.CoverGlideModule"
|
android:name="android.webkit.WebView.EnableSafeBrowsing"
|
||||||
android:value="GlideModule" />
|
android:value="false" />
|
||||||
|
<meta-data
|
||||||
|
android:name="android.webkit.WebView.MetricsOptOut"
|
||||||
|
android:value="true" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
Binary file not shown.
Binary file not shown.
37790
app/src/main/baseline-prof.txt
Normal file
37790
app/src/main/baseline-prof.txt
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
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.common.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
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package eu.kanade.core.preference
|
||||||
|
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import tachiyomi.core.common.preference.Preference
|
||||||
|
|
||||||
|
class PreferenceMutableState<T>(
|
||||||
|
private val preference: Preference<T>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
) : MutableState<T> {
|
||||||
|
|
||||||
|
private val state = mutableStateOf(preference.get())
|
||||||
|
|
||||||
|
init {
|
||||||
|
preference.changes()
|
||||||
|
.onEach { state.value = it }
|
||||||
|
.launchIn(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override var value: T
|
||||||
|
get() = state.value
|
||||||
|
set(value) {
|
||||||
|
preference.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun component1(): T {
|
||||||
|
return state.value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun component2(): (T) -> Unit {
|
||||||
|
return preference::set
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Preference<T>.asState(scope: CoroutineScope) = PreferenceMutableState(this, scope)
|
156
app/src/main/java/eu/kanade/core/util/CollectionUtils.kt
Normal file
156
app/src/main/java/eu/kanade/core/util/CollectionUtils.kt
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
package eu.kanade.core.util
|
||||||
|
|
||||||
|
import androidx.compose.ui.util.fastForEach
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
|
fun <T : R, R : Any> List<T>.insertSeparators(
|
||||||
|
generator: (before: T?, after: T?) -> R?,
|
||||||
|
): List<R> {
|
||||||
|
if (isEmpty()) return emptyList()
|
||||||
|
val newList = mutableListOf<R>()
|
||||||
|
for (i in -1..lastIndex) {
|
||||||
|
val before = getOrNull(i)
|
||||||
|
before?.let(newList::add)
|
||||||
|
val after = getOrNull(i + 1)
|
||||||
|
val separator = generator.invoke(before, after)
|
||||||
|
separator?.let(newList::add)
|
||||||
|
}
|
||||||
|
return newList
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to [eu.kanade.core.util.insertSeparators] but iterates from last to first element
|
||||||
|
*/
|
||||||
|
fun <T : R, R : Any> List<T>.insertSeparatorsReversed(
|
||||||
|
generator: (before: T?, after: T?) -> R?,
|
||||||
|
): List<R> {
|
||||||
|
if (isEmpty()) return emptyList()
|
||||||
|
val newList = mutableListOf<R>()
|
||||||
|
for (i in size downTo 0) {
|
||||||
|
val after = getOrNull(i)
|
||||||
|
after?.let(newList::add)
|
||||||
|
val before = getOrNull(i - 1)
|
||||||
|
val separator = generator.invoke(before, after)
|
||||||
|
separator?.let(newList::add)
|
||||||
|
}
|
||||||
|
return newList.asReversed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
||||||
|
if (shouldAdd) {
|
||||||
|
add(value)
|
||||||
|
} else {
|
||||||
|
remove(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing only elements matching the given [predicate].
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
|
||||||
|
contract { callsInPlace(predicate) }
|
||||||
|
val destination = ArrayList<T>()
|
||||||
|
fastForEach { if (predicate(it)) destination.add(it) }
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing all elements not matching the given [predicate].
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
|
||||||
|
contract { callsInPlace(predicate) }
|
||||||
|
val destination = ArrayList<T>()
|
||||||
|
fastForEach { if (!predicate(it)) destination.add(it) }
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing only the non-null results of applying the
|
||||||
|
* given [transform] function to each element in the original collection.
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
|
||||||
|
contract { callsInPlace(transform) }
|
||||||
|
val destination = ArrayList<R>()
|
||||||
|
fastForEach { element ->
|
||||||
|
transform(element)?.let(destination::add)
|
||||||
|
}
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits the original collection into pair of lists,
|
||||||
|
* where *first* list contains elements for which [predicate] yielded `true`,
|
||||||
|
* while *second* list contains elements for which [predicate] yielded `false`.
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T> List<T>.fastPartition(predicate: (T) -> Boolean): Pair<List<T>, List<T>> {
|
||||||
|
contract { callsInPlace(predicate) }
|
||||||
|
val first = ArrayList<T>()
|
||||||
|
val second = ArrayList<T>()
|
||||||
|
fastForEach {
|
||||||
|
if (predicate(it)) {
|
||||||
|
first.add(it)
|
||||||
|
} else {
|
||||||
|
second.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Pair(first, second)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of entries not matching the given [predicate].
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T> List<T>.fastCountNot(predicate: (T) -> Boolean): Int {
|
||||||
|
contract { callsInPlace(predicate) }
|
||||||
|
var count = size
|
||||||
|
fastForEach { if (predicate(it)) --count }
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing only elements from the given collection
|
||||||
|
* having distinct keys returned by the given [selector] function.
|
||||||
|
*
|
||||||
|
* Among elements of the given collection with equal keys, only the first one will be present in the resulting list.
|
||||||
|
* The elements in the resulting list are in the same order as they were in the source collection.
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T, K> List<T>.fastDistinctBy(selector: (T) -> K): List<T> {
|
||||||
|
contract { callsInPlace(selector) }
|
||||||
|
val set = HashSet<K>()
|
||||||
|
val list = ArrayList<T>()
|
||||||
|
fastForEach {
|
||||||
|
val key = selector(it)
|
||||||
|
if (set.add(key)) list.add(it)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
13
app/src/main/java/eu/kanade/core/util/SourceUtil.kt
Normal file
13
app/src/main/java/eu/kanade/core/util/SourceUtil.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package eu.kanade.core.util
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ifSourcesLoaded(): Boolean {
|
||||||
|
return remember { Injekt.get<SourceManager>().isInitialized }.collectAsState().value
|
||||||
|
}
|
195
app/src/main/java/eu/kanade/domain/DomainModule.kt
Normal file
195
app/src/main/java/eu/kanade/domain/DomainModule.kt
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
package eu.kanade.domain
|
||||||
|
|
||||||
|
import eu.kanade.domain.chapter.interactor.GetAvailableScanlators
|
||||||
|
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||||
|
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||||
|
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||||
|
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
||||||
|
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||||
|
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||||
|
import eu.kanade.domain.extension.interactor.TrustExtension
|
||||||
|
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
|
||||||
|
import eu.kanade.domain.manga.interactor.SetExcludedScanlators
|
||||||
|
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
||||||
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
|
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||||
|
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
||||||
|
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
||||||
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
|
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||||
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
|
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||||
|
import eu.kanade.domain.track.interactor.AddTracks
|
||||||
|
import eu.kanade.domain.track.interactor.RefreshTracks
|
||||||
|
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
|
||||||
|
import eu.kanade.domain.track.interactor.TrackChapter
|
||||||
|
import mihon.data.repository.ExtensionRepoRepositoryImpl
|
||||||
|
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
||||||
|
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount
|
||||||
|
import mihon.domain.extensionrepo.interactor.ReplaceExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
|
||||||
|
import mihon.domain.extensionrepo.service.ExtensionRepoService
|
||||||
|
import mihon.domain.upcoming.interactor.GetUpcomingManga
|
||||||
|
import tachiyomi.data.category.CategoryRepositoryImpl
|
||||||
|
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
||||||
|
import tachiyomi.data.history.HistoryRepositoryImpl
|
||||||
|
import tachiyomi.data.manga.MangaRepositoryImpl
|
||||||
|
import tachiyomi.data.release.ReleaseServiceImpl
|
||||||
|
import tachiyomi.data.source.SourceRepositoryImpl
|
||||||
|
import tachiyomi.data.source.StubSourceRepositoryImpl
|
||||||
|
import tachiyomi.data.track.TrackRepositoryImpl
|
||||||
|
import tachiyomi.data.updates.UpdatesRepositoryImpl
|
||||||
|
import tachiyomi.domain.category.interactor.CreateCategoryWithName
|
||||||
|
import tachiyomi.domain.category.interactor.DeleteCategory
|
||||||
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
|
import tachiyomi.domain.category.interactor.RenameCategory
|
||||||
|
import tachiyomi.domain.category.interactor.ReorderCategory
|
||||||
|
import tachiyomi.domain.category.interactor.ResetCategoryFlags
|
||||||
|
import tachiyomi.domain.category.interactor.SetDisplayMode
|
||||||
|
import tachiyomi.domain.category.interactor.SetMangaCategories
|
||||||
|
import tachiyomi.domain.category.interactor.SetSortModeForCategory
|
||||||
|
import tachiyomi.domain.category.interactor.UpdateCategory
|
||||||
|
import tachiyomi.domain.category.repository.CategoryRepository
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetChapter
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
|
import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
||||||
|
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||||
|
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||||
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
import tachiyomi.domain.history.interactor.GetHistory
|
||||||
|
import tachiyomi.domain.history.interactor.GetNextChapters
|
||||||
|
import tachiyomi.domain.history.interactor.GetTotalReadDuration
|
||||||
|
import tachiyomi.domain.history.interactor.RemoveHistory
|
||||||
|
import tachiyomi.domain.history.interactor.UpsertHistory
|
||||||
|
import tachiyomi.domain.history.repository.HistoryRepository
|
||||||
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||||
|
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
||||||
|
import tachiyomi.domain.manga.interactor.GetFavorites
|
||||||
|
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
||||||
|
import tachiyomi.domain.manga.interactor.GetManga
|
||||||
|
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
|
||||||
|
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
|
||||||
|
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||||
|
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
||||||
|
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
|
||||||
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
||||||
|
import tachiyomi.domain.release.service.ReleaseService
|
||||||
|
import tachiyomi.domain.source.interactor.GetRemoteManga
|
||||||
|
import tachiyomi.domain.source.interactor.GetSourcesWithNonLibraryManga
|
||||||
|
import tachiyomi.domain.source.repository.SourceRepository
|
||||||
|
import tachiyomi.domain.source.repository.StubSourceRepository
|
||||||
|
import tachiyomi.domain.track.interactor.DeleteTrack
|
||||||
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
|
import tachiyomi.domain.track.interactor.GetTracksPerManga
|
||||||
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
|
import tachiyomi.domain.track.repository.TrackRepository
|
||||||
|
import tachiyomi.domain.updates.interactor.GetUpdates
|
||||||
|
import tachiyomi.domain.updates.repository.UpdatesRepository
|
||||||
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
|
import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
|
import uy.kohesive.injekt.api.addFactory
|
||||||
|
import uy.kohesive.injekt.api.addSingletonFactory
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class DomainModule : InjektModule {
|
||||||
|
|
||||||
|
override fun InjektRegistrar.registerInjectables() {
|
||||||
|
addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) }
|
||||||
|
addFactory { GetCategories(get()) }
|
||||||
|
addFactory { ResetCategoryFlags(get(), get()) }
|
||||||
|
addFactory { SetDisplayMode(get()) }
|
||||||
|
addFactory { SetSortModeForCategory(get(), get()) }
|
||||||
|
addFactory { CreateCategoryWithName(get(), get()) }
|
||||||
|
addFactory { RenameCategory(get()) }
|
||||||
|
addFactory { ReorderCategory(get()) }
|
||||||
|
addFactory { UpdateCategory(get()) }
|
||||||
|
addFactory { DeleteCategory(get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
|
||||||
|
addFactory { GetDuplicateLibraryManga(get()) }
|
||||||
|
addFactory { GetFavorites(get()) }
|
||||||
|
addFactory { GetLibraryManga(get()) }
|
||||||
|
addFactory { GetMangaWithChapters(get(), get()) }
|
||||||
|
addFactory { GetMangaByUrlAndSourceId(get()) }
|
||||||
|
addFactory { GetManga(get()) }
|
||||||
|
addFactory { GetNextChapters(get(), get(), get()) }
|
||||||
|
addFactory { GetUpcomingManga(get()) }
|
||||||
|
addFactory { ResetViewerFlags(get()) }
|
||||||
|
addFactory { SetMangaChapterFlags(get()) }
|
||||||
|
addFactory { FetchInterval(get()) }
|
||||||
|
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
||||||
|
addFactory { SetMangaViewerFlags(get()) }
|
||||||
|
addFactory { NetworkToLocalManga(get()) }
|
||||||
|
addFactory { UpdateManga(get(), get()) }
|
||||||
|
addFactory { SetMangaCategories(get()) }
|
||||||
|
addFactory { GetExcludedScanlators(get()) }
|
||||||
|
addFactory { SetExcludedScanlators(get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||||
|
addFactory { GetApplicationRelease(get(), get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
||||||
|
addFactory { TrackChapter(get(), get(), get(), get()) }
|
||||||
|
addFactory { AddTracks(get(), get(), get(), get()) }
|
||||||
|
addFactory { RefreshTracks(get(), get(), get(), get()) }
|
||||||
|
addFactory { DeleteTrack(get()) }
|
||||||
|
addFactory { GetTracksPerManga(get()) }
|
||||||
|
addFactory { GetTracks(get()) }
|
||||||
|
addFactory { InsertTrack(get()) }
|
||||||
|
addFactory { SyncChapterProgressWithTrack(get(), get(), get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||||
|
addFactory { GetChapter(get()) }
|
||||||
|
addFactory { GetChaptersByMangaId(get()) }
|
||||||
|
addFactory { GetChapterByUrlAndMangaId(get()) }
|
||||||
|
addFactory { UpdateChapter(get()) }
|
||||||
|
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
||||||
|
addFactory { ShouldUpdateDbChapter() }
|
||||||
|
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||||
|
addFactory { GetAvailableScanlators(get()) }
|
||||||
|
addFactory { FilterChaptersForDownload(get(), get(), get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||||
|
addFactory { GetHistory(get()) }
|
||||||
|
addFactory { UpsertHistory(get()) }
|
||||||
|
addFactory { RemoveHistory(get()) }
|
||||||
|
addFactory { GetTotalReadDuration(get()) }
|
||||||
|
|
||||||
|
addFactory { DeleteDownload(get(), get()) }
|
||||||
|
|
||||||
|
addFactory { GetExtensionsByType(get(), get()) }
|
||||||
|
addFactory { GetExtensionSources(get()) }
|
||||||
|
addFactory { GetExtensionLanguages(get(), get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<UpdatesRepository> { UpdatesRepositoryImpl(get()) }
|
||||||
|
addFactory { GetUpdates(get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
|
||||||
|
addSingletonFactory<StubSourceRepository> { StubSourceRepositoryImpl(get()) }
|
||||||
|
addFactory { GetEnabledSources(get(), get()) }
|
||||||
|
addFactory { GetLanguagesWithSources(get(), get()) }
|
||||||
|
addFactory { GetRemoteManga(get()) }
|
||||||
|
addFactory { GetSourcesWithFavoriteCount(get(), get()) }
|
||||||
|
addFactory { GetSourcesWithNonLibraryManga(get()) }
|
||||||
|
addFactory { SetMigrateSorting(get()) }
|
||||||
|
addFactory { ToggleLanguage(get()) }
|
||||||
|
addFactory { ToggleSource(get()) }
|
||||||
|
addFactory { ToggleSourcePin(get()) }
|
||||||
|
addFactory { TrustExtension(get(), get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) }
|
||||||
|
addFactory { ExtensionRepoService(get(), get()) }
|
||||||
|
addFactory { GetExtensionRepo(get()) }
|
||||||
|
addFactory { GetExtensionRepoCount(get()) }
|
||||||
|
addFactory { CreateExtensionRepo(get(), get()) }
|
||||||
|
addFactory { DeleteExtensionRepo(get()) }
|
||||||
|
addFactory { ReplaceExtensionRepo(get()) }
|
||||||
|
addFactory { UpdateExtensionRepo(get(), get()) }
|
||||||
|
}
|
||||||
|
}
|
33
app/src/main/java/eu/kanade/domain/base/BasePreferences.kt
Normal file
33
app/src/main/java/eu/kanade/domain/base/BasePreferences.kt
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package eu.kanade.domain.base
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import tachiyomi.core.common.preference.Preference
|
||||||
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
|
||||||
|
class BasePreferences(
|
||||||
|
val context: Context,
|
||||||
|
private val preferenceStore: PreferenceStore,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun downloadedOnly() = preferenceStore.getBoolean(
|
||||||
|
Preference.appStateKey("pref_downloaded_only"),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun incognitoMode() = preferenceStore.getBoolean(Preference.appStateKey("incognito_mode"), false)
|
||||||
|
|
||||||
|
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
||||||
|
|
||||||
|
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
|
||||||
|
|
||||||
|
enum class ExtensionInstaller(val titleRes: StringResource, val requiresSystemPermission: Boolean) {
|
||||||
|
LEGACY(MR.strings.ext_installer_legacy, true),
|
||||||
|
PACKAGEINSTALLER(MR.strings.ext_installer_packageinstaller, true),
|
||||||
|
SHIZUKU(MR.strings.ext_installer_shizuku, false),
|
||||||
|
PRIVATE(MR.strings.ext_installer_private, false),
|
||||||
|
}
|
||||||
|
|
||||||
|
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package eu.kanade.domain.base
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import eu.kanade.domain.base.BasePreferences.ExtensionInstaller
|
||||||
|
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
|
||||||
|
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import tachiyomi.core.common.preference.Preference
|
||||||
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
|
import tachiyomi.core.common.preference.getEnum
|
||||||
|
|
||||||
|
class ExtensionInstallerPreference(
|
||||||
|
private val context: Context,
|
||||||
|
preferenceStore: PreferenceStore,
|
||||||
|
) : Preference<ExtensionInstaller> {
|
||||||
|
|
||||||
|
private val basePref = preferenceStore.getEnum(key(), defaultValue())
|
||||||
|
|
||||||
|
override fun key() = "extension_installer"
|
||||||
|
|
||||||
|
val entries get() = ExtensionInstaller.entries.run {
|
||||||
|
if (context.hasMiuiPackageInstaller) {
|
||||||
|
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
|
||||||
|
} else {
|
||||||
|
toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun defaultValue() = if (context.hasMiuiPackageInstaller) {
|
||||||
|
ExtensionInstaller.LEGACY
|
||||||
|
} else {
|
||||||
|
ExtensionInstaller.PACKAGEINSTALLER
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun check(value: ExtensionInstaller): ExtensionInstaller {
|
||||||
|
when (value) {
|
||||||
|
ExtensionInstaller.PACKAGEINSTALLER -> {
|
||||||
|
if (context.hasMiuiPackageInstaller) return ExtensionInstaller.LEGACY
|
||||||
|
}
|
||||||
|
ExtensionInstaller.SHIZUKU -> {
|
||||||
|
if (!context.isShizukuInstalled) return defaultValue()
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(): ExtensionInstaller {
|
||||||
|
val value = basePref.get()
|
||||||
|
val checkedValue = check(value)
|
||||||
|
if (value != checkedValue) {
|
||||||
|
basePref.set(checkedValue)
|
||||||
|
}
|
||||||
|
return checkedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun set(value: ExtensionInstaller) {
|
||||||
|
basePref.set(check(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isSet() = basePref.isSet()
|
||||||
|
|
||||||
|
override fun delete() = basePref.delete()
|
||||||
|
|
||||||
|
override fun changes() = basePref.changes()
|
||||||
|
|
||||||
|
override fun stateIn(scope: CoroutineScope) = basePref.stateIn(scope)
|
||||||
|
}
|
@ -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() }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package eu.kanade.domain.chapter.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||||
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.common.util.lang.withNonCancellableContext
|
||||||
|
import tachiyomi.core.common.util.system.logcat
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.chapter.model.ChapterUpdate
|
||||||
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
import tachiyomi.domain.download.service.DownloadPreferences
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
|
class SetReadStatus(
|
||||||
|
private val downloadPreferences: DownloadPreferences,
|
||||||
|
private val deleteDownload: DeleteDownload,
|
||||||
|
private val mangaRepository: MangaRepository,
|
||||||
|
private val chapterRepository: ChapterRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val mapper = { chapter: Chapter, read: Boolean ->
|
||||||
|
ChapterUpdate(
|
||||||
|
read = read,
|
||||||
|
lastPageRead = if (!read) 0 else null,
|
||||||
|
id = chapter.id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun await(read: Boolean, vararg chapters: Chapter): Result = withNonCancellableContext {
|
||||||
|
val chaptersToUpdate = chapters.filter {
|
||||||
|
when (read) {
|
||||||
|
true -> !it.read
|
||||||
|
false -> it.read || it.lastPageRead > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chaptersToUpdate.isEmpty()) {
|
||||||
|
return@withNonCancellableContext Result.NoChapters
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
chapterRepository.updateAll(
|
||||||
|
chaptersToUpdate.map { mapper(it, read) },
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
|
return@withNonCancellableContext Result.InternalError(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read && downloadPreferences.removeAfterMarkedAsRead().get()) {
|
||||||
|
chaptersToUpdate
|
||||||
|
.groupBy { it.mangaId }
|
||||||
|
.forEach { (mangaId, chapters) ->
|
||||||
|
deleteDownload.awaitAll(
|
||||||
|
manga = mangaRepository.getMangaById(mangaId),
|
||||||
|
chapters = chapters.toTypedArray(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Success
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun await(mangaId: Long, read: Boolean): Result = withNonCancellableContext {
|
||||||
|
await(
|
||||||
|
read = read,
|
||||||
|
chapters = chapterRepository
|
||||||
|
.getChapterByMangaId(mangaId)
|
||||||
|
.toTypedArray(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun await(manga: Manga, read: Boolean) =
|
||||||
|
await(manga.id, read)
|
||||||
|
|
||||||
|
sealed interface Result {
|
||||||
|
data object Success : Result
|
||||||
|
data object NoChapters : Result
|
||||||
|
data class InternalError(val error: Throwable) : Result
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,213 @@
|
|||||||
|
package eu.kanade.domain.chapter.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.chapter.model.copyFromSChapter
|
||||||
|
import eu.kanade.domain.chapter.model.toSChapter
|
||||||
|
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
|
||||||
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
|
import eu.kanade.domain.manga.model.toSManga
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import tachiyomi.data.chapter.ChapterSanitizer
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
|
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||||
|
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.chapter.model.NoChaptersException
|
||||||
|
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||||
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
import tachiyomi.domain.chapter.service.ChapterRecognition
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.source.local.isLocal
|
||||||
|
import java.lang.Long.max
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import java.util.TreeSet
|
||||||
|
|
||||||
|
class SyncChaptersWithSource(
|
||||||
|
private val downloadManager: DownloadManager,
|
||||||
|
private val downloadProvider: DownloadProvider,
|
||||||
|
private val chapterRepository: ChapterRepository,
|
||||||
|
private val shouldUpdateDbChapter: ShouldUpdateDbChapter,
|
||||||
|
private val updateManga: UpdateManga,
|
||||||
|
private val updateChapter: UpdateChapter,
|
||||||
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
|
private val getExcludedScanlators: GetExcludedScanlators,
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to synchronize db chapters with source ones
|
||||||
|
*
|
||||||
|
* @param rawSourceChapters the chapters from the source.
|
||||||
|
* @param manga the manga the chapters belong to.
|
||||||
|
* @param source the source the manga belongs to.
|
||||||
|
* @return Newly added chapters
|
||||||
|
*/
|
||||||
|
suspend fun await(
|
||||||
|
rawSourceChapters: List<SChapter>,
|
||||||
|
manga: Manga,
|
||||||
|
source: Source,
|
||||||
|
manualFetch: Boolean = false,
|
||||||
|
fetchWindow: Pair<Long, Long> = Pair(0, 0),
|
||||||
|
): List<Chapter> {
|
||||||
|
if (rawSourceChapters.isEmpty() && !source.isLocal()) {
|
||||||
|
throw NoChaptersException()
|
||||||
|
}
|
||||||
|
|
||||||
|
val now = ZonedDateTime.now()
|
||||||
|
val nowMillis = now.toInstant().toEpochMilli()
|
||||||
|
|
||||||
|
val sourceChapters = rawSourceChapters
|
||||||
|
.distinctBy { it.url }
|
||||||
|
.mapIndexed { i, sChapter ->
|
||||||
|
Chapter.create()
|
||||||
|
.copyFromSChapter(sChapter)
|
||||||
|
.copy(name = with(ChapterSanitizer) { sChapter.name.sanitize(manga.title) })
|
||||||
|
.copy(mangaId = manga.id, sourceOrder = i.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
val dbChapters = getChaptersByMangaId.await(manga.id)
|
||||||
|
|
||||||
|
val newChapters = mutableListOf<Chapter>()
|
||||||
|
val updatedChapters = mutableListOf<Chapter>()
|
||||||
|
val removedChapters = dbChapters.filterNot { dbChapter ->
|
||||||
|
sourceChapters.any { sourceChapter ->
|
||||||
|
dbChapter.url == sourceChapter.url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to not set upload date of older chapters
|
||||||
|
// to a higher value than newer chapters
|
||||||
|
var maxSeenUploadDate = 0L
|
||||||
|
|
||||||
|
for (sourceChapter in sourceChapters) {
|
||||||
|
var chapter = sourceChapter
|
||||||
|
|
||||||
|
// Update metadata from source if necessary.
|
||||||
|
if (source is HttpSource) {
|
||||||
|
val sChapter = chapter.toSChapter()
|
||||||
|
source.prepareNewChapter(sChapter, manga.toSManga())
|
||||||
|
chapter = chapter.copyFromSChapter(sChapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recognize chapter number for the chapter.
|
||||||
|
val chapterNumber = ChapterRecognition.parseChapterNumber(manga.title, chapter.name, chapter.chapterNumber)
|
||||||
|
chapter = chapter.copy(chapterNumber = chapterNumber)
|
||||||
|
|
||||||
|
val dbChapter = dbChapters.find { it.url == chapter.url }
|
||||||
|
|
||||||
|
if (dbChapter == null) {
|
||||||
|
val toAddChapter = if (chapter.dateUpload == 0L) {
|
||||||
|
val altDateUpload = if (maxSeenUploadDate == 0L) nowMillis else maxSeenUploadDate
|
||||||
|
chapter.copy(dateUpload = altDateUpload)
|
||||||
|
} else {
|
||||||
|
maxSeenUploadDate = max(maxSeenUploadDate, sourceChapter.dateUpload)
|
||||||
|
chapter
|
||||||
|
}
|
||||||
|
newChapters.add(toAddChapter)
|
||||||
|
} else {
|
||||||
|
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
||||||
|
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
|
||||||
|
downloadManager.isChapterDownloaded(
|
||||||
|
dbChapter.name,
|
||||||
|
dbChapter.scanlator,
|
||||||
|
manga.title,
|
||||||
|
manga.source,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (shouldRenameChapter) {
|
||||||
|
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
||||||
|
}
|
||||||
|
var toChangeChapter = dbChapter.copy(
|
||||||
|
name = chapter.name,
|
||||||
|
chapterNumber = chapter.chapterNumber,
|
||||||
|
scanlator = chapter.scanlator,
|
||||||
|
sourceOrder = chapter.sourceOrder,
|
||||||
|
)
|
||||||
|
if (chapter.dateUpload != 0L) {
|
||||||
|
toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload)
|
||||||
|
}
|
||||||
|
updatedChapters.add(toChangeChapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if there's nothing to add, delete, or update to avoid unnecessary db transactions.
|
||||||
|
if (newChapters.isEmpty() && removedChapters.isEmpty() && updatedChapters.isEmpty()) {
|
||||||
|
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
|
||||||
|
updateManga.awaitUpdateFetchInterval(
|
||||||
|
manga,
|
||||||
|
now,
|
||||||
|
fetchWindow,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val reAdded = mutableListOf<Chapter>()
|
||||||
|
|
||||||
|
val deletedChapterNumbers = TreeSet<Double>()
|
||||||
|
val deletedReadChapterNumbers = TreeSet<Double>()
|
||||||
|
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
|
||||||
|
|
||||||
|
removedChapters.forEach { chapter ->
|
||||||
|
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
||||||
|
if (chapter.bookmark) deletedBookmarkedChapterNumbers.add(chapter.chapterNumber)
|
||||||
|
deletedChapterNumbers.add(chapter.chapterNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { it.dateFetch }
|
||||||
|
.associate { it.chapterNumber to it.dateFetch }
|
||||||
|
|
||||||
|
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
||||||
|
// Sources MUST return the chapters from most to less recent, which is common.
|
||||||
|
var itemCount = newChapters.size
|
||||||
|
var updatedToAdd = newChapters.map { toAddItem ->
|
||||||
|
var chapter = toAddItem.copy(dateFetch = nowMillis + itemCount--)
|
||||||
|
|
||||||
|
if (!chapter.isRecognizedNumber || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
||||||
|
|
||||||
|
chapter = chapter.copy(
|
||||||
|
read = chapter.chapterNumber in deletedReadChapterNumbers,
|
||||||
|
bookmark = chapter.chapterNumber in deletedBookmarkedChapterNumbers,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Try to to use the fetch date of the original entry to not pollute 'Updates' tab
|
||||||
|
deletedChapterNumberDateFetchMap[chapter.chapterNumber]?.let {
|
||||||
|
chapter = chapter.copy(dateFetch = it)
|
||||||
|
}
|
||||||
|
|
||||||
|
reAdded.add(chapter)
|
||||||
|
|
||||||
|
chapter
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedChapters.isNotEmpty()) {
|
||||||
|
val toDeleteIds = removedChapters.map { it.id }
|
||||||
|
chapterRepository.removeChaptersWithIds(toDeleteIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedToAdd.isNotEmpty()) {
|
||||||
|
updatedToAdd = chapterRepository.addAll(updatedToAdd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedChapters.isNotEmpty()) {
|
||||||
|
val chapterUpdates = updatedChapters.map { it.toChapterUpdate() }
|
||||||
|
updateChapter.awaitAll(chapterUpdates)
|
||||||
|
}
|
||||||
|
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
|
||||||
|
|
||||||
|
// Set this manga as updated since chapters were changed
|
||||||
|
// Note that last_update actually represents last time the chapter list changed at all
|
||||||
|
updateManga.awaitUpdateLastUpdate(manga.id)
|
||||||
|
|
||||||
|
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
||||||
|
|
||||||
|
val excludedScanlators = getExcludedScanlators.await(manga.id).toHashSet()
|
||||||
|
|
||||||
|
return updatedToAdd.filterNot {
|
||||||
|
it.url in reAddedUrls || it.scanlator in excludedScanlators
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
app/src/main/java/eu/kanade/domain/chapter/model/Chapter.kt
Normal file
42
app/src/main/java/eu/kanade/domain/chapter/model/Chapter.kt
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package eu.kanade.domain.chapter.model
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
|
||||||
|
|
||||||
|
// TODO: Remove when all deps are migrated
|
||||||
|
fun Chapter.toSChapter(): SChapter {
|
||||||
|
return SChapter.create().also {
|
||||||
|
it.url = url
|
||||||
|
it.name = name
|
||||||
|
it.date_upload = dateUpload
|
||||||
|
it.chapter_number = chapterNumber.toFloat()
|
||||||
|
it.scanlator = scanlator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
|
||||||
|
return this.copy(
|
||||||
|
name = sChapter.name,
|
||||||
|
url = sChapter.url,
|
||||||
|
dateUpload = sChapter.date_upload,
|
||||||
|
chapterNumber = sChapter.chapter_number.toDouble(),
|
||||||
|
scanlator = sChapter.scanlator?.ifBlank { null }?.trim(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
||||||
|
it.id = id
|
||||||
|
it.manga_id = mangaId
|
||||||
|
it.url = url
|
||||||
|
it.name = name
|
||||||
|
it.scanlator = scanlator
|
||||||
|
it.read = read
|
||||||
|
it.bookmark = bookmark
|
||||||
|
it.last_page_read = lastPageRead.toInt()
|
||||||
|
it.date_fetch = dateFetch
|
||||||
|
it.date_upload = dateUpload
|
||||||
|
it.chapter_number = chapterNumber.toFloat()
|
||||||
|
it.source_order = sourceOrder.toInt()
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package eu.kanade.domain.chapter.model
|
||||||
|
|
||||||
|
import eu.kanade.domain.manga.model.downloadedFilter
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.chapter.service.getChapterSort
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.model.applyFilter
|
||||||
|
import tachiyomi.source.local.isLocal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the view filters to the list of chapters obtained from the database.
|
||||||
|
* @return an observable of the list of chapters filtered and sorted.
|
||||||
|
*/
|
||||||
|
fun List<Chapter>.applyFilters(manga: Manga, downloadManager: DownloadManager): List<Chapter> {
|
||||||
|
val isLocalManga = manga.isLocal()
|
||||||
|
val unreadFilter = manga.unreadFilter
|
||||||
|
val downloadedFilter = manga.downloadedFilter
|
||||||
|
val bookmarkedFilter = manga.bookmarkedFilter
|
||||||
|
|
||||||
|
return filter { chapter -> applyFilter(unreadFilter) { !chapter.read } }
|
||||||
|
.filter { chapter -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
|
||||||
|
.filter { chapter ->
|
||||||
|
applyFilter(downloadedFilter) {
|
||||||
|
val downloaded = downloadManager.isChapterDownloaded(
|
||||||
|
chapter.name,
|
||||||
|
chapter.scanlator,
|
||||||
|
manga.title,
|
||||||
|
manga.source,
|
||||||
|
)
|
||||||
|
downloaded || isLocalManga
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sortedWith(getChapterSort(manga))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the view filters to the list of chapters obtained from the database.
|
||||||
|
* @return an observable of the list of chapters filtered and sorted.
|
||||||
|
*/
|
||||||
|
fun List<ChapterList.Item>.applyFilters(manga: Manga): Sequence<ChapterList.Item> {
|
||||||
|
val isLocalManga = manga.isLocal()
|
||||||
|
val unreadFilter = manga.unreadFilter
|
||||||
|
val downloadedFilter = manga.downloadedFilter
|
||||||
|
val bookmarkedFilter = manga.bookmarkedFilter
|
||||||
|
return asSequence()
|
||||||
|
.filter { (chapter) -> applyFilter(unreadFilter) { !chapter.read } }
|
||||||
|
.filter { (chapter) -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
|
||||||
|
.filter { applyFilter(downloadedFilter) { it.isDownloaded || isLocalManga } }
|
||||||
|
.sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) }
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package eu.kanade.domain.download.interactor
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
import tachiyomi.core.common.util.lang.withNonCancellableContext
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
|
|
||||||
|
class DeleteDownload(
|
||||||
|
private val sourceManager: SourceManager,
|
||||||
|
private val downloadManager: DownloadManager,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun awaitAll(manga: Manga, vararg chapters: Chapter) = withNonCancellableContext {
|
||||||
|
sourceManager.get(manga.source)?.let { source ->
|
||||||
|
downloadManager.deleteChapters(chapters.toList(), manga, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package eu.kanade.domain.extension.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
|
||||||
|
class GetExtensionLanguages(
|
||||||
|
private val preferences: SourcePreferences,
|
||||||
|
private val extensionManager: ExtensionManager,
|
||||||
|
) {
|
||||||
|
fun subscribe(): Flow<List<String>> {
|
||||||
|
return combine(
|
||||||
|
preferences.enabledLanguages().changes(),
|
||||||
|
extensionManager.availableExtensionsFlow,
|
||||||
|
) { enabledLanguage, availableExtensions ->
|
||||||
|
availableExtensions
|
||||||
|
.flatMap { ext ->
|
||||||
|
if (ext.sources.isEmpty()) {
|
||||||
|
listOf(ext.lang)
|
||||||
|
} else {
|
||||||
|
ext.sources.map { it.lang }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.distinct()
|
||||||
|
.sortedWith(
|
||||||
|
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package eu.kanade.domain.extension.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
class GetExtensionSources(
|
||||||
|
private val preferences: SourcePreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun subscribe(extension: Extension.Installed): Flow<List<ExtensionSourceItem>> {
|
||||||
|
val isMultiSource = extension.sources.size > 1
|
||||||
|
val isMultiLangSingleSource =
|
||||||
|
isMultiSource && extension.sources.map { it.name }.distinct().size == 1
|
||||||
|
|
||||||
|
return preferences.disabledSources().changes().map { disabledSources ->
|
||||||
|
fun Source.isEnabled() = id.toString() !in disabledSources
|
||||||
|
|
||||||
|
extension.sources
|
||||||
|
.map { source ->
|
||||||
|
ExtensionSourceItem(
|
||||||
|
source = source,
|
||||||
|
enabled = source.isEnabled(),
|
||||||
|
labelAsName = isMultiSource && !isMultiLangSingleSource,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ExtensionSourceItem(
|
||||||
|
val source: Source,
|
||||||
|
val enabled: Boolean,
|
||||||
|
val labelAsName: Boolean,
|
||||||
|
)
|
@ -0,0 +1,60 @@
|
|||||||
|
package eu.kanade.domain.extension.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.extension.model.Extensions
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
|
||||||
|
class GetExtensionsByType(
|
||||||
|
private val preferences: SourcePreferences,
|
||||||
|
private val extensionManager: ExtensionManager,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun subscribe(): Flow<Extensions> {
|
||||||
|
val showNsfwSources = preferences.showNsfwSource().get()
|
||||||
|
|
||||||
|
return combine(
|
||||||
|
preferences.enabledLanguages().changes(),
|
||||||
|
extensionManager.installedExtensionsFlow,
|
||||||
|
extensionManager.untrustedExtensionsFlow,
|
||||||
|
extensionManager.availableExtensionsFlow,
|
||||||
|
) { enabledLanguages, _installed, _untrusted, _available ->
|
||||||
|
val (updates, installed) = _installed
|
||||||
|
.filter { (showNsfwSources || !it.isNsfw) }
|
||||||
|
.sortedWith(
|
||||||
|
compareBy<Extension.Installed> { !it.isObsolete }
|
||||||
|
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
||||||
|
)
|
||||||
|
.partition { it.hasUpdate }
|
||||||
|
|
||||||
|
val untrusted = _untrusted
|
||||||
|
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||||
|
|
||||||
|
val available = _available
|
||||||
|
.filter { extension ->
|
||||||
|
_installed.none { it.pkgName == extension.pkgName } &&
|
||||||
|
_untrusted.none { it.pkgName == extension.pkgName } &&
|
||||||
|
(showNsfwSources || !extension.isNsfw)
|
||||||
|
}
|
||||||
|
.flatMap { ext ->
|
||||||
|
if (ext.sources.isEmpty()) {
|
||||||
|
return@flatMap if (ext.lang in enabledLanguages) listOf(ext) else emptyList()
|
||||||
|
}
|
||||||
|
ext.sources.filter { it.lang in enabledLanguages }
|
||||||
|
.map {
|
||||||
|
ext.copy(
|
||||||
|
name = it.name,
|
||||||
|
lang = it.lang,
|
||||||
|
pkgName = "${ext.pkgName}-${it.id}",
|
||||||
|
sources = listOf(it),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||||
|
|
||||||
|
Extensions(updates, installed, available, untrusted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
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 mihon.domain.extensionrepo.repository.ExtensionRepoRepository
|
||||||
|
import tachiyomi.core.common.preference.getAndSet
|
||||||
|
|
||||||
|
class TrustExtension(
|
||||||
|
private val extensionRepoRepository: ExtensionRepoRepository,
|
||||||
|
private val preferences: SourcePreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean {
|
||||||
|
val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet()
|
||||||
|
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}"
|
||||||
|
return trustedFingerprints.any { fingerprints.contains(it) } || 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()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package eu.kanade.domain.extension.model
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
|
|
||||||
|
data class Extensions(
|
||||||
|
val updates: List<Extension.Installed>,
|
||||||
|
val installed: List<Extension.Installed>,
|
||||||
|
val available: List<Extension.Available>,
|
||||||
|
val untrusted: List<Extension.Untrusted>,
|
||||||
|
)
|
@ -0,0 +1,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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
|
class SetMangaViewerFlags(
|
||||||
|
private val mangaRepository: MangaRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun awaitSetReadingMode(id: Long, flag: Long) {
|
||||||
|
val manga = mangaRepository.getMangaById(id)
|
||||||
|
mangaRepository.update(
|
||||||
|
MangaUpdate(
|
||||||
|
id = id,
|
||||||
|
viewerFlags = manga.viewerFlags.setFlag(flag, ReadingMode.MASK.toLong()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun awaitSetOrientation(id: Long, flag: Long) {
|
||||||
|
val manga = mangaRepository.getMangaById(id)
|
||||||
|
mangaRepository.update(
|
||||||
|
MangaUpdate(
|
||||||
|
id = id,
|
||||||
|
viewerFlags = manga.viewerFlags.setFlag(flag, ReaderOrientation.MASK.toLong()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Long.setFlag(flag: Long, mask: Long): Long {
|
||||||
|
return this and mask.inv() or (flag and mask)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.manga.model.hasCustomCover
|
||||||
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
import tachiyomi.source.local.isLocal
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
class UpdateManga(
|
||||||
|
private val mangaRepository: MangaRepository,
|
||||||
|
private val fetchInterval: FetchInterval,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
|
||||||
|
return mangaRepository.update(mangaUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun awaitAll(mangaUpdates: List<MangaUpdate>): Boolean {
|
||||||
|
return mangaRepository.updateAll(mangaUpdates)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun awaitUpdateFromSource(
|
||||||
|
localManga: Manga,
|
||||||
|
remoteManga: SManga,
|
||||||
|
manualFetch: Boolean,
|
||||||
|
coverCache: CoverCache = Injekt.get(),
|
||||||
|
): Boolean {
|
||||||
|
val remoteTitle = try {
|
||||||
|
remoteManga.title
|
||||||
|
} catch (_: UninitializedPropertyAccessException) {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the manga isn't a favorite, set its title from source and update in db
|
||||||
|
val title = if (remoteTitle.isEmpty() || localManga.favorite) null else remoteTitle
|
||||||
|
|
||||||
|
val coverLastModified =
|
||||||
|
when {
|
||||||
|
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
||||||
|
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
||||||
|
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
||||||
|
localManga.isLocal() -> Instant.now().toEpochMilli()
|
||||||
|
localManga.hasCustomCover(coverCache) -> {
|
||||||
|
coverCache.deleteFromCache(localManga, false)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
coverCache.deleteFromCache(localManga, false)
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val thumbnailUrl = remoteManga.thumbnail_url?.takeIf { it.isNotEmpty() }
|
||||||
|
|
||||||
|
return mangaRepository.update(
|
||||||
|
MangaUpdate(
|
||||||
|
id = localManga.id,
|
||||||
|
title = title,
|
||||||
|
coverLastModified = coverLastModified,
|
||||||
|
author = remoteManga.author,
|
||||||
|
artist = remoteManga.artist,
|
||||||
|
description = remoteManga.description,
|
||||||
|
genre = remoteManga.getGenres(),
|
||||||
|
thumbnailUrl = thumbnailUrl,
|
||||||
|
status = remoteManga.status.toLong(),
|
||||||
|
updateStrategy = remoteManga.update_strategy,
|
||||||
|
initialized = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun awaitUpdateFetchInterval(
|
||||||
|
manga: Manga,
|
||||||
|
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||||
|
window: Pair<Long, Long> = fetchInterval.getWindow(dateTime),
|
||||||
|
): Boolean {
|
||||||
|
return mangaRepository.update(
|
||||||
|
fetchInterval.toMangaUpdate(manga, dateTime, window),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
||||||
|
return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Instant.now().toEpochMilli()))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun awaitUpdateCoverLastModified(mangaId: Long): Boolean {
|
||||||
|
return mangaRepository.update(MangaUpdate(id = mangaId, coverLastModified = Instant.now().toEpochMilli()))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun awaitUpdateFavorite(mangaId: Long, favorite: Boolean): Boolean {
|
||||||
|
val dateAdded = when (favorite) {
|
||||||
|
true -> Instant.now().toEpochMilli()
|
||||||
|
false -> 0
|
||||||
|
}
|
||||||
|
return mangaRepository.update(
|
||||||
|
MangaUpdate(id = mangaId, favorite = favorite, dateAdded = dateAdded),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
130
app/src/main/java/eu/kanade/domain/manga/model/Manga.kt
Normal file
130
app/src/main/java/eu/kanade/domain/manga/model/Manga.kt
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package eu.kanade.domain.manga.model
|
||||||
|
|
||||||
|
import eu.kanade.domain.base.BasePreferences
|
||||||
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
|
import tachiyomi.core.common.preference.TriState
|
||||||
|
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
||||||
|
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
// TODO: move these into the domain model
|
||||||
|
val Manga.readingMode: Long
|
||||||
|
get() = viewerFlags and ReadingMode.MASK.toLong()
|
||||||
|
|
||||||
|
val Manga.readerOrientation: Long
|
||||||
|
get() = viewerFlags and ReaderOrientation.MASK.toLong()
|
||||||
|
|
||||||
|
val Manga.downloadedFilter: TriState
|
||||||
|
get() {
|
||||||
|
if (forceDownloaded()) return TriState.ENABLED_IS
|
||||||
|
return when (downloadedFilterRaw) {
|
||||||
|
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
||||||
|
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
||||||
|
else -> TriState.DISABLED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun Manga.chaptersFiltered(): Boolean {
|
||||||
|
return unreadFilter != TriState.DISABLED ||
|
||||||
|
downloadedFilter != TriState.DISABLED ||
|
||||||
|
bookmarkedFilter != TriState.DISABLED
|
||||||
|
}
|
||||||
|
fun Manga.forceDownloaded(): Boolean {
|
||||||
|
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Manga.toSManga(): SManga = SManga.create().also {
|
||||||
|
it.url = url
|
||||||
|
it.title = title
|
||||||
|
it.artist = artist
|
||||||
|
it.author = author
|
||||||
|
it.description = description
|
||||||
|
it.genre = genre.orEmpty().joinToString()
|
||||||
|
it.status = status.toInt()
|
||||||
|
it.thumbnail_url = thumbnailUrl
|
||||||
|
it.initialized = initialized
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Manga.copyFrom(other: SManga): Manga {
|
||||||
|
val author = other.author ?: author
|
||||||
|
val artist = other.artist ?: artist
|
||||||
|
val description = other.description ?: description
|
||||||
|
val genres = if (other.genre != null) {
|
||||||
|
other.getGenres()
|
||||||
|
} else {
|
||||||
|
genre
|
||||||
|
}
|
||||||
|
val thumbnailUrl = other.thumbnail_url ?: thumbnailUrl
|
||||||
|
return this.copy(
|
||||||
|
author = author,
|
||||||
|
artist = artist,
|
||||||
|
description = description,
|
||||||
|
genre = genres,
|
||||||
|
thumbnailUrl = thumbnailUrl,
|
||||||
|
status = other.status.toLong(),
|
||||||
|
updateStrategy = other.update_strategy,
|
||||||
|
initialized = other.initialized && initialized,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SManga.toDomainManga(sourceId: Long): Manga {
|
||||||
|
return Manga.create().copy(
|
||||||
|
url = url,
|
||||||
|
title = title,
|
||||||
|
artist = artist,
|
||||||
|
author = author,
|
||||||
|
description = description,
|
||||||
|
genre = getGenres(),
|
||||||
|
status = status.toLong(),
|
||||||
|
thumbnailUrl = thumbnail_url,
|
||||||
|
updateStrategy = update_strategy,
|
||||||
|
initialized = initialized,
|
||||||
|
source = sourceId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
||||||
|
return coverCache.getCustomCoverFile(id).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a ComicInfo instance based on the manga and chapter metadata.
|
||||||
|
*/
|
||||||
|
fun getComicInfo(
|
||||||
|
manga: Manga,
|
||||||
|
chapter: Chapter,
|
||||||
|
urls: List<String>,
|
||||||
|
categories: List<String>?,
|
||||||
|
sourceName: 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(urls.joinToString(" ")),
|
||||||
|
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()) },
|
||||||
|
source = ComicInfo.SourceMihon(sourceName),
|
||||||
|
inker = null,
|
||||||
|
colorist = null,
|
||||||
|
letterer = null,
|
||||||
|
coverArtist = null,
|
||||||
|
tags = null,
|
||||||
|
)
|
@ -0,0 +1,42 @@
|
|||||||
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import tachiyomi.domain.source.model.Pin
|
||||||
|
import tachiyomi.domain.source.model.Pins
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.domain.source.repository.SourceRepository
|
||||||
|
import tachiyomi.source.local.isLocal
|
||||||
|
|
||||||
|
class GetEnabledSources(
|
||||||
|
private val repository: SourceRepository,
|
||||||
|
private val preferences: SourcePreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun subscribe(): Flow<List<Source>> {
|
||||||
|
return combine(
|
||||||
|
preferences.pinnedSources().changes(),
|
||||||
|
preferences.enabledLanguages().changes(),
|
||||||
|
preferences.disabledSources().changes(),
|
||||||
|
preferences.lastUsedSource().changes(),
|
||||||
|
repository.getSources(),
|
||||||
|
) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources ->
|
||||||
|
sources
|
||||||
|
.filter { it.lang in enabledLanguages || it.isLocal() }
|
||||||
|
.filterNot { it.id.toString() in disabledSources }
|
||||||
|
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||||
|
.flatMap {
|
||||||
|
val flag = if ("${it.id}" in pinnedSourceIds) Pins.pinned else Pins.unpinned
|
||||||
|
val source = it.copy(pin = flag)
|
||||||
|
val toFlatten = mutableListOf(source)
|
||||||
|
if (source.id == lastUsedSource) {
|
||||||
|
toFlatten.add(source.copy(isUsedLast = true, pin = source.pin - Pin.Actual))
|
||||||
|
}
|
||||||
|
toFlatten
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.domain.source.repository.SourceRepository
|
||||||
|
import java.util.SortedMap
|
||||||
|
|
||||||
|
class GetLanguagesWithSources(
|
||||||
|
private val repository: SourceRepository,
|
||||||
|
private val preferences: SourcePreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun subscribe(): Flow<SortedMap<String, List<Source>>> {
|
||||||
|
return combine(
|
||||||
|
preferences.enabledLanguages().changes(),
|
||||||
|
preferences.disabledSources().changes(),
|
||||||
|
repository.getOnlineSources(),
|
||||||
|
) { enabledLanguage, disabledSource, onlineSources ->
|
||||||
|
val sortedSources = onlineSources.sortedWith(
|
||||||
|
compareBy<Source> { it.id.toString() in disabledSource }
|
||||||
|
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
||||||
|
)
|
||||||
|
|
||||||
|
sortedSources
|
||||||
|
.groupBy { it.lang }
|
||||||
|
.toSortedMap(
|
||||||
|
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import tachiyomi.core.common.util.lang.compareToWithCollator
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.domain.source.repository.SourceRepository
|
||||||
|
import tachiyomi.source.local.isLocal
|
||||||
|
import java.util.Collections
|
||||||
|
|
||||||
|
class GetSourcesWithFavoriteCount(
|
||||||
|
private val repository: SourceRepository,
|
||||||
|
private val preferences: SourcePreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun subscribe(): Flow<List<Pair<Source, Long>>> {
|
||||||
|
return combine(
|
||||||
|
preferences.migrationSortingDirection().changes(),
|
||||||
|
preferences.migrationSortingMode().changes(),
|
||||||
|
repository.getSourcesWithFavoriteCount(),
|
||||||
|
) { direction, mode, list ->
|
||||||
|
list
|
||||||
|
.filterNot { it.first.isLocal() }
|
||||||
|
.sortedWith(sortFn(direction, mode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sortFn(
|
||||||
|
direction: SetMigrateSorting.Direction,
|
||||||
|
sorting: SetMigrateSorting.Mode,
|
||||||
|
): java.util.Comparator<Pair<Source, Long>> {
|
||||||
|
val sortFn: (Pair<Source, Long>, Pair<Source, Long>) -> Int = { a, b ->
|
||||||
|
when (sorting) {
|
||||||
|
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
||||||
|
when {
|
||||||
|
a.first.isStub && !b.first.isStub -> -1
|
||||||
|
b.first.isStub && !a.first.isStub -> 1
|
||||||
|
else -> a.first.name.lowercase().compareToWithCollator(b.first.name.lowercase())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetMigrateSorting.Mode.TOTAL -> {
|
||||||
|
when {
|
||||||
|
a.first.isStub && !b.first.isStub -> -1
|
||||||
|
b.first.isStub && !a.first.isStub -> 1
|
||||||
|
else -> a.second.compareTo(b.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (direction) {
|
||||||
|
SetMigrateSorting.Direction.ASCENDING -> Comparator(sortFn)
|
||||||
|
SetMigrateSorting.Direction.DESCENDING -> Collections.reverseOrder(sortFn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
|
||||||
|
class SetMigrateSorting(
|
||||||
|
private val preferences: SourcePreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun await(mode: Mode, direction: Direction) {
|
||||||
|
preferences.migrationSortingMode().set(mode)
|
||||||
|
preferences.migrationSortingDirection().set(direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Mode {
|
||||||
|
ALPHABETICAL,
|
||||||
|
TOTAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Direction {
|
||||||
|
ASCENDING,
|
||||||
|
DESCENDING,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import tachiyomi.core.common.preference.getAndSet
|
||||||
|
|
||||||
|
class ToggleLanguage(
|
||||||
|
val preferences: SourcePreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun await(language: String) {
|
||||||
|
val isEnabled = language in preferences.enabledLanguages().get()
|
||||||
|
preferences.enabledLanguages().getAndSet { enabled ->
|
||||||
|
if (isEnabled) enabled.minus(language) else enabled.plus(language)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import tachiyomi.core.common.preference.getAndSet
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
|
||||||
|
class ToggleSource(
|
||||||
|
private val preferences: SourcePreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun await(source: Source, enable: Boolean = isEnabled(source.id)) {
|
||||||
|
await(source.id, enable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun await(sourceId: Long, enable: Boolean = isEnabled(sourceId)) {
|
||||||
|
preferences.disabledSources().getAndSet { disabled ->
|
||||||
|
if (enable) disabled.minus("$sourceId") else disabled.plus("$sourceId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun await(sourceIds: List<Long>, enable: Boolean) {
|
||||||
|
val transformedSourceIds = sourceIds.map { it.toString() }
|
||||||
|
preferences.disabledSources().getAndSet { disabled ->
|
||||||
|
if (enable) disabled.minus(transformedSourceIds) else disabled.plus(transformedSourceIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isEnabled(sourceId: Long): Boolean {
|
||||||
|
return sourceId.toString() in preferences.disabledSources().get()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import tachiyomi.core.common.preference.getAndSet
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
|
||||||
|
class ToggleSourcePin(
|
||||||
|
private val preferences: SourcePreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun await(source: Source) {
|
||||||
|
val isPinned = source.id.toString() in preferences.pinnedSources().get()
|
||||||
|
preferences.pinnedSources().getAndSet { pinned ->
|
||||||
|
if (isPinned) pinned.minus("${source.id}") else pinned.plus("${source.id}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
app/src/main/java/eu/kanade/domain/source/model/Source.kt
Normal file
16
app/src/main/java/eu/kanade/domain/source/model/Source.kt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package eu.kanade.domain.source.model
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
val Source.icon: ImageBitmap?
|
||||||
|
get() {
|
||||||
|
return Injekt.get<ExtensionManager>().getAppIconForSource(id)
|
||||||
|
?.toBitmap()
|
||||||
|
?.asImageBitmap()
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package eu.kanade.domain.source.service
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import tachiyomi.core.common.preference.Preference
|
||||||
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
|
import tachiyomi.core.common.preference.getEnum
|
||||||
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
|
||||||
|
class SourcePreferences(
|
||||||
|
private val preferenceStore: PreferenceStore,
|
||||||
|
) {
|
||||||
|
|
||||||
|
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 disabledSources() = preferenceStore.getStringSet("hidden_catalogues", emptySet())
|
||||||
|
|
||||||
|
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
||||||
|
|
||||||
|
fun lastUsedSource() = preferenceStore.getLong(
|
||||||
|
Preference.appStateKey("last_catalogue_source"),
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true)
|
||||||
|
|
||||||
|
fun migrationSortingMode() = preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL)
|
||||||
|
|
||||||
|
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 trustedExtensions() = preferenceStore.getStringSet(
|
||||||
|
Preference.appStateKey("trusted_extensions"),
|
||||||
|
emptySet(),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun globalSearchFilterState() = preferenceStore.getBoolean(
|
||||||
|
Preference.appStateKey("has_filters_toggle_state"),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
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.data.track.TrackerManager
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||||
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
|
import tachiyomi.core.common.util.lang.withNonCancellableContext
|
||||||
|
import tachiyomi.core.common.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.InsertTrack
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
|
class AddTracks(
|
||||||
|
private val insertTrack: InsertTrack,
|
||||||
|
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
|
||||||
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
|
private val trackerManager: TrackerManager,
|
||||||
|
) {
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
trackerManager.loggedInTrackers()
|
||||||
|
.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(idRequired = false)!!)
|
||||||
|
|
||||||
|
syncChapterProgressWithTrack.await(
|
||||||
|
manga.id,
|
||||||
|
track.toDomainTrack(idRequired = false)!!,
|
||||||
|
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()).toDomainTrack()!!
|
||||||
|
insertTrack.await(updatedTrack)
|
||||||
|
syncChapterProgressWithTrack.await(mangaId, updatedTrack, service)
|
||||||
|
null
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
service to e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.awaitAll()
|
||||||
|
.filterNotNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package eu.kanade.domain.track.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
|
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.common.util.system.logcat
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
|
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||||
|
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||||
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
|
import tachiyomi.domain.track.model.Track
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
class SyncChapterProgressWithTrack(
|
||||||
|
private val updateChapter: UpdateChapter,
|
||||||
|
private val insertTrack: InsertTrack,
|
||||||
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun await(
|
||||||
|
mangaId: Long,
|
||||||
|
remoteTrack: Track,
|
||||||
|
tracker: Tracker,
|
||||||
|
) {
|
||||||
|
if (tracker !is EnhancedTracker) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val sortedChapters = getChaptersByMangaId.await(mangaId)
|
||||||
|
.sortedBy { it.chapterNumber }
|
||||||
|
.filter { it.isRecognizedNumber }
|
||||||
|
|
||||||
|
val chapterUpdates = sortedChapters
|
||||||
|
.filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read }
|
||||||
|
.map { it.copy(read = true).toChapterUpdate() }
|
||||||
|
|
||||||
|
// only take into account continuous reading
|
||||||
|
val localLastRead = sortedChapters.takeWhile { it.read }.lastOrNull()?.chapterNumber ?: 0F
|
||||||
|
val lastRead = max(remoteTrack.lastChapterRead, localLastRead.toDouble())
|
||||||
|
val updatedTrack = remoteTrack.copy(lastChapterRead = lastRead)
|
||||||
|
|
||||||
|
try {
|
||||||
|
tracker.update(updatedTrack.toDbTrack())
|
||||||
|
updateChapter.awaitAll(chapterUpdates)
|
||||||
|
insertTrack.await(updatedTrack)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
logcat(LogPriority.WARN, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
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.common.util.lang.withNonCancellableContext
|
||||||
|
import tachiyomi.core.common.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, setupJobOnFailure: Boolean = true) {
|
||||||
|
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)
|
||||||
|
if (setupJobOnFailure) {
|
||||||
|
DelayedTrackingUpdateJob.setupTask(context)
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.awaitAll()
|
||||||
|
.mapNotNull { it.exceptionOrNull() }
|
||||||
|
.forEach { logcat(LogPriority.WARN, it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
app/src/main/java/eu/kanade/domain/track/model/Track.kt
Normal file
48
app/src/main/java/eu/kanade/domain/track/model/Track.kt
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package eu.kanade.domain.track.model
|
||||||
|
|
||||||
|
import tachiyomi.domain.track.model.Track
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track as DbTrack
|
||||||
|
|
||||||
|
fun Track.copyPersonalFrom(other: Track): Track {
|
||||||
|
return this.copy(
|
||||||
|
lastChapterRead = other.lastChapterRead,
|
||||||
|
score = other.score,
|
||||||
|
status = other.status,
|
||||||
|
startDate = other.startDate,
|
||||||
|
finishDate = other.finishDate,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Track.toDbTrack(): DbTrack = DbTrack.create(trackerId).also {
|
||||||
|
it.id = id
|
||||||
|
it.manga_id = mangaId
|
||||||
|
it.remote_id = remoteId
|
||||||
|
it.library_id = libraryId
|
||||||
|
it.title = title
|
||||||
|
it.last_chapter_read = lastChapterRead
|
||||||
|
it.total_chapters = totalChapters
|
||||||
|
it.status = status
|
||||||
|
it.score = score
|
||||||
|
it.tracking_url = remoteUrl
|
||||||
|
it.started_reading_date = startDate
|
||||||
|
it.finished_reading_date = finishDate
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
||||||
|
val trackId = id ?: if (!idRequired) -1 else return null
|
||||||
|
return Track(
|
||||||
|
id = trackId,
|
||||||
|
mangaId = manga_id,
|
||||||
|
trackerId = tracker_id,
|
||||||
|
remoteId = remote_id,
|
||||||
|
libraryId = library_id,
|
||||||
|
title = title,
|
||||||
|
lastChapterRead = last_chapter_read,
|
||||||
|
totalChapters = total_chapters,
|
||||||
|
status = status,
|
||||||
|
score = score,
|
||||||
|
remoteUrl = tracking_url,
|
||||||
|
startDate = started_reading_date,
|
||||||
|
finishDate = finished_reading_date,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package eu.kanade.domain.track.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.work.BackoffPolicy
|
||||||
|
import androidx.work.Constraints
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.ExistingWorkPolicy
|
||||||
|
import androidx.work.NetworkType
|
||||||
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import eu.kanade.domain.track.interactor.TrackChapter
|
||||||
|
import eu.kanade.domain.track.store.DelayedTrackingStore
|
||||||
|
import eu.kanade.tachiyomi.util.system.workManager
|
||||||
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
|
import tachiyomi.core.common.util.system.logcat
|
||||||
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class DelayedTrackingUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||||
|
CoroutineWorker(context, workerParams) {
|
||||||
|
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
if (runAttemptCount > 3) {
|
||||||
|
return Result.failure()
|
||||||
|
}
|
||||||
|
|
||||||
|
val getTracks = Injekt.get<GetTracks>()
|
||||||
|
val trackChapter = Injekt.get<TrackChapter>()
|
||||||
|
|
||||||
|
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
|
||||||
|
|
||||||
|
withIOContext {
|
||||||
|
delayedTrackingStore.getItems()
|
||||||
|
.mapNotNull {
|
||||||
|
val track = getTracks.awaitOne(it.trackId)
|
||||||
|
if (track == null) {
|
||||||
|
delayedTrackingStore.remove(it.trackId)
|
||||||
|
}
|
||||||
|
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
||||||
|
}
|
||||||
|
.forEach { track ->
|
||||||
|
logcat(LogPriority.DEBUG) {
|
||||||
|
"Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}"
|
||||||
|
}
|
||||||
|
trackChapter.await(context, track.mangaId, track.lastChapterRead, setupJobOnFailure = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (delayedTrackingStore.getItems().isEmpty()) Result.success() else Result.retry()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "DelayedTrackingUpdate"
|
||||||
|
|
||||||
|
fun setupTask(context: Context) {
|
||||||
|
val constraints = Constraints(
|
||||||
|
requiredNetworkType = NetworkType.CONNECTED,
|
||||||
|
)
|
||||||
|
|
||||||
|
val request = OneTimeWorkRequestBuilder<DelayedTrackingUpdateJob>()
|
||||||
|
.setConstraints(constraints)
|
||||||
|
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5, TimeUnit.MINUTES)
|
||||||
|
.addTag(TAG)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
context.workManager.enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package eu.kanade.domain.track.service
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||||
|
import tachiyomi.core.common.preference.Preference
|
||||||
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
|
|
||||||
|
class TrackPreferences(
|
||||||
|
private val preferenceStore: PreferenceStore,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun trackUsername(tracker: Tracker) = preferenceStore.getString(
|
||||||
|
Preference.privateKey("pref_mangasync_username_${tracker.id}"),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
fun trackPassword(tracker: Tracker) = preferenceStore.getString(
|
||||||
|
Preference.privateKey("pref_mangasync_password_${tracker.id}"),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
fun trackAuthExpired(tracker: Tracker) = preferenceStore.getBoolean(
|
||||||
|
Preference.privateKey("pref_tracker_auth_expired_${tracker.id}"),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun setCredentials(tracker: Tracker, username: String, password: String) {
|
||||||
|
trackUsername(tracker).set(username)
|
||||||
|
trackPassword(tracker).set(password)
|
||||||
|
trackAuthExpired(tracker).set(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun trackToken(tracker: Tracker) = preferenceStore.getString(Preference.privateKey("track_token_${tracker.id}"), "")
|
||||||
|
|
||||||
|
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
||||||
|
|
||||||
|
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package eu.kanade.domain.track.store
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.common.util.system.logcat
|
||||||
|
|
||||||
|
class DelayedTrackingStore(context: Context) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preference file where queued tracking updates are stored.
|
||||||
|
*/
|
||||||
|
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
fun add(trackId: Long, lastChapterRead: Double) {
|
||||||
|
val previousLastChapterRead = preferences.getFloat(trackId.toString(), 0f)
|
||||||
|
if (lastChapterRead > previousLastChapterRead) {
|
||||||
|
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last chapter read: $lastChapterRead" }
|
||||||
|
preferences.edit {
|
||||||
|
putFloat(trackId.toString(), lastChapterRead.toFloat())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(trackId: Long) {
|
||||||
|
preferences.edit {
|
||||||
|
remove(trackId.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getItems(): List<DelayedTrackingItem> {
|
||||||
|
return preferences.all.mapNotNull {
|
||||||
|
DelayedTrackingItem(
|
||||||
|
trackId = it.key.toLong(),
|
||||||
|
lastChapterRead = it.value.toString().toFloat(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DelayedTrackingItem(
|
||||||
|
val trackId: Long,
|
||||||
|
val lastChapterRead: Float,
|
||||||
|
)
|
||||||
|
}
|
43
app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt
Normal file
43
app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package eu.kanade.domain.ui
|
||||||
|
|
||||||
|
import eu.kanade.domain.ui.model.AppTheme
|
||||||
|
import eu.kanade.domain.ui.model.TabletUiMode
|
||||||
|
import eu.kanade.domain.ui.model.ThemeMode
|
||||||
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
||||||
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
|
import tachiyomi.core.common.preference.getEnum
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.time.format.FormatStyle
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class UiPreferences(
|
||||||
|
private val preferenceStore: PreferenceStore,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun themeMode() = preferenceStore.getEnum("pref_theme_mode_key", ThemeMode.SYSTEM)
|
||||||
|
|
||||||
|
fun appTheme() = preferenceStore.getEnum(
|
||||||
|
"pref_app_theme",
|
||||||
|
if (DeviceUtil.isDynamicColorAvailable) {
|
||||||
|
AppTheme.MONET
|
||||||
|
} else {
|
||||||
|
AppTheme.DEFAULT
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
|
||||||
|
|
||||||
|
fun relativeTime() = preferenceStore.getBoolean("relative_time_v2", true)
|
||||||
|
|
||||||
|
fun dateFormat() = preferenceStore.getString("app_date_format", "")
|
||||||
|
|
||||||
|
fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun dateFormat(format: String): DateTimeFormatter = when (format) {
|
||||||
|
"" -> DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
|
||||||
|
else -> DateTimeFormatter.ofPattern(format, Locale.getDefault())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
app/src/main/java/eu/kanade/domain/ui/model/AppTheme.kt
Normal file
28
app/src/main/java/eu/kanade/domain/ui/model/AppTheme.kt
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package eu.kanade.domain.ui.model
|
||||||
|
|
||||||
|
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 titleRes: StringResource?) {
|
||||||
|
DEFAULT(MR.strings.label_default),
|
||||||
|
MONET(MR.strings.theme_monet),
|
||||||
|
GREEN_APPLE(MR.strings.theme_greenapple),
|
||||||
|
LAVENDER(MR.strings.theme_lavender),
|
||||||
|
MIDNIGHT_DUSK(MR.strings.theme_midnightdusk),
|
||||||
|
|
||||||
|
// TODO: re-enable for preview
|
||||||
|
NORD(MR.strings.theme_nord.takeIf { isDevFlavor || isPreviewBuildType }),
|
||||||
|
STRAWBERRY_DAIQUIRI(MR.strings.theme_strawberrydaiquiri),
|
||||||
|
TAKO(MR.strings.theme_tako),
|
||||||
|
TEALTURQUOISE(MR.strings.theme_tealturquoise),
|
||||||
|
TIDAL_WAVE(MR.strings.theme_tidalwave),
|
||||||
|
YINYANG(MR.strings.theme_yinyang),
|
||||||
|
YOTSUBA(MR.strings.theme_yotsuba),
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
DARK_BLUE(null),
|
||||||
|
HOT_PINK(null),
|
||||||
|
BLUE(null),
|
||||||
|
}
|
11
app/src/main/java/eu/kanade/domain/ui/model/TabletUiMode.kt
Normal file
11
app/src/main/java/eu/kanade/domain/ui/model/TabletUiMode.kt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package eu.kanade.domain.ui.model
|
||||||
|
|
||||||
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
|
||||||
|
enum class TabletUiMode(val titleRes: StringResource) {
|
||||||
|
AUTOMATIC(MR.strings.automatic_background),
|
||||||
|
ALWAYS(MR.strings.lock_always),
|
||||||
|
LANDSCAPE(MR.strings.landscape),
|
||||||
|
NEVER(MR.strings.lock_never),
|
||||||
|
}
|
19
app/src/main/java/eu/kanade/domain/ui/model/ThemeMode.kt
Normal file
19
app/src/main/java/eu/kanade/domain/ui/model/ThemeMode.kt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package eu.kanade.domain.ui.model
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
|
||||||
|
enum class ThemeMode {
|
||||||
|
LIGHT,
|
||||||
|
DARK,
|
||||||
|
SYSTEM,
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAppCompatDelegateThemeMode(themeMode: ThemeMode) {
|
||||||
|
AppCompatDelegate.setDefaultNightMode(
|
||||||
|
when (themeMode) {
|
||||||
|
ThemeMode.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
|
||||||
|
ThemeMode.DARK -> AppCompatDelegate.MODE_NIGHT_YES
|
||||||
|
ThemeMode.SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||||
|
import androidx.compose.material.icons.outlined.Public
|
||||||
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.SnackbarResult
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.paging.LoadState
|
||||||
|
import androidx.paging.compose.LazyPagingItems
|
||||||
|
import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
|
||||||
|
import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
|
||||||
|
import eu.kanade.presentation.browse.components.BrowseSourceList
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.util.formattedMessage
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
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
|
||||||
|
fun BrowseSourceContent(
|
||||||
|
source: Source?,
|
||||||
|
mangaList: LazyPagingItems<StateFlow<Manga>>,
|
||||||
|
columns: GridCells,
|
||||||
|
displayMode: LibraryDisplayMode,
|
||||||
|
snackbarHostState: SnackbarHostState,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
onWebViewClick: () -> Unit,
|
||||||
|
onHelpClick: () -> Unit,
|
||||||
|
onLocalSourceHelpClick: () -> Unit,
|
||||||
|
onMangaClick: (Manga) -> Unit,
|
||||||
|
onMangaLongClick: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val errorState = mangaList.loadState.refresh.takeIf { it is LoadState.Error }
|
||||||
|
?: mangaList.loadState.append.takeIf { it is LoadState.Error }
|
||||||
|
|
||||||
|
val getErrorMessage: (LoadState.Error) -> String = { state ->
|
||||||
|
with(context) { state.error.formattedMessage }
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(errorState) {
|
||||||
|
if (mangaList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
||||||
|
val result = snackbarHostState.showSnackbar(
|
||||||
|
message = getErrorMessage(errorState),
|
||||||
|
actionLabel = context.stringResource(MR.strings.action_retry),
|
||||||
|
duration = SnackbarDuration.Indefinite,
|
||||||
|
)
|
||||||
|
when (result) {
|
||||||
|
SnackbarResult.Dismissed -> snackbarHostState.currentSnackbarData?.dismiss()
|
||||||
|
SnackbarResult.ActionPerformed -> mangaList.retry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mangaList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) {
|
||||||
|
EmptyScreen(
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
message = getErrorMessage(errorState),
|
||||||
|
actions = if (source is LocalSource) {
|
||||||
|
persistentListOf(
|
||||||
|
EmptyScreenAction(
|
||||||
|
stringRes = MR.strings.local_source_help_guide,
|
||||||
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
|
onClick = onLocalSourceHelpClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
persistentListOf(
|
||||||
|
EmptyScreenAction(
|
||||||
|
stringRes = MR.strings.action_retry,
|
||||||
|
icon = Icons.Outlined.Refresh,
|
||||||
|
onClick = mangaList::refresh,
|
||||||
|
),
|
||||||
|
EmptyScreenAction(
|
||||||
|
stringRes = MR.strings.action_open_in_web_view,
|
||||||
|
icon = Icons.Outlined.Public,
|
||||||
|
onClick = onWebViewClick,
|
||||||
|
),
|
||||||
|
EmptyScreenAction(
|
||||||
|
stringRes = MR.strings.label_help,
|
||||||
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
|
onClick = onHelpClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mangaList.itemCount == 0 && mangaList.loadState.refresh is LoadState.Loading) {
|
||||||
|
LoadingScreen(
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
when (displayMode) {
|
||||||
|
LibraryDisplayMode.ComfortableGrid -> {
|
||||||
|
BrowseSourceComfortableGrid(
|
||||||
|
mangaList = mangaList,
|
||||||
|
columns = columns,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
onMangaClick = onMangaClick,
|
||||||
|
onMangaLongClick = onMangaLongClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
LibraryDisplayMode.List -> {
|
||||||
|
BrowseSourceList(
|
||||||
|
mangaList = mangaList,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
onMangaClick = onMangaClick,
|
||||||
|
onMangaLongClick = onMangaLongClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> {
|
||||||
|
BrowseSourceCompactGrid(
|
||||||
|
mangaList = mangaList,
|
||||||
|
columns = columns,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
onMangaClick = onMangaClick,
|
||||||
|
onMangaLongClick = onMangaLongClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun MissingSourceScreen(
|
||||||
|
source: StubSource,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
AppBar(
|
||||||
|
title = source.name,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
EmptyScreen(
|
||||||
|
message = stringResource(MR.strings.source_not_installed, source.toString()),
|
||||||
|
modifier = Modifier.padding(paddingValues),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,445 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.util.DisplayMetrics
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||||
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.domain.extension.interactor.ExtensionSourceItem
|
||||||
|
import eu.kanade.presentation.browse.components.ExtensionIcon
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
|
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
||||||
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
|
||||||
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
|
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
|
||||||
|
fun ExtensionDetailsScreen(
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
state: ExtensionDetailsScreenModel.State,
|
||||||
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
|
onClickEnableAll: () -> Unit,
|
||||||
|
onClickDisableAll: () -> Unit,
|
||||||
|
onClickClearCookies: () -> Unit,
|
||||||
|
onClickUninstall: () -> 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(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
AppBar(
|
||||||
|
title = stringResource(MR.strings.label_extension_info),
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
actions = {
|
||||||
|
AppBarActions(
|
||||||
|
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||||
|
.apply {
|
||||||
|
if (url != null) {
|
||||||
|
add(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.action_open_repo),
|
||||||
|
icon = Icons.AutoMirrored.Outlined.Launch,
|
||||||
|
onClick = {
|
||||||
|
uriHandler.openUri(url)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
if (state.extension == null) {
|
||||||
|
EmptyScreen(
|
||||||
|
MR.strings.empty_screen,
|
||||||
|
modifier = Modifier.padding(paddingValues),
|
||||||
|
)
|
||||||
|
return@Scaffold
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtensionDetails(
|
||||||
|
contentPadding = paddingValues,
|
||||||
|
extension = state.extension,
|
||||||
|
sources = state.sources,
|
||||||
|
onClickSourcePreferences = onClickSourcePreferences,
|
||||||
|
onClickUninstall = onClickUninstall,
|
||||||
|
onClickSource = onClickSource,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ExtensionDetails(
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
extension: Extension.Installed,
|
||||||
|
sources: ImmutableList<ExtensionSourceItem>,
|
||||||
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
|
onClickUninstall: () -> Unit,
|
||||||
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
var showNsfwWarning by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
ScrollbarLazyColumn(
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
) {
|
||||||
|
if (extension.isObsolete) {
|
||||||
|
item {
|
||||||
|
WarningBanner(MR.strings.obsolete_extension_message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
DetailsHeader(
|
||||||
|
extension = extension,
|
||||||
|
onClickUninstall = onClickUninstall,
|
||||||
|
onClickAppInfo = {
|
||||||
|
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||||
|
data = Uri.fromParts("package", extension.pkgName, null)
|
||||||
|
context.startActivity(this)
|
||||||
|
}
|
||||||
|
Unit
|
||||||
|
}.takeIf { extension.isShared },
|
||||||
|
onClickAgeRating = {
|
||||||
|
showNsfwWarning = true
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items(
|
||||||
|
items = sources,
|
||||||
|
key = { it.source.id },
|
||||||
|
) { source ->
|
||||||
|
SourceSwitchPreference(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
|
source = source,
|
||||||
|
onClickSourcePreferences = onClickSourcePreferences,
|
||||||
|
onClickSource = onClickSource,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (showNsfwWarning) {
|
||||||
|
NsfwWarningDialog(
|
||||||
|
onClickConfirm = {
|
||||||
|
showNsfwWarning = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DetailsHeader(
|
||||||
|
extension: Extension,
|
||||||
|
onClickAgeRating: () -> Unit,
|
||||||
|
onClickUninstall: () -> Unit,
|
||||||
|
onClickAppInfo: (() -> Unit)?,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
|
start = MaterialTheme.padding.medium,
|
||||||
|
end = MaterialTheme.padding.medium,
|
||||||
|
top = MaterialTheme.padding.medium,
|
||||||
|
bottom = MaterialTheme.padding.small,
|
||||||
|
)
|
||||||
|
.clickable {
|
||||||
|
val extDebugInfo = buildString {
|
||||||
|
append(
|
||||||
|
"""
|
||||||
|
Extension name: ${extension.name} (lang: ${extension.lang}; package: ${extension.pkgName})
|
||||||
|
Extension version: ${extension.versionName} (lib: ${extension.libVersion}; version code: ${extension.versionCode})
|
||||||
|
NSFW: ${extension.isNsfw}
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (extension is Extension.Installed) {
|
||||||
|
append("\n\n")
|
||||||
|
append(
|
||||||
|
"""
|
||||||
|
Update available: ${extension.hasUpdate}
|
||||||
|
Obsolete: ${extension.isObsolete}
|
||||||
|
Shared: ${extension.isShared}
|
||||||
|
Repository: ${extension.repoUrl}
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.copyToClipboard("Extension Debug information", extDebugInfo)
|
||||||
|
},
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
ExtensionIcon(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(112.dp),
|
||||||
|
extension = extension,
|
||||||
|
density = DisplayMetrics.DENSITY_XXXHIGH,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = extension.name,
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
|
||||||
|
val strippedPkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = strippedPkgName,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
|
horizontal = MaterialTheme.padding.extraLarge,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
),
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
InfoText(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
primaryText = extension.versionName,
|
||||||
|
secondaryText = stringResource(MR.strings.ext_info_version),
|
||||||
|
)
|
||||||
|
|
||||||
|
InfoDivider()
|
||||||
|
|
||||||
|
InfoText(
|
||||||
|
modifier = Modifier.weight(if (extension.isNsfw) 1.5f else 1f),
|
||||||
|
primaryText = LocaleHelper.getSourceDisplayName(extension.lang, context),
|
||||||
|
secondaryText = stringResource(MR.strings.ext_info_language),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (extension.isNsfw) {
|
||||||
|
InfoDivider()
|
||||||
|
|
||||||
|
InfoText(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
primaryText = stringResource(MR.strings.ext_nsfw_short),
|
||||||
|
primaryTextStyle = MaterialTheme.typography.bodyLarge.copy(
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
),
|
||||||
|
secondaryText = stringResource(MR.strings.ext_info_age_rating),
|
||||||
|
onClick = onClickAgeRating,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
start = MaterialTheme.padding.medium,
|
||||||
|
end = MaterialTheme.padding.medium,
|
||||||
|
top = MaterialTheme.padding.small,
|
||||||
|
bottom = MaterialTheme.padding.medium,
|
||||||
|
),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
||||||
|
) {
|
||||||
|
OutlinedButton(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
onClick = onClickUninstall,
|
||||||
|
) {
|
||||||
|
Text(stringResource(MR.strings.ext_uninstall))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onClickAppInfo != null) {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
onClick = onClickAppInfo,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(MR.strings.ext_app_info),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InfoText(
|
||||||
|
primaryText: String,
|
||||||
|
secondaryText: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||||
|
onClick: (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
val clickableModifier = if (onClick != null) {
|
||||||
|
Modifier.clickable(interactionSource = null, indication = null, onClick = onClick)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier.then(clickableModifier),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = primaryText,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = primaryTextStyle,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = secondaryText + if (onClick != null) " ⓘ" else "",
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun InfoDivider() {
|
||||||
|
VerticalDivider(
|
||||||
|
modifier = Modifier.height(20.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SourceSwitchPreference(
|
||||||
|
source: ExtensionSourceItem,
|
||||||
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
TextPreferenceWidget(
|
||||||
|
modifier = modifier,
|
||||||
|
title = if (source.labelAsName) {
|
||||||
|
source.source.toString()
|
||||||
|
} else {
|
||||||
|
LocaleHelper.getSourceDisplayName(source.source.lang, context)
|
||||||
|
},
|
||||||
|
widget = {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
if (source.source is ConfigurableSource) {
|
||||||
|
IconButton(onClick = { onClickSourcePreferences(source.source.id) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Settings,
|
||||||
|
contentDescription = stringResource(MR.strings.label_settings),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = source.enabled,
|
||||||
|
onCheckedChange = null,
|
||||||
|
modifier = Modifier.padding(start = TrailingWidgetBuffer),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPreferenceClick = { onClickSource(source.source.id) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NsfwWarningDialog(
|
||||||
|
onClickConfirm: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(MR.strings.ext_nsfw_warning))
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onClickConfirm) {
|
||||||
|
Text(text = stringResource(MR.strings.action_ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = onClickConfirm,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
|
||||||
|
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
|
||||||
|
fun ExtensionFilterScreen(
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
state: ExtensionFilterState.Success,
|
||||||
|
onClickToggle: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
AppBar(
|
||||||
|
title = stringResource(MR.strings.label_extensions),
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { contentPadding ->
|
||||||
|
if (state.isEmpty) {
|
||||||
|
EmptyScreen(
|
||||||
|
stringRes = MR.strings.empty_screen,
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
)
|
||||||
|
return@Scaffold
|
||||||
|
}
|
||||||
|
ExtensionFilterContent(
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
state = state,
|
||||||
|
onClickLang = onClickToggle,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ExtensionFilterContent(
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
state: ExtensionFilterState.Success,
|
||||||
|
onClickLang: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
) {
|
||||||
|
items(state.languages) { language ->
|
||||||
|
SwitchPreferenceWidget(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
|
title = LocaleHelper.getSourceDisplayName(language, context),
|
||||||
|
checked = language in state.enabledLanguages,
|
||||||
|
onCheckedChanged = { onClickLang(language) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,542 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
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.Button
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
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.ExtensionIcon
|
||||||
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
|
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
||||||
|
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
||||||
|
import eu.kanade.presentation.util.animateItemFastScroll
|
||||||
|
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
||||||
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
||||||
|
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
|
||||||
|
fun ExtensionScreen(
|
||||||
|
state: ExtensionsScreenModel.State,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
searchQuery: String?,
|
||||||
|
onLongClickItem: (Extension) -> Unit,
|
||||||
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
|
onOpenWebView: (Extension.Available) -> Unit,
|
||||||
|
onInstallExtension: (Extension.Available) -> Unit,
|
||||||
|
onUninstallExtension: (Extension) -> Unit,
|
||||||
|
onUpdateExtension: (Extension.Installed) -> Unit,
|
||||||
|
onTrustExtension: (Extension.Untrusted) -> Unit,
|
||||||
|
onOpenExtension: (Extension.Installed) -> Unit,
|
||||||
|
onClickUpdateAll: () -> Unit,
|
||||||
|
onRefresh: () -> Unit,
|
||||||
|
) {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
|
PullRefresh(
|
||||||
|
refreshing = state.isRefreshing,
|
||||||
|
onRefresh = onRefresh,
|
||||||
|
enabled = !state.isLoading,
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
|
state.isEmpty -> {
|
||||||
|
val msg = if (!searchQuery.isNullOrEmpty()) {
|
||||||
|
MR.strings.no_results_found
|
||||||
|
} else {
|
||||||
|
MR.strings.empty_screen
|
||||||
|
}
|
||||||
|
EmptyScreen(
|
||||||
|
stringRes = msg,
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
actions = persistentListOf(
|
||||||
|
EmptyScreenAction(
|
||||||
|
stringRes = MR.strings.label_extension_repos,
|
||||||
|
icon = Icons.Outlined.Settings,
|
||||||
|
onClick = { navigator.push(ExtensionReposScreen()) },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
ExtensionContent(
|
||||||
|
state = state,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
onLongClickItem = onLongClickItem,
|
||||||
|
onClickItemCancel = onClickItemCancel,
|
||||||
|
onOpenWebView = onOpenWebView,
|
||||||
|
onInstallExtension = onInstallExtension,
|
||||||
|
onUninstallExtension = onUninstallExtension,
|
||||||
|
onUpdateExtension = onUpdateExtension,
|
||||||
|
onTrustExtension = onTrustExtension,
|
||||||
|
onOpenExtension = onOpenExtension,
|
||||||
|
onClickUpdateAll = onClickUpdateAll,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ExtensionContent(
|
||||||
|
state: ExtensionsScreenModel.State,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
onLongClickItem: (Extension) -> Unit,
|
||||||
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
|
onOpenWebView: (Extension.Available) -> Unit,
|
||||||
|
onInstallExtension: (Extension.Available) -> Unit,
|
||||||
|
onUninstallExtension: (Extension) -> Unit,
|
||||||
|
onUpdateExtension: (Extension.Installed) -> Unit,
|
||||||
|
onTrustExtension: (Extension.Untrusted) -> Unit,
|
||||||
|
onOpenExtension: (Extension.Installed) -> Unit,
|
||||||
|
onClickUpdateAll: () -> Unit,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
|
||||||
|
val installGranted = rememberRequestPackageInstallsPermissionState(initialValue = true)
|
||||||
|
|
||||||
|
FastScrollLazyColumn(
|
||||||
|
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) ->
|
||||||
|
item(
|
||||||
|
contentType = "header",
|
||||||
|
key = "extensionHeader-${header.hashCode()}",
|
||||||
|
) {
|
||||||
|
when (header) {
|
||||||
|
is ExtensionUiModel.Header.Resource -> {
|
||||||
|
val action: @Composable RowScope.() -> Unit =
|
||||||
|
if (header.textRes == MR.strings.ext_updates_pending) {
|
||||||
|
{
|
||||||
|
Button(onClick = { onClickUpdateAll() }) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(MR.strings.ext_update_all),
|
||||||
|
style = LocalTextStyle.current.copy(
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
ExtensionHeader(
|
||||||
|
textRes = header.textRes,
|
||||||
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
|
action = action,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is ExtensionUiModel.Header.Text -> {
|
||||||
|
ExtensionHeader(
|
||||||
|
text = header.text,
|
||||||
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(
|
||||||
|
items = items,
|
||||||
|
contentType = { "item" },
|
||||||
|
key = { item ->
|
||||||
|
when (item.extension) {
|
||||||
|
is Extension.Untrusted -> "extension-untrusted-${item.hashCode()}"
|
||||||
|
is Extension.Installed -> "extension-installed-${item.hashCode()}"
|
||||||
|
is Extension.Available -> "extension-available-${item.hashCode()}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) { item ->
|
||||||
|
ExtensionItem(
|
||||||
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
|
item = item,
|
||||||
|
onClickItem = {
|
||||||
|
when (it) {
|
||||||
|
is Extension.Available -> onInstallExtension(it)
|
||||||
|
is Extension.Installed -> onOpenExtension(it)
|
||||||
|
is Extension.Untrusted -> {
|
||||||
|
trustState = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClickItem = onLongClickItem,
|
||||||
|
onClickItemSecondaryAction = {
|
||||||
|
when (it) {
|
||||||
|
is Extension.Available -> onOpenWebView(it)
|
||||||
|
is Extension.Installed -> onOpenExtension(it)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClickItemCancel = onClickItemCancel,
|
||||||
|
onClickItemAction = {
|
||||||
|
when (it) {
|
||||||
|
is Extension.Available -> onInstallExtension(it)
|
||||||
|
is Extension.Installed -> {
|
||||||
|
if (it.hasUpdate) {
|
||||||
|
onUpdateExtension(it)
|
||||||
|
} else {
|
||||||
|
onOpenExtension(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Extension.Untrusted -> {
|
||||||
|
trustState = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (trustState != null) {
|
||||||
|
ExtensionTrustDialog(
|
||||||
|
onClickConfirm = {
|
||||||
|
onTrustExtension(trustState!!)
|
||||||
|
trustState = null
|
||||||
|
},
|
||||||
|
onClickDismiss = {
|
||||||
|
onUninstallExtension(trustState!!)
|
||||||
|
trustState = null
|
||||||
|
},
|
||||||
|
onDismissRequest = {
|
||||||
|
trustState = null
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ExtensionItem(
|
||||||
|
item: ExtensionUiModel.Item,
|
||||||
|
onClickItem: (Extension) -> Unit,
|
||||||
|
onLongClickItem: (Extension) -> Unit,
|
||||||
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
|
onClickItemAction: (Extension) -> Unit,
|
||||||
|
onClickItemSecondaryAction: (Extension) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val (extension, installStep) = item
|
||||||
|
BaseBrowseItem(
|
||||||
|
modifier = modifier
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = { onClickItem(extension) },
|
||||||
|
onLongClick = { onLongClickItem(extension) },
|
||||||
|
),
|
||||||
|
onClickItem = { onClickItem(extension) },
|
||||||
|
onLongClickItem = { onLongClickItem(extension) },
|
||||||
|
icon = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
val idle = installStep.isCompleted()
|
||||||
|
if (!idle) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(40.dp),
|
||||||
|
strokeWidth = 2.dp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val padding by animateDpAsState(
|
||||||
|
targetValue = if (idle) 0.dp else 8.dp,
|
||||||
|
label = "iconPadding",
|
||||||
|
)
|
||||||
|
ExtensionIcon(
|
||||||
|
extension = extension,
|
||||||
|
modifier = Modifier
|
||||||
|
.matchParentSize()
|
||||||
|
.padding(padding),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
action = {
|
||||||
|
ExtensionItemActions(
|
||||||
|
extension = extension,
|
||||||
|
installStep = installStep,
|
||||||
|
onClickItemCancel = onClickItemCancel,
|
||||||
|
onClickItemAction = onClickItemAction,
|
||||||
|
onClickItemSecondaryAction = onClickItemSecondaryAction,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
ExtensionItemContent(
|
||||||
|
extension = extension,
|
||||||
|
installStep = installStep,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ExtensionItemContent(
|
||||||
|
extension: Extension,
|
||||||
|
installStep: InstallStep,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.padding(start = MaterialTheme.padding.medium),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = extension.name,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
// Won't look good but it's not like we can ellipsize overflowing content
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||||
|
) {
|
||||||
|
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||||
|
if (extension is Extension.Installed && extension.lang.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = LocaleHelper.getSourceDisplayName(extension.lang, LocalContext.current),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension.versionName.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = extension.versionName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val warning = when {
|
||||||
|
extension is Extension.Untrusted -> MR.strings.ext_untrusted
|
||||||
|
extension is Extension.Installed && extension.isObsolete -> MR.strings.ext_obsolete
|
||||||
|
extension.isNsfw -> MR.strings.ext_nsfw_short
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (warning != null) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(warning).uppercase(),
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!installStep.isCompleted()) {
|
||||||
|
DotSeparatorNoSpaceText()
|
||||||
|
Text(
|
||||||
|
text = when (installStep) {
|
||||||
|
InstallStep.Pending -> stringResource(MR.strings.ext_pending)
|
||||||
|
InstallStep.Downloading -> stringResource(MR.strings.ext_downloading)
|
||||||
|
InstallStep.Installing -> stringResource(MR.strings.ext_installing)
|
||||||
|
else -> error("Must not show non-install process text")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ExtensionItemActions(
|
||||||
|
extension: Extension,
|
||||||
|
installStep: InstallStep,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClickItemCancel: (Extension) -> Unit = {},
|
||||||
|
onClickItemAction: (Extension) -> Unit = {},
|
||||||
|
onClickItemSecondaryAction: (Extension) -> Unit = {},
|
||||||
|
) {
|
||||||
|
val isIdle = installStep.isCompleted()
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
!isIdle -> {
|
||||||
|
IconButton(onClick = { onClickItemCancel(extension) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Close,
|
||||||
|
contentDescription = stringResource(MR.strings.action_cancel),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
installStep == InstallStep.Error -> {
|
||||||
|
IconButton(onClick = { onClickItemAction(extension) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Refresh,
|
||||||
|
contentDescription = stringResource(MR.strings.action_retry),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Extension.Untrusted -> {
|
||||||
|
IconButton(onClick = { onClickItemAction(extension) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.VerifiedUser,
|
||||||
|
contentDescription = stringResource(MR.strings.ext_trust),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ExtensionHeader(
|
||||||
|
textRes: StringResource,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
action: @Composable RowScope.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
ExtensionHeader(
|
||||||
|
text = stringResource(textRes),
|
||||||
|
modifier = modifier,
|
||||||
|
action = action,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ExtensionHeader(
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
action: @Composable RowScope.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier.padding(horizontal = MaterialTheme.padding.medium),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
.weight(1f),
|
||||||
|
style = MaterialTheme.typography.header,
|
||||||
|
)
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ExtensionTrustDialog(
|
||||||
|
onClickConfirm: () -> Unit,
|
||||||
|
onClickDismiss: () -> Unit,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(MR.strings.untrusted_extension))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(MR.strings.untrusted_extension_message))
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onClickConfirm) {
|
||||||
|
Text(text = stringResource(MR.strings.ext_trust))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onClickDismiss) {
|
||||||
|
Text(text = stringResource(MR.strings.ext_uninstall))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
||||||
|
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.tachiyomi.source.CatalogueSource
|
||||||
|
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 tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GlobalSearchScreen(
|
||||||
|
state: SearchScreenModel.State,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
|
onSearch: (String) -> Unit,
|
||||||
|
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||||
|
onToggleResults: () -> Unit,
|
||||||
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
|
onClickItem: (Manga) -> Unit,
|
||||||
|
onLongClickItem: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
GlobalSearchToolbar(
|
||||||
|
searchQuery = state.searchQuery,
|
||||||
|
progress = state.progress,
|
||||||
|
total = state.total,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
|
onSearch = onSearch,
|
||||||
|
sourceFilter = state.sourceFilter,
|
||||||
|
onChangeSearchFilter = onChangeSearchFilter,
|
||||||
|
onlyShowHasResults = state.onlyShowHasResults,
|
||||||
|
onToggleResults = onToggleResults,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
GlobalSearchContent(
|
||||||
|
items = state.filteredItems,
|
||||||
|
contentPadding = paddingValues,
|
||||||
|
getManga = getManga,
|
||||||
|
onClickSource = onClickSource,
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onLongClickItem = onLongClickItem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun GlobalSearchContent(
|
||||||
|
items: Map<CatalogueSource, SearchItemResult>,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
|
onClickItem: (Manga) -> Unit,
|
||||||
|
onLongClickItem: (Manga) -> Unit,
|
||||||
|
fromSourceId: Long? = null,
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
) {
|
||||||
|
items.forEach { (source, result) ->
|
||||||
|
item(key = source.id) {
|
||||||
|
GlobalSearchResultItem(
|
||||||
|
title = fromSourceId?.let {
|
||||||
|
"▶ ${source.name}".takeIf { source.id == fromSourceId }
|
||||||
|
} ?: source.name,
|
||||||
|
subtitle = LocaleHelper.getLocalizedDisplayName(source.lang),
|
||||||
|
onClick = { onClickSource(source) },
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
|
) {
|
||||||
|
when (result) {
|
||||||
|
SearchItemResult.Loading -> {
|
||||||
|
GlobalSearchLoadingResultItem()
|
||||||
|
}
|
||||||
|
is SearchItemResult.Success -> {
|
||||||
|
GlobalSearchCardRow(
|
||||||
|
titles = result.result,
|
||||||
|
getManga = getManga,
|
||||||
|
onClick = onClickItem,
|
||||||
|
onLongClick = onLongClickItem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is SearchItemResult.Error -> {
|
||||||
|
GlobalSearchErrorResultItem(message = result.throwable.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreenModel
|
||||||
|
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
|
||||||
|
fun MigrateMangaScreen(
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
title: String?,
|
||||||
|
state: MigrateMangaScreenModel.State,
|
||||||
|
onClickItem: (Manga) -> Unit,
|
||||||
|
onClickCover: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
AppBar(
|
||||||
|
title = title,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { contentPadding ->
|
||||||
|
if (state.isEmpty) {
|
||||||
|
EmptyScreen(
|
||||||
|
stringRes = MR.strings.empty_screen,
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
)
|
||||||
|
return@Scaffold
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrateMangaContent(
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
state = state,
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onClickCover = onClickCover,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MigrateMangaContent(
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
state: MigrateMangaScreenModel.State,
|
||||||
|
onClickItem: (Manga) -> Unit,
|
||||||
|
onClickCover: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
FastScrollLazyColumn(
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
) {
|
||||||
|
items(state.titles) { manga ->
|
||||||
|
MigrateMangaItem(
|
||||||
|
manga = manga,
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onClickCover = onClickCover,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MigrateMangaItem(
|
||||||
|
manga: Manga,
|
||||||
|
onClickItem: (Manga) -> Unit,
|
||||||
|
onClickCover: (Manga) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
BaseMangaListItem(
|
||||||
|
modifier = modifier,
|
||||||
|
manga = manga,
|
||||||
|
onClickItem = { onClickItem(manga) },
|
||||||
|
onClickCover = { onClickCover(manga) },
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||||
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MigrateSearchScreen(
|
||||||
|
state: SearchScreenModel.State,
|
||||||
|
fromSourceId: Long?,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
|
onSearch: (String) -> Unit,
|
||||||
|
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||||
|
onToggleResults: () -> Unit,
|
||||||
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
|
onClickItem: (Manga) -> Unit,
|
||||||
|
onLongClickItem: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
GlobalSearchToolbar(
|
||||||
|
searchQuery = state.searchQuery,
|
||||||
|
progress = state.progress,
|
||||||
|
total = state.total,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
|
onSearch = onSearch,
|
||||||
|
sourceFilter = state.sourceFilter,
|
||||||
|
onChangeSearchFilter = onChangeSearchFilter,
|
||||||
|
onlyShowHasResults = state.onlyShowHasResults,
|
||||||
|
onToggleResults = onToggleResults,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
GlobalSearchContent(
|
||||||
|
fromSourceId = fromSourceId,
|
||||||
|
items = state.filteredItems,
|
||||||
|
contentPadding = paddingValues,
|
||||||
|
getManga = getManga,
|
||||||
|
onClickSource = onClickSource,
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onLongClickItem = onLongClickItem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,205 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.ArrowDownward
|
||||||
|
import androidx.compose.material.icons.outlined.ArrowUpward
|
||||||
|
import androidx.compose.material.icons.outlined.Numbers
|
||||||
|
import androidx.compose.material.icons.outlined.SortByAlpha
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
|
import eu.kanade.presentation.browse.components.SourceIcon
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel
|
||||||
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
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
|
||||||
|
fun MigrateSourceScreen(
|
||||||
|
state: MigrateSourceScreenModel.State,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
onClickItem: (Source) -> Unit,
|
||||||
|
onToggleSortingDirection: () -> Unit,
|
||||||
|
onToggleSortingMode: () -> Unit,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
when {
|
||||||
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
|
state.isEmpty -> EmptyScreen(
|
||||||
|
stringRes = MR.strings.information_empty_library,
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
)
|
||||||
|
else ->
|
||||||
|
MigrateSourceList(
|
||||||
|
list = state.items,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onLongClickItem = { source ->
|
||||||
|
val sourceId = source.id.toString()
|
||||||
|
context.copyToClipboard(sourceId, sourceId)
|
||||||
|
},
|
||||||
|
sortingMode = state.sortingMode,
|
||||||
|
onToggleSortingMode = onToggleSortingMode,
|
||||||
|
sortingDirection = state.sortingDirection,
|
||||||
|
onToggleSortingDirection = onToggleSortingDirection,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MigrateSourceList(
|
||||||
|
list: ImmutableList<Pair<Source, Long>>,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
onClickItem: (Source) -> Unit,
|
||||||
|
onLongClickItem: (Source) -> Unit,
|
||||||
|
sortingMode: SetMigrateSorting.Mode,
|
||||||
|
onToggleSortingMode: () -> Unit,
|
||||||
|
sortingDirection: SetMigrateSorting.Direction,
|
||||||
|
onToggleSortingDirection: () -> Unit,
|
||||||
|
) {
|
||||||
|
ScrollbarLazyColumn(
|
||||||
|
contentPadding = contentPadding + topSmallPaddingValues,
|
||||||
|
) {
|
||||||
|
stickyHeader(key = STICKY_HEADER_KEY_PREFIX) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
.padding(start = MaterialTheme.padding.medium),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(MR.strings.migration_selection_prompt),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
style = MaterialTheme.typography.header,
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(onClick = onToggleSortingMode) {
|
||||||
|
when (sortingMode) {
|
||||||
|
SetMigrateSorting.Mode.ALPHABETICAL -> Icon(
|
||||||
|
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) {
|
||||||
|
when (sortingDirection) {
|
||||||
|
SetMigrateSorting.Direction.ASCENDING -> Icon(
|
||||||
|
Icons.Outlined.ArrowUpward,
|
||||||
|
contentDescription = stringResource(MR.strings.action_asc),
|
||||||
|
)
|
||||||
|
SetMigrateSorting.Direction.DESCENDING -> Icon(
|
||||||
|
Icons.Outlined.ArrowDownward,
|
||||||
|
contentDescription = stringResource(MR.strings.action_desc),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(
|
||||||
|
items = list,
|
||||||
|
key = { (source, _) -> "migrate-${source.id}" },
|
||||||
|
) { (source, count) ->
|
||||||
|
MigrateSourceItem(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
|
source = source,
|
||||||
|
count = count,
|
||||||
|
onClickItem = { onClickItem(source) },
|
||||||
|
onLongClickItem = { onLongClickItem(source) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MigrateSourceItem(
|
||||||
|
source: Source,
|
||||||
|
count: Long,
|
||||||
|
onClickItem: () -> Unit,
|
||||||
|
onLongClickItem: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
BaseSourceItem(
|
||||||
|
modifier = modifier,
|
||||||
|
source = source,
|
||||||
|
showLanguageInContent = source.lang != "",
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onLongClickItem = onLongClickItem,
|
||||||
|
icon = { SourceIcon(source = source) },
|
||||||
|
action = {
|
||||||
|
BadgeGroup {
|
||||||
|
Badge(text = "$count")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content = { _, sourceLangString ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = MaterialTheme.padding.medium)
|
||||||
|
.weight(1f),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = source.name.ifBlank { source.id.toString() },
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
if (sourceLangString != null) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
text = sourceLangString,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (source.isStub) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
text = stringResource(MR.strings.not_installed),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
|
import eu.kanade.presentation.util.animateItemFastScroll
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
|
||||||
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
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
|
||||||
|
fun SourcesFilterScreen(
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
state: SourcesFilterScreenModel.State.Success,
|
||||||
|
onClickLanguage: (String) -> Unit,
|
||||||
|
onClickSource: (Source) -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
AppBar(
|
||||||
|
title = stringResource(MR.strings.label_sources),
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { contentPadding ->
|
||||||
|
if (state.isEmpty) {
|
||||||
|
EmptyScreen(
|
||||||
|
stringRes = MR.strings.source_filter_empty_screen,
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
)
|
||||||
|
return@Scaffold
|
||||||
|
}
|
||||||
|
SourcesFilterContent(
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
state = state,
|
||||||
|
onClickLanguage = onClickLanguage,
|
||||||
|
onClickSource = onClickSource,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SourcesFilterContent(
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
state: SourcesFilterScreenModel.State.Success,
|
||||||
|
onClickLanguage: (String) -> Unit,
|
||||||
|
onClickSource: (Source) -> Unit,
|
||||||
|
) {
|
||||||
|
FastScrollLazyColumn(
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
) {
|
||||||
|
state.items.forEach { (language, sources) ->
|
||||||
|
val enabled = language in state.enabledLanguages
|
||||||
|
item(
|
||||||
|
key = language,
|
||||||
|
contentType = "source-filter-header",
|
||||||
|
) {
|
||||||
|
SourcesFilterHeader(
|
||||||
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
|
language = language,
|
||||||
|
enabled = enabled,
|
||||||
|
onClickItem = onClickLanguage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (enabled) {
|
||||||
|
items(
|
||||||
|
items = sources,
|
||||||
|
key = { "source-filter-${it.key()}" },
|
||||||
|
contentType = { "source-filter-item" },
|
||||||
|
) { source ->
|
||||||
|
SourcesFilterItem(
|
||||||
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
|
source = source,
|
||||||
|
enabled = "${source.id}" !in state.disabledSources,
|
||||||
|
onClickItem = onClickSource,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SourcesFilterHeader(
|
||||||
|
language: String,
|
||||||
|
enabled: Boolean,
|
||||||
|
onClickItem: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
SwitchPreferenceWidget(
|
||||||
|
modifier = modifier,
|
||||||
|
title = LocaleHelper.getSourceDisplayName(language, LocalContext.current),
|
||||||
|
checked = enabled,
|
||||||
|
onCheckedChanged = { onClickItem(language) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SourcesFilterItem(
|
||||||
|
source: Source,
|
||||||
|
enabled: Boolean,
|
||||||
|
onClickItem: (Source) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
BaseSourceItem(
|
||||||
|
modifier = modifier,
|
||||||
|
source = source,
|
||||||
|
showLanguageInContent = false,
|
||||||
|
onClickItem = { onClickItem(source) },
|
||||||
|
action = {
|
||||||
|
Checkbox(checked = enabled, onCheckedChange = null)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
204
app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt
Normal file
204
app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.PushPin
|
||||||
|
import androidx.compose.material.icons.outlined.PushPin
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
|
||||||
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import tachiyomi.domain.source.model.Pin
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
|
import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
|
||||||
|
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
|
||||||
|
fun SourcesScreen(
|
||||||
|
state: SourcesScreenModel.State,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
onClickItem: (Source, Listing) -> Unit,
|
||||||
|
onClickPin: (Source) -> Unit,
|
||||||
|
onLongClickItem: (Source) -> Unit,
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
|
state.isEmpty -> EmptyScreen(
|
||||||
|
stringRes = MR.strings.source_empty_screen,
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
)
|
||||||
|
else -> {
|
||||||
|
ScrollbarLazyColumn(
|
||||||
|
contentPadding = contentPadding + topSmallPaddingValues,
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = state.items,
|
||||||
|
contentType = {
|
||||||
|
when (it) {
|
||||||
|
is SourceUiModel.Header -> "header"
|
||||||
|
is SourceUiModel.Item -> "item"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key = {
|
||||||
|
when (it) {
|
||||||
|
is SourceUiModel.Header -> it.hashCode()
|
||||||
|
is SourceUiModel.Item -> "source-${it.source.key()}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) { model ->
|
||||||
|
when (model) {
|
||||||
|
is SourceUiModel.Header -> {
|
||||||
|
SourceHeader(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
|
language = model.language,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is SourceUiModel.Item -> SourceItem(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
|
source = model.source,
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onLongClickItem = onLongClickItem,
|
||||||
|
onClickPin = onClickPin,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SourceHeader(
|
||||||
|
language: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
Text(
|
||||||
|
text = LocaleHelper.getSourceDisplayName(language, context),
|
||||||
|
modifier = modifier
|
||||||
|
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
||||||
|
style = MaterialTheme.typography.header,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SourceItem(
|
||||||
|
source: Source,
|
||||||
|
onClickItem: (Source, Listing) -> Unit,
|
||||||
|
onLongClickItem: (Source) -> Unit,
|
||||||
|
onClickPin: (Source) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
BaseSourceItem(
|
||||||
|
modifier = modifier,
|
||||||
|
source = source,
|
||||||
|
onClickItem = { onClickItem(source, Listing.Popular) },
|
||||||
|
onLongClickItem = { onLongClickItem(source) },
|
||||||
|
action = {
|
||||||
|
if (source.supportsLatest) {
|
||||||
|
TextButton(onClick = { onClickItem(source, Listing.Latest) }) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(MR.strings.latest),
|
||||||
|
style = LocalTextStyle.current.copy(
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SourcePinButton(
|
||||||
|
isPinned = Pin.Pinned in source.pin,
|
||||||
|
onClick = { onClickPin(source) },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SourcePinButton(
|
||||||
|
isPinned: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
val icon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin
|
||||||
|
val tint = if (isPinned) {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = SECONDARY_ALPHA,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin
|
||||||
|
IconButton(onClick = onClick) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
tint = tint,
|
||||||
|
contentDescription = stringResource(description),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SourceOptionsDialog(
|
||||||
|
source: Source,
|
||||||
|
onClickPin: () -> Unit,
|
||||||
|
onClickDisable: () -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
title = {
|
||||||
|
Text(text = source.visualName)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
val textId = if (Pin.Pinned in source.pin) MR.strings.action_unpin else MR.strings.action_pin
|
||||||
|
Text(
|
||||||
|
text = stringResource(textId),
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(onClick = onClickPin)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
)
|
||||||
|
if (!source.isLocal()) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(MR.strings.action_disable),
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(onClick = onClickDisable)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
confirmButton = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface SourceUiModel {
|
||||||
|
data class Item(val source: Source) : SourceUiModel
|
||||||
|
data class Header(val language: String) : SourceUiModel
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BaseBrowseItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClickItem: () -> Unit = {},
|
||||||
|
onLongClickItem: () -> Unit = {},
|
||||||
|
icon: @Composable RowScope.() -> Unit = {},
|
||||||
|
action: @Composable RowScope.() -> Unit = {},
|
||||||
|
content: @Composable RowScope.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = onClickItem,
|
||||||
|
onLongClick = onLongClickItem,
|
||||||
|
)
|
||||||
|
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
icon()
|
||||||
|
content()
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BaseSourceItem(
|
||||||
|
source: Source,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
showLanguageInContent: Boolean = true,
|
||||||
|
onClickItem: () -> Unit = {},
|
||||||
|
onLongClickItem: () -> Unit = {},
|
||||||
|
icon: @Composable RowScope.(Source) -> Unit = defaultIcon,
|
||||||
|
action: @Composable RowScope.(Source) -> Unit = {},
|
||||||
|
content: @Composable RowScope.(Source, String?) -> Unit = defaultContent,
|
||||||
|
) {
|
||||||
|
val sourceLangString = LocaleHelper.getSourceDisplayName(source.lang, LocalContext.current).takeIf {
|
||||||
|
showLanguageInContent
|
||||||
|
}
|
||||||
|
BaseBrowseItem(
|
||||||
|
modifier = modifier,
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onLongClickItem = onLongClickItem,
|
||||||
|
icon = { icon.invoke(this, source) },
|
||||||
|
action = { action.invoke(this, source) },
|
||||||
|
content = { content.invoke(this, source, sourceLangString) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val defaultIcon: @Composable RowScope.(Source) -> Unit = { source ->
|
||||||
|
SourceIcon(source = source)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val defaultContent: @Composable RowScope.(Source, String?) -> Unit = { source, sourceLangString ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = MaterialTheme.padding.medium)
|
||||||
|
.weight(1f),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = source.name,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
if (sourceLangString != null) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
text = sourceLangString,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import android.util.DisplayMetrics
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Dangerous
|
||||||
|
import androidx.compose.material.icons.filled.Warning
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.painter.ColorPainter
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.imageResource
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
import coil3.compose.AsyncImage
|
||||||
|
import eu.kanade.domain.source.model.icon
|
||||||
|
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
|
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||||
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
|
import tachiyomi.domain.source.model.Source
|
||||||
|
import tachiyomi.source.local.isLocal
|
||||||
|
|
||||||
|
private val defaultModifier = Modifier
|
||||||
|
.height(40.dp)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SourceIcon(
|
||||||
|
source: Source,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val icon = source.icon
|
||||||
|
|
||||||
|
when {
|
||||||
|
source.isStub && icon == null -> {
|
||||||
|
Image(
|
||||||
|
imageVector = Icons.Filled.Warning,
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
|
||||||
|
modifier = modifier.then(defaultModifier),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
icon != null -> {
|
||||||
|
Image(
|
||||||
|
bitmap = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = modifier.then(defaultModifier),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
source.isLocal() -> {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.mipmap.ic_local_source),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = modifier.then(defaultModifier),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.mipmap.ic_default_source),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = modifier.then(defaultModifier),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExtensionIcon(
|
||||||
|
extension: Extension,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
density: Int = DisplayMetrics.DENSITY_DEFAULT,
|
||||||
|
) {
|
||||||
|
when (extension) {
|
||||||
|
is Extension.Available -> {
|
||||||
|
AsyncImage(
|
||||||
|
model = extension.iconUrl,
|
||||||
|
contentDescription = null,
|
||||||
|
placeholder = ColorPainter(Color(0x1F888888)),
|
||||||
|
error = rememberResourceBitmapPainter(id = R.drawable.cover_error),
|
||||||
|
modifier = modifier
|
||||||
|
.clip(MaterialTheme.shapes.extraSmall),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is Extension.Installed -> {
|
||||||
|
val icon by extension.getIcon(density)
|
||||||
|
when (icon) {
|
||||||
|
Result.Loading -> Box(modifier = modifier)
|
||||||
|
is Result.Success -> Image(
|
||||||
|
bitmap = (icon as Result.Success<ImageBitmap>).value,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
Result.Error -> Image(
|
||||||
|
bitmap = ImageBitmap.imageResource(id = R.mipmap.ic_default_source),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Extension.Untrusted -> Image(
|
||||||
|
imageVector = Icons.Filled.Dangerous,
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
|
||||||
|
modifier = modifier.then(defaultModifier),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Extension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT): State<Result<ImageBitmap>> {
|
||||||
|
val context = LocalContext.current
|
||||||
|
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
|
||||||
|
withIOContext {
|
||||||
|
value = try {
|
||||||
|
val appInfo = ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo!!
|
||||||
|
val appResources = context.packageManager.getResourcesForApplication(appInfo)
|
||||||
|
Result.Success(
|
||||||
|
appResources.getDrawableForDensity(appInfo.icon, density, null)!!
|
||||||
|
.toBitmap()
|
||||||
|
.asImageBitmap(),
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Result<out T> {
|
||||||
|
data object Loading : Result<Nothing>()
|
||||||
|
data object Error : Result<Nothing>()
|
||||||
|
data class Success<out T>(val value: T) : Result<T>()
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.paging.LoadState
|
||||||
|
import androidx.paging.compose.LazyPagingItems
|
||||||
|
import eu.kanade.presentation.library.components.CommonMangaItemDefaults
|
||||||
|
import eu.kanade.presentation.library.components.MangaComfortableGridItem
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BrowseSourceComfortableGrid(
|
||||||
|
mangaList: LazyPagingItems<StateFlow<Manga>>,
|
||||||
|
columns: GridCells,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
onMangaClick: (Manga) -> Unit,
|
||||||
|
onMangaLongClick: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = columns,
|
||||||
|
contentPadding = contentPadding + PaddingValues(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridVerticalSpacer),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridHorizontalSpacer),
|
||||||
|
) {
|
||||||
|
if (mangaList.loadState.prepend is LoadState.Loading) {
|
||||||
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
|
BrowseSourceLoadingItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(count = mangaList.itemCount) { index ->
|
||||||
|
val manga by mangaList[index]?.collectAsState() ?: return@items
|
||||||
|
BrowseSourceComfortableGridItem(
|
||||||
|
manga = manga,
|
||||||
|
onClick = { onMangaClick(manga) },
|
||||||
|
onLongClick = { onMangaLongClick(manga) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mangaList.loadState.refresh is LoadState.Loading || mangaList.loadState.append is LoadState.Loading) {
|
||||||
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
|
BrowseSourceLoadingItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BrowseSourceComfortableGridItem(
|
||||||
|
manga: Manga,
|
||||||
|
onClick: () -> Unit = {},
|
||||||
|
onLongClick: () -> Unit = onClick,
|
||||||
|
) {
|
||||||
|
MangaComfortableGridItem(
|
||||||
|
title = manga.title,
|
||||||
|
coverData = MangaCover(
|
||||||
|
mangaId = manga.id,
|
||||||
|
sourceId = manga.source,
|
||||||
|
isMangaFavorite = manga.favorite,
|
||||||
|
url = manga.thumbnailUrl,
|
||||||
|
lastModified = manga.coverLastModified,
|
||||||
|
),
|
||||||
|
coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f,
|
||||||
|
coverBadgeStart = {
|
||||||
|
InLibraryBadge(enabled = manga.favorite)
|
||||||
|
},
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.paging.LoadState
|
||||||
|
import androidx.paging.compose.LazyPagingItems
|
||||||
|
import eu.kanade.presentation.library.components.CommonMangaItemDefaults
|
||||||
|
import eu.kanade.presentation.library.components.MangaCompactGridItem
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BrowseSourceCompactGrid(
|
||||||
|
mangaList: LazyPagingItems<StateFlow<Manga>>,
|
||||||
|
columns: GridCells,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
onMangaClick: (Manga) -> Unit,
|
||||||
|
onMangaLongClick: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = columns,
|
||||||
|
contentPadding = contentPadding + PaddingValues(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridVerticalSpacer),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridHorizontalSpacer),
|
||||||
|
) {
|
||||||
|
if (mangaList.loadState.prepend is LoadState.Loading) {
|
||||||
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
|
BrowseSourceLoadingItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(count = mangaList.itemCount) { index ->
|
||||||
|
val manga by mangaList[index]?.collectAsState() ?: return@items
|
||||||
|
BrowseSourceCompactGridItem(
|
||||||
|
manga = manga,
|
||||||
|
onClick = { onMangaClick(manga) },
|
||||||
|
onLongClick = { onMangaLongClick(manga) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mangaList.loadState.refresh is LoadState.Loading || mangaList.loadState.append is LoadState.Loading) {
|
||||||
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
|
BrowseSourceLoadingItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BrowseSourceCompactGridItem(
|
||||||
|
manga: Manga,
|
||||||
|
onClick: () -> Unit = {},
|
||||||
|
onLongClick: () -> Unit = onClick,
|
||||||
|
) {
|
||||||
|
MangaCompactGridItem(
|
||||||
|
title = manga.title,
|
||||||
|
coverData = MangaCover(
|
||||||
|
mangaId = manga.id,
|
||||||
|
sourceId = manga.source,
|
||||||
|
isMangaFavorite = manga.favorite,
|
||||||
|
url = manga.thumbnailUrl,
|
||||||
|
lastModified = manga.coverLastModified,
|
||||||
|
),
|
||||||
|
coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f,
|
||||||
|
coverBadgeStart = {
|
||||||
|
InLibraryBadge(enabled = manga.favorite)
|
||||||
|
},
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RemoveMangaDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onConfirm: () -> Unit,
|
||||||
|
mangaToRemove: Manga,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onDismissRequest()
|
||||||
|
onConfirm()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(MR.strings.action_remove))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(MR.strings.are_you_sure))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(MR.strings.remove_manga, mangaToRemove.title))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.paging.LoadState
|
||||||
|
import androidx.paging.compose.LazyPagingItems
|
||||||
|
import eu.kanade.presentation.library.components.CommonMangaItemDefaults
|
||||||
|
import eu.kanade.presentation.library.components.MangaListItem
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BrowseSourceList(
|
||||||
|
mangaList: LazyPagingItems<StateFlow<Manga>>,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
onMangaClick: (Manga) -> Unit,
|
||||||
|
onMangaLongClick: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = contentPadding + PaddingValues(vertical = 8.dp),
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
if (mangaList.loadState.prepend is LoadState.Loading) {
|
||||||
|
BrowseSourceLoadingItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(count = mangaList.itemCount) { index ->
|
||||||
|
val manga by mangaList[index]?.collectAsState() ?: return@items
|
||||||
|
BrowseSourceListItem(
|
||||||
|
manga = manga,
|
||||||
|
onClick = { onMangaClick(manga) },
|
||||||
|
onLongClick = { onMangaLongClick(manga) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
if (mangaList.loadState.refresh is LoadState.Loading || mangaList.loadState.append is LoadState.Loading) {
|
||||||
|
BrowseSourceLoadingItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BrowseSourceListItem(
|
||||||
|
manga: Manga,
|
||||||
|
onClick: () -> Unit = {},
|
||||||
|
onLongClick: () -> Unit = onClick,
|
||||||
|
) {
|
||||||
|
MangaListItem(
|
||||||
|
title = manga.title,
|
||||||
|
coverData = MangaCover(
|
||||||
|
mangaId = manga.id,
|
||||||
|
sourceId = manga.source,
|
||||||
|
isMangaFavorite = manga.favorite,
|
||||||
|
url = manga.thumbnailUrl,
|
||||||
|
lastModified = manga.coverLastModified,
|
||||||
|
),
|
||||||
|
coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f,
|
||||||
|
badge = {
|
||||||
|
InLibraryBadge(enabled = manga.favorite)
|
||||||
|
},
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun BrowseSourceLoadingItem() {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ViewList
|
||||||
|
import androidx.compose.material.icons.filled.ViewModule
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
|
import eu.kanade.presentation.components.AppBarTitle
|
||||||
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
|
import eu.kanade.presentation.components.RadioMenuItem
|
||||||
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.source.local.LocalSource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BrowseSourceToolbar(
|
||||||
|
searchQuery: String?,
|
||||||
|
onSearchQueryChange: (String?) -> Unit,
|
||||||
|
source: Source?,
|
||||||
|
displayMode: LibraryDisplayMode,
|
||||||
|
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
onWebViewClick: () -> Unit,
|
||||||
|
onHelpClick: () -> Unit,
|
||||||
|
onSettingsClick: () -> Unit,
|
||||||
|
onSearch: (String) -> Unit,
|
||||||
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
|
) {
|
||||||
|
// Avoid capturing unstable source in actions lambda
|
||||||
|
val title = source?.name
|
||||||
|
val isLocalSource = source is LocalSource
|
||||||
|
val isConfigurableSource = source is ConfigurableSource
|
||||||
|
|
||||||
|
var selectingDisplayMode by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
SearchToolbar(
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
titleContent = { AppBarTitle(title) },
|
||||||
|
searchQuery = searchQuery,
|
||||||
|
onChangeSearchQuery = onSearchQueryChange,
|
||||||
|
onSearch = onSearch,
|
||||||
|
onClickCloseSearch = navigateUp,
|
||||||
|
actions = {
|
||||||
|
AppBarActions(
|
||||||
|
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||||
|
.apply {
|
||||||
|
add(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.action_display_mode),
|
||||||
|
icon = if (displayMode == LibraryDisplayMode.List) {
|
||||||
|
Icons.AutoMirrored.Filled.ViewList
|
||||||
|
} else {
|
||||||
|
Icons.Filled.ViewModule
|
||||||
|
},
|
||||||
|
onClick = { selectingDisplayMode = true },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (isLocalSource) {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.label_help),
|
||||||
|
onClick = onHelpClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_open_in_web_view),
|
||||||
|
onClick = onWebViewClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isConfigurableSource) {
|
||||||
|
add(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(MR.strings.action_settings),
|
||||||
|
onClick = onSettingsClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = selectingDisplayMode,
|
||||||
|
onDismissRequest = { selectingDisplayMode = false },
|
||||||
|
) {
|
||||||
|
RadioMenuItem(
|
||||||
|
text = { Text(text = stringResource(MR.strings.action_display_comfortable_grid)) },
|
||||||
|
isChecked = displayMode == LibraryDisplayMode.ComfortableGrid,
|
||||||
|
) {
|
||||||
|
selectingDisplayMode = false
|
||||||
|
onDisplayModeChange(LibraryDisplayMode.ComfortableGrid)
|
||||||
|
}
|
||||||
|
RadioMenuItem(
|
||||||
|
text = { Text(text = stringResource(MR.strings.action_display_grid)) },
|
||||||
|
isChecked = displayMode == LibraryDisplayMode.CompactGrid,
|
||||||
|
) {
|
||||||
|
selectingDisplayMode = false
|
||||||
|
onDisplayModeChange(LibraryDisplayMode.CompactGrid)
|
||||||
|
}
|
||||||
|
RadioMenuItem(
|
||||||
|
text = { Text(text = stringResource(MR.strings.action_display_list)) },
|
||||||
|
isChecked = displayMode == LibraryDisplayMode.List,
|
||||||
|
) {
|
||||||
|
selectingDisplayMode = false
|
||||||
|
onDisplayModeChange(LibraryDisplayMode.List)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.presentation.library.components.CommonMangaItemDefaults
|
||||||
|
import eu.kanade.presentation.library.components.MangaComfortableGridItem
|
||||||
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
|
import tachiyomi.domain.manga.model.asMangaCover
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GlobalSearchCardRow(
|
||||||
|
titles: List<Manga>,
|
||||||
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
|
onClick: (Manga) -> Unit,
|
||||||
|
onLongClick: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
if (titles.isEmpty()) {
|
||||||
|
EmptyResultItem()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyRow(
|
||||||
|
contentPadding = PaddingValues(MaterialTheme.padding.small),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||||
|
) {
|
||||||
|
items(titles) {
|
||||||
|
val title by getManga(it)
|
||||||
|
MangaItem(
|
||||||
|
title = title.title,
|
||||||
|
cover = title.asMangaCover(),
|
||||||
|
isFavorite = title.favorite,
|
||||||
|
onClick = { onClick(title) },
|
||||||
|
onLongClick = { onLongClick(title) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MangaItem(
|
||||||
|
title: String,
|
||||||
|
cover: MangaCover,
|
||||||
|
isFavorite: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.width(96.dp)) {
|
||||||
|
MangaComfortableGridItem(
|
||||||
|
title = title,
|
||||||
|
titleMaxLines = 3,
|
||||||
|
coverData = cover,
|
||||||
|
coverBadgeStart = {
|
||||||
|
InLibraryBadge(enabled = isFavorite)
|
||||||
|
},
|
||||||
|
coverAlpha = if (isFavorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun EmptyResultItem() {
|
||||||
|
Text(
|
||||||
|
text = stringResource(MR.strings.no_results_found),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.ArrowForward
|
||||||
|
import androidx.compose.material.icons.outlined.Error
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GlobalSearchResultItem(
|
||||||
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
Column(modifier = modifier) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
start = MaterialTheme.padding.medium,
|
||||||
|
end = MaterialTheme.padding.extraSmall,
|
||||||
|
)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
|
Text(text = subtitle)
|
||||||
|
}
|
||||||
|
IconButton(onClick = onClick) {
|
||||||
|
Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GlobalSearchLoadingResultItem() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = MaterialTheme.padding.medium),
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(16.dp)
|
||||||
|
.align(Alignment.Center),
|
||||||
|
strokeWidth = 2.dp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GlobalSearchErrorResultItem(message: String?) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
Icon(imageVector = Icons.Outlined.Error, contentDescription = null)
|
||||||
|
Spacer(Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = message ?: stringResource(MR.strings.unknown_error),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.DoneAll
|
||||||
|
import androidx.compose.material.icons.outlined.FilterList
|
||||||
|
import androidx.compose.material.icons.outlined.PushPin
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.FilterChipDefaults
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GlobalSearchToolbar(
|
||||||
|
searchQuery: String?,
|
||||||
|
progress: Int,
|
||||||
|
total: Int,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
|
onSearch: (String) -> Unit,
|
||||||
|
sourceFilter: SourceFilter,
|
||||||
|
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||||
|
onlyShowHasResults: Boolean,
|
||||||
|
onToggleResults: () -> Unit,
|
||||||
|
scrollBehavior: TopAppBarScrollBehavior,
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
||||||
|
Box {
|
||||||
|
SearchToolbar(
|
||||||
|
searchQuery = searchQuery,
|
||||||
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
|
onSearch = onSearch,
|
||||||
|
onClickCloseSearch = navigateUp,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
if (progress in 1..<total) {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
progress = { progress / total.toFloat() },
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomStart)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.horizontalScroll(rememberScrollState())
|
||||||
|
.padding(horizontal = MaterialTheme.padding.small),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
|
) {
|
||||||
|
// TODO: make this UX better; it only applies when triggering a new search
|
||||||
|
FilterChip(
|
||||||
|
selected = sourceFilter == SourceFilter.PinnedOnly,
|
||||||
|
onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.PushPin,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(FilterChipDefaults.IconSize),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(MR.strings.pinned_sources))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
FilterChip(
|
||||||
|
selected = sourceFilter == SourceFilter.All,
|
||||||
|
onClick = { onChangeSearchFilter(SourceFilter.All) },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.DoneAll,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(FilterChipDefaults.IconSize),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(MR.strings.all))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
VerticalDivider()
|
||||||
|
|
||||||
|
FilterChip(
|
||||||
|
selected = onlyShowHasResults,
|
||||||
|
onClick = { onToggleResults() },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.FilterList,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(FilterChipDefaults.IconSize),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(MR.strings.has_results))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user