mirror of
https://github.com/mihonapp/mihon.git
synced 2026-10-17 06:02:28 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19f1d00fcb | ||
|
|
8c099aca90 | ||
|
|
6c6a07c0c0 | ||
|
|
8e323e30de | ||
|
|
91ccc0ca40 | ||
|
|
735cea35f9 | ||
|
|
41265cce32 | ||
|
|
176a5409b2 | ||
|
|
f23170bfaa | ||
|
|
bc7f7e70a1 | ||
|
|
a1b5ea811a | ||
|
+21 |
9d8d70dc61 | ||
|
|
06497622f6 | ||
|
|
c397d657d1 | ||
|
|
6f99c7b970 | ||
|
|
1cb38a1c33 | ||
|
|
b3e190c627 | ||
|
|
3bb734dde7 | ||
|
|
5f5a034112 | ||
|
|
eb5f57a48c | ||
|
|
903add50c9 | ||
|
|
c7e782c179 | ||
|
|
53724fd561 | ||
|
|
5f80403c5d | ||
|
|
d4ad76d576 | ||
|
|
46b5cce007 | ||
|
|
80541831bb | ||
|
|
ee6a9876ec | ||
|
|
df37d81dfb | ||
|
|
e99a50f612 | ||
|
|
50d0e8ae0a | ||
|
|
6552ffe315 | ||
|
|
381eb07ec9 | ||
|
|
3eafa16c64 | ||
|
|
672ff416c5 | ||
|
|
16376f509c | ||
|
|
56fde24532 | ||
|
|
518d2ab7df | ||
|
|
082bd66673 | ||
|
|
5880f4b0bd | ||
|
|
d156496cfa | ||
|
|
eb80ab410e | ||
|
|
5896d2c939 | ||
|
|
e3610cbe2c | ||
|
|
94cab02c1e | ||
|
|
f00cac265f | ||
|
|
aa6742bf3e | ||
|
|
4c37f4c764 | ||
|
|
430b13bb81 | ||
|
|
c15f7bee85 | ||
|
|
1a030420b1 | ||
|
|
a6f0cec083 | ||
|
|
6ae79d255d | ||
|
|
4b9974e20a | ||
|
|
509eee5dfb | ||
|
|
63a71fa447 | ||
|
|
d4327c8d48 | ||
|
|
5f1d76a73a | ||
|
|
35f6a607b8 | ||
|
|
378518c0fd | ||
|
|
69fb33887f | ||
|
|
f87c4d55e3 | ||
|
|
c612a6ca8d | ||
|
|
a1ad33baef | ||
|
|
8c27bc07fc | ||
|
|
95bc599ac0 | ||
|
|
cd27b10750 | ||
|
|
7f8851de6a | ||
|
|
a330258f6a | ||
|
|
d28c605a89 | ||
|
|
87c1675882 | ||
|
|
b446d2a6b1 | ||
|
|
748211c19b | ||
|
|
1fd02d0c82 | ||
|
|
4951f0bddd | ||
|
|
d391566832 | ||
|
|
0b564f1f20 | ||
|
|
b639e44a03 | ||
|
|
f68c1bcc9e | ||
|
|
4fabe9b405 | ||
|
|
ae3aa7926a | ||
|
|
82338d7d64 | ||
|
|
3b94df6108 | ||
|
|
5bb92a75c7 | ||
|
|
48179083f1 | ||
|
|
b4a3ec0b32 | ||
|
|
cb2a0ea570 | ||
|
|
9c3519046f | ||
|
|
da9b5d9b9d | ||
|
|
09d3e16183 | ||
|
|
cfefa51296 | ||
|
|
b65825b3ca | ||
|
|
981b16094a | ||
|
|
bb877e4b26 | ||
|
|
5b8adfbdb0 | ||
|
|
9678b8fd79 | ||
|
|
8e0c911f93 | ||
|
|
a2f3ec94aa | ||
|
|
38ccd7d5fc | ||
|
|
4faf89fc6f | ||
|
|
5b32a990a2 | ||
|
|
cb6daa5e4c | ||
|
|
d68347f5fb | ||
|
|
e676c8e6e1 | ||
|
|
34fa9138d9 | ||
|
|
7a917968e3 | ||
|
|
5f548243e1 | ||
|
|
34d22e9495 | ||
|
|
bc4fc765b2 | ||
|
|
253a560b20 | ||
|
|
eb3d2c6676 | ||
|
|
b1c6994977 | ||
|
|
2990762df9 | ||
|
|
8e6af6492b | ||
|
|
c4a606112a | ||
|
+6 |
230dc6fd05 | ||
|
|
e4b7002909 | ||
|
|
02c6427823 | ||
|
|
50b0ccb928 | ||
|
|
1712b9cdc5 | ||
|
|
b201ed163e | ||
|
|
3521ccfb4f | ||
|
|
b7cdfb1b30 | ||
|
|
4b1ae0526b | ||
|
|
378e734eed | ||
|
|
ed3b5305f1 | ||
|
|
cfc69d8d0b | ||
|
|
2a1859d7d7 | ||
|
|
8a2a2b1f36 | ||
|
|
af04276bed | ||
|
|
25d4bf5e2f | ||
|
|
d75db63258 | ||
|
|
52fc38e965 | ||
|
|
580863e891 | ||
|
|
85eb539c4d | ||
|
|
09ee38bb7a | ||
|
|
af4f17efc5 | ||
|
|
fa197a4ab2 | ||
|
|
c3243882b1 | ||
|
|
a938708e1f | ||
|
|
2e635ff2ce | ||
|
|
209805359e | ||
|
|
eb5b762515 | ||
|
|
7c5034275f | ||
|
|
d75a125b26 | ||
|
|
54d2c7b9e0 | ||
|
|
8b9d0a89c8 | ||
|
|
3bde967291 | ||
|
|
b40f1d3975 | ||
|
|
42daa3f432 | ||
|
|
f8e82b9322 | ||
|
|
cb0e329716 | ||
|
|
34afdabcfe | ||
|
|
bc32308327 | ||
|
|
c95269afd7 | ||
|
|
4af8f1eab0 | ||
|
+2 |
2366fbb4b3 | ||
|
|
e930f38608 | ||
|
|
b58d2a5d4b | ||
|
|
d3dcb7fe4b | ||
|
|
56b70ef529 | ||
|
|
5cf305b620 | ||
|
|
87665736a9 | ||
|
|
2f9edb551f | ||
|
|
d4b88b13b8 | ||
|
|
99edfbe4a8 | ||
|
|
fa7155896c | ||
|
|
51fb9c0b76 | ||
|
|
c495753315 | ||
|
|
11ced33fdb | ||
|
|
8c480c6355 | ||
|
|
e9ca71d837 | ||
|
|
e4091c0160 | ||
|
|
a2d22a7d55 | ||
|
|
db22517339 | ||
|
|
33faa5a356 | ||
|
|
8c5ae44f51 | ||
|
|
575d7e8b89 | ||
|
|
8c00b44eda | ||
|
|
77a86d486f | ||
|
|
4b379576f4 | ||
|
|
be88ce199c | ||
|
|
32b8ef6240 | ||
|
|
f82b8300b0 | ||
|
|
ce5820b496 | ||
|
|
f6b2684323 | ||
|
|
0d768af3d0 | ||
|
|
08f0bcc0d8 | ||
|
|
a3c45a05b8 | ||
|
|
6926914385 | ||
|
|
f3d5491a7d | ||
|
|
81871a3469 | ||
|
|
0024278f4b | ||
|
|
f9f938f5af | ||
|
|
1d7c838ae6 | ||
|
|
d219c5e3bb | ||
|
|
cc3f640faa | ||
|
|
84265febf3 | ||
|
|
171a06a8ba | ||
|
|
565379779f | ||
|
|
0394714274 | ||
|
|
75b445fa8f | ||
|
|
0cc724108b | ||
|
|
1e6c35d887 | ||
|
|
5b85084d0a | ||
|
|
8b2d35f306 | ||
|
|
47816d4b21 | ||
|
|
89bbdb17fb | ||
|
|
3c6f0f1697 | ||
|
|
ab214526c6 | ||
|
|
e6cc38de06 | ||
|
+12 |
7c7d35e2c6 | ||
|
|
9867c160f7 | ||
|
|
20c899f2cd | ||
|
|
3f64529412 | ||
|
|
27749fc583 | ||
|
|
2248bdac54 | ||
|
|
42c7329a9e | ||
|
|
22d5c9d9f4 | ||
|
|
5be5a4e819 | ||
|
|
b924d582f4 | ||
|
|
6f601a18c5 | ||
|
|
1ff9a5625c | ||
|
|
cae9fbf321 | ||
|
|
d17976c910 | ||
|
|
b609166702 | ||
|
|
cf93afab45 | ||
|
|
b543bc089a | ||
|
|
4e4bdffdf3 | ||
|
|
100cea0757 | ||
|
|
86344e20d3 | ||
|
|
e092b4208a | ||
|
|
1bf4eff931 | ||
|
|
b70edfac58 | ||
|
|
9c0eebb55c | ||
|
|
f6f1d13add | ||
|
|
31a9ff261e | ||
|
|
c0a1203541 | ||
|
|
82ffc8efa6 | ||
|
|
d6e17b04c3 | ||
|
|
e6c6700351 | ||
|
|
ede2f56bc6 | ||
|
|
2037ec4500 | ||
|
|
533a578bdb | ||
|
|
fa966fcb97 | ||
|
|
05d90ea4d6 | ||
|
|
49c4d08b22 | ||
|
|
bbe9aa8561 | ||
|
|
13975d6f7e | ||
|
|
e659e90c26 | ||
|
|
99e6fa3c06 | ||
|
|
cf13012629 | ||
|
|
93b355a9ef | ||
|
|
a4f5a8184c | ||
|
|
4ce249c1a0 | ||
|
|
edcf84d902 | ||
|
|
bd5c4d48f9 | ||
|
|
51b3ab3fd1 | ||
|
|
e6f72000ba | ||
|
|
9bf2d78a42 | ||
|
|
89c4e3bb39 | ||
|
|
47fe792ddc | ||
|
|
906d6f3cdb | ||
|
|
e059190fab | ||
|
|
a39b5a56e8 | ||
|
|
3e6afee13b | ||
|
|
9a11ec8ead | ||
|
|
1263df9d41 | ||
|
|
c96b6ae562 | ||
|
|
23c427cf60 | ||
|
|
e3260d56f7 | ||
|
|
f37afbcec9 | ||
|
|
7a1c8a1b61 | ||
|
|
532b5cf290 | ||
|
|
3cb1b2e17a | ||
|
|
4c9cfd8da5 | ||
|
|
2ecb70a590 | ||
|
|
2e0786f699 | ||
|
|
e7e4d9b6b3 | ||
|
|
5fe7dd9f06 | ||
|
|
4cb05cc738 | ||
|
|
876c3f951b | ||
|
|
08a61a42e9 | ||
|
|
cadd36ad9a | ||
|
|
97327555b3 | ||
|
|
5b88f88198 | ||
|
|
556371e1c8 | ||
|
|
c222a28bd1 | ||
|
|
7161bc2e82 | ||
|
|
c3d858a561 | ||
|
|
13552c5ffa | ||
|
|
e6ca458e1f | ||
|
|
05c7df2ed3 | ||
|
|
4a3339a21f | ||
|
|
6f497ed031 | ||
|
|
8b862c2e46 | ||
|
|
fc2c8c06a9 | ||
|
|
c17fc6792a | ||
|
|
9e27ae3d2b | ||
|
|
8b5dd39f1c | ||
|
|
8d81c94679 | ||
|
|
e5a693f224 | ||
|
|
7947e1bd6a | ||
|
|
cab729c939 | ||
|
|
f6a95a7416 | ||
|
|
32db0a5f4c | ||
|
|
2ec67ac0c1 | ||
|
|
c2bc95191b | ||
|
|
b1b79a63f0 | ||
|
|
ec99ab3aef | ||
|
|
69e5a1aea7 | ||
|
+2 |
84d620978b | ||
|
|
412815af06 | ||
|
|
f7fb68692a | ||
|
|
aa300cb53e | ||
|
|
855eea2ada | ||
|
|
f4703ed83a | ||
|
|
506d51a007 | ||
|
|
9f9155121c | ||
|
|
282110ef21 | ||
|
|
ace387f8bf | ||
|
|
5e428071c9 | ||
|
|
0acd80dd95 | ||
|
|
bdb0ce4779 | ||
|
|
e8bdf58530 | ||
|
|
e36b4ce60b | ||
|
|
6d543024a3 | ||
|
|
0e0b6d9283 | ||
|
|
38b1bd7383 | ||
|
+20 |
8609553896 | ||
|
|
5f0c460668 | ||
|
|
ac28b6c80c | ||
|
|
cc28776735 | ||
|
|
6ab87c7931 | ||
|
|
8662f80fbf | ||
|
|
6508766ccd | ||
|
|
09ec9fc8c5 | ||
|
|
87c6f34a55 | ||
|
|
643762f913 | ||
|
|
7e880014b0 | ||
|
|
f36c259c1f | ||
|
|
aef3beb15f | ||
|
|
e9469451ac | ||
|
|
f9793d3323 | ||
|
|
93ba6acea5 | ||
|
|
5e7fecc2c1 | ||
|
|
343074da5f | ||
|
|
7c08b75555 | ||
|
|
cbf72f4c60 | ||
|
|
0b6de39f2f | ||
|
|
72c4d1fdee | ||
|
|
fa96366b55 | ||
|
|
3ff25bc984 | ||
|
|
e9224bc2ba | ||
|
|
3c731c2cf5 | ||
|
|
5ac58d01b8 | ||
|
|
eefaf028ce | ||
|
|
582ccca1ab | ||
|
|
8f972115a8 | ||
|
|
6f6c033811 | ||
|
|
57a0ab6711 | ||
|
|
58b25d697f | ||
|
|
1a31c7c7ee | ||
|
|
ad6b651b37 | ||
|
|
96e5131358 | ||
|
|
1d5bc8d2c2 | ||
|
|
6cee911239 | ||
|
|
96347e3f76 | ||
|
|
9a45d248b1 | ||
|
|
04168ecec8 | ||
|
|
607f0ea9cd | ||
|
|
27a4f6f45c | ||
|
|
5236d003d2 | ||
|
|
d61a41e819 | ||
|
|
5637860dd2 | ||
|
|
d4d18d0898 | ||
|
|
065147472e | ||
|
|
6f635782c2 | ||
|
|
86d85f74c0 | ||
|
|
29e6a2c4a6 | ||
|
|
60c66bbd3a | ||
|
|
060e5b2e2e | ||
|
|
4ac9fcd4d3 | ||
|
|
d3b7f7e55f | ||
|
|
0d926626a1 | ||
|
|
6495a2ea43 | ||
|
|
94f711ba2a | ||
|
|
9f5c4e03b2 | ||
|
|
49562e1915 | ||
|
|
57c82b30ba | ||
|
|
e573f72cfd | ||
|
|
95357a8625 | ||
|
|
bd90307df9 | ||
|
|
16b5317b90 | ||
|
|
83f4b48629 | ||
|
|
4665dc50f6 | ||
|
|
85f5e5019e | ||
|
|
4bc3b9f3b6 | ||
|
|
2c0d3678d9 | ||
|
|
feda410152 | ||
|
|
200c2df5ba | ||
|
|
be09cddde2 | ||
|
|
498317de52 | ||
|
|
33b876edc6 | ||
|
|
e7251f2034 | ||
|
|
3d3c36078a | ||
|
|
c6a96b3970 | ||
|
|
fb3dc1c984 | ||
|
|
029e36bfb4 | ||
|
|
d88dbe6409 | ||
|
|
d0bad9f0bd | ||
|
|
5c88f3860d | ||
|
|
7d717ee7fd | ||
|
|
a93f71b82b | ||
|
|
a8b6629b08 | ||
|
|
9bf3f15fff | ||
|
|
1c3e96bf7f | ||
|
+53 |
45c1a31488 | ||
|
|
32257e438e | ||
|
|
49a84c8914 | ||
|
|
095ef8e74b | ||
|
|
4de3bf574a | ||
|
|
549d74a2c9 | ||
|
|
e4de208cf7 | ||
|
|
f1193866f4 | ||
|
|
3782e1b414 | ||
|
|
c4407eda0e | ||
|
|
33e0121a2a | ||
|
|
b93337cb3d | ||
|
|
358adb8cd1 | ||
|
|
22f851173b | ||
|
|
982ebcf777 | ||
|
|
e62cd0e816 | ||
|
|
1365b28106 | ||
|
|
7ec28eb3bd | ||
|
|
ff9dfe45ed | ||
|
|
967750ba58 | ||
|
|
269af7fe2b | ||
|
|
62eec15fe6 | ||
|
|
2ef8ae11c9 | ||
|
|
4f1faf49f3 | ||
|
|
6d717ea88b | ||
|
|
fbb5e6b92f | ||
|
|
8f5f29e737 | ||
|
|
a4b9c704b6 | ||
|
|
9352201b03 | ||
|
|
c715e981bf | ||
|
|
7f56555d63 | ||
|
|
8636b7a685 | ||
|
|
a49670bf0d | ||
|
|
61cee5c5e0 | ||
|
|
d805f0cd2a | ||
|
|
ce07259e8e | ||
|
|
2f10e7beaa | ||
|
|
4ef8fb9588 | ||
|
|
084e626669 | ||
|
|
f93ccaaaa4 | ||
|
|
5585388e2d | ||
|
|
d60241690b | ||
|
|
84aa07b7f0 | ||
|
|
0cc1224094 | ||
|
|
a5a0d83302 | ||
|
|
1fde0275e3 | ||
|
|
a992f2d467 | ||
|
|
d8dd170d1b | ||
|
|
6953090dab | ||
|
|
ab452a9945 | ||
|
|
7dd595f16e | ||
|
|
6eb2a022f1 | ||
|
|
d61c66c286 | ||
|
|
1e4ee14608 | ||
|
|
63943debc2 | ||
|
|
d2c1ff6adf | ||
|
|
b9e02e92be | ||
|
|
103218681a | ||
|
|
07136d3969 | ||
|
|
4962deeb0c | ||
|
|
cecf4596f9 | ||
|
|
0c77afbe03 | ||
|
|
d126b84f95 | ||
|
|
2df3382148 | ||
|
|
ee19050cc0 | ||
|
|
8de1fa854d | ||
|
|
019fc08da2 | ||
|
|
288f577a45 | ||
|
|
0290a2d815 | ||
|
|
a47d4ebbdd | ||
|
|
89954e68e3 | ||
|
|
c3b590cd3d | ||
|
|
cb3c5e9c9c | ||
|
|
7fa2834009 | ||
|
|
95e3c22429 | ||
|
|
8bd70342fc | ||
|
|
92ec6b17a3 | ||
|
|
591e9c1356 | ||
|
|
7fed9c2ccf | ||
|
|
ccb554c877 | ||
|
|
5235713d83 | ||
|
|
4692010400 | ||
|
|
be528ba12b | ||
|
|
405e536cbf | ||
|
|
8714653a2f | ||
|
|
2b126f1ff5 | ||
|
|
5919f34fc9 | ||
|
|
a4df33caf9 | ||
|
|
3580d2da6c | ||
|
|
77eb558742 | ||
|
|
e1f6d14393 | ||
|
|
2e180005a0 | ||
|
|
0f59fc1dd4 | ||
|
|
d1055475e2 | ||
|
|
32470657dd | ||
|
|
158896cfa9 | ||
|
|
92b376d9af | ||
|
|
1a2f09a622 | ||
|
|
209e982fe4 | ||
|
|
0109102901 | ||
|
|
4117a51674 | ||
|
|
4090a61d08 | ||
|
|
c406513557 | ||
|
|
625c85cbd6 | ||
|
|
737ceeea57 | ||
|
|
213b673b13 | ||
|
|
7933c9eeb7 | ||
|
|
f0de8f973b | ||
|
|
e8c6e3364d | ||
|
|
c12bdbae8e | ||
|
|
33d407ee9c | ||
|
|
ef8c3ca119 | ||
|
|
0cb3a4aeb6 | ||
|
|
8b45ef0e5d | ||
|
|
86ebf55815 | ||
|
|
ddf282b103 | ||
|
|
744b809d45 | ||
|
|
cd2ce44efa | ||
|
|
cae7c3dc58 | ||
|
|
c0074402e7 | ||
|
|
7deeabe844 | ||
|
|
536393a6d9 | ||
|
|
f8cb506137 | ||
|
|
98230ed30f | ||
|
|
d721a4321b | ||
|
|
1ac4b72cfe | ||
|
|
9331f2b93f | ||
|
|
99c2a99973 | ||
|
|
001716e34b | ||
|
|
37e19edf8a | ||
|
|
8b7f355988 | ||
|
|
0b77733673 | ||
|
|
9be558d6c0 | ||
|
|
0c8c5dbba6 | ||
|
|
1c982c2a01 | ||
|
|
eeab61fc94 | ||
|
|
a036407c75 | ||
|
|
615d93f780 | ||
|
|
9750c1e4bd | ||
|
|
e2915a1f69 | ||
|
|
f6617a7a22 | ||
|
|
ef37a4c80b | ||
|
|
df2b4c754b | ||
|
|
6632a12228 | ||
|
|
0cb1925cf1 | ||
|
|
a31b3b7bbf | ||
|
|
fea85241af | ||
|
|
e273a26c9b | ||
|
|
818e6931c6 | ||
|
|
ecc6ede081 | ||
|
|
fefa8f8498 | ||
|
|
f1e2efcb37 | ||
|
|
180318f57d | ||
|
|
ed749de806 | ||
|
|
bb33b0029e | ||
|
|
3a19e449b1 | ||
|
|
818edf2776 | ||
|
|
47d2646751 | ||
|
|
a1a7d67afb | ||
|
|
2090a380e0 | ||
|
|
8e5cfe9d0a | ||
|
|
3249228c49 | ||
|
|
d9c4b56336 | ||
|
|
5e029b1fe6 | ||
|
|
12abd9938b | ||
|
|
c1225a5ef9 | ||
|
|
a594ad392d | ||
|
|
2259164fde | ||
|
|
80de032819 | ||
|
|
0d35b6fdaf | ||
|
|
f81da3dcce | ||
|
|
2ce9fa0271 | ||
|
|
300bee865f | ||
|
|
542d30c14a | ||
|
|
5d2110f3fb | ||
|
|
4e68339783 | ||
|
|
c8ffabc84a | ||
|
|
77e79233ab | ||
|
|
8a21148578 | ||
|
|
e91db86fae | ||
|
|
556290f2d3 | ||
|
|
8b947919ac | ||
|
|
b62a9b40eb | ||
|
|
a6b532ee57 | ||
|
|
8fbe630308 | ||
|
|
132d77aa99 | ||
|
|
b00bbe91be | ||
|
|
3e5d3d099f | ||
|
|
941dde341e | ||
|
|
365d167eac | ||
|
|
d4aaf6521e | ||
|
|
f7046a503b | ||
|
|
953c4e7bc0 |
+5
-1
@@ -7,7 +7,7 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.xml]
|
||||
[*.{xml,sq,sqm,aidl}]
|
||||
indent_size = 4
|
||||
|
||||
# noinspection EditorConfigKeyCorrectness
|
||||
@@ -23,6 +23,10 @@ ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||
ktlint_code_style = intellij_idea
|
||||
ktlint_function_naming_ignore_when_annotated_with = Composable
|
||||
ktlint_standard_class-signature = disabled
|
||||
ktlint_standard_comment-wrapping = disabled
|
||||
ktlint_standard_discouraged-comment-location = disabled
|
||||
ktlint_standard_function-expression-body = disabled
|
||||
ktlint_standard_function-signature = disabled
|
||||
ktlint_standard_type-argument-comment = disabled
|
||||
ktlint_standard_type-parameter-comment = disabled
|
||||
ktlint_standard_blank-line-between-when-conditions = disabled
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
21
|
||||
@@ -0,0 +1 @@
|
||||
patreon: mihon
|
||||
+2
-3
@@ -1,8 +1,7 @@
|
||||
name: ⭐ Feature request
|
||||
description: Suggest a feature to improve Mihon
|
||||
labels: [Feature request]
|
||||
labels: [feature request]
|
||||
body:
|
||||
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
attributes:
|
||||
@@ -31,7 +30,7 @@ body:
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: I have updated the app to version **[0.18.0](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
- label: I have updated the app to version **[0.20.0](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
required: true
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
required: true
|
||||
@@ -1,8 +1,7 @@
|
||||
name: 🐞 Issue report
|
||||
description: Report an issue in Mihon
|
||||
labels: [Bug]
|
||||
labels: [bug]
|
||||
body:
|
||||
|
||||
- type: textarea
|
||||
id: reproduce-steps
|
||||
attributes:
|
||||
@@ -53,7 +52,7 @@ body:
|
||||
label: Mihon version
|
||||
description: You can find your Mihon version in **More → About**.
|
||||
placeholder: |
|
||||
Example: "0.18.0"
|
||||
Example: "0.20.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -96,7 +95,7 @@ body:
|
||||
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.18.0](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
- label: I have updated the app to version **[0.20.0](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
required: true
|
||||
- label: I have filled out all of the requested information in this form, including specific version numbers.
|
||||
required: true
|
||||
@@ -0,0 +1,74 @@
|
||||
name: Build & Test
|
||||
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'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build & Test App
|
||||
runs-on: 'ubuntu-24.04'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Dependency Review
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
java-version-file: .github/.java-version
|
||||
distribution: temurin
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
|
||||
- name: Check code format
|
||||
run: ./gradlew spotlessCheck
|
||||
|
||||
- name: Run unit tests
|
||||
id: unit_tests
|
||||
run: ./gradlew testDebugUnitTest
|
||||
|
||||
- name: Verify SQLDelight migrations
|
||||
run: ./gradlew verifySqlDelightMigration
|
||||
|
||||
- name: Upload test report
|
||||
if: steps.unit_tests.outcome == 'failure'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: test-report-${{ github.sha }}
|
||||
path: app/build/reports/tests/testDebugUnitTest
|
||||
|
||||
- name: Build app
|
||||
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: arm64-v8a-${{ github.sha }}
|
||||
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
|
||||
|
||||
- name: Upload mapping
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: mapping-${{ github.sha }}
|
||||
path: app/build/outputs/mapping/release
|
||||
@@ -1,59 +0,0 @@
|
||||
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-24.04'
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
|
||||
- name: Set up gradle
|
||||
uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
||||
|
||||
- name: Check code format
|
||||
run: ./gradlew spotlessCheck
|
||||
|
||||
- name: Build app
|
||||
run: ./gradlew assembleRelease
|
||||
|
||||
- name: Run unit tests
|
||||
run: ./gradlew testReleaseUnitTest
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: arm64-v8a-${{ github.sha }}
|
||||
path: app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk
|
||||
|
||||
- name: Upload mapping
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: mapping-${{ github.sha }}
|
||||
path: app/build/outputs/mapping/release
|
||||
@@ -1,125 +0,0 @@
|
||||
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-24.04'
|
||||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
|
||||
- name: Set up gradle
|
||||
uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
|
||||
|
||||
- name: Check code format
|
||||
run: ./gradlew spotlessCheck
|
||||
|
||||
- name: Build app
|
||||
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
|
||||
|
||||
- name: Run unit tests
|
||||
run: ./gradlew testReleaseUnitTest
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: arm64-v8a-${{ github.sha }}
|
||||
path: app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk
|
||||
|
||||
- name: Upload mapping
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: mapping-${{ github.sha }}
|
||||
path: app/build/outputs/mapping/release
|
||||
|
||||
# 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@f30bdd30588842ac76044ecdbd4b6d0e3e813478
|
||||
with:
|
||||
releaseDirectory: app/build/outputs/apk/release
|
||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||
alias: ${{ secrets.ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: '35.0.1'
|
||||
|
||||
- name: Clean up build artifacts
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||
run: |
|
||||
set -e
|
||||
|
||||
mv app/build/outputs/apk/release/app-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
|
||||
|
||||
mv app/build/outputs/apk/release/app-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
|
||||
|
||||
mv app/build/outputs/apk/release/app-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
|
||||
|
||||
mv app/build/outputs/apk/release/app-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
|
||||
|
||||
mv app/build/outputs/apk/release/app-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@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
|
||||
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
|
||||
token: ${{ secrets.MIHON_BOT_TOKEN }}
|
||||
@@ -0,0 +1,164 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
MIHON_GITHUB_RELEASE: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
get_tag:
|
||||
if: github.repository == 'mihonapp/mihon'
|
||||
name: Extract tag name
|
||||
runs-on: 'ubuntu-24.04'
|
||||
outputs:
|
||||
tag: ${{ steps.extract.outputs.tag }}
|
||||
|
||||
steps:
|
||||
- name: Get tag name
|
||||
id: extract
|
||||
run: echo "tag=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
if: github.repository == 'mihonapp/mihon'
|
||||
name: Build
|
||||
runs-on: 'ubuntu-24.04'
|
||||
needs: get_tag
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
java-version-file: .github/.java-version
|
||||
distribution: temurin
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
|
||||
- name: Build
|
||||
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
|
||||
env:
|
||||
storeFileBase64: ${{ secrets.SIGNING_KEY }}
|
||||
storePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
keyAlias: ${{ secrets.ALIAS }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Rename APK
|
||||
run: |
|
||||
set -e
|
||||
|
||||
mv app/build/outputs/apk/release/app-universal-release.apk mihon-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mv app/build/outputs/apk/release/app-arm64-v8a-release.apk mihon-arm64-v8a-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mv app/build/outputs/apk/release/app-armeabi-v7a-release.apk mihon-armeabi-v7a-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mv app/build/outputs/apk/release/app-x86-release.apk mihon-x86-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mv app/build/outputs/apk/release/app-x86_64-release.apk mihon-x86_64-${{ needs.get_tag.outputs.tag }}.apk
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: mihon
|
||||
path: |
|
||||
mihon-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mihon-arm64-v8a-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mihon-armeabi-v7a-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mihon-x86-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mihon-x86_64-${{ needs.get_tag.outputs.tag }}.apk
|
||||
|
||||
build_foss:
|
||||
if: github.repository == 'mihonapp/mihon'
|
||||
name: Build (FOSS)
|
||||
runs-on: ubuntu-24.04
|
||||
needs: get_tag
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
|
||||
with:
|
||||
java-version-file: .github/.java-version
|
||||
distribution: temurin
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
|
||||
with:
|
||||
cache-disabled: true
|
||||
|
||||
- name: Build
|
||||
run: ./gradlew assembleFoss -Penable-updater
|
||||
env:
|
||||
storeFileBase64: ${{ secrets.SIGNING_KEY }}
|
||||
storePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
keyAlias: ${{ secrets.ALIAS }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Rename APK
|
||||
run: |
|
||||
set -e
|
||||
|
||||
mv app/build/outputs/apk/foss/app-universal-foss.apk mihon-${{ needs.get_tag.outputs.tag }}-foss.apk
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: mihon-foss
|
||||
path: mihon-${{ needs.get_tag.outputs.tag }}-foss.apk
|
||||
|
||||
release:
|
||||
if: github.repository == 'mihonapp/mihon'
|
||||
name: Create GitHub Release
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [get_tag, build, build_foss]
|
||||
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Delete all artifacts
|
||||
uses: geekyeggo/delete-artifact@176a747ab7e287e3ff4787bf8a148716375ca118 # v6.0.0
|
||||
with:
|
||||
failOnError: false
|
||||
name: |
|
||||
mihon
|
||||
mihon-foss
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
|
||||
with:
|
||||
tag_name: ${{ needs.get_tag.outputs.tag }}
|
||||
name: Mihon ${{ needs.get_tag.outputs.tag }}
|
||||
body: |
|
||||
Consider supporting the development on [Patreon](https://www.patreon.com/mihon/membership) or [OpenCollective](https://opencollective.com/mihon/contribute) to keep Mihon improving!
|
||||
|
||||
Check out the [past release notes](https://github.com/mihonapp/mihon/releases) if you're upgrading from an earlier version.
|
||||
|
||||
<!-->
|
||||
|
||||
|
||||
|
||||
<!-->
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> ### If you are unsure which version to download then go with `mihon-${{ needs.get_tag.outputs.tag }}.apk`
|
||||
files: |
|
||||
mihon-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mihon-${{ needs.get_tag.outputs.tag }}-foss.apk
|
||||
mihon-arm64-v8a-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mihon-armeabi-v7a-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mihon-x86-${{ needs.get_tag.outputs.tag }}.apk
|
||||
mihon-x86_64-${{ needs.get_tag.outputs.tag }}.apk
|
||||
draft: true
|
||||
prerelease: false
|
||||
token: ${{ secrets.MIHON_BOT_TOKEN }}
|
||||
@@ -10,6 +10,7 @@ build
|
||||
/captures
|
||||
|
||||
# Configuration files
|
||||
keystore.properties
|
||||
local.properties
|
||||
|
||||
# macOS specific files
|
||||
|
||||
+239
-1
@@ -12,6 +12,233 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.20.0] - 2026-06-27
|
||||
### Added
|
||||
- Add support for `tachiyomix` 1.6 extensions ([@AntsyLich](https://github.com/AntsyLich))
|
||||
- Add support for `tachiyomix` extension index format ([@AntsyLich](https://github.com/AntsyLich)) ([#3349](https://github.com/mihonapp/mihon/pull/3349))
|
||||
- Add vertical chapter navigator for long strip mode ([@AntsyLich](https://github.com/AntsyLich)) ([#3377](https://github.com/mihonapp/mihon/pull/3377))
|
||||
- Add option to open app settings from system settings ([@JFronny](https://github.com/JFronny)) ([#3180](https://github.com/mihonapp/mihon/pull/3180))
|
||||
|
||||
### Changed
|
||||
- Change the term "extension repo" to "extension store" ([@AntsyLich](https://github.com/AntsyLich)) ([#3349](https://github.com/mihonapp/mihon/pull/3349))
|
||||
- Change the term "Obsolete" to "Orphaned" for extensions ([@AntsyLich](https://github.com/AntsyLich)) ([#3383](https://github.com/mihonapp/mihon/pull/3383))
|
||||
- Remove text limit of manga notes ([@AntsyLich](https://github.com/AntsyLich)) ([#3410](https://github.com/mihonapp/mihon/pull/3410))
|
||||
- Tweak Catppuccin color scheme for clarity ([@xenitane](https://github.com/xenitane)) ([#3436](https://github.com/mihonapp/mihon/pull/3436))
|
||||
- Update app shortcuts to use app icon color scheme ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#2835](https://github.com/mihonapp/mihon/pull/2835))
|
||||
|
||||
### Improved
|
||||
- Batch database operations during backup restore for improved performance ([@Lolle2000la](https://github.com/Lolle2000la)) ([#3267](https://github.com/mihonapp/mihon/pull/3267))
|
||||
|
||||
### Fixed
|
||||
- Add missing `outlineVariant` color to Nord theme ([@CompileConnected](https://github.com/CompileConnected)) ([#3184](https://github.com/mihonapp/mihon/pull/3184))
|
||||
- Continue reading button missing when unread filter is off ([@AntsyLich](https://github.com/AntsyLich)) ([#3382](https://github.com/mihonapp/mihon/pull/3382))
|
||||
- Fix random `FileNotFoundException` when reading chapters ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3154](https://github.com/mihonapp/mihon/pull/3154))
|
||||
|
||||
## [v0.19.9] - 2026-04-11
|
||||
### Fixed
|
||||
- Regression with installing/updating extension ([@AntsyLich](https://github.com/AntsyLich))
|
||||
|
||||
## [v0.19.8] - 2026-04-11
|
||||
### Added
|
||||
- Add dedicated "Support Us" screen ([@AntsyLich](https://github.com/AntsyLich)) ([#3200](https://github.com/mihonapp/mihon/pull/3200))
|
||||
- Add a one time popup asking for donation from long time users ([@AntsyLich](https://github.com/AntsyLich)) ([#3203](https://github.com/mihonapp/mihon/pull/3203))
|
||||
|
||||
### Changed
|
||||
- Don't prompt users to enable Google Play services if disabled or unavailable ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3152](https://github.com/mihonapp/mihon/pull/3152))
|
||||
- Make "Support Us" more prominent in More screen ([@AntsyLich](https://github.com/AntsyLich)) ([#3196](https://github.com/mihonapp/mihon/pull/3196))
|
||||
|
||||
### Improved
|
||||
- Show informative error when trying to add unapproved titles to list on MAL ([@MajorTanya](https://github.com/MajorTanya)) ([#3155](https://github.com/mihonapp/mihon/pull/3155))
|
||||
- Show AniList publishing type based on country of origin ([@zweimach](https://github.com/zweimach)) ([#3100](https://github.com/mihonapp/mihon/pull/3100))
|
||||
|
||||
### Fixed
|
||||
- Fix app trying to split long strip when not needed ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3121](https://github.com/mihonapp/mihon/pull/3121))
|
||||
|
||||
### Removed
|
||||
- Remove the empty "Updated to X.Y.Z" popup after app update ([@AntsyLich](https://github.com/AntsyLich)) ([#3203](https://github.com/mihonapp/mihon/pull/3203))
|
||||
|
||||
## [v0.19.7] - 2026-03-23
|
||||
Same as v0.19.6
|
||||
|
||||
## [v0.19.6] - 2026-03-23
|
||||
### Fixed
|
||||
- Fix app crashing when trying to add extension repo with existing signature ([@AntsyLich](https://github.com/AntsyLich)) ([`cb0e329`](https://github.com/mihonapp/mihon/commit/cb0e329))
|
||||
- Potentially fix 'database is locked' crash ([@AntsyLich](https://github.com/AntsyLich)) ([`f8e82b9`](https://github.com/mihonapp/mihon/commit/f8e82b9))
|
||||
- Fix occasional crash when mass installing/uninstalling extension using `PackageManager` ([@AntsyLich](https://github.com/AntsyLich)) ([`42daa3f`](https://github.com/mihonapp/mihon/commit/42daa3f))
|
||||
- Fix app crash on startup on some Android TV ([@AntsyLich](https://github.com/AntsyLich)) ([`b40f1d3`](https://github.com/mihonapp/mihon/commit/b40f1d3))
|
||||
|
||||
## [v0.19.5] - 2026-03-20
|
||||
### Changed
|
||||
- Retry in reader now redownloads image ([@AntsyLich](https://github.com/AntsyLich)) ([#3089](https://github.com/mihonapp/mihon/pull/3089))
|
||||
|
||||
### Fixed
|
||||
- Fix performance regression introduced in v0.19.4 ([@AntsyLich](https://github.com/AntsyLich)) ([#3082](https://github.com/mihonapp/mihon/pull/3082))
|
||||
- Fix duplicate key crash in duplicate detection ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3040](https://github.com/mihonapp/mihon/pull/3040))
|
||||
- Fix MangaUpdates HTTP 4XX errors ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3021](https://github.com/mihonapp/mihon/pull/3021))
|
||||
- Fix WebView JavaScript dialogs popup after screen is closed ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3041](https://github.com/mihonapp/mihon/pull/3041))
|
||||
- Fix extension actions disappearing after installing and uninstalling in same session ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3049](https://github.com/mihonapp/mihon/pull/3049))
|
||||
|
||||
## [v0.19.4] - 2026-02-25
|
||||
### Added
|
||||
- Automatically remove downloads on Suwayomi after reading, configurable via extension settings ([@cpiber](https://github.com/cpiber)) ([#2673](https://github.com/mihonapp/mihon/pull/2673))
|
||||
- Display author & artist name in MAL search results ([@MajorTanya](https://github.com/MajorTanya)) ([#2833](https://github.com/mihonapp/mihon/pull/2833))
|
||||
- Add filter options to Updates tab ([@MajorTanya](https://github.com/MajorTanya)) ([#2851](https://github.com/mihonapp/mihon/pull/2851))
|
||||
- Add bookmarked chapters to chapter download options ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#2891](https://github.com/mihonapp/mihon/pull/2891))
|
||||
- Add `src:` prefix to search the library by source ID ([@MajorTanya](https://github.com/MajorTanya)) ([#2927](https://github.com/mihonapp/mihon/pull/2927))
|
||||
- Add `src:local` as a way to search for Local Source entries ([@MajorTanya](https://github.com/MajorTanya)) ([#2928](https://github.com/mihonapp/mihon/pull/2928))
|
||||
|
||||
### Improved
|
||||
- Minimize memory usage by reducing in-memory cover cache size ([@Lolle2000la](https://github.com/Lolle2000la)) ([#2266](https://github.com/mihonapp/mihon/pull/2266))
|
||||
- Optimize MAL search queries ([@MajorTanya](https://github.com/MajorTanya)) ([#2832](https://github.com/mihonapp/mihon/pull/2832))
|
||||
- Reword download reindexing message to avoid confusion ([@MajorTanya](https://github.com/MajorTanya)) ([#2874](https://github.com/mihonapp/mihon/pull/2874))
|
||||
- Optimize tracked library filter ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#2977](https://github.com/mihonapp/mihon/pull/2977))
|
||||
- Utilize tracker for library duplicate detection ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#2978](https://github.com/mihonapp/mihon/pull/2978))
|
||||
|
||||
### Changed
|
||||
- Update tracker icons ([@AntsyLich](https://github.com/AntsyLich)) ([#2773](https://github.com/mihonapp/mihon/pull/2773))
|
||||
- Add a small increment to chapter number before comparison to fix progress sync issues for Suwayomi ([@cpiber](https://github.com/cpiber)) ([#2657](https://github.com/mihonapp/mihon/pull/2675))
|
||||
- Add all pages of adjacent chapters in the UI instead of only the first or last three ([@AntsyLich](https://github.com/AntsyLich)) ([#2995](https://github.com/mihonapp/mihon/pull/2995))
|
||||
- Going back now first clears search query on browse extension tab ([@cuong-tran](https://github.com/cuong-tran)) ([#2906](https://github.com/mihonapp/mihon/pull/2906))
|
||||
- Automatic library updates now run even when connected to a VPN ([@AntsyLich](https://github.com/AntsyLich)) ([#2773](https://github.com/mihonapp/mihon/pull/2773))
|
||||
|
||||
### Fixed
|
||||
- Fix reader tap zones triggering after scrolling is stopped by tapping ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#2680](https://github.com/mihonapp/mihon/pull/2680))
|
||||
- Fix shizuku installer not updating installed extensions ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#2697](https://github.com/mihonapp/mihon/pull/2697))
|
||||
- Fix mass migration not using the same search queries as individual migration ([@AntsyLich](https://github.com/AntsyLich)) ([#2736](https://github.com/mihonapp/mihon/pull/2736))
|
||||
- Fix reader not saving read duration when changing chapter ([@AntsyLich](https://github.com/AntsyLich), [@KotlinHero](https://github.com/KotlinHero)) ([#2784](https://github.com/mihonapp/mihon/pull/2784))
|
||||
- Fix pre-1970 upload date display in chapter list ([@MajorTanya](https://github.com/MajorTanya)) ([#2779](https://github.com/mihonapp/mihon/pull/2779))
|
||||
- Fix crash when trying to install/update extensions while shizuku is not running ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#2837](https://github.com/mihonapp/mihon/pull/2837))
|
||||
- Fix Add Repo input not taking up the full dialog width ([@cuong-tran](https://github.com/cuong-tran)) ([#2816](https://github.com/mihonapp/mihon/pull/2816))
|
||||
- Fix migration's selected sources order not preserved ([@AntsyLich](https://github.com/AntsyLich)) ([#2993](https://github.com/mihonapp/mihon/pull/2993))
|
||||
- Fix migration dialog not showing for consecutive prompts from the same screen ([@AntsyLich](https://github.com/AntsyLich)) ([#2994](https://github.com/mihonapp/mihon/pull/2994))
|
||||
- Fix extension install/update stuck at pending ([@AntsyLich](https://github.com/AntsyLich)) ([#3000](https://github.com/mihonapp/mihon/pull/3000))
|
||||
- Fix crash when downloading multiple chapters with Arabic locale ([@MajorTanya](https://github.com/MajorTanya)) ([#2676](https://github.com/mihonapp/mihon/pull/2676))
|
||||
|
||||
### Other
|
||||
- Enable logcat logging on stable and debug builds without enabling verbose logging ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#2836](https://github.com/mihonapp/mihon/pull/2836))
|
||||
|
||||
## [v0.19.3] - 2025-11-07
|
||||
### Improved
|
||||
- Improved various aspects of the WebView multi window support ([@TheUnlocked](https://github.com/TheUnlocked)) ([#2662](https://github.com/mihonapp/mihon/pull/2662))
|
||||
|
||||
### Removed
|
||||
- Revert "Fix reader tap zones triggering after scrolling was stopped by the user" due to introduction of regression ([@AntsyLich](https://github.com/AntsyLich)) ([#2670](https://github.com/mihonapp/mihon/pull/2670))
|
||||
|
||||
### Fixed
|
||||
- Fix WebView crash introduced in 0.19.2 ([@bapeey](https://github.com/bapeey)) ([#2649](https://github.com/mihonapp/mihon/pull/2649))
|
||||
- Fix extra padding appearing in reader after user interactions ([@AntsyLich](https://github.com/AntsyLich)) ([#2669](https://github.com/mihonapp/mihon/pull/2669))
|
||||
- Fix long strip reader not scrolling on consecutive taps ([@AntsyLich](https://github.com/AntsyLich)) ([#2670](https://github.com/mihonapp/mihon/pull/2670))
|
||||
|
||||
## [v0.19.2] - 2025-11-02
|
||||
### Added
|
||||
- Advanced setting to limit download filenames to ASCII characters. This is provided only as a workaround for OSes that do not properly handle standard Unicode filenames. This setting is generally not recommended and should only be used as a last resort ([@raxod502](https://github.com/radian-software)) ([#2305](https://github.com/mihonapp/mihon/pull/2305))
|
||||
- Option to customize the number of concurrent source and page downloads ([@AntsyLich](https://github.com/AntsyLich)) ([#2637](https://github.com/mihonapp/mihon/pull/2637))
|
||||
|
||||
### Changed
|
||||
- Increased default concurrent page downloads to 5 ([@AntsyLich](https://github.com/AntsyLich)) ([#2637](https://github.com/mihonapp/mihon/pull/2637))
|
||||
|
||||
### Improved
|
||||
- Spoofing of `X-Requested-With` header to support newer WebView versions ([@Guzmazow](https://github.com/Guzmazow)) ([#2491](https://github.com/mihonapp/mihon/pull/2491))
|
||||
- Download support for chapters with the same metadata. Now a hash based on chapter's url is appended to download filename to tell them apart, letting you download both. Existing downloaded chapters will continue to work normally ([@raxod502](https://github.com/radian-software)) ([#2305](https://github.com/mihonapp/mihon/pull/2305))
|
||||
- Auto refresh extension list whenever a repository is added or removed ([@c2y5](https://github.com/c2y5)) ([#2483](https://github.com/mihonapp/mihon/pull/2483))
|
||||
- Added proper multi window support in WebView instead of treating everything as a redirect ([@TheUnlocked](https://github.com/TheUnlocked)) ([#2584](https://github.com/mihonapp/mihon/pull/2584))
|
||||
|
||||
### Fixed
|
||||
- Fix height of description not being calculated correctly if images are present ([@Secozzi](https://github.com/Secozzi)) ([#2382](https://github.com/mihonapp/mihon/pull/2382))
|
||||
- Fix migration progress not updating after manual search ([@Secozzi](https://github.com/Secozzi)) ([#2484](https://github.com/mihonapp/mihon/pull/2484))
|
||||
- Fix category migration flag being ignored due to incorrect check against chapter flag ([@Secozzi](https://github.com/Secozzi)) ([#2484](https://github.com/mihonapp/mihon/pull/2484))
|
||||
- Fix disabling incognito mode from notification ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#2512](https://github.com/mihonapp/mihon/pull/2512))
|
||||
- Fix mass migration advanced search query building ([@AntsyLich](https://github.com/AntsyLich)) ([#2629](https://github.com/mihonapp/mihon/pull/2629))
|
||||
- Fix migration dialog migrating to wrong entry ([@AntsyLich](https://github.com/AntsyLich)) ([#2631](https://github.com/mihonapp/mihon/pull/2631))
|
||||
- Fix migration "Attempt to invoke virtual method" crash ([@AntsyLich](https://github.com/AntsyLich)) ([#2632](https://github.com/mihonapp/mihon/pull/2632))
|
||||
- Fix reader "Unable to edit key" error ([@AntsyLich](https://github.com/AntsyLich)) ([#2634](https://github.com/mihonapp/mihon/pull/2634))
|
||||
- Fix extension download stuck in pending state in some cases ([@c2y5](https://github.com/c2y5)) ([#2483](https://github.com/mihonapp/mihon/pull/2483))
|
||||
- Fix scrollbar not showing when animator duration scale animation is turned off ([@anirudhsnayak](https://github.com/anirudhsnayak)) ([#2398](https://github.com/mihonapp/mihon/pull/2398))
|
||||
- Fix date picker not allowing the same start and finish date in negative time zones ([@AntsyLich](https://github.com/AntsyLich), [@kashish-aggarwal21](https://github.com/kashish-aggarwal21)) ([#2617](https://github.com/mihonapp/mihon/pull/2617))
|
||||
- Fix reader tap zones triggering after scrolling was stopped by the user ([@Naputt1](https://github.com/Naputt1), [@AntsyLich](https://github.com/AntsyLich)) ([#2518](https://github.com/mihonapp/mihon/pull/2518))
|
||||
- Fix reader page indicator being partially visible on some devices ([@AntsyLich](https://github.com/AntsyLich)) ([#1908](https://github.com/mihonapp/mihon/pull/1908))
|
||||
- Fix inconsistent system bar and reader app bar background ([@AntsyLich](https://github.com/AntsyLich)) ([#1908](https://github.com/mihonapp/mihon/pull/1908))
|
||||
- Fix transparent system bar background in reader on Android 15+ ([@AntsyLich](https://github.com/AntsyLich)) ([#1908](https://github.com/mihonapp/mihon/pull/1908))
|
||||
|
||||
### Other
|
||||
- Delegate Suwayomi tracker authentication to extension ([@cpiber](https://github.com/cpiber)) ([#2476](https://github.com/mihonapp/mihon/pull/2476))
|
||||
- Fix Kitsu tracker to conform to tracker data structure properly ([@cpiber](https://github.com/cpiber)) ([#2609](https://github.com/mihonapp/mihon/pull/2609))
|
||||
- Update Suwayomi tracker to use GraphQL API instead of REST API ([@cpiber](https://github.com/cpiber)) ([#2585](https://github.com/mihonapp/mihon/pull/2585))
|
||||
|
||||
## [v0.19.1] - 2025-08-07
|
||||
### Changed
|
||||
- LocalSource now reads ComicInfo.xml file for chapter (if available) to display chapter title, number and scanlator ([@raxod502](https://github.com/radian-software)) ([#2332](https://github.com/mihonapp/mihon/pull/2332))
|
||||
|
||||
### Removed
|
||||
- Predictive back support ([@AntsyLich](https://github.com/AntsyLich)) ([#2362](https://github.com/mihonapp/mihon/pull/2362))
|
||||
|
||||
### Fixed
|
||||
- Fix scrollbar sometimes not showing during scroll or not reaching the bottom with few items ([@anirudhsnayak](https://github.com/anirudhsnayak)) ([#2304](https://github.com/mihonapp/mihon/pull/2304))
|
||||
- Fix local source EPUB files not loading ([@AntsyLich](https://github.com/AntsyLich)) ([#2369](https://github.com/mihonapp/mihon/pull/2369))
|
||||
- Fix title text color in light mode on mass migration list ([@AntsyLich](https://github.com/AntsyLich)) ([#2370](https://github.com/mihonapp/mihon/pull/2370))
|
||||
- Fix 'Default' category showing in library with no user-added categories ([@AntsyLich](https://github.com/AntsyLich)) ([#2371](https://github.com/mihonapp/mihon/pull/2371))
|
||||
- Fix crash when opening filter sheet with an empty library ([@krysanify](https://github.com/krysanify/)) ([#2355](https://github.com/mihonapp/mihon/pull/2355))
|
||||
- Fix mark as read/unread not working for selected library items ([@krysanify](https://github.com/krysanify/)) ([#2355](https://github.com/mihonapp/mihon/pull/2355))
|
||||
|
||||
## [v0.19.0] - 2025-08-04
|
||||
### Added
|
||||
- Add more Kaomoji for empty/error screens ([@ianfhunter](https://github.com/ianfhunter/)) ([#1909](https://github.com/mihonapp/mihon/pull/1909))
|
||||
- Add user manga notes ([@imkunet](https://github.com/imkunet), [@AntsyLich](https://github.com/AntsyLich)) ([#428](https://github.com/mihonapp/mihon/pull/428))
|
||||
- Fix user notes not restoring when manga doesn't exist in DB ([@AntsyLich](https://github.com/AntsyLich)) ([#1945](https://github.com/mihonapp/mihon/pull/1945))
|
||||
- Add markdown support for manga descriptions ([@Secozzi](https://github.com/Secozzi)) ([#1948](https://github.com/mihonapp/mihon/pull/1948))
|
||||
- Use simpler markdown flavour ([@Secozzi](https://github.com/Secozzi)) ([#2000](https://github.com/mihonapp/mihon/pull/2000))
|
||||
- Use Github markdown flavour for Github releases & fix bullet list alignment ([@Secozzi](https://github.com/Secozzi)) ([#2024](https://github.com/mihonapp/mihon/pull/2024))
|
||||
- Add option to toggle image loading ([@Secozzi](https://github.com/Secozzi)) ([#2076](https://github.com/mihonapp/mihon/pull/2076))
|
||||
- Add Nord Theme ([@Riztard](https://github.com/Riztard)) ([#1951](https://github.com/mihonapp/mihon/pull/1951))
|
||||
- Option to keep read manga when clearing database ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#1979](https://github.com/mihonapp/mihon/pull/1979))
|
||||
- Add advanced option to always update manga title from source ([@FlaminSarge](https://github.com/FlaminSarge)) ([#1182](https://github.com/mihonapp/mihon/pull/1182))
|
||||
- Full predictive back support ([@AntsyLich](https://github.com/AntsyLich)) ([#2085](https://github.com/mihonapp/mihon/pull/2085))
|
||||
- Add Catppuccin theme (mocha for dark and latte for light, mauve accent) ([@claymorwan](https://github.com/claymorwan/)) ([#2117](https://github.com/mihonapp/mihon/pull/2117))
|
||||
- Manga mass migration ([@AntsyLich](https://github.com/AntsyLich), [@jobobby04](https://github.com/jobobby04)) ([#2110](https://github.com/mihonapp/mihon/pull/2110), [#2336](https://github.com/mihonapp/mihon/pull/2336), [#2338](https://github.com/mihonapp/mihon/pull/2338), [`f119386`](https://github.com/mihonapp/mihon/commit/f119386))
|
||||
|
||||
### Improved
|
||||
- Significantly improve browsing speed (near instantaneous) ([@AntsyLich](https://github.com/AntsyLich)) ([#1946](https://github.com/mihonapp/mihon/pull/1946))
|
||||
- Deduplicate entries when browsing ([@AntsyLich](https://github.com/AntsyLich)) ([#1957](https://github.com/mihonapp/mihon/pull/1957))
|
||||
- Update non-library manga data when browsing ([@AntsyLich](https://github.com/AntsyLich)) ([#1967](https://github.com/mihonapp/mihon/pull/1967))
|
||||
- Surface image loading error in Reader ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#1981](https://github.com/mihonapp/mihon/pull/1981))
|
||||
- Include source headers when opening failed images from reader ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#2004](https://github.com/mihonapp/mihon/pull/2004))
|
||||
- Added autofill support to tracker login dialog ([@AntsyLich](https://github.com/AntsyLich)) ([#2069](https://github.com/mihonapp/mihon/pull/2069))
|
||||
- Added option to hide missing chapter count ([@User826](https://github.com/User826), [@AntsyLich](https://github.com/AntsyLich)) ([#2108](https://github.com/mihonapp/mihon/pull/2108))
|
||||
- Use median to determine smart update interval, making it more resilient to long hiatuses ([@Kladki](https://github.com/Kladki)) ([#2251](https://github.com/mihonapp/mihon/pull/2251))
|
||||
- Optimize library code to potentially better handle big user libraries ([@AntsyLich](https://github.com/AntsyLich)) ([#2329](https://github.com/mihonapp/mihon/pull/2329), [#2341](https://github.com/mihonapp/mihon/pull/2341))
|
||||
|
||||
### Changed
|
||||
- Display all similarly named duplicates in duplicate manga dialogue ([@NarwhalHorns](https://github.com/NarwhalHorns), [@AntsyLich](https://github.com/AntsyLich)) ([#1861](https://github.com/mihonapp/mihon/pull/1861))
|
||||
- Display chapter count on items in duplicate manga dialogue ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#1963](https://github.com/mihonapp/mihon/pull/1963))
|
||||
- Update Facebook and Reddit icons ([@Joehuu](https://github.com/Joehuu)) ([#1994](https://github.com/mihonapp/mihon/pull/1994))
|
||||
- Switch default user agent to Android Chrome ([@AntsyLich](https://github.com/AntsyLich)) ([#2048](https://github.com/mihonapp/mihon/pull/2048))
|
||||
- Changed log in button text when processing tracker login ([@AntsyLich](https://github.com/AntsyLich)) ([#2069](https://github.com/mihonapp/mihon/pull/2069))
|
||||
- Disable reader's 'Keep screen on' setting by default ([@AntsyLich](https://github.com/AntsyLich)) ([#2095](https://github.com/mihonapp/mihon/pull/2095))
|
||||
- Update manga without chapters even if restricted by source ([@AntsyLich](https://github.com/AntsyLich)) ([#2224](https://github.com/mihonapp/mihon/pull/224))
|
||||
- Make local source default chapter sorting match file explorer behavior ([@AntsyLich](https://github.com/AntsyLich)) ([#2224](https://github.com/mihonapp/mihon/pull/224))
|
||||
- Include Manga `initialized` status in backup ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#2285](https://github.com/mihonapp/mihon/pull/2285))
|
||||
|
||||
### Fixed
|
||||
- Fix Bangumi search results including novels ([@MajorTanya](https://github.com/MajorTanya)) ([#1885](https://github.com/mihonapp/mihon/pull/1885))
|
||||
- Fix next chapter button occasionally jumping to the last page of the current chapter ([@perokhe](https://github.com/perokhe)) ([#1920](https://github.com/mihonapp/mihon/pull/1920))
|
||||
- Fix page number not appearing when opening chapter ([@perokhe](https://github.com/perokhe)) ([#1936](https://github.com/mihonapp/mihon/pull/1936))
|
||||
- Fix backup sharing from notifications not working when app is in background ([@JaymanR](https://github.com/JaymanR))([#1929](https://github.com/mihonapp/mihon/pull/1929))
|
||||
- Fix mark existing duplicate read chapters as read option not working in some cases ([@AntsyLich](https://github.com/AntsyLich)) ([#1944](https://github.com/mihonapp/mihon/pull/1944))
|
||||
- Fix app bar action tooltips blocking clicks ([@Bartuzen](https://github.com/Bartuzen)) ([#1928](https://github.com/mihonapp/mihon/pull/1928))
|
||||
- Fix unintended app permissions due to Firebase misconfiguration ([@AntsyLich](https://github.com/AntsyLich)) ([#1960](https://github.com/mihonapp/mihon/pull/1960))
|
||||
- Fix navigation issue after migrating a duplicated entry from History tab ([@cuong-tran](https://github.com/cuong-tran)) ([#1980](https://github.com/mihonapp/mihon/pull/1980))
|
||||
- Fix duplicate requests in WebView due to empty reasonPhrase ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#2003](https://github.com/mihonapp/mihon/pull/2003))
|
||||
- Fix content under source browse screen top appbar is interactable ([@AntsyLich](https://github.com/AntsyLich)) ([#2026](https://github.com/mihonapp/mihon/pull/2026))
|
||||
- Fix crash when trying use source sort filter without a pre-selection ([@AntsyLich](https://github.com/AntsyLich)) ([#2036](https://github.com/mihonapp/mihon/pull/2036))
|
||||
- Fix empty layout not appearing in browse source screen in some cases ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#2043](https://github.com/mihonapp/mihon/pull/2043))
|
||||
- Fix Pill not following the local text style ([@AntsyLich](https://github.com/AntsyLich)) ([`f8cb506`](https://github.com/mihonapp/mihon/commit/f8cb506))
|
||||
- Fix downloader stopping after failing to create download directory of a manga ([@AntsyLich](https://github.com/AntsyLich)) ([#2068](https://github.com/mihonapp/mihon/pull/2068))
|
||||
- Fix pressing `Enter` while searching also triggering navigation back on physical keyboards ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#2077](https://github.com/mihonapp/mihon/pull/2077))
|
||||
- Ensure app waits for Cloudflare challenge to complete before continuing ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#2200](https://github.com/mihonapp/mihon/pull/2200))
|
||||
|
||||
### Removed
|
||||
- Remove Okhttp networking from WebView Screen ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#2020](https://github.com/mihonapp/mihon/pull/2020))
|
||||
|
||||
## [v0.18.0] - 2025-03-20
|
||||
### Added
|
||||
- Add option to always decode long strip images with SSIV ([@AntsyLich](https://github.com/AntsyLich)) ([`c5655e8`](https://github.com/mihonapp/mihon/commit/c5655e8803bc32d0931657f0b7bc6afeab70feaf))
|
||||
@@ -323,7 +550,18 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
|
||||
- Branding to Mihon ([@AntsyLich](https://github.com/AntsyLich))
|
||||
- Minimum supported Android version to 8 ([@AntsyLich](https://github.com/AntsyLich)) ([`dfb3091`](https://github.com/mihonapp/mihon/commit/dfb3091e380dda3e9bfb64bf5c9a685cf3a03d0e))
|
||||
|
||||
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.18.0...main
|
||||
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.20.0...main
|
||||
[v0.20.0]: https://github.com/mihonapp/mihon/compare/v0.19.9...v0.20.0
|
||||
[v0.19.9]: https://github.com/mihonapp/mihon/compare/v0.19.8...v0.19.9
|
||||
[v0.19.8]: https://github.com/mihonapp/mihon/compare/v0.19.7...v0.19.8
|
||||
[v0.19.7]: https://github.com/mihonapp/mihon/compare/v0.19.6...v0.19.7
|
||||
[v0.19.6]: https://github.com/mihonapp/mihon/compare/v0.19.5...v0.19.6
|
||||
[v0.19.5]: https://github.com/mihonapp/mihon/compare/v0.19.4...v0.19.5
|
||||
[v0.19.4]: https://github.com/mihonapp/mihon/compare/v0.19.3...v0.19.4
|
||||
[v0.19.3]: https://github.com/mihonapp/mihon/compare/v0.19.2...v0.19.3
|
||||
[v0.19.2]: https://github.com/mihonapp/mihon/compare/v0.19.1...v0.19.2
|
||||
[v0.19.1]: https://github.com/mihonapp/mihon/compare/v0.19.0...v0.19.1
|
||||
[v0.19.0]: https://github.com/mihonapp/mihon/compare/v0.18.0...v0.19.0
|
||||
[v0.18.0]: https://github.com/mihonapp/mihon/compare/v0.17.1...v0.18.0
|
||||
[v0.17.1]: https://github.com/mihonapp/mihon/compare/v0.17.0...v0.17.1
|
||||
[v0.17.0]: https://github.com/mihonapp/mihon/compare/v0.16.5...v0.17.0
|
||||
|
||||
@@ -10,16 +10,16 @@
|
||||
Discover and read manga, webtoons, comics, and more – easier than ever on your Android device.
|
||||
|
||||
[](https://discord.gg/mihon)
|
||||
[](https://github.com/mihonapp/mihon/releases)
|
||||
[](https://mihon.app/download)
|
||||
|
||||
[](https://github.com/mihonapp/mihon/actions/workflows/build_push.yml)
|
||||
[](https://github.com/mihonapp/mihon/actions/workflows/build_push.yml)
|
||||
[](/LICENSE)
|
||||
[](https://hosted.weblate.org/engage/mihon/)
|
||||
|
||||
## Download
|
||||
|
||||
[](https://github.com/mihonapp/mihon/releases)
|
||||
[](https://github.com/mihonapp/mihon-preview/releases)
|
||||
[](https://mihon.app/download)
|
||||
[](https://mihon.app/download)
|
||||
|
||||
*Requires Android 8.0 or higher.*
|
||||
|
||||
|
||||
+128
-89
@@ -1,14 +1,20 @@
|
||||
import mihon.buildlogic.Config
|
||||
import mihon.buildlogic.getBuildTime
|
||||
import mihon.buildlogic.getCommitCount
|
||||
import mihon.buildlogic.getGitSha
|
||||
import mihon.gradle.Config
|
||||
import mihon.gradle.getBuildTime
|
||||
import mihon.gradle.getLatestCommitCount
|
||||
import mihon.gradle.getLatestCommitSha
|
||||
import mihon.gradle.tasks.ReplaceShortcutsPlaceholderTask
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
import kotlin.io.encoding.Base64
|
||||
|
||||
plugins {
|
||||
id("mihon.android.application")
|
||||
id("mihon.android.application.compose")
|
||||
id("com.github.zellius.shortcut-helper")
|
||||
kotlin("plugin.serialization")
|
||||
alias(mihonx.plugins.android.application)
|
||||
alias(mihonx.plugins.compose)
|
||||
alias(mihonx.plugins.spotless)
|
||||
|
||||
alias(libs.plugins.aboutLibraries)
|
||||
alias(libs.plugins.androidx.baselineProfile)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
if (Config.includeTelemetry) {
|
||||
@@ -18,7 +24,7 @@ if (Config.includeTelemetry) {
|
||||
}
|
||||
}
|
||||
|
||||
shortcutHelper.setFilePath("./shortcuts.xml")
|
||||
val keystorePropertiesFile = rootProject.file("keystore.properties")
|
||||
|
||||
android {
|
||||
namespace = "eu.kanade.tachiyomi"
|
||||
@@ -26,31 +32,62 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "app.mihon"
|
||||
|
||||
versionCode = 11
|
||||
versionName = "0.18.0"
|
||||
versionCode = 24
|
||||
versionName = "0.20.0"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"")
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getLatestCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getLatestCommitSha()}\"")
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = false)}\"")
|
||||
buildConfigField("boolean", "TELEMETRY_INCLUDED", "${Config.includeTelemetry}")
|
||||
buildConfigField("boolean", "UPDATER_ENABLED", "${Config.enableUpdater}")
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
if (System.getenv("MIHON_GITHUB_RELEASE").toBoolean()) {
|
||||
val tempStoreFile = file(System.getenv("RUNNER_TEMP")).resolve("antsy.keystore")
|
||||
|
||||
val storeFileBytes = System.getenv("storeFileBase64").let(Base64::decode)
|
||||
tempStoreFile.outputStream().use { it.write(storeFileBytes) }
|
||||
|
||||
signingConfigs {
|
||||
named("debug") {
|
||||
storeFile = tempStoreFile
|
||||
storePassword = System.getenv("storePassword")
|
||||
keyAlias = System.getenv("keyAlias")
|
||||
keyPassword = System.getenv("keyPassword")
|
||||
}
|
||||
}
|
||||
} else if (keystorePropertiesFile.exists()) {
|
||||
val keystoreProperties = FileInputStream(keystorePropertiesFile).use { Properties().apply { load(it) } }
|
||||
|
||||
signingConfigs {
|
||||
named("debug") {
|
||||
storeFile = file(keystoreProperties.getProperty("storeFile"))
|
||||
storePassword = keystoreProperties.getProperty("storePassword")
|
||||
keyAlias = keystoreProperties.getProperty("keyAlias")
|
||||
keyPassword = keystoreProperties.getProperty("keyPassword")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
val debug by getting {
|
||||
val debug = getByName("debug") {
|
||||
applicationIdSuffix = ".dev"
|
||||
versionNameSuffix = "-${getCommitCount()}"
|
||||
versionNameSuffix = "-${getLatestCommitCount()}"
|
||||
isPseudoLocalesEnabled = true
|
||||
}
|
||||
val release by getting {
|
||||
val release = getByName("release") {
|
||||
isMinifyEnabled = Config.enableCodeShrink
|
||||
isShrinkResources = Config.enableCodeShrink
|
||||
|
||||
signingConfig = debug.signingConfig
|
||||
|
||||
isProfileable = true
|
||||
|
||||
proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro")
|
||||
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = true)}\"")
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = true)}\"")
|
||||
}
|
||||
|
||||
val commonMatchingFallbacks = listOf(release.name)
|
||||
@@ -68,29 +105,24 @@ android {
|
||||
applicationIdSuffix = ".debug"
|
||||
|
||||
versionNameSuffix = debug.versionNameSuffix
|
||||
signingConfig = debug.signingConfig
|
||||
|
||||
matchingFallbacks.addAll(commonMatchingFallbacks)
|
||||
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"")
|
||||
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = false)}\"")
|
||||
}
|
||||
create("benchmark") {
|
||||
initWith(release)
|
||||
|
||||
isDebuggable = false
|
||||
isProfileable = true
|
||||
versionNameSuffix = "-benchmark"
|
||||
applicationIdSuffix = ".benchmark"
|
||||
|
||||
signingConfig = debug.signingConfig
|
||||
|
||||
matchingFallbacks.addAll(commonMatchingFallbacks)
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("preview").res.srcDirs("src/debug/res")
|
||||
getByName("benchmark").res.srcDirs("src/debug/res")
|
||||
getByName("preview").res.directories.add("src/debug/res")
|
||||
getByName("benchmark").res.directories.add("src/debug/res")
|
||||
}
|
||||
|
||||
splits {
|
||||
@@ -138,11 +170,7 @@ android {
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
|
||||
// Disable some unused things
|
||||
aidl = false
|
||||
renderScript = false
|
||||
shaders = false
|
||||
aidl = true
|
||||
}
|
||||
|
||||
lint {
|
||||
@@ -159,6 +187,7 @@ kotlin {
|
||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3ExpressiveApi",
|
||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||
"-opt-in=coil3.annotation.ExperimentalCoilApi",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
@@ -169,7 +198,14 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
baselineProfile {
|
||||
baselineProfileOutputDir = "baselineProfiles"
|
||||
mergeIntoMain = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
baselineProfile(projects.baselineProfile)
|
||||
|
||||
implementation(projects.i18n)
|
||||
implementation(projects.core.archive)
|
||||
implementation(projects.core.common)
|
||||
@@ -183,93 +219,93 @@ dependencies {
|
||||
implementation(projects.telemetry)
|
||||
|
||||
// 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(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.compose.foundation)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation(libs.androidx.compose.materialIcons)
|
||||
implementation(libs.androidx.compose.animation)
|
||||
implementation(libs.androidx.compose.animationGraphics)
|
||||
debugImplementation(libs.androidx.compose.uiTooling)
|
||||
implementation(libs.androidx.compose.uiToolingPreview)
|
||||
implementation(libs.androidx.compose.uiUtil)
|
||||
|
||||
implementation(androidx.interpolator)
|
||||
implementation(libs.androidx.interpolator)
|
||||
|
||||
implementation(androidx.paging.runtime)
|
||||
implementation(androidx.paging.compose)
|
||||
implementation(libs.androidx.paging.runtime)
|
||||
implementation(libs.androidx.paging.compose)
|
||||
|
||||
implementation(libs.bundles.sqlite)
|
||||
implementation(libs.androidx.sqlite.bundled)
|
||||
|
||||
implementation(kotlinx.reflect)
|
||||
implementation(kotlinx.immutables)
|
||||
implementation(libs.kotlin.reflect)
|
||||
|
||||
implementation(platform(kotlinx.coroutines.bom))
|
||||
implementation(kotlinx.bundles.coroutines)
|
||||
implementation(libs.bundles.kotlinx.coroutines)
|
||||
|
||||
implementation(libs.sqldelight.async)
|
||||
|
||||
// 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(libs.androidx.annotation)
|
||||
implementation(libs.androidx.appCompat)
|
||||
implementation(libs.androidx.biometric)
|
||||
implementation(libs.androidx.constraintLayout)
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.coreSplashScreen)
|
||||
implementation(libs.androidx.recyclerView)
|
||||
implementation(libs.androidx.viewPager)
|
||||
implementation(libs.androidx.profileInstaller)
|
||||
|
||||
implementation(androidx.bundles.lifecycle)
|
||||
implementation(libs.bundles.androidx.lifecycle)
|
||||
|
||||
// Job scheduling
|
||||
implementation(androidx.workmanager)
|
||||
implementation(libs.androidx.work)
|
||||
|
||||
// RxJava
|
||||
implementation(libs.rxjava)
|
||||
implementation(libs.rxJava)
|
||||
|
||||
// Networking
|
||||
implementation(libs.bundles.okhttp)
|
||||
implementation(libs.okio)
|
||||
implementation(libs.conscrypt.android) // TLS 1.3 support for Android < 10
|
||||
implementation(libs.conscrypt) // TLS 1.3 support for Android < 10
|
||||
|
||||
// Data serialization (JSON, protobuf, xml)
|
||||
implementation(kotlinx.bundles.serialization)
|
||||
implementation(libs.bundles.serialization)
|
||||
|
||||
// HTML parser
|
||||
implementation(libs.jsoup)
|
||||
|
||||
// Disk
|
||||
implementation(libs.disklrucache)
|
||||
implementation(libs.diskLruCache)
|
||||
implementation(libs.unifile)
|
||||
|
||||
// Preferences
|
||||
implementation(libs.preferencektx)
|
||||
implementation(libs.androidx.preference)
|
||||
|
||||
// Dependency injection
|
||||
implementation(libs.injekt)
|
||||
|
||||
// Image loading
|
||||
implementation(platform(libs.coil.bom))
|
||||
implementation(libs.bundles.coil)
|
||||
implementation(libs.subsamplingscaleimageview) {
|
||||
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) {
|
||||
implementation(libs.flexibleAdapter)
|
||||
implementation(libs.photoView)
|
||||
implementation(libs.directionalViewPager) {
|
||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||
}
|
||||
implementation(libs.insetter)
|
||||
implementation(libs.bundles.richtext)
|
||||
implementation(libs.composeRichEditor)
|
||||
implementation(libs.aboutLibraries.compose)
|
||||
implementation(libs.bundles.voyager)
|
||||
implementation(libs.compose.materialmotion)
|
||||
implementation(libs.composeMaterialMotion)
|
||||
implementation(libs.swipe)
|
||||
implementation(libs.compose.webview)
|
||||
implementation(libs.compose.grid)
|
||||
implementation(libs.composeWebview)
|
||||
implementation(libs.composeGrid)
|
||||
implementation(libs.reorderable)
|
||||
implementation(libs.bundles.markdown)
|
||||
implementation(libs.materialKolor)
|
||||
|
||||
// Logging
|
||||
implementation(libs.logcat)
|
||||
@@ -277,34 +313,37 @@ dependencies {
|
||||
// Shizuku
|
||||
implementation(libs.bundles.shizuku)
|
||||
|
||||
// String similarity
|
||||
implementation(libs.stringSimilarity)
|
||||
|
||||
// Tests
|
||||
testImplementation(libs.bundles.test)
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
|
||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||
// debugImplementation(libs.leakcanary.android)
|
||||
implementation(libs.leakcanary.plumber)
|
||||
// debugImplementation(libs.leakCanary.android)
|
||||
implementation(libs.leakCanary.plumber)
|
||||
|
||||
testImplementation(kotlinx.coroutines.test)
|
||||
testImplementation(libs.kotlinx.coroutines.test)
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
beforeVariants { variantBuilder ->
|
||||
// Disables standardBenchmark
|
||||
if (variantBuilder.buildType == "benchmark") {
|
||||
variantBuilder.enable = variantBuilder.productFlavors.containsAll(
|
||||
listOf("default" to "dev"),
|
||||
)
|
||||
onVariants { variant ->
|
||||
val resSource = variant.sources.res ?: return@onVariants
|
||||
|
||||
val variantName = variant.name.replaceFirstChar { it.uppercase() }
|
||||
val replaceShortcutsPlaceholderTask = tasks.register<ReplaceShortcutsPlaceholderTask>(
|
||||
"replace${variantName}ShortcutPlaceholder",
|
||||
) {
|
||||
applicationId.set(variant.applicationId)
|
||||
shortcutsFile.set(projectDir.resolve("src/main/shortcuts.xml"))
|
||||
}
|
||||
resSource.addGeneratedSourceDirectory(replaceShortcutsPlaceholderTask) { it.outputDir }
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath(kotlinx.gradle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="accent_blue">#7EBBED</color>
|
||||
<color name="icon_background">#2E3943</color>
|
||||
</resources>
|
||||
@@ -60,8 +60,8 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Deep link to add repos -->
|
||||
<intent-filter android:label="@string/action_add_repo">
|
||||
<!-- Legacy deeplink to add extension store -->
|
||||
<intent-filter android:label="@string/action_addExtensionStore">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
@@ -71,6 +71,17 @@
|
||||
<data android:host="add-repo" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Deeplink to add extension store -->
|
||||
<intent-filter android:label="@string/action_addExtensionStore">
|
||||
<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="extension-store" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Open backup files -->
|
||||
<intent-filter android:label="@string/pref_restore_backup">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
@@ -130,6 +141,10 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
<intent-filter android:label="@string/app_settings">
|
||||
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package mihon.app.shizuku;
|
||||
|
||||
interface IShellInterface {
|
||||
void install(in AssetFileDescriptor apk) = 1;
|
||||
|
||||
void destroy() = 16777114;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -25,16 +25,17 @@ 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.data.extension.repository.ExtensionStoreRepositoryImpl
|
||||
import mihon.data.extension.service.ExtensionStoreService
|
||||
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.extension.interactor.AddExtensionStore
|
||||
import mihon.domain.extension.interactor.GetExtensionStoreCountAsFlow
|
||||
import mihon.domain.extension.interactor.GetExtensionStores
|
||||
import mihon.domain.extension.interactor.RemoveExtensionStore
|
||||
import mihon.domain.extension.interactor.UpdateExtensionStores
|
||||
import mihon.domain.extension.repository.ExtensionStoreRepository
|
||||
import mihon.domain.migration.usecases.MigrateMangaUseCase
|
||||
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
||||
import mihon.domain.upcoming.interactor.GetUpcomingManga
|
||||
import tachiyomi.data.category.CategoryRepositoryImpl
|
||||
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
||||
@@ -56,6 +57,7 @@ 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.GetBookmarkedChaptersByMangaId
|
||||
import tachiyomi.domain.chapter.interactor.GetChapter
|
||||
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
|
||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||
@@ -79,6 +81,7 @@ 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.interactor.UpdateMangaNotes
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
||||
import tachiyomi.domain.release.service.ReleaseService
|
||||
@@ -129,9 +132,15 @@ class DomainModule : InjektModule {
|
||||
addFactory { SetMangaViewerFlags(get()) }
|
||||
addFactory { NetworkToLocalManga(get()) }
|
||||
addFactory { UpdateManga(get(), get()) }
|
||||
addFactory { UpdateMangaNotes(get()) }
|
||||
addFactory { SetMangaCategories(get()) }
|
||||
addFactory { GetExcludedScanlators(get()) }
|
||||
addFactory { SetExcludedScanlators(get()) }
|
||||
addFactory {
|
||||
MigrateMangaUseCase(
|
||||
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(),
|
||||
)
|
||||
}
|
||||
|
||||
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||
addFactory { GetApplicationRelease(get(), get()) }
|
||||
@@ -149,6 +158,7 @@ class DomainModule : InjektModule {
|
||||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||
addFactory { GetChapter(get()) }
|
||||
addFactory { GetChaptersByMangaId(get()) }
|
||||
addFactory { GetBookmarkedChaptersByMangaId(get()) }
|
||||
addFactory { GetChapterByUrlAndMangaId(get()) }
|
||||
addFactory { UpdateChapter(get()) }
|
||||
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
||||
@@ -185,15 +195,17 @@ class DomainModule : InjektModule {
|
||||
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()) }
|
||||
addSingletonFactory { ExtensionStoreService(get(), get(), get()) }
|
||||
addSingletonFactory<ExtensionStoreRepository> { ExtensionStoreRepositoryImpl(get(), get()) }
|
||||
addFactory { AddExtensionStore(get()) }
|
||||
addFactory { GetExtensionStoreCountAsFlow(get()) }
|
||||
addFactory { GetExtensionStores(get()) }
|
||||
addFactory { RemoveExtensionStore(get()) }
|
||||
addFactory { UpdateExtensionStores(get()) }
|
||||
|
||||
addFactory { ToggleIncognito(get()) }
|
||||
addFactory { GetIncognitoState(get(), get(), get()) }
|
||||
|
||||
addFactory { UpdateMangaFromRemote(get(), get(), get(), get(), get(), get(), get()) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,19 +9,22 @@ import tachiyomi.i18n.MR
|
||||
|
||||
class BasePreferences(
|
||||
val context: Context,
|
||||
private val preferenceStore: PreferenceStore,
|
||||
preferenceStore: PreferenceStore,
|
||||
) {
|
||||
|
||||
fun downloadedOnly() = preferenceStore.getBoolean(
|
||||
val downloadedOnly: Preference<Boolean> = preferenceStore.getBoolean(
|
||||
Preference.appStateKey("pref_downloaded_only"),
|
||||
false,
|
||||
)
|
||||
|
||||
fun incognitoMode() = preferenceStore.getBoolean(Preference.appStateKey("incognito_mode"), false)
|
||||
val incognitoMode: Preference<Boolean> = preferenceStore.getBoolean(Preference.appStateKey("incognito_mode"), false)
|
||||
|
||||
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
||||
val extensionInstaller: ExtensionInstallerPreference = ExtensionInstallerPreference(context, preferenceStore)
|
||||
|
||||
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
|
||||
val shownOnboardingFlow: Preference<Boolean> = preferenceStore.getBoolean(
|
||||
Preference.appStateKey("onboarding_complete"),
|
||||
false,
|
||||
)
|
||||
|
||||
enum class ExtensionInstaller(val titleRes: StringResource, val requiresSystemPermission: Boolean) {
|
||||
LEGACY(MR.strings.ext_installer_legacy, true),
|
||||
@@ -30,9 +33,22 @@ class BasePreferences(
|
||||
PRIVATE(MR.strings.ext_installer_private, false),
|
||||
}
|
||||
|
||||
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
|
||||
val displayProfile: Preference<String> = preferenceStore.getString("pref_display_profile_key", "")
|
||||
|
||||
fun hardwareBitmapThreshold() = preferenceStore.getInt("pref_hardware_bitmap_threshold", GLUtil.SAFE_TEXTURE_LIMIT)
|
||||
val hardwareBitmapThreshold: Preference<Int> = preferenceStore.getInt(
|
||||
"pref_hardware_bitmap_threshold",
|
||||
GLUtil.SAFE_TEXTURE_LIMIT,
|
||||
)
|
||||
|
||||
fun alwaysDecodeLongStripWithSSIV() = preferenceStore.getBoolean("pref_always_decode_long_strip_with_ssiv", false)
|
||||
val alwaysDecodeLongStripWithSSIV: Preference<Boolean> = preferenceStore.getBoolean(
|
||||
"pref_always_decode_long_strip_with_ssiv",
|
||||
false,
|
||||
)
|
||||
|
||||
val installationId: Preference<String> = preferenceStore.getString(Preference.appStateKey("installation_id"), "")
|
||||
|
||||
val donationCampaignShown: Preference<Boolean> = preferenceStore.getBoolean(
|
||||
Preference.appStateKey("donation_campaign_shown"),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class SetReadStatus(
|
||||
return@withNonCancellableContext Result.InternalError(e)
|
||||
}
|
||||
|
||||
if (read && downloadPreferences.removeAfterMarkedAsRead().get()) {
|
||||
if (read && downloadPreferences.removeAfterMarkedAsRead.get()) {
|
||||
chaptersToUpdate
|
||||
.groupBy { it.mangaId }
|
||||
.forEach { (mangaId, chapters) ->
|
||||
|
||||
@@ -89,6 +89,7 @@ class SyncChaptersWithSource(
|
||||
// Update metadata from source if necessary.
|
||||
if (source is HttpSource) {
|
||||
val sChapter = chapter.toSChapter()
|
||||
@Suppress("DEPRECATION")
|
||||
source.prepareNewChapter(sChapter, manga.toSManga())
|
||||
chapter = chapter.copyFromSChapter(sChapter)
|
||||
}
|
||||
@@ -114,6 +115,7 @@ class SyncChaptersWithSource(
|
||||
downloadManager.isChapterDownloaded(
|
||||
dbChapter.name,
|
||||
dbChapter.scanlator,
|
||||
dbChapter.url,
|
||||
manga.title,
|
||||
manga.source,
|
||||
)
|
||||
@@ -121,12 +123,14 @@ class SyncChaptersWithSource(
|
||||
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)
|
||||
}
|
||||
@@ -168,7 +172,7 @@ class SyncChaptersWithSource(
|
||||
val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { it.dateFetch }
|
||||
.associate { it.chapterNumber to it.dateFetch }
|
||||
|
||||
val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead().get()
|
||||
val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead.get()
|
||||
.contains(LibraryPreferences.MARK_DUPLICATE_CHAPTER_READ_NEW)
|
||||
|
||||
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
|
||||
|
||||
@@ -13,6 +13,7 @@ fun Chapter.toSChapter(): SChapter {
|
||||
it.date_upload = dateUpload
|
||||
it.chapter_number = chapterNumber.toFloat()
|
||||
it.scanlator = scanlator
|
||||
it.memo = memo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +24,7 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
|
||||
dateUpload = sChapter.date_upload,
|
||||
chapterNumber = sChapter.chapter_number.toDouble(),
|
||||
scanlator = sChapter.scanlator?.ifBlank { null }?.trim(),
|
||||
memo = sChapter.memo,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,4 +41,5 @@ fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
||||
it.date_upload = dateUpload
|
||||
it.chapter_number = chapterNumber.toFloat()
|
||||
it.source_order = sourceOrder.toInt()
|
||||
it.memo = memo
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ fun List<Chapter>.applyFilters(manga: Manga, downloadManager: DownloadManager):
|
||||
val downloaded = downloadManager.isChapterDownloaded(
|
||||
chapter.name,
|
||||
chapter.scanlator,
|
||||
chapter.url,
|
||||
manga.title,
|
||||
manga.source,
|
||||
)
|
||||
|
||||
@@ -12,16 +12,12 @@ class GetExtensionLanguages(
|
||||
) {
|
||||
fun subscribe(): Flow<List<String>> {
|
||||
return combine(
|
||||
preferences.enabledLanguages().changes(),
|
||||
preferences.enabledLanguages.changes(),
|
||||
extensionManager.availableExtensionsFlow,
|
||||
) { enabledLanguage, availableExtensions ->
|
||||
availableExtensions
|
||||
.flatMap { ext ->
|
||||
if (ext.sources.isEmpty()) {
|
||||
listOf(ext.lang)
|
||||
} else {
|
||||
ext.sources.map { it.lang }
|
||||
}
|
||||
ext.sources.map { it.lang }
|
||||
}
|
||||
.distinct()
|
||||
.sortedWith(
|
||||
|
||||
@@ -15,7 +15,7 @@ class GetExtensionSources(
|
||||
val isMultiLangSingleSource =
|
||||
isMultiSource && extension.sources.map { it.name }.distinct().size == 1
|
||||
|
||||
return preferences.disabledSources().changes().map { disabledSources ->
|
||||
return preferences.disabledSources.changes().map { disabledSources ->
|
||||
fun Source.isEnabled() = id.toString() !in disabledSources
|
||||
|
||||
extension.sources
|
||||
|
||||
@@ -13,10 +13,10 @@ class GetExtensionsByType(
|
||||
) {
|
||||
|
||||
fun subscribe(): Flow<Extensions> {
|
||||
val showNsfwSources = preferences.showNsfwSource().get()
|
||||
val showNsfwSources = preferences.showNsfwSource.get()
|
||||
|
||||
return combine(
|
||||
preferences.enabledLanguages().changes(),
|
||||
preferences.enabledLanguages.changes(),
|
||||
extensionManager.installedExtensionsFlow,
|
||||
extensionManager.untrustedExtensionsFlow,
|
||||
extensionManager.availableExtensionsFlow,
|
||||
@@ -39,9 +39,6 @@ class GetExtensionsByType(
|
||||
(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(
|
||||
|
||||
@@ -3,22 +3,22 @@ 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 mihon.domain.extension.repository.ExtensionStoreRepository
|
||||
import tachiyomi.core.common.preference.getAndSet
|
||||
|
||||
class TrustExtension(
|
||||
private val extensionRepoRepository: ExtensionRepoRepository,
|
||||
private val repository: ExtensionStoreRepository,
|
||||
private val preferences: SourcePreferences,
|
||||
) {
|
||||
|
||||
suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean {
|
||||
val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet()
|
||||
val trustedFingerprints = repository.getAll().map { it.signingKey }.toHashSet()
|
||||
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}"
|
||||
return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions().get()
|
||||
return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions.get()
|
||||
}
|
||||
|
||||
fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
|
||||
preferences.trustedExtensions().getAndSet { exts ->
|
||||
preferences.trustedExtensions.getAndSet { exts ->
|
||||
// Remove previously trusted versions
|
||||
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
|
||||
|
||||
@@ -27,6 +27,6 @@ class TrustExtension(
|
||||
}
|
||||
|
||||
fun revokeAll() {
|
||||
preferences.trustedExtensions().delete()
|
||||
preferences.trustedExtensions.delete()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import app.cash.sqldelight.async.coroutines.awaitAsList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import tachiyomi.data.DatabaseHandler
|
||||
import tachiyomi.data.Database
|
||||
import tachiyomi.data.subscribeToList
|
||||
|
||||
class GetExcludedScanlators(
|
||||
private val handler: DatabaseHandler,
|
||||
private val database: Database,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long): Set<String> {
|
||||
return handler.awaitList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||
}
|
||||
return database.excluded_scanlatorsQueries
|
||||
.getExcludedScanlatorsByMangaId(mangaId)
|
||||
.awaitAsList()
|
||||
.toSet()
|
||||
}
|
||||
|
||||
fun subscribe(mangaId: Long): Flow<Set<String>> {
|
||||
return handler.subscribeToList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||
}
|
||||
return database.excluded_scanlatorsQueries
|
||||
.getExcludedScanlatorsByMangaId(mangaId)
|
||||
.subscribeToList()
|
||||
.map { it.toSet() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import tachiyomi.data.DatabaseHandler
|
||||
import app.cash.sqldelight.async.coroutines.awaitAsList
|
||||
import tachiyomi.data.Database
|
||||
|
||||
class SetExcludedScanlators(
|
||||
private val handler: DatabaseHandler,
|
||||
private val database: Database,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long, excludedScanlators: Set<String>) {
|
||||
handler.await(inTransaction = true) {
|
||||
val currentExcluded = handler.awaitList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||
}.toSet()
|
||||
database.transaction {
|
||||
val currentExcluded = database.excluded_scanlatorsQueries
|
||||
.getExcludedScanlatorsByMangaId(mangaId)
|
||||
.awaitAsList()
|
||||
.toSet()
|
||||
val toAdd = excludedScanlators.minus(currentExcluded)
|
||||
for (scanlator in toAdd) {
|
||||
excluded_scanlatorsQueries.insert(mangaId, scanlator)
|
||||
database.excluded_scanlatorsQueries.insert(mangaId, scanlator)
|
||||
}
|
||||
val toRemove = currentExcluded.minus(excludedScanlators)
|
||||
excluded_scanlatorsQueries.remove(mangaId, toRemove)
|
||||
database.excluded_scanlatorsQueries.remove(mangaId, toRemove)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
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
|
||||
|
||||
@@ -26,56 +20,6 @@ class UpdateManga(
|
||||
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(),
|
||||
|
||||
@@ -22,7 +22,7 @@ val Manga.readerOrientation: Long
|
||||
|
||||
val Manga.downloadedFilter: TriState
|
||||
get() {
|
||||
if (Injekt.get<BasePreferences>().downloadedOnly().get()) return TriState.ENABLED_IS
|
||||
if (Injekt.get<BasePreferences>().downloadedOnly.get()) return TriState.ENABLED_IS
|
||||
return when (downloadedFilterRaw) {
|
||||
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
||||
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
||||
@@ -45,6 +45,7 @@ fun Manga.toSManga(): SManga = SManga.create().also {
|
||||
it.status = status.toInt()
|
||||
it.thumbnail_url = thumbnailUrl
|
||||
it.initialized = initialized
|
||||
it.memo = memo
|
||||
}
|
||||
|
||||
fun Manga.copyFrom(other: SManga): Manga {
|
||||
@@ -66,22 +67,7 @@ fun Manga.copyFrom(other: SManga): Manga {
|
||||
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,
|
||||
memo = other.memo,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ class GetEnabledSources(
|
||||
|
||||
fun subscribe(): Flow<List<Source>> {
|
||||
return combine(
|
||||
preferences.pinnedSources().changes(),
|
||||
preferences.enabledLanguages().changes(),
|
||||
preferences.disabledSources().changes(),
|
||||
preferences.lastUsedSource().changes(),
|
||||
preferences.pinnedSources.changes(),
|
||||
preferences.enabledLanguages.changes(),
|
||||
preferences.disabledSources.changes(),
|
||||
preferences.lastUsedSource.changes(),
|
||||
repository.getSources(),
|
||||
) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources ->
|
||||
sources
|
||||
|
||||
@@ -13,19 +13,19 @@ class GetIncognitoState(
|
||||
private val extensionManager: ExtensionManager,
|
||||
) {
|
||||
fun await(sourceId: Long?): Boolean {
|
||||
if (basePreferences.incognitoMode().get()) return true
|
||||
if (basePreferences.incognitoMode.get()) return true
|
||||
if (sourceId == null) return false
|
||||
val extensionPackage = extensionManager.getExtensionPackage(sourceId) ?: return false
|
||||
|
||||
return extensionPackage in sourcePreferences.incognitoExtensions().get()
|
||||
return extensionPackage in sourcePreferences.incognitoExtensions.get()
|
||||
}
|
||||
|
||||
fun subscribe(sourceId: Long?): Flow<Boolean> {
|
||||
if (sourceId == null) return basePreferences.incognitoMode().changes()
|
||||
if (sourceId == null) return basePreferences.incognitoMode.changes()
|
||||
|
||||
return combine(
|
||||
basePreferences.incognitoMode().changes(),
|
||||
sourcePreferences.incognitoExtensions().changes(),
|
||||
basePreferences.incognitoMode.changes(),
|
||||
sourcePreferences.incognitoExtensions.changes(),
|
||||
extensionManager.getExtensionPackageAsFlow(sourceId),
|
||||
) { incognito, incognitoExtensions, extensionPackage ->
|
||||
incognito || (extensionPackage in incognitoExtensions)
|
||||
|
||||
@@ -15,8 +15,8 @@ class GetLanguagesWithSources(
|
||||
|
||||
fun subscribe(): Flow<SortedMap<String, List<Source>>> {
|
||||
return combine(
|
||||
preferences.enabledLanguages().changes(),
|
||||
preferences.disabledSources().changes(),
|
||||
preferences.enabledLanguages.changes(),
|
||||
preferences.disabledSources.changes(),
|
||||
repository.getOnlineSources(),
|
||||
) { enabledLanguage, disabledSource, onlineSources ->
|
||||
val sortedSources = onlineSources.sortedWith(
|
||||
|
||||
@@ -16,8 +16,8 @@ class GetSourcesWithFavoriteCount(
|
||||
|
||||
fun subscribe(): Flow<List<Pair<Source, Long>>> {
|
||||
return combine(
|
||||
preferences.migrationSortingDirection().changes(),
|
||||
preferences.migrationSortingMode().changes(),
|
||||
preferences.migrationSortingDirection.changes(),
|
||||
preferences.migrationSortingMode.changes(),
|
||||
repository.getSourcesWithFavoriteCount(),
|
||||
) { direction, mode, list ->
|
||||
list
|
||||
|
||||
@@ -7,8 +7,8 @@ class SetMigrateSorting(
|
||||
) {
|
||||
|
||||
fun await(mode: Mode, direction: Direction) {
|
||||
preferences.migrationSortingMode().set(mode)
|
||||
preferences.migrationSortingDirection().set(direction)
|
||||
preferences.migrationSortingMode.set(mode)
|
||||
preferences.migrationSortingDirection.set(direction)
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
|
||||
@@ -7,7 +7,7 @@ class ToggleIncognito(
|
||||
private val preferences: SourcePreferences,
|
||||
) {
|
||||
fun await(extensions: String, enable: Boolean) {
|
||||
preferences.incognitoExtensions().getAndSet {
|
||||
preferences.incognitoExtensions.getAndSet {
|
||||
if (enable) it.plus(extensions) else it.minus(extensions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ class ToggleLanguage(
|
||||
) {
|
||||
|
||||
fun await(language: String) {
|
||||
val isEnabled = language in preferences.enabledLanguages().get()
|
||||
preferences.enabledLanguages().getAndSet { enabled ->
|
||||
val isEnabled = language in preferences.enabledLanguages.get()
|
||||
preferences.enabledLanguages.getAndSet { enabled ->
|
||||
if (isEnabled) enabled.minus(language) else enabled.plus(language)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,19 +13,19 @@ class ToggleSource(
|
||||
}
|
||||
|
||||
fun await(sourceId: Long, enable: Boolean = isEnabled(sourceId)) {
|
||||
preferences.disabledSources().getAndSet { disabled ->
|
||||
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 ->
|
||||
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()
|
||||
return sourceId.toString() in preferences.disabledSources.get()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ class ToggleSourcePin(
|
||||
) {
|
||||
|
||||
fun await(source: Source) {
|
||||
val isPinned = source.id.toString() in preferences.pinnedSources().get()
|
||||
preferences.pinnedSources().getAndSet { pinned ->
|
||||
val isPinned = source.id.toString() in preferences.pinnedSources.get()
|
||||
preferences.pinnedSources.getAndSet { pinned ->
|
||||
if (isPinned) pinned.minus("${source.id}") else pinned.plus("${source.id}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,57 +2,88 @@ package eu.kanade.domain.source.service
|
||||
|
||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import mihon.domain.migration.models.MigrationFlag
|
||||
import tachiyomi.core.common.preference.Preference
|
||||
import tachiyomi.core.common.preference.PreferenceStore
|
||||
import tachiyomi.core.common.preference.getEnum
|
||||
import tachiyomi.core.common.preference.getLongArray
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
|
||||
class SourcePreferences(
|
||||
private val preferenceStore: PreferenceStore,
|
||||
preferenceStore: PreferenceStore,
|
||||
) {
|
||||
|
||||
fun sourceDisplayMode() = preferenceStore.getObject(
|
||||
val sourceDisplayMode: Preference<LibraryDisplayMode> = preferenceStore.getObjectFromString(
|
||||
"pref_display_mode_catalogue",
|
||||
LibraryDisplayMode.default,
|
||||
LibraryDisplayMode.Serializer::serialize,
|
||||
LibraryDisplayMode.Serializer::deserialize,
|
||||
)
|
||||
|
||||
fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
|
||||
val enabledLanguages: Preference<Set<String>> = preferenceStore.getStringSet(
|
||||
"source_languages",
|
||||
LocaleHelper.getDefaultEnabledLanguages(),
|
||||
)
|
||||
|
||||
fun disabledSources() = preferenceStore.getStringSet("hidden_catalogues", emptySet())
|
||||
val disabledSources: Preference<Set<String>> = preferenceStore.getStringSet("hidden_catalogues", emptySet())
|
||||
|
||||
fun incognitoExtensions() = preferenceStore.getStringSet("incognito_extensions", emptySet())
|
||||
val incognitoExtensions: Preference<Set<String>> = preferenceStore.getStringSet("incognito_extensions", emptySet())
|
||||
|
||||
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
||||
val pinnedSources: Preference<Set<String>> = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
||||
|
||||
fun lastUsedSource() = preferenceStore.getLong(
|
||||
val lastUsedSource: Preference<Long> = preferenceStore.getLong(
|
||||
Preference.appStateKey("last_catalogue_source"),
|
||||
-1,
|
||||
)
|
||||
|
||||
fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true)
|
||||
val showNsfwSource: Preference<Boolean> = preferenceStore.getBoolean("show_nsfw_source", true)
|
||||
|
||||
fun migrationSortingMode() = preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL)
|
||||
val migrationSortingMode: Preference<SetMigrateSorting.Mode> = preferenceStore.getEnum(
|
||||
"pref_migration_sorting",
|
||||
SetMigrateSorting.Mode.ALPHABETICAL,
|
||||
)
|
||||
|
||||
fun migrationSortingDirection() = preferenceStore.getEnum(
|
||||
val migrationSortingDirection: Preference<SetMigrateSorting.Direction> = preferenceStore.getEnum(
|
||||
"pref_migration_direction",
|
||||
SetMigrateSorting.Direction.ASCENDING,
|
||||
)
|
||||
|
||||
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
||||
val hideInLibraryItems: Preference<Boolean> = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
||||
|
||||
fun extensionRepos() = preferenceStore.getStringSet("extension_repos", emptySet())
|
||||
val extensionRepos: Preference<Set<String>> = preferenceStore.getStringSet("extension_repos", emptySet())
|
||||
|
||||
fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
||||
val extensionUpdatesCount: Preference<Int> = preferenceStore.getInt("ext_updates_count", 0)
|
||||
|
||||
fun trustedExtensions() = preferenceStore.getStringSet(
|
||||
val trustedExtensions: Preference<Set<String>> = preferenceStore.getStringSet(
|
||||
Preference.appStateKey("trusted_extensions"),
|
||||
emptySet(),
|
||||
)
|
||||
|
||||
fun globalSearchFilterState() = preferenceStore.getBoolean(
|
||||
val globalSearchFilterState: Preference<Boolean> = preferenceStore.getBoolean(
|
||||
Preference.appStateKey("has_filters_toggle_state"),
|
||||
false,
|
||||
)
|
||||
|
||||
val migrationSources: Preference<List<Long>> = preferenceStore.getLongArray("migration_sources", emptyList())
|
||||
|
||||
val migrationFlags: Preference<Set<MigrationFlag>> = preferenceStore.getObjectFromInt(
|
||||
key = "migration_flags",
|
||||
defaultValue = MigrationFlag.entries.toSet(),
|
||||
serializer = { MigrationFlag.toBit(it) },
|
||||
deserializer = { value: Int -> MigrationFlag.fromBit(value) },
|
||||
)
|
||||
|
||||
val migrationDeepSearchMode: Preference<Boolean> = preferenceStore.getBoolean("migration_deep_search", false)
|
||||
|
||||
val migrationPrioritizeByChapters: Preference<Boolean> = preferenceStore.getBoolean(
|
||||
"migration_prioritize_by_chapters",
|
||||
false,
|
||||
)
|
||||
|
||||
val migrationHideUnmatched: Preference<Boolean> = preferenceStore.getBoolean("migration_hide_unmatched", false)
|
||||
|
||||
val migrationHideWithoutUpdates: Preference<Boolean> = preferenceStore.getBoolean(
|
||||
"migration_hide_without_updates",
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -34,11 +34,11 @@ class TrackPreferences(
|
||||
|
||||
fun trackToken(tracker: Tracker) = preferenceStore.getString(Preference.privateKey("track_token_${tracker.id}"), "")
|
||||
|
||||
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
||||
val anilistScoreType: Preference<String> = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
||||
|
||||
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
||||
val autoUpdateTrack: Preference<Boolean> = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
||||
|
||||
fun autoUpdateTrackOnMarkRead() = preferenceStore.getEnum(
|
||||
val autoUpdateTrackOnMarkRead: Preference<AutoTrackState> = preferenceStore.getEnum(
|
||||
"pref_auto_update_manga_on_mark_read",
|
||||
AutoTrackState.ALWAYS,
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ 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.Preference
|
||||
import tachiyomi.core.common.preference.PreferenceStore
|
||||
import tachiyomi.core.common.preference.getEnum
|
||||
import java.time.format.DateTimeFormatter
|
||||
@@ -12,12 +13,12 @@ import java.time.format.FormatStyle
|
||||
import java.util.Locale
|
||||
|
||||
class UiPreferences(
|
||||
private val preferenceStore: PreferenceStore,
|
||||
preferenceStore: PreferenceStore,
|
||||
) {
|
||||
|
||||
fun themeMode() = preferenceStore.getEnum("pref_theme_mode_key", ThemeMode.SYSTEM)
|
||||
val themeMode: Preference<ThemeMode> = preferenceStore.getEnum("pref_theme_mode_key", ThemeMode.SYSTEM)
|
||||
|
||||
fun appTheme() = preferenceStore.getEnum(
|
||||
val appTheme: Preference<AppTheme> = preferenceStore.getEnum(
|
||||
"pref_app_theme",
|
||||
if (DeviceUtil.isDynamicColorAvailable) {
|
||||
AppTheme.MONET
|
||||
@@ -26,13 +27,15 @@ class UiPreferences(
|
||||
},
|
||||
)
|
||||
|
||||
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
|
||||
val themeDarkAmoled: Preference<Boolean> = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
|
||||
|
||||
fun relativeTime() = preferenceStore.getBoolean("relative_time_v2", true)
|
||||
val relativeTime: Preference<Boolean> = preferenceStore.getBoolean("relative_time_v2", true)
|
||||
|
||||
fun dateFormat() = preferenceStore.getString("app_date_format", "")
|
||||
val dateFormat: Preference<String> = preferenceStore.getString("app_date_format", "")
|
||||
|
||||
fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
|
||||
val tabletUiMode: Preference<TabletUiMode> = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
|
||||
|
||||
val imagesInDescription: Preference<Boolean> = preferenceStore.getBoolean("pref_render_images_description", true)
|
||||
|
||||
companion object {
|
||||
fun dateFormat(format: String): DateTimeFormatter = when (format) {
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package eu.kanade.domain.ui.model
|
||||
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import tachiyomi.i18n.MR
|
||||
|
||||
enum class AppTheme(val titleRes: StringResource?) {
|
||||
DEFAULT(MR.strings.label_default),
|
||||
MONET(MR.strings.theme_monet),
|
||||
CATPPUCCIN(MR.strings.theme_catppuccin),
|
||||
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.takeUnless { isReleaseBuildType }),
|
||||
NORD(MR.strings.theme_nord),
|
||||
STRAWBERRY_DAIQUIRI(MR.strings.theme_strawberrydaiquiri),
|
||||
TAKO(MR.strings.theme_tako),
|
||||
TEALTURQUOISE(MR.strings.theme_tealturquoise),
|
||||
|
||||
@@ -22,7 +22,6 @@ 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
|
||||
@@ -73,12 +72,20 @@ fun BrowseSourceContent(
|
||||
}
|
||||
}
|
||||
|
||||
if (mangaList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) {
|
||||
if (mangaList.itemCount == 0 && mangaList.loadState.refresh is LoadState.Loading) {
|
||||
LoadingScreen(Modifier.padding(contentPadding))
|
||||
return
|
||||
}
|
||||
|
||||
if (mangaList.itemCount == 0) {
|
||||
EmptyScreen(
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
message = getErrorMessage(errorState),
|
||||
message = when (errorState) {
|
||||
is LoadState.Error -> getErrorMessage(errorState)
|
||||
else -> stringResource(MR.strings.no_results_found)
|
||||
},
|
||||
actions = if (source is LocalSource) {
|
||||
persistentListOf(
|
||||
listOf(
|
||||
EmptyScreenAction(
|
||||
stringRes = MR.strings.local_source_help_guide,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
@@ -86,7 +93,7 @@ fun BrowseSourceContent(
|
||||
),
|
||||
)
|
||||
} else {
|
||||
persistentListOf(
|
||||
listOf(
|
||||
EmptyScreenAction(
|
||||
stringRes = MR.strings.action_retry,
|
||||
icon = Icons.Outlined.Refresh,
|
||||
@@ -109,13 +116,6 @@ fun BrowseSourceContent(
|
||||
return
|
||||
}
|
||||
|
||||
if (mangaList.itemCount == 0 && mangaList.loadState.refresh is LoadState.Loading) {
|
||||
LoadingScreen(
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
when (displayMode) {
|
||||
LibraryDisplayMode.ComfortableGrid -> {
|
||||
BrowseSourceComfortableGrid(
|
||||
|
||||
@@ -56,8 +56,6 @@ 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
|
||||
@@ -80,12 +78,12 @@ fun ExtensionDetailsScreen(
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val url = remember(state.extension) {
|
||||
val regex = """https://raw.githubusercontent.com/(.+?)/(.+?)/.+""".toRegex()
|
||||
regex.find(state.extension?.repoUrl.orEmpty())
|
||||
regex.find(state.extension?.store?.indexUrl.orEmpty())
|
||||
?.let {
|
||||
val (user, repo) = it.destructured
|
||||
"https://github.com/$user/$repo"
|
||||
}
|
||||
?: state.extension?.repoUrl
|
||||
?: state.extension?.store?.indexUrl
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
@@ -95,37 +93,35 @@ fun ExtensionDetailsScreen(
|
||||
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,
|
||||
),
|
||||
actions = buildList {
|
||||
if (url != null) {
|
||||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_open_repo),
|
||||
icon = Icons.AutoMirrored.Outlined.Launch,
|
||||
onClick = {
|
||||
uriHandler.openUri(url)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
.build(),
|
||||
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,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
@@ -157,7 +153,7 @@ fun ExtensionDetailsScreen(
|
||||
private fun ExtensionDetails(
|
||||
contentPadding: PaddingValues,
|
||||
extension: Extension.Installed,
|
||||
sources: ImmutableList<ExtensionSourceItem>,
|
||||
sources: List<ExtensionSourceItem>,
|
||||
incognitoMode: Boolean,
|
||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||
onClickUninstall: () -> Unit,
|
||||
@@ -248,14 +244,17 @@ private fun DetailsHeader(
|
||||
|
||||
if (extension is Extension.Installed) {
|
||||
append("\n\n")
|
||||
append(
|
||||
appendLine(
|
||||
"""
|
||||
Update available: ${extension.hasUpdate}
|
||||
Obsolete: ${extension.isObsolete}
|
||||
Orphaned: ${extension.isObsolete}
|
||||
Shared: ${extension.isShared}
|
||||
Repository: ${extension.repoUrl}
|
||||
""".trimIndent(),
|
||||
)
|
||||
val store = extension.store
|
||||
if (store != null) {
|
||||
append("Repository: ${store.indexUrl}")
|
||||
}
|
||||
}
|
||||
}
|
||||
context.copyToClipboard("Extension Debug information", extDebugInfo)
|
||||
|
||||
@@ -47,7 +47,7 @@ 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.more.settings.screen.browse.ExtensionStoresScreen
|
||||
import eu.kanade.presentation.util.animateItemFastScroll
|
||||
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
@@ -56,7 +56,6 @@ 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
|
||||
@@ -104,11 +103,11 @@ fun ExtensionScreen(
|
||||
EmptyScreen(
|
||||
stringRes = msg,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
actions = persistentListOf(
|
||||
actions = listOf(
|
||||
EmptyScreenAction(
|
||||
stringRes = MR.strings.label_extension_repos,
|
||||
stringRes = MR.strings.extensionStores,
|
||||
icon = Icons.Outlined.Settings,
|
||||
onClick = { navigator.push(ExtensionReposScreen()) },
|
||||
onClick = { navigator.push(ExtensionStoresScreen()) },
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -353,13 +352,17 @@ private fun ExtensionItemContent(
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||
) {
|
||||
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||
var hasAlreadyShownAnElement by remember { mutableStateOf(false) }
|
||||
if (extension is Extension.Installed && extension.lang.isNotEmpty()) {
|
||||
hasAlreadyShownAnElement = true
|
||||
Text(
|
||||
text = LocaleHelper.getSourceDisplayName(extension.lang, LocalContext.current),
|
||||
)
|
||||
}
|
||||
|
||||
if (extension.versionName.isNotEmpty()) {
|
||||
if (hasAlreadyShownAnElement) DotSeparatorNoSpaceText()
|
||||
hasAlreadyShownAnElement = true
|
||||
Text(
|
||||
text = extension.versionName,
|
||||
)
|
||||
@@ -372,6 +375,8 @@ private fun ExtensionItemContent(
|
||||
else -> null
|
||||
}
|
||||
if (warning != null) {
|
||||
if (hasAlreadyShownAnElement) DotSeparatorNoSpaceText()
|
||||
hasAlreadyShownAnElement = true
|
||||
Text(
|
||||
text = stringResource(warning).uppercase(),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
@@ -379,6 +384,12 @@ private fun ExtensionItemContent(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
if (extension is Extension.Installed && !extension.isShared) {
|
||||
if (hasAlreadyShownAnElement) DotSeparatorNoSpaceText()
|
||||
Text(
|
||||
text = stringResource(MR.strings.ext_installer_private),
|
||||
)
|
||||
}
|
||||
|
||||
if (!installStep.isCompleted()) {
|
||||
DotSeparatorNoSpaceText()
|
||||
|
||||
@@ -10,7 +10,7 @@ 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.source.Source
|
||||
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
|
||||
@@ -27,7 +27,7 @@ fun GlobalSearchScreen(
|
||||
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||
onToggleResults: () -> Unit,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onLongClickItem: (Manga) -> Unit,
|
||||
) {
|
||||
@@ -40,6 +40,7 @@ fun GlobalSearchScreen(
|
||||
navigateUp = navigateUp,
|
||||
onChangeSearchQuery = onChangeSearchQuery,
|
||||
onSearch = onSearch,
|
||||
hideSourceFilter = false,
|
||||
sourceFilter = state.sourceFilter,
|
||||
onChangeSearchFilter = onChangeSearchFilter,
|
||||
onlyShowHasResults = state.onlyShowHasResults,
|
||||
@@ -61,10 +62,10 @@ fun GlobalSearchScreen(
|
||||
|
||||
@Composable
|
||||
internal fun GlobalSearchContent(
|
||||
items: Map<CatalogueSource, SearchItemResult>,
|
||||
items: Map<Source, SearchItemResult>,
|
||||
contentPadding: PaddingValues,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onLongClickItem: (Manga) -> Unit,
|
||||
fromSourceId: Long? = null,
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
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) },
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ 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.source.Source
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
@@ -19,7 +19,7 @@ fun MigrateSearchScreen(
|
||||
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||
onToggleResults: () -> Unit,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onLongClickItem: (Manga) -> Unit,
|
||||
) {
|
||||
@@ -32,6 +32,7 @@ fun MigrateSearchScreen(
|
||||
navigateUp = navigateUp,
|
||||
onChangeSearchQuery = onChangeSearchQuery,
|
||||
onSearch = onSearch,
|
||||
hideSourceFilter = true,
|
||||
sourceFilter = state.sourceFilter,
|
||||
onChangeSearchFilter = onChangeSearchFilter,
|
||||
onlyShowHasResults = state.onlyShowHasResults,
|
||||
|
||||
@@ -26,7 +26,6 @@ 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
|
||||
@@ -76,7 +75,7 @@ fun MigrateSourceScreen(
|
||||
|
||||
@Composable
|
||||
private fun MigrateSourceList(
|
||||
list: ImmutableList<Pair<Source, Long>>,
|
||||
list: List<Pair<Source, Long>>,
|
||||
contentPadding: PaddingValues,
|
||||
onClickItem: (Source) -> Unit,
|
||||
onLongClickItem: (Source) -> Unit,
|
||||
|
||||
@@ -18,7 +18,6 @@ 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
|
||||
@@ -54,44 +53,42 @@ fun BrowseSourceToolbar(
|
||||
onClickCloseSearch = navigateUp,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||
.apply {
|
||||
actions = buildList {
|
||||
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.Action(
|
||||
title = stringResource(MR.strings.action_display_mode),
|
||||
icon = if (displayMode == LibraryDisplayMode.List) {
|
||||
Icons.AutoMirrored.Filled.ViewList
|
||||
} else {
|
||||
Icons.Filled.ViewModule
|
||||
},
|
||||
onClick = { selectingDisplayMode = true },
|
||||
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 (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(),
|
||||
if (isConfigurableSource) {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.action_settings),
|
||||
onClick = onSettingsClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
DropdownMenu(
|
||||
|
||||
@@ -40,6 +40,7 @@ fun GlobalSearchToolbar(
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
hideSourceFilter: Boolean,
|
||||
sourceFilter: SourceFilter,
|
||||
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||
onlyShowHasResults: Boolean,
|
||||
@@ -73,38 +74,40 @@ fun GlobalSearchToolbar(
|
||||
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))
|
||||
},
|
||||
)
|
||||
if (!hideSourceFilter) {
|
||||
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()
|
||||
VerticalDivider()
|
||||
}
|
||||
|
||||
FilterChip(
|
||||
selected = onlyShowHasResults,
|
||||
|
||||
@@ -27,8 +27,6 @@ import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import eu.kanade.core.preference.asToggleableState
|
||||
import eu.kanade.presentation.category.visualName
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
import tachiyomi.core.common.preference.CheckboxState
|
||||
import tachiyomi.domain.category.model.Category
|
||||
@@ -41,7 +39,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||
fun CategoryCreateDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onCreate: (String) -> Unit,
|
||||
categories: ImmutableList<String>,
|
||||
categories: List<String>,
|
||||
) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
|
||||
@@ -103,7 +101,7 @@ fun CategoryCreateDialog(
|
||||
fun CategoryRenameDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onRename: (String) -> Unit,
|
||||
categories: ImmutableList<String>,
|
||||
categories: List<String>,
|
||||
category: String,
|
||||
) {
|
||||
var name by remember { mutableStateOf(category) }
|
||||
@@ -195,7 +193,7 @@ fun CategoryDeleteDialog(
|
||||
|
||||
@Composable
|
||||
fun ChangeCategoryDialog(
|
||||
initialSelection: ImmutableList<CheckboxState<Category>>,
|
||||
initialSelection: List<CheckboxState<Category>>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onEditCategories: () -> Unit,
|
||||
onConfirm: (List<Long>, List<Long>) -> Unit,
|
||||
@@ -267,7 +265,7 @@ fun ChangeCategoryDialog(
|
||||
if (index != -1) {
|
||||
val mutableList = selection.toMutableList()
|
||||
mutableList[index] = it.next()
|
||||
selection = mutableList.toList().toImmutableList()
|
||||
selection = mutableList.toList()
|
||||
}
|
||||
}
|
||||
Row(
|
||||
|
||||
+2
-2
@@ -4,11 +4,11 @@ import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Add
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.SmallExtendedFloatingActionButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||
|
||||
@@ -18,7 +18,7 @@ fun CategoryFloatingActionButton(
|
||||
onCreate: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ExtendedFloatingActionButton(
|
||||
SmallExtendedFloatingActionButton(
|
||||
text = { Text(text = stringResource(MR.strings.action_add)) },
|
||||
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
|
||||
onClick = onCreate,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.kanade.presentation.components
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
@@ -28,8 +27,8 @@ fun NavigatorAdaptiveSheet(
|
||||
screen = screen,
|
||||
content = { sheetNavigator ->
|
||||
AdaptiveSheet(
|
||||
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
|
||||
onDismissRequest = onDismissRequest,
|
||||
enableImplicitDismiss = enableSwipeDismiss(sheetNavigator),
|
||||
) {
|
||||
ScreenTransition(
|
||||
navigator = sheetNavigator,
|
||||
@@ -38,11 +37,6 @@ fun NavigatorAdaptiveSheet(
|
||||
fadeOut(animationSpec = tween(90))
|
||||
},
|
||||
)
|
||||
|
||||
BackHandler(
|
||||
enabled = sheetNavigator.size > 1,
|
||||
onBack = sheetNavigator::pop,
|
||||
)
|
||||
}
|
||||
|
||||
// Make sure screens are disposed no matter what
|
||||
@@ -69,7 +63,7 @@ fun NavigatorAdaptiveSheet(
|
||||
fun AdaptiveSheet(
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enableSwipeDismiss: Boolean = true,
|
||||
enableImplicitDismiss: Boolean = true,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val isTabletUi = isTabletUi()
|
||||
@@ -79,10 +73,10 @@ fun AdaptiveSheet(
|
||||
properties = dialogProperties,
|
||||
) {
|
||||
AdaptiveSheetImpl(
|
||||
modifier = modifier,
|
||||
isTabletUi = isTabletUi,
|
||||
enableSwipeDismiss = enableSwipeDismiss,
|
||||
enableImplicitDismiss = enableImplicitDismiss,
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.TooltipAnchorPosition
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.TooltipDefaults.rememberTooltipPositionProvider
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
@@ -36,6 +37,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -49,7 +51,6 @@ import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.clearFocusOnSoftKeyboardHide
|
||||
@@ -57,8 +58,6 @@ import tachiyomi.presentation.core.util.runOnEnterKeyPressed
|
||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
import tachiyomi.presentation.core.util.showSoftKeyboard
|
||||
|
||||
const val SEARCH_DEBOUNCE_MILLIS = 250L
|
||||
|
||||
@Composable
|
||||
fun AppBar(
|
||||
title: String?,
|
||||
@@ -188,19 +187,20 @@ fun AppBarTitle(
|
||||
|
||||
@Composable
|
||||
fun AppBarActions(
|
||||
actions: ImmutableList<AppBar.AppBarAction>,
|
||||
actions: List<AppBar.AppBarAction>,
|
||||
) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
|
||||
actions.filterIsInstance<AppBar.Action>().map {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(it.title)
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState(),
|
||||
focusable = false,
|
||||
) {
|
||||
IconButton(
|
||||
onClick = it.onClick,
|
||||
@@ -218,13 +218,14 @@ fun AppBarActions(
|
||||
val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>()
|
||||
if (overflowActions.isNotEmpty()) {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(stringResource(MR.strings.action_menu_overflow_description))
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState(),
|
||||
focusable = false,
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { showMenu = !showMenu },
|
||||
@@ -289,6 +290,7 @@ fun SearchToolbar(
|
||||
onSearch(searchQuery)
|
||||
focusManager.clearFocus()
|
||||
keyboardController?.hide()
|
||||
focusManager.moveFocus(FocusDirection.Next)
|
||||
}
|
||||
|
||||
BasicTextField(
|
||||
@@ -345,13 +347,14 @@ fun SearchToolbar(
|
||||
// Don't show search action
|
||||
} else if (searchQuery == null) {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(stringResource(MR.strings.action_search))
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState(),
|
||||
focusable = false,
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
@@ -364,13 +367,14 @@ fun SearchToolbar(
|
||||
}
|
||||
} else if (searchQuery.isNotEmpty()) {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||
tooltip = {
|
||||
PlainTooltip {
|
||||
Text(stringResource(MR.strings.action_reset))
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState(),
|
||||
focusable = false,
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
|
||||
@@ -22,7 +22,7 @@ fun relativeDateText(
|
||||
Instant.ofEpochMilli(dateEpochMillis),
|
||||
ZoneId.systemDefault(),
|
||||
)
|
||||
.takeIf { dateEpochMillis > 0L },
|
||||
.takeIf { dateEpochMillis != 0L },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ fun relativeDateText(
|
||||
val context = LocalContext.current
|
||||
|
||||
val preferences = remember { Injekt.get<UiPreferences>() }
|
||||
val relativeTime = remember { preferences.relativeTime().get() }
|
||||
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
|
||||
val relativeTime = remember { preferences.relativeTime.get() }
|
||||
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat.get()) }
|
||||
|
||||
return localDate?.toRelativeString(
|
||||
context = context,
|
||||
|
||||
@@ -4,40 +4,69 @@ import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import eu.kanade.presentation.manga.DownloadAction
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun DownloadDropdownMenu(
|
||||
modifier: Modifier = Modifier,
|
||||
expanded: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
onDownloadClicked: (DownloadAction) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
offset: DpOffset? = null,
|
||||
) {
|
||||
val options = persistentListOf(
|
||||
if (offset != null) {
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier,
|
||||
offset = offset,
|
||||
content = {
|
||||
DownloadDropdownMenuItems(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onDownloadClicked = onDownloadClicked,
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier,
|
||||
content = {
|
||||
DownloadDropdownMenuItems(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onDownloadClicked = onDownloadClicked,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DownloadDropdownMenuItems(
|
||||
onDismissRequest: () -> Unit,
|
||||
onDownloadClicked: (DownloadAction) -> Unit,
|
||||
) {
|
||||
val options = listOf(
|
||||
DownloadAction.NEXT_1_CHAPTER to pluralStringResource(MR.plurals.download_amount, 1, 1),
|
||||
DownloadAction.NEXT_5_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 5, 5),
|
||||
DownloadAction.NEXT_10_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 10, 10),
|
||||
DownloadAction.NEXT_25_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 25, 25),
|
||||
DownloadAction.UNREAD_CHAPTERS to stringResource(MR.strings.download_unread),
|
||||
DownloadAction.BOOKMARKED_CHAPTERS to stringResource(MR.strings.download_bookmarked),
|
||||
)
|
||||
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier,
|
||||
) {
|
||||
options.map { (downloadAction, string) ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = string) },
|
||||
onClick = {
|
||||
onDownloadClicked(downloadAction)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
}
|
||||
options.map { (downloadAction, string) ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = string) },
|
||||
onClick = {
|
||||
onDownloadClicked(downloadAction)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
||||
@@ -31,7 +30,7 @@ private fun WithActionPreview() {
|
||||
Surface {
|
||||
EmptyScreen(
|
||||
stringRes = MR.strings.empty_screen,
|
||||
actions = persistentListOf(
|
||||
actions = listOf(
|
||||
EmptyScreenAction(
|
||||
stringRes = MR.strings.action_retry,
|
||||
icon = Icons.Outlined.Refresh,
|
||||
|
||||
@@ -27,7 +27,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.TabText
|
||||
@@ -41,7 +40,7 @@ object TabbedDialogPaddings {
|
||||
@Composable
|
||||
fun TabbedDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
tabTitles: ImmutableList<String>,
|
||||
tabTitles: List<String>,
|
||||
modifier: Modifier = Modifier,
|
||||
tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null,
|
||||
pagerState: PagerState = rememberPagerState { tabTitles.size },
|
||||
|
||||
@@ -22,8 +22,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.zIndex
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.TabText
|
||||
@@ -32,7 +30,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||
@Composable
|
||||
fun TabbedScreen(
|
||||
titleRes: StringResource,
|
||||
tabs: ImmutableList<TabContent>,
|
||||
tabs: List<TabContent>,
|
||||
state: PagerState = rememberPagerState { tabs.size },
|
||||
searchQuery: String? = null,
|
||||
onChangeSearchQuery: (String?) -> Unit = {},
|
||||
@@ -94,6 +92,6 @@ data class TabContent(
|
||||
val titleRes: StringResource,
|
||||
val badgeNumber: Int? = null,
|
||||
val searchEnabled: Boolean = false,
|
||||
val actions: ImmutableList<AppBar.AppBarAction> = persistentListOf(),
|
||||
val actions: List<AppBar.AppBarAction> = listOf(),
|
||||
val content: @Composable (contentPadding: PaddingValues, snackbarHostState: SnackbarHostState) -> Unit,
|
||||
)
|
||||
|
||||
@@ -20,7 +20,6 @@ import eu.kanade.presentation.history.components.HistoryItem
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import eu.kanade.presentation.util.animateItemFastScroll
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
@@ -49,7 +48,7 @@ fun HistoryScreen(
|
||||
onChangeSearchQuery = onSearchQueryChange,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
persistentListOf(
|
||||
listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.pref_clear_history),
|
||||
icon = Icons.Outlined.DeleteSweep,
|
||||
|
||||
@@ -21,7 +21,6 @@ import eu.kanade.presentation.components.TabbedDialog
|
||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||
import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.core.common.preference.TriState
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
@@ -47,7 +46,7 @@ fun LibrarySettingsDialog(
|
||||
) {
|
||||
TabbedDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
tabTitles = persistentListOf(
|
||||
tabTitles = listOf(
|
||||
stringResource(MR.strings.action_filter),
|
||||
stringResource(MR.strings.action_sort),
|
||||
stringResource(MR.strings.action_display),
|
||||
@@ -78,9 +77,9 @@ fun LibrarySettingsDialog(
|
||||
private fun ColumnScope.FilterPage(
|
||||
screenModel: LibrarySettingsScreenModel,
|
||||
) {
|
||||
val filterDownloaded by screenModel.libraryPreferences.filterDownloaded().collectAsState()
|
||||
val downloadedOnly by screenModel.preferences.downloadedOnly().collectAsState()
|
||||
val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions().collectAsState()
|
||||
val filterDownloaded by screenModel.libraryPreferences.filterDownloaded.collectAsState()
|
||||
val downloadedOnly by screenModel.preferences.downloadedOnly.collectAsState()
|
||||
val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions.collectAsState()
|
||||
|
||||
TriStateItem(
|
||||
label = stringResource(MR.strings.label_downloaded),
|
||||
@@ -92,25 +91,25 @@ private fun ColumnScope.FilterPage(
|
||||
enabled = !downloadedOnly,
|
||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterDownloaded) },
|
||||
)
|
||||
val filterUnread by screenModel.libraryPreferences.filterUnread().collectAsState()
|
||||
val filterUnread by screenModel.libraryPreferences.filterUnread.collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(MR.strings.action_filter_unread),
|
||||
state = filterUnread,
|
||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterUnread) },
|
||||
)
|
||||
val filterStarted by screenModel.libraryPreferences.filterStarted().collectAsState()
|
||||
val filterStarted by screenModel.libraryPreferences.filterStarted.collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(MR.strings.label_started),
|
||||
state = filterStarted,
|
||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterStarted) },
|
||||
)
|
||||
val filterBookmarked by screenModel.libraryPreferences.filterBookmarked().collectAsState()
|
||||
val filterBookmarked by screenModel.libraryPreferences.filterBookmarked.collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(MR.strings.action_filter_bookmarked),
|
||||
state = filterBookmarked,
|
||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterBookmarked) },
|
||||
)
|
||||
val filterCompleted by screenModel.libraryPreferences.filterCompleted().collectAsState()
|
||||
val filterCompleted by screenModel.libraryPreferences.filterCompleted.collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(MR.strings.completed),
|
||||
state = filterCompleted,
|
||||
@@ -118,7 +117,7 @@ private fun ColumnScope.FilterPage(
|
||||
)
|
||||
// TODO: re-enable when custom intervals are ready for stable
|
||||
if ((!isReleaseBuildType) && LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in autoUpdateMangaRestrictions) {
|
||||
val filterIntervalCustom by screenModel.libraryPreferences.filterIntervalCustom().collectAsState()
|
||||
val filterIntervalCustom by screenModel.libraryPreferences.filterIntervalCustom.collectAsState()
|
||||
TriStateItem(
|
||||
label = stringResource(MR.strings.action_filter_interval_custom),
|
||||
state = filterIntervalCustom,
|
||||
@@ -229,7 +228,7 @@ private val displayModes = listOf(
|
||||
private fun ColumnScope.DisplayPage(
|
||||
screenModel: LibrarySettingsScreenModel,
|
||||
) {
|
||||
val displayMode by screenModel.libraryPreferences.displayMode().collectAsState()
|
||||
val displayMode by screenModel.libraryPreferences.displayMode.collectAsState()
|
||||
SettingsChipRow(MR.strings.action_display_mode) {
|
||||
displayModes.map { (titleRes, mode) ->
|
||||
FilterChip(
|
||||
@@ -244,9 +243,9 @@ private fun ColumnScope.DisplayPage(
|
||||
val configuration = LocalConfiguration.current
|
||||
val columnPreference = remember {
|
||||
if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
screenModel.libraryPreferences.landscapeColumns()
|
||||
screenModel.libraryPreferences.landscapeColumns
|
||||
} else {
|
||||
screenModel.libraryPreferences.portraitColumns()
|
||||
screenModel.libraryPreferences.portraitColumns
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +254,7 @@ private fun ColumnScope.DisplayPage(
|
||||
value = columns,
|
||||
valueRange = 0..10,
|
||||
label = stringResource(MR.strings.pref_library_columns),
|
||||
valueText = if (columns > 0) {
|
||||
valueString = if (columns > 0) {
|
||||
columns.toString()
|
||||
} else {
|
||||
stringResource(MR.strings.label_auto)
|
||||
@@ -268,32 +267,32 @@ private fun ColumnScope.DisplayPage(
|
||||
HeadingItem(MR.strings.overlay_header)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_download_badge),
|
||||
pref = screenModel.libraryPreferences.downloadBadge(),
|
||||
pref = screenModel.libraryPreferences.downloadBadge,
|
||||
)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_unread_badge),
|
||||
pref = screenModel.libraryPreferences.unreadBadge(),
|
||||
pref = screenModel.libraryPreferences.unreadBadge,
|
||||
)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_local_badge),
|
||||
pref = screenModel.libraryPreferences.localBadge(),
|
||||
pref = screenModel.libraryPreferences.localBadge,
|
||||
)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_language_badge),
|
||||
pref = screenModel.libraryPreferences.languageBadge(),
|
||||
pref = screenModel.libraryPreferences.languageBadge,
|
||||
)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_show_continue_reading_button),
|
||||
pref = screenModel.libraryPreferences.showContinueReadingButton(),
|
||||
pref = screenModel.libraryPreferences.showContinueReadingButton,
|
||||
)
|
||||
|
||||
HeadingItem(MR.strings.tabs_header)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_show_tabs),
|
||||
pref = screenModel.libraryPreferences.categoryTabs(),
|
||||
pref = screenModel.libraryPreferences.categoryTabs,
|
||||
)
|
||||
CheckboxItem(
|
||||
label = stringResource(MR.strings.action_display_show_number_of_items),
|
||||
pref = screenModel.libraryPreferences.categoryNumberOfItems(),
|
||||
pref = screenModel.libraryPreferences.categoryNumberOfItems,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import tachiyomi.presentation.core.components.Badge
|
||||
|
||||
@Composable
|
||||
internal fun DownloadsBadge(count: Long) {
|
||||
internal fun DownloadsBadge(count: Int) {
|
||||
if (count > 0) {
|
||||
Badge(
|
||||
text = "$count",
|
||||
|
||||
+6
-7
@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.util.fastAny
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||
import tachiyomi.domain.library.model.LibraryManga
|
||||
import tachiyomi.domain.manga.model.MangaCover
|
||||
@@ -15,7 +14,7 @@ internal fun LibraryComfortableGrid(
|
||||
items: List<LibraryItem>,
|
||||
columns: Int,
|
||||
contentPadding: PaddingValues,
|
||||
selection: List<LibraryManga>,
|
||||
selection: Set<Long>,
|
||||
onClick: (LibraryManga) -> Unit,
|
||||
onLongClick: (LibraryManga) -> Unit,
|
||||
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
||||
@@ -35,7 +34,7 @@ internal fun LibraryComfortableGrid(
|
||||
) { libraryItem ->
|
||||
val manga = libraryItem.libraryManga.manga
|
||||
MangaComfortableGridItem(
|
||||
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
|
||||
isSelected = manga.id in selection,
|
||||
title = manga.title,
|
||||
coverData = MangaCover(
|
||||
mangaId = manga.id,
|
||||
@@ -45,13 +44,13 @@ internal fun LibraryComfortableGrid(
|
||||
lastModified = manga.coverLastModified,
|
||||
),
|
||||
coverBadgeStart = {
|
||||
DownloadsBadge(count = libraryItem.downloadCount)
|
||||
UnreadBadge(count = libraryItem.unreadCount)
|
||||
DownloadsBadge(count = libraryItem.badges.downloadCount)
|
||||
UnreadBadge(count = libraryItem.badges.unreadCount)
|
||||
},
|
||||
coverBadgeEnd = {
|
||||
LanguageBadge(
|
||||
isLocal = libraryItem.isLocal,
|
||||
sourceLanguage = libraryItem.sourceLanguage,
|
||||
isLocal = libraryItem.badges.isLocal,
|
||||
sourceLanguage = libraryItem.badges.sourceLanguage,
|
||||
)
|
||||
},
|
||||
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||
|
||||
@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.util.fastAny
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||
import tachiyomi.domain.library.model.LibraryManga
|
||||
import tachiyomi.domain.manga.model.MangaCover
|
||||
@@ -16,7 +15,7 @@ internal fun LibraryCompactGrid(
|
||||
showTitle: Boolean,
|
||||
columns: Int,
|
||||
contentPadding: PaddingValues,
|
||||
selection: List<LibraryManga>,
|
||||
selection: Set<Long>,
|
||||
onClick: (LibraryManga) -> Unit,
|
||||
onLongClick: (LibraryManga) -> Unit,
|
||||
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
||||
@@ -36,7 +35,7 @@ internal fun LibraryCompactGrid(
|
||||
) { libraryItem ->
|
||||
val manga = libraryItem.libraryManga.manga
|
||||
MangaCompactGridItem(
|
||||
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
|
||||
isSelected = manga.id in selection,
|
||||
title = manga.title.takeIf { showTitle },
|
||||
coverData = MangaCover(
|
||||
mangaId = manga.id,
|
||||
@@ -46,13 +45,13 @@ internal fun LibraryCompactGrid(
|
||||
lastModified = manga.coverLastModified,
|
||||
),
|
||||
coverBadgeStart = {
|
||||
DownloadsBadge(count = libraryItem.downloadCount)
|
||||
UnreadBadge(count = libraryItem.unreadCount)
|
||||
DownloadsBadge(count = libraryItem.badges.downloadCount)
|
||||
UnreadBadge(count = libraryItem.badges.unreadCount)
|
||||
},
|
||||
coverBadgeEnd = {
|
||||
LanguageBadge(
|
||||
isLocal = libraryItem.isLocal,
|
||||
sourceLanguage = libraryItem.sourceLanguage,
|
||||
isLocal = libraryItem.badges.isLocal,
|
||||
sourceLanguage = libraryItem.badges.sourceLanguage,
|
||||
)
|
||||
},
|
||||
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||
|
||||
@@ -29,22 +29,22 @@ import kotlin.time.Duration.Companion.seconds
|
||||
fun LibraryContent(
|
||||
categories: List<Category>,
|
||||
searchQuery: String?,
|
||||
selection: List<LibraryManga>,
|
||||
selection: Set<Long>,
|
||||
contentPadding: PaddingValues,
|
||||
currentPage: () -> Int,
|
||||
currentPage: Int,
|
||||
hasActiveFilters: Boolean,
|
||||
showPageTabs: Boolean,
|
||||
onChangeCurrentPage: (Int) -> Unit,
|
||||
onMangaClicked: (Long) -> Unit,
|
||||
onClickManga: (Long) -> Unit,
|
||||
onContinueReadingClicked: ((LibraryManga) -> Unit)?,
|
||||
onToggleSelection: (LibraryManga) -> Unit,
|
||||
onToggleRangeSelection: (LibraryManga) -> Unit,
|
||||
onRefresh: (Category?) -> Boolean,
|
||||
onToggleSelection: (Category, LibraryManga) -> Unit,
|
||||
onToggleRangeSelection: (Category, LibraryManga) -> Unit,
|
||||
onRefresh: () -> Boolean,
|
||||
onGlobalSearchClicked: () -> Unit,
|
||||
getNumberOfMangaForCategory: (Category) -> Int?,
|
||||
getItemCountForCategory: (Category) -> Int?,
|
||||
getDisplayMode: (Int) -> PreferenceMutableState<LibraryDisplayMode>,
|
||||
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
|
||||
getLibraryForPage: (Int) -> List<LibraryItem>,
|
||||
getItemsForCategory: (Category) -> List<LibraryItem>,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(
|
||||
@@ -53,13 +53,12 @@ fun LibraryContent(
|
||||
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
||||
),
|
||||
) {
|
||||
val coercedCurrentPage = remember { currentPage().coerceAtMost(categories.lastIndex) }
|
||||
val pagerState = rememberPagerState(coercedCurrentPage) { categories.size }
|
||||
val pagerState = rememberPagerState(currentPage) { categories.size }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
|
||||
|
||||
if (showPageTabs && categories.size > 1) {
|
||||
if (showPageTabs && categories.isNotEmpty() && (categories.size > 1 || !categories.first().isSystemCategory)) {
|
||||
LaunchedEffect(categories) {
|
||||
if (categories.size <= pagerState.currentPage) {
|
||||
pagerState.scrollToPage(categories.size - 1)
|
||||
@@ -68,23 +67,20 @@ fun LibraryContent(
|
||||
LibraryTabs(
|
||||
categories = categories,
|
||||
pagerState = pagerState,
|
||||
getNumberOfMangaForCategory = getNumberOfMangaForCategory,
|
||||
) { scope.launch { pagerState.animateScrollToPage(it) } }
|
||||
}
|
||||
|
||||
val notSelectionMode = selection.isEmpty()
|
||||
val onClickManga = { manga: LibraryManga ->
|
||||
if (notSelectionMode) {
|
||||
onMangaClicked(manga.manga.id)
|
||||
} else {
|
||||
onToggleSelection(manga)
|
||||
}
|
||||
getItemCountForCategory = getItemCountForCategory,
|
||||
onTabItemClick = {
|
||||
scope.launch {
|
||||
pagerState.animateScrollToPage(it)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
PullRefresh(
|
||||
refreshing = isRefreshing,
|
||||
enabled = selection.isEmpty(),
|
||||
onRefresh = {
|
||||
val started = onRefresh(categories[currentPage()])
|
||||
val started = onRefresh()
|
||||
if (!started) return@PullRefresh
|
||||
scope.launch {
|
||||
// Fake refresh status but hide it after a second as it's a long running task
|
||||
@@ -93,19 +89,25 @@ fun LibraryContent(
|
||||
isRefreshing = false
|
||||
}
|
||||
},
|
||||
enabled = notSelectionMode,
|
||||
) {
|
||||
LibraryPager(
|
||||
state = pagerState,
|
||||
contentPadding = PaddingValues(bottom = contentPadding.calculateBottomPadding()),
|
||||
hasActiveFilters = hasActiveFilters,
|
||||
selectedManga = selection,
|
||||
selection = selection,
|
||||
searchQuery = searchQuery,
|
||||
onGlobalSearchClicked = onGlobalSearchClicked,
|
||||
getCategoryForPage = { page -> categories[page] },
|
||||
getDisplayMode = getDisplayMode,
|
||||
getColumnsForOrientation = getColumnsForOrientation,
|
||||
getLibraryForPage = getLibraryForPage,
|
||||
onClickManga = onClickManga,
|
||||
getItemsForCategory = getItemsForCategory,
|
||||
onClickManga = { category, manga ->
|
||||
if (selection.isNotEmpty()) {
|
||||
onToggleSelection(category, manga)
|
||||
} else {
|
||||
onClickManga(manga.manga.id)
|
||||
}
|
||||
},
|
||||
onLongClickManga = onToggleRangeSelection,
|
||||
onClickContinueReading = onContinueReadingClicked,
|
||||
)
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastAny
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||
import tachiyomi.domain.library.model.LibraryManga
|
||||
import tachiyomi.domain.manga.model.MangaCover
|
||||
@@ -18,7 +17,7 @@ import tachiyomi.presentation.core.util.plus
|
||||
internal fun LibraryList(
|
||||
items: List<LibraryItem>,
|
||||
contentPadding: PaddingValues,
|
||||
selection: List<LibraryManga>,
|
||||
selection: Set<Long>,
|
||||
onClick: (LibraryManga) -> Unit,
|
||||
onLongClick: (LibraryManga) -> Unit,
|
||||
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
||||
@@ -45,7 +44,7 @@ internal fun LibraryList(
|
||||
) { libraryItem ->
|
||||
val manga = libraryItem.libraryManga.manga
|
||||
MangaListItem(
|
||||
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
|
||||
isSelected = manga.id in selection,
|
||||
title = manga.title,
|
||||
coverData = MangaCover(
|
||||
mangaId = manga.id,
|
||||
@@ -55,11 +54,11 @@ internal fun LibraryList(
|
||||
lastModified = manga.coverLastModified,
|
||||
),
|
||||
badge = {
|
||||
DownloadsBadge(count = libraryItem.downloadCount)
|
||||
UnreadBadge(count = libraryItem.unreadCount)
|
||||
DownloadsBadge(count = libraryItem.badges.downloadCount)
|
||||
UnreadBadge(count = libraryItem.badges.unreadCount)
|
||||
LanguageBadge(
|
||||
isLocal = libraryItem.isLocal,
|
||||
sourceLanguage = libraryItem.sourceLanguage,
|
||||
isLocal = libraryItem.badges.isLocal,
|
||||
sourceLanguage = libraryItem.badges.sourceLanguage,
|
||||
)
|
||||
},
|
||||
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||
|
||||
@@ -20,6 +20,7 @@ import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.core.preference.PreferenceMutableState
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
import tachiyomi.domain.library.model.LibraryManga
|
||||
import tachiyomi.i18n.MR
|
||||
@@ -31,14 +32,15 @@ fun LibraryPager(
|
||||
state: PagerState,
|
||||
contentPadding: PaddingValues,
|
||||
hasActiveFilters: Boolean,
|
||||
selectedManga: List<LibraryManga>,
|
||||
selection: Set<Long>,
|
||||
searchQuery: String?,
|
||||
onGlobalSearchClicked: () -> Unit,
|
||||
getCategoryForPage: (Int) -> Category,
|
||||
getDisplayMode: (Int) -> PreferenceMutableState<LibraryDisplayMode>,
|
||||
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
|
||||
getLibraryForPage: (Int) -> List<LibraryItem>,
|
||||
onClickManga: (LibraryManga) -> Unit,
|
||||
onLongClickManga: (LibraryManga) -> Unit,
|
||||
getItemsForCategory: (Category) -> List<LibraryItem>,
|
||||
onClickManga: (Category, LibraryManga) -> Unit,
|
||||
onLongClickManga: (Category, LibraryManga) -> Unit,
|
||||
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
||||
) {
|
||||
HorizontalPager(
|
||||
@@ -50,9 +52,10 @@ fun LibraryPager(
|
||||
// To make sure only one offscreen page is being composed
|
||||
return@HorizontalPager
|
||||
}
|
||||
val library = getLibraryForPage(page)
|
||||
val category = getCategoryForPage(page)
|
||||
val items = getItemsForCategory(category)
|
||||
|
||||
if (library.isEmpty()) {
|
||||
if (items.isEmpty()) {
|
||||
LibraryPagerEmptyScreen(
|
||||
searchQuery = searchQuery,
|
||||
hasActiveFilters = hasActiveFilters,
|
||||
@@ -72,12 +75,15 @@ fun LibraryPager(
|
||||
remember { mutableIntStateOf(0) }
|
||||
}
|
||||
|
||||
val onClickManga: (LibraryManga) -> Unit = { onClickManga(category, it) }
|
||||
val onLongClickManga: (LibraryManga) -> Unit = { onLongClickManga(category, it) }
|
||||
|
||||
when (displayMode) {
|
||||
LibraryDisplayMode.List -> {
|
||||
LibraryList(
|
||||
items = library,
|
||||
items = items,
|
||||
contentPadding = contentPadding,
|
||||
selection = selectedManga,
|
||||
selection = selection,
|
||||
onClick = onClickManga,
|
||||
onLongClick = onLongClickManga,
|
||||
onClickContinueReading = onClickContinueReading,
|
||||
@@ -87,11 +93,11 @@ fun LibraryPager(
|
||||
}
|
||||
LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> {
|
||||
LibraryCompactGrid(
|
||||
items = library,
|
||||
items = items,
|
||||
showTitle = displayMode is LibraryDisplayMode.CompactGrid,
|
||||
columns = columns,
|
||||
contentPadding = contentPadding,
|
||||
selection = selectedManga,
|
||||
selection = selection,
|
||||
onClick = onClickManga,
|
||||
onLongClick = onLongClickManga,
|
||||
onClickContinueReading = onClickContinueReading,
|
||||
@@ -101,10 +107,10 @@ fun LibraryPager(
|
||||
}
|
||||
LibraryDisplayMode.ComfortableGrid -> {
|
||||
LibraryComfortableGrid(
|
||||
items = library,
|
||||
items = items,
|
||||
columns = columns,
|
||||
contentPadding = contentPadding,
|
||||
selection = selectedManga,
|
||||
selection = selection,
|
||||
onClick = onClickManga,
|
||||
onLongClick = onLongClickManga,
|
||||
onClickContinueReading = onClickContinueReading,
|
||||
|
||||
@@ -18,13 +18,11 @@ import tachiyomi.presentation.core.components.material.TabText
|
||||
internal fun LibraryTabs(
|
||||
categories: List<Category>,
|
||||
pagerState: PagerState,
|
||||
getNumberOfMangaForCategory: (Category) -> Int?,
|
||||
getItemCountForCategory: (Category) -> Int?,
|
||||
onTabItemClick: (Int) -> Unit,
|
||||
) {
|
||||
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
|
||||
Column(
|
||||
modifier = Modifier.zIndex(1f),
|
||||
) {
|
||||
Column(modifier = Modifier.zIndex(2f)) {
|
||||
PrimaryScrollableTabRow(
|
||||
selectedTabIndex = currentPageIndex,
|
||||
edgePadding = 0.dp,
|
||||
@@ -39,7 +37,7 @@ internal fun LibraryTabs(
|
||||
text = {
|
||||
TabText(
|
||||
text = category.visualName,
|
||||
badgeCount = getNumberOfMangaForCategory(category),
|
||||
badgeCount = getItemCountForCategory(category),
|
||||
)
|
||||
},
|
||||
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
|
||||
|
||||
@@ -19,7 +19,6 @@ import androidx.compose.ui.unit.sp
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.Pill
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
@@ -96,7 +95,7 @@ private fun LibraryRegularToolbar(
|
||||
actions = {
|
||||
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
||||
AppBarActions(
|
||||
persistentListOf(
|
||||
listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_filter),
|
||||
icon = Icons.Outlined.FilterList,
|
||||
@@ -133,7 +132,7 @@ private fun LibrarySelectionToolbar(
|
||||
titleContent = { Text(text = "$selectedCount") },
|
||||
actions = {
|
||||
AppBarActions(
|
||||
persistentListOf(
|
||||
listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_select_all),
|
||||
icon = Icons.Outlined.SelectAll,
|
||||
|
||||
@@ -31,7 +31,6 @@ import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.manga.model.downloadedFilter
|
||||
import eu.kanade.presentation.components.TabbedDialog
|
||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.core.common.preference.TriState
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
@@ -66,11 +65,11 @@ fun ChapterSettingsDialog(
|
||||
)
|
||||
}
|
||||
|
||||
val downloadedOnly = remember { Injekt.get<BasePreferences>().downloadedOnly().get() }
|
||||
val downloadedOnly = remember { Injekt.get<BasePreferences>().downloadedOnly.get() }
|
||||
|
||||
TabbedDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
tabTitles = persistentListOf(
|
||||
tabTitles = listOf(
|
||||
stringResource(MR.strings.action_filter),
|
||||
stringResource(MR.strings.action_sort),
|
||||
stringResource(MR.strings.action_display),
|
||||
|
||||
@@ -1,44 +1,95 @@
|
||||
package eu.kanade.presentation.manga
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.background
|
||||
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.PaddingValues
|
||||
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.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Brush
|
||||
import androidx.compose.material.icons.filled.PersonOutline
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material.icons.outlined.Add
|
||||
import androidx.compose.material.icons.outlined.Book
|
||||
import androidx.compose.material.icons.outlined.SwapVert
|
||||
import androidx.compose.material.icons.outlined.AttachMoney
|
||||
import androidx.compose.material.icons.outlined.Block
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
import androidx.compose.material.icons.outlined.Done
|
||||
import androidx.compose.material.icons.outlined.DoneAll
|
||||
import androidx.compose.material.icons.outlined.Pause
|
||||
import androidx.compose.material.icons.outlined.Schedule
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.TextMeasurer
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.util.fastMaxOfOrNull
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import eu.kanade.presentation.components.AdaptiveSheet
|
||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||
import eu.kanade.presentation.manga.components.MangaCover
|
||||
import eu.kanade.presentation.more.settings.LocalPreferenceMinHeight
|
||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaWithChapterCount
|
||||
import tachiyomi.domain.source.model.StubSource
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.Badge
|
||||
import tachiyomi.presentation.core.components.BadgeGroup
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
@Composable
|
||||
fun DuplicateMangaDialog(
|
||||
duplicates: List<MangaWithChapterCount>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
onOpenManga: () -> Unit,
|
||||
onMigrate: () -> Unit,
|
||||
onOpenManga: (manga: Manga) -> Unit,
|
||||
onMigrate: (manga: Manga) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val sourceManager = remember { Injekt.get<SourceManager>() }
|
||||
val minHeight = LocalPreferenceMinHeight.current
|
||||
val horizontalPadding = PaddingValues(horizontal = TabbedDialogPaddings.Horizontal)
|
||||
val horizontalPaddingModifier = Modifier.padding(horizontalPadding)
|
||||
|
||||
AdaptiveSheet(
|
||||
modifier = modifier,
|
||||
@@ -46,81 +97,310 @@ fun DuplicateMangaDialog(
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
vertical = TabbedDialogPaddings.Vertical,
|
||||
horizontal = TabbedDialogPaddings.Horizontal,
|
||||
)
|
||||
.padding(vertical = TabbedDialogPaddings.Vertical)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(TitlePadding),
|
||||
text = stringResource(MR.strings.are_you_sure),
|
||||
text = stringResource(MR.strings.possible_duplicates_title),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
modifier = Modifier
|
||||
.then(horizontalPaddingModifier)
|
||||
.padding(top = MaterialTheme.padding.small),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(MR.strings.confirm_add_duplicate_manga),
|
||||
text = stringResource(MR.strings.possible_duplicates_summary),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.then(horizontalPaddingModifier),
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(PaddingSize))
|
||||
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.action_show_manga),
|
||||
icon = Icons.Outlined.Book,
|
||||
onPreferenceClick = {
|
||||
onDismissRequest()
|
||||
onOpenManga()
|
||||
},
|
||||
)
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.action_migrate_duplicate),
|
||||
icon = Icons.Outlined.SwapVert,
|
||||
onPreferenceClick = {
|
||||
onDismissRequest()
|
||||
onMigrate()
|
||||
},
|
||||
)
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.action_add_anyway),
|
||||
icon = Icons.Outlined.Add,
|
||||
onPreferenceClick = {
|
||||
onDismissRequest()
|
||||
onConfirm()
|
||||
},
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.sizeIn(minHeight = minHeight)
|
||||
.clickable { onDismissRequest.invoke() }
|
||||
.padding(ButtonPadding)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
LazyRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||
modifier = Modifier.height(getMaximumMangaCardHeight(duplicates)),
|
||||
contentPadding = horizontalPadding,
|
||||
) {
|
||||
OutlinedButton(onClick = onDismissRequest, modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 8.dp),
|
||||
text = stringResource(MR.strings.action_cancel),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontSize = 16.sp,
|
||||
items(
|
||||
items = duplicates,
|
||||
key = { it.manga.id },
|
||||
) {
|
||||
DuplicateMangaListItem(
|
||||
duplicate = it,
|
||||
getSource = { sourceManager.getOrStub(it.manga.source) },
|
||||
onMigrate = { onMigrate(it.manga) },
|
||||
onDismissRequest = onDismissRequest,
|
||||
onOpenManga = { onOpenManga(it.manga) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(modifier = horizontalPaddingModifier) {
|
||||
HorizontalDivider()
|
||||
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.action_add_anyway),
|
||||
icon = Icons.Outlined.Add,
|
||||
onPreferenceClick = {
|
||||
onDismissRequest()
|
||||
onConfirm()
|
||||
},
|
||||
modifier = Modifier.clip(CircleShape),
|
||||
)
|
||||
}
|
||||
|
||||
OutlinedButton(
|
||||
onClick = onDismissRequest,
|
||||
modifier = Modifier
|
||||
.then(horizontalPaddingModifier)
|
||||
.padding(bottom = MaterialTheme.padding.medium)
|
||||
.heightIn(min = minHeight)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = MaterialTheme.padding.extraSmall),
|
||||
text = stringResource(MR.strings.action_cancel),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val PaddingSize = 16.dp
|
||||
@Composable
|
||||
private fun DuplicateMangaListItem(
|
||||
duplicate: MangaWithChapterCount,
|
||||
getSource: () -> Source,
|
||||
onDismissRequest: () -> Unit,
|
||||
onOpenManga: () -> Unit,
|
||||
onMigrate: () -> Unit,
|
||||
) {
|
||||
val source = getSource()
|
||||
val manga = duplicate.manga
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.width(MangaCardWidth)
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.combinedClickable(
|
||||
onLongClick = { onOpenManga() },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
onMigrate()
|
||||
},
|
||||
)
|
||||
.padding(MaterialTheme.padding.small),
|
||||
) {
|
||||
Box {
|
||||
MangaCover.Book(
|
||||
data = ImageRequest.Builder(LocalContext.current)
|
||||
.data(manga)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
BadgeGroup(
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.align(Alignment.TopStart),
|
||||
) {
|
||||
Badge(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
textColor = MaterialTheme.colorScheme.onSecondary,
|
||||
text = pluralStringResource(
|
||||
MR.plurals.manga_num_chapters,
|
||||
duplicate.chapterCount.toInt(),
|
||||
duplicate.chapterCount,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val ButtonPadding = PaddingValues(top = 16.dp, bottom = 16.dp)
|
||||
private val TitlePadding = PaddingValues(bottom = 16.dp, top = 8.dp)
|
||||
Spacer(modifier = Modifier.height(MaterialTheme.padding.extraSmall))
|
||||
|
||||
Text(
|
||||
text = manga.title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 2,
|
||||
)
|
||||
|
||||
if (!manga.author.isNullOrBlank()) {
|
||||
MangaDetailRow(
|
||||
text = manga.author!!,
|
||||
iconImageVector = Icons.Filled.PersonOutline,
|
||||
maxLines = 2,
|
||||
)
|
||||
}
|
||||
|
||||
if (!manga.artist.isNullOrBlank() && manga.author != manga.artist) {
|
||||
MangaDetailRow(
|
||||
text = manga.artist!!,
|
||||
iconImageVector = Icons.Filled.Brush,
|
||||
maxLines = 2,
|
||||
)
|
||||
}
|
||||
|
||||
MangaDetailRow(
|
||||
text = when (manga.status) {
|
||||
SManga.ONGOING.toLong() -> stringResource(MR.strings.ongoing)
|
||||
SManga.COMPLETED.toLong() -> stringResource(MR.strings.completed)
|
||||
SManga.LICENSED.toLong() -> stringResource(MR.strings.licensed)
|
||||
SManga.PUBLISHING_FINISHED.toLong() -> stringResource(MR.strings.publishing_finished)
|
||||
SManga.CANCELLED.toLong() -> stringResource(MR.strings.cancelled)
|
||||
SManga.ON_HIATUS.toLong() -> stringResource(MR.strings.on_hiatus)
|
||||
else -> stringResource(MR.strings.unknown)
|
||||
},
|
||||
iconImageVector = when (manga.status) {
|
||||
SManga.ONGOING.toLong() -> Icons.Outlined.Schedule
|
||||
SManga.COMPLETED.toLong() -> Icons.Outlined.DoneAll
|
||||
SManga.LICENSED.toLong() -> Icons.Outlined.AttachMoney
|
||||
SManga.PUBLISHING_FINISHED.toLong() -> Icons.Outlined.Done
|
||||
SManga.CANCELLED.toLong() -> Icons.Outlined.Close
|
||||
SManga.ON_HIATUS.toLong() -> Icons.Outlined.Pause
|
||||
else -> Icons.Outlined.Block
|
||||
},
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
if (source is StubSource) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Warning,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = source.name,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MangaDetailRow(
|
||||
text: String,
|
||||
iconImageVector: ImageVector,
|
||||
maxLines: Int = 1,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.secondaryItemAlpha()
|
||||
.padding(top = MaterialTheme.padding.extraSmall),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = iconImageVector,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(MangaDetailsIconWidth),
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = maxLines,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getMaximumMangaCardHeight(duplicates: List<MangaWithChapterCount>): Dp {
|
||||
val density = LocalDensity.current
|
||||
val typography = MaterialTheme.typography
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
|
||||
val smallPadding = with(density) { MaterialTheme.padding.small.roundToPx() }
|
||||
val extraSmallPadding = with(density) { MaterialTheme.padding.extraSmall.roundToPx() }
|
||||
|
||||
val width = with(density) { MangaCardWidth.roundToPx() - (2 * smallPadding) }
|
||||
val iconWidth = with(density) { MangaDetailsIconWidth.roundToPx() }
|
||||
|
||||
val coverHeight = width / MangaCover.Book.ratio
|
||||
val constraints = Constraints(maxWidth = width)
|
||||
val detailsConstraints = Constraints(maxWidth = width - iconWidth - extraSmallPadding)
|
||||
|
||||
return remember(
|
||||
duplicates,
|
||||
density,
|
||||
typography,
|
||||
textMeasurer,
|
||||
smallPadding,
|
||||
extraSmallPadding,
|
||||
coverHeight,
|
||||
constraints,
|
||||
detailsConstraints,
|
||||
) {
|
||||
duplicates.fastMaxOfOrNull {
|
||||
calculateMangaCardHeight(
|
||||
manga = it.manga,
|
||||
density = density,
|
||||
typography = typography,
|
||||
textMeasurer = textMeasurer,
|
||||
smallPadding = smallPadding,
|
||||
extraSmallPadding = extraSmallPadding,
|
||||
coverHeight = coverHeight,
|
||||
constraints = constraints,
|
||||
detailsConstraints = detailsConstraints,
|
||||
)
|
||||
}
|
||||
?: 0.dp
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateMangaCardHeight(
|
||||
manga: Manga,
|
||||
density: Density,
|
||||
typography: Typography,
|
||||
textMeasurer: TextMeasurer,
|
||||
smallPadding: Int,
|
||||
extraSmallPadding: Int,
|
||||
coverHeight: Float,
|
||||
constraints: Constraints,
|
||||
detailsConstraints: Constraints,
|
||||
): Dp {
|
||||
val titleHeight = textMeasurer.measureHeight(manga.title, typography.titleSmall, 2, constraints)
|
||||
val authorHeight = if (!manga.author.isNullOrBlank()) {
|
||||
textMeasurer.measureHeight(manga.author!!, typography.bodySmall, 2, detailsConstraints)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
val artistHeight = if (!manga.artist.isNullOrBlank() && manga.author != manga.artist) {
|
||||
textMeasurer.measureHeight(manga.artist!!, typography.bodySmall, 2, detailsConstraints)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
val statusHeight = textMeasurer.measureHeight("", typography.bodySmall, 2, detailsConstraints)
|
||||
val sourceHeight = textMeasurer.measureHeight("", typography.labelSmall, 1, constraints)
|
||||
|
||||
val totalHeight = coverHeight + titleHeight + authorHeight + artistHeight + statusHeight + sourceHeight
|
||||
return with(density) { ((2 * smallPadding) + totalHeight + (5 * extraSmallPadding)).toDp() }
|
||||
}
|
||||
|
||||
private fun TextMeasurer.measureHeight(
|
||||
text: String,
|
||||
style: TextStyle,
|
||||
maxLines: Int,
|
||||
constraints: Constraints,
|
||||
): Int = measure(
|
||||
text = text,
|
||||
style = style,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = maxLines,
|
||||
constraints = constraints,
|
||||
)
|
||||
.size
|
||||
.height
|
||||
|
||||
private val MangaCardWidth = 150.dp
|
||||
private val MangaDetailsIconWidth = 16.dp
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package eu.kanade.presentation.manga
|
||||
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarTitle
|
||||
import eu.kanade.presentation.manga.components.MangaNotesTextArea
|
||||
import eu.kanade.tachiyomi.ui.manga.notes.MangaNotesScreen
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun MangaNotesScreen(
|
||||
state: MangaNotesScreen.State,
|
||||
navigateUp: () -> Unit,
|
||||
onUpdate: (String) -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { topBarScrollBehavior ->
|
||||
AppBar(
|
||||
titleContent = {
|
||||
AppBarTitle(
|
||||
title = stringResource(MR.strings.action_edit_notes),
|
||||
subtitle = state.manga.title,
|
||||
)
|
||||
},
|
||||
navigateUp = navigateUp,
|
||||
scrollBehavior = topBarScrollBehavior,
|
||||
)
|
||||
},
|
||||
) { contentPadding ->
|
||||
MangaNotesTextArea(
|
||||
state = state,
|
||||
onUpdate = onUpdate,
|
||||
modifier = Modifier
|
||||
.padding(contentPadding)
|
||||
.consumeWindowInsets(contentPadding)
|
||||
.imePadding(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package eu.kanade.presentation.manga
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
@@ -27,9 +24,11 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.SmallExtendedFloatingActionButton
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.animateFloatingActionButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -71,7 +70,6 @@ import tachiyomi.domain.source.model.StubSource
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.TwoPanelBox
|
||||
import tachiyomi.presentation.core.components.VerticalFastScroller
|
||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
@@ -112,6 +110,7 @@ fun MangaScreen(
|
||||
onEditCategoryClicked: (() -> Unit)?,
|
||||
onEditFetchIntervalClicked: (() -> Unit)?,
|
||||
onMigrateClicked: (() -> Unit)?,
|
||||
onEditNotesClicked: () -> Unit,
|
||||
|
||||
// For bottom action menu
|
||||
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
|
||||
@@ -123,7 +122,7 @@ fun MangaScreen(
|
||||
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
|
||||
// Chapter selection
|
||||
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||
onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
|
||||
onAllChapterSelected: (Boolean) -> Unit,
|
||||
onInvertSelection: () -> Unit,
|
||||
) {
|
||||
@@ -160,6 +159,7 @@ fun MangaScreen(
|
||||
onEditCategoryClicked = onEditCategoryClicked,
|
||||
onEditIntervalClicked = onEditFetchIntervalClicked,
|
||||
onMigrateClicked = onMigrateClicked,
|
||||
onEditNotesClicked = onEditNotesClicked,
|
||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
||||
@@ -195,6 +195,7 @@ fun MangaScreen(
|
||||
onEditCategoryClicked = onEditCategoryClicked,
|
||||
onEditIntervalClicked = onEditFetchIntervalClicked,
|
||||
onMigrateClicked = onMigrateClicked,
|
||||
onEditNotesClicked = onEditNotesClicked,
|
||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
||||
@@ -240,6 +241,7 @@ private fun MangaScreenSmallImpl(
|
||||
onEditCategoryClicked: (() -> Unit)?,
|
||||
onEditIntervalClicked: (() -> Unit)?,
|
||||
onMigrateClicked: (() -> Unit)?,
|
||||
onEditNotesClicked: () -> Unit,
|
||||
|
||||
// For bottom action menu
|
||||
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
|
||||
@@ -251,7 +253,7 @@ private fun MangaScreenSmallImpl(
|
||||
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
|
||||
// Chapter selection
|
||||
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||
onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
|
||||
onAllChapterSelected: (Boolean) -> Unit,
|
||||
onInvertSelection: () -> Unit,
|
||||
) {
|
||||
@@ -265,13 +267,9 @@ private fun MangaScreenSmallImpl(
|
||||
)
|
||||
}
|
||||
|
||||
BackHandler(onBack = {
|
||||
if (isAnySelected) {
|
||||
onAllChapterSelected(false)
|
||||
} else {
|
||||
navigateUp()
|
||||
}
|
||||
})
|
||||
BackHandler(enabled = isAnySelected) {
|
||||
onAllChapterSelected(false)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -302,6 +300,7 @@ private fun MangaScreenSmallImpl(
|
||||
onClickEditCategory = onEditCategoryClicked,
|
||||
onClickRefresh = onRefresh,
|
||||
onClickMigrate = onMigrateClicked,
|
||||
onClickEditNotes = onEditNotesClicked,
|
||||
actionModeCounter = selectedChapterCount,
|
||||
onCancelActionMode = { onAllChapterSelected(false) },
|
||||
onSelectAll = { onAllChapterSelected(true) },
|
||||
@@ -329,25 +328,23 @@ private fun MangaScreenSmallImpl(
|
||||
val isFABVisible = remember(chapters) {
|
||||
chapters.fastAny { !it.chapter.read } && !isAnySelected
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = isFABVisible,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
ExtendedFloatingActionButton(
|
||||
text = {
|
||||
val isReading = remember(state.chapters) {
|
||||
state.chapters.fastAny { it.chapter.read }
|
||||
}
|
||||
Text(
|
||||
text = stringResource(if (isReading) MR.strings.action_resume else MR.strings.action_start),
|
||||
)
|
||||
},
|
||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||
onClick = onContinueReading,
|
||||
expanded = chapterListState.shouldExpandFAB(),
|
||||
)
|
||||
}
|
||||
SmallExtendedFloatingActionButton(
|
||||
text = {
|
||||
val isReading = remember(state.chapters) {
|
||||
state.chapters.fastAny { it.chapter.read }
|
||||
}
|
||||
Text(
|
||||
text = stringResource(if (isReading) MR.strings.action_resume else MR.strings.action_start),
|
||||
)
|
||||
},
|
||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||
onClick = onContinueReading,
|
||||
expanded = chapterListState.shouldExpandFAB(),
|
||||
modifier = Modifier.animateFloatingActionButton(
|
||||
visible = isFABVisible,
|
||||
alignment = Alignment.BottomEnd,
|
||||
),
|
||||
)
|
||||
},
|
||||
) { contentPadding ->
|
||||
val topPadding = contentPadding.calculateTopPadding()
|
||||
@@ -414,8 +411,10 @@ private fun MangaScreenSmallImpl(
|
||||
defaultExpandState = state.isFromSource,
|
||||
description = state.manga.description,
|
||||
tagsProvider = { state.manga.genre },
|
||||
notes = state.manga.notes,
|
||||
onTagSearch = onTagSearch,
|
||||
onCopyTagToClipboard = onCopyTagToClipboard,
|
||||
onEditNotes = onEditNotesClicked,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -484,6 +483,7 @@ fun MangaScreenLargeImpl(
|
||||
onEditCategoryClicked: (() -> Unit)?,
|
||||
onEditIntervalClicked: (() -> Unit)?,
|
||||
onMigrateClicked: (() -> Unit)?,
|
||||
onEditNotesClicked: () -> Unit,
|
||||
|
||||
// For bottom action menu
|
||||
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
|
||||
@@ -495,7 +495,7 @@ fun MangaScreenLargeImpl(
|
||||
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
|
||||
// Chapter selection
|
||||
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||
onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
|
||||
onAllChapterSelected: (Boolean) -> Unit,
|
||||
onInvertSelection: () -> Unit,
|
||||
) {
|
||||
@@ -515,13 +515,9 @@ fun MangaScreenLargeImpl(
|
||||
|
||||
val chapterListState = rememberLazyListState()
|
||||
|
||||
BackHandler(onBack = {
|
||||
if (isAnySelected) {
|
||||
onAllChapterSelected(false)
|
||||
} else {
|
||||
navigateUp()
|
||||
}
|
||||
})
|
||||
BackHandler(enabled = isAnySelected) {
|
||||
onAllChapterSelected(false)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -539,6 +535,7 @@ fun MangaScreenLargeImpl(
|
||||
onClickEditCategory = onEditCategoryClicked,
|
||||
onClickRefresh = onRefresh,
|
||||
onClickMigrate = onMigrateClicked,
|
||||
onClickEditNotes = onEditNotesClicked,
|
||||
onCancelActionMode = { onAllChapterSelected(false) },
|
||||
actionModeCounter = selectedChapterCount,
|
||||
onSelectAll = { onAllChapterSelected(true) },
|
||||
@@ -571,27 +568,25 @@ fun MangaScreenLargeImpl(
|
||||
val isFABVisible = remember(chapters) {
|
||||
chapters.fastAny { !it.chapter.read } && !isAnySelected
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = isFABVisible,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
ExtendedFloatingActionButton(
|
||||
text = {
|
||||
val isReading = remember(state.chapters) {
|
||||
state.chapters.fastAny { it.chapter.read }
|
||||
}
|
||||
Text(
|
||||
text = stringResource(
|
||||
if (isReading) MR.strings.action_resume else MR.strings.action_start,
|
||||
),
|
||||
)
|
||||
},
|
||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||
onClick = onContinueReading,
|
||||
expanded = chapterListState.shouldExpandFAB(),
|
||||
)
|
||||
}
|
||||
SmallExtendedFloatingActionButton(
|
||||
text = {
|
||||
val isReading = remember(state.chapters) {
|
||||
state.chapters.fastAny { it.chapter.read }
|
||||
}
|
||||
Text(
|
||||
text = stringResource(
|
||||
if (isReading) MR.strings.action_resume else MR.strings.action_start,
|
||||
),
|
||||
)
|
||||
},
|
||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||
onClick = onContinueReading,
|
||||
expanded = chapterListState.shouldExpandFAB(),
|
||||
modifier = Modifier.animateFloatingActionButton(
|
||||
visible = isFABVisible,
|
||||
alignment = Alignment.BottomEnd,
|
||||
),
|
||||
)
|
||||
},
|
||||
) { contentPadding ->
|
||||
PullRefresh(
|
||||
@@ -640,8 +635,10 @@ fun MangaScreenLargeImpl(
|
||||
defaultExpandState = true,
|
||||
description = state.manga.description,
|
||||
tagsProvider = { state.manga.genre },
|
||||
notes = state.manga.notes,
|
||||
onTagSearch = onTagSearch,
|
||||
onCopyTagToClipboard = onCopyTagToClipboard,
|
||||
onEditNotes = onEditNotesClicked,
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -742,7 +739,7 @@ private fun LazyListScope.sharedChapterItems(
|
||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||
onChapterClicked: (Chapter) -> Unit,
|
||||
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||
onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
|
||||
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
) {
|
||||
items(
|
||||
@@ -790,14 +787,14 @@ private fun LazyListScope.sharedChapterItems(
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||
onLongClick = {
|
||||
onChapterSelected(item, !item.selected, true, true)
|
||||
onChapterSelected(item, !item.selected, true)
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
},
|
||||
onClick = {
|
||||
onChapterItemClick(
|
||||
chapterItem = item,
|
||||
isAnyChapterSelected = isAnyChapterSelected,
|
||||
onToggleSelection = { onChapterSelected(item, !item.selected, true, false) },
|
||||
onToggleSelection = { onChapterSelected(item, !item.selected, false) },
|
||||
onChapterClicked = onChapterClicked,
|
||||
)
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ enum class DownloadAction {
|
||||
NEXT_10_CHAPTERS,
|
||||
NEXT_25_CHAPTERS,
|
||||
UNREAD_CHAPTERS,
|
||||
BOOKMARKED_CHAPTERS,
|
||||
}
|
||||
|
||||
enum class EditCoverAction {
|
||||
|
||||
+77
-30
@@ -9,6 +9,7 @@ import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
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.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
@@ -28,7 +29,10 @@ import androidx.compose.material.icons.outlined.BookmarkRemove
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.DoneAll
|
||||
import androidx.compose.material.icons.outlined.Download
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material.icons.outlined.RemoveDone
|
||||
import androidx.compose.material.icons.outlined.SwapCalls
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
@@ -48,8 +52,10 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.components.DownloadDropdownMenu
|
||||
import eu.kanade.presentation.components.DropdownMenu
|
||||
import eu.kanade.presentation.manga.DownloadAction
|
||||
import eu.kanade.tachiyomi.R
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -85,10 +91,10 @@ fun MangaBottomActionMenu(
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) }
|
||||
var resetJob: Job? = remember { null }
|
||||
var resetJob by remember { mutableStateOf<Job?>(null) }
|
||||
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
(0..<7).forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||
confirm.indices.forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||
resetJob?.cancel()
|
||||
resetJob = scope.launch {
|
||||
delay(1.seconds)
|
||||
@@ -185,7 +191,7 @@ private fun RowScope.Button(
|
||||
targetValue = if (toConfirm) 2f else 1f,
|
||||
label = "weight",
|
||||
)
|
||||
Column(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.weight(animatedWeight)
|
||||
@@ -195,24 +201,28 @@ private fun RowScope.Button(
|
||||
onLongClick = onLongClick,
|
||||
onClick = onClick,
|
||||
),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = title,
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = toConfirm,
|
||||
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
|
||||
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(),
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
overflow = TextOverflow.Visible,
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = title,
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = toConfirm,
|
||||
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
|
||||
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
overflow = TextOverflow.Visible,
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
content?.invoke()
|
||||
}
|
||||
@@ -226,6 +236,7 @@ fun LibraryBottomActionMenu(
|
||||
onMarkAsUnreadClicked: () -> Unit,
|
||||
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||
onDeleteClicked: () -> Unit,
|
||||
onMigrateClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
@@ -240,17 +251,18 @@ fun LibraryBottomActionMenu(
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val confirm = remember { mutableStateListOf(false, false, false, false, false) }
|
||||
var resetJob: Job? = remember { null }
|
||||
val confirm = remember { mutableStateListOf(false, false, false, false, false, false) }
|
||||
var resetJob by remember { mutableStateOf<Job?>(null) }
|
||||
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
(0..<5).forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||
confirm.indices.forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||
resetJob?.cancel()
|
||||
resetJob = scope.launch {
|
||||
delay(1.seconds)
|
||||
if (isActive) confirm[toConfirmIndex] = false
|
||||
}
|
||||
}
|
||||
val itemOverflow = onDownloadClicked != null
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.windowInsetsPadding(
|
||||
@@ -289,22 +301,57 @@ fun LibraryBottomActionMenu(
|
||||
onLongClick = { onLongClickItem(3) },
|
||||
onClick = { downloadExpanded = !downloadExpanded },
|
||||
) {
|
||||
val onDismissRequest = { downloadExpanded = false }
|
||||
DownloadDropdownMenu(
|
||||
expanded = downloadExpanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onDismissRequest = { downloadExpanded = false },
|
||||
onDownloadClicked = onDownloadClicked,
|
||||
offset = BottomBarMenuDpOffset,
|
||||
)
|
||||
}
|
||||
}
|
||||
Button(
|
||||
title = stringResource(MR.strings.action_delete),
|
||||
icon = Icons.Outlined.Delete,
|
||||
toConfirm = confirm[4],
|
||||
onLongClick = { onLongClickItem(4) },
|
||||
onClick = onDeleteClicked,
|
||||
)
|
||||
if (!itemOverflow) {
|
||||
Button(
|
||||
title = stringResource(MR.strings.migrate),
|
||||
icon = Icons.Outlined.SwapCalls,
|
||||
toConfirm = confirm[4],
|
||||
onLongClick = { onLongClickItem(4) },
|
||||
onClick = onMigrateClicked,
|
||||
)
|
||||
Button(
|
||||
title = stringResource(MR.strings.action_delete),
|
||||
icon = Icons.Outlined.Delete,
|
||||
toConfirm = confirm[5],
|
||||
onLongClick = { onLongClickItem(5) },
|
||||
onClick = onDeleteClicked,
|
||||
)
|
||||
} else {
|
||||
var overflowMenuOpen by remember { mutableStateOf(false) }
|
||||
Button(
|
||||
title = stringResource(MR.strings.label_more),
|
||||
icon = Icons.Outlined.MoreVert,
|
||||
toConfirm = false,
|
||||
onLongClick = {},
|
||||
onClick = { overflowMenuOpen = true },
|
||||
) {
|
||||
DropdownMenu(
|
||||
expanded = overflowMenuOpen,
|
||||
onDismissRequest = { overflowMenuOpen = false },
|
||||
offset = BottomBarMenuDpOffset,
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(MR.strings.migrate)) },
|
||||
onClick = onMigrateClicked,
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(MR.strings.action_delete)) },
|
||||
onClick = onDeleteClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val BottomBarMenuDpOffset = DpOffset(0.dp, 0.dp)
|
||||
|
||||
@@ -36,6 +36,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import androidx.core.view.updatePadding
|
||||
import coil3.asDrawable
|
||||
import coil3.imageLoader
|
||||
@@ -47,7 +48,6 @@ import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.components.DropdownMenu
|
||||
import eu.kanade.presentation.manga.EditCoverAction
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
@@ -92,7 +92,7 @@ fun MangaCoverDialog(
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
ActionsPill {
|
||||
AppBarActions(
|
||||
actions = persistentListOf(
|
||||
actions = listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(MR.strings.action_share),
|
||||
icon = Icons.Outlined.Share,
|
||||
@@ -171,15 +171,13 @@ fun MangaCoverDialog(
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.target { image ->
|
||||
val drawable = image.asDrawable(view.context.resources)
|
||||
|
||||
// Copy bitmap in case it came from memory cache
|
||||
// Because SSIV needs to thoroughly read the image
|
||||
val copy = (drawable as? BitmapDrawable)?.let {
|
||||
BitmapDrawable(
|
||||
view.context.resources,
|
||||
it.bitmap.copy(Bitmap.Config.HARDWARE, false),
|
||||
)
|
||||
} ?: drawable
|
||||
val copy = (drawable as? BitmapDrawable)
|
||||
?.bitmap
|
||||
?.copy(Bitmap.Config.HARDWARE, false)
|
||||
?.toDrawable(view.context.resources)
|
||||
?: drawable
|
||||
view.setImage(copy, ReaderPageImageView.Config(zoomDuration = 500))
|
||||
}
|
||||
.build()
|
||||
|
||||
@@ -20,7 +20,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||
@@ -124,7 +123,7 @@ fun SetIntervalDialog(
|
||||
it.toString()
|
||||
}
|
||||
}
|
||||
.toImmutableList()
|
||||
|
||||
WheelTextPicker(
|
||||
items = items,
|
||||
size = size,
|
||||
|
||||
@@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Brush
|
||||
@@ -52,6 +53,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
@@ -67,9 +69,13 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.withLink
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -77,10 +83,17 @@ import androidx.compose.ui.unit.sp
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import com.mikepenz.markdown.model.markdownAnnotator
|
||||
import com.mikepenz.markdown.model.markdownAnnotatorConfig
|
||||
import com.mikepenz.markdown.utils.getUnescapedTextInNode
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.presentation.components.DropdownMenu
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import org.intellij.markdown.MarkdownElementTypes
|
||||
import org.intellij.markdown.MarkdownTokenTypes
|
||||
import org.intellij.markdown.ast.findChildOfType
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
|
||||
@@ -90,12 +103,12 @@ import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.clickableNoIndication
|
||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
|
||||
|
||||
@Composable
|
||||
fun MangaInfoBox(
|
||||
isTabletUi: Boolean,
|
||||
@@ -236,8 +249,10 @@ fun ExpandableMangaDescription(
|
||||
defaultExpandState: Boolean,
|
||||
description: String?,
|
||||
tagsProvider: () -> List<String>?,
|
||||
notes: String,
|
||||
onTagSearch: (String) -> Unit,
|
||||
onCopyTagToClipboard: (tag: String) -> Unit,
|
||||
onEditNotes: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
@@ -246,15 +261,12 @@ fun ExpandableMangaDescription(
|
||||
}
|
||||
val desc =
|
||||
description.takeIf { !it.isNullOrBlank() } ?: stringResource(MR.strings.description_placeholder)
|
||||
val trimmedDescription = remember(desc) {
|
||||
desc
|
||||
.replace(whitespaceLineRegex, "\n")
|
||||
.trimEnd()
|
||||
}
|
||||
|
||||
MangaSummary(
|
||||
expandedDescription = desc,
|
||||
shrunkDescription = trimmedDescription,
|
||||
description = desc,
|
||||
expanded = expanded,
|
||||
notes = notes,
|
||||
onEditNotesClicked = onEditNotes,
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
@@ -555,41 +567,96 @@ private fun ColumnScope.MangaContentInfo(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun descriptionAnnotator(loadImages: Boolean, linkStyle: SpanStyle) = remember(loadImages, linkStyle) {
|
||||
markdownAnnotator(
|
||||
annotate = { content, child ->
|
||||
if (!loadImages && child.type == MarkdownElementTypes.IMAGE) {
|
||||
val inlineLink = child.findChildOfType(MarkdownElementTypes.INLINE_LINK)
|
||||
|
||||
val url = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_DESTINATION)
|
||||
?.getUnescapedTextInNode(content)
|
||||
?: inlineLink?.findChildOfType(MarkdownElementTypes.AUTOLINK)
|
||||
?.findChildOfType(MarkdownTokenTypes.AUTOLINK)
|
||||
?.getUnescapedTextInNode(content)
|
||||
?: return@markdownAnnotator false
|
||||
|
||||
val textNode = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TITLE)
|
||||
?: inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TEXT)
|
||||
val altText = textNode?.findChildOfType(MarkdownTokenTypes.TEXT)
|
||||
?.getUnescapedTextInNode(content).orEmpty()
|
||||
|
||||
withLink(LinkAnnotation.Url(url = url)) {
|
||||
pushStyle(linkStyle)
|
||||
appendInlineContent(MARKDOWN_INLINE_IMAGE_TAG)
|
||||
append(altText)
|
||||
pop()
|
||||
}
|
||||
|
||||
return@markdownAnnotator true
|
||||
}
|
||||
|
||||
if (child.type in DISALLOWED_MARKDOWN_TYPES) {
|
||||
append(content.substring(child.startOffset, child.endOffset))
|
||||
return@markdownAnnotator true
|
||||
}
|
||||
|
||||
false
|
||||
},
|
||||
config = markdownAnnotatorConfig(
|
||||
eolAsNewLine = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MangaSummary(
|
||||
expandedDescription: String,
|
||||
shrunkDescription: String,
|
||||
description: String,
|
||||
notes: String,
|
||||
expanded: Boolean,
|
||||
onEditNotesClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val preferences = remember { Injekt.get<UiPreferences>() }
|
||||
val loadImages = remember { preferences.imagesInDescription.get() }
|
||||
val animProgress by animateFloatAsState(
|
||||
targetValue = if (expanded) 1f else 0f,
|
||||
label = "summary",
|
||||
)
|
||||
var infoHeight by remember { mutableIntStateOf(0) }
|
||||
Layout(
|
||||
modifier = modifier.clipToBounds(),
|
||||
contents = listOf(
|
||||
{
|
||||
Text(
|
||||
text = "\n\n", // Shows at least 3 lines
|
||||
// Shows at least 3 lines if no notes
|
||||
// when there are notes show 6
|
||||
text = if (notes.isBlank()) "\n\n" else "\n\n\n\n\n",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
},
|
||||
{
|
||||
Text(
|
||||
text = expandedDescription,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
},
|
||||
{
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = if (expanded) expandedDescription else shrunkDescription,
|
||||
maxLines = Int.MAX_VALUE,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
modifier = Modifier.secondaryItemAlpha(),
|
||||
Column(
|
||||
modifier = Modifier.onSizeChanged { size ->
|
||||
infoHeight = size.height
|
||||
},
|
||||
) {
|
||||
MangaNotesSection(
|
||||
content = notes,
|
||||
expanded = expanded,
|
||||
onEditNotes = onEditNotesClicked,
|
||||
)
|
||||
SelectionContainer {
|
||||
MarkdownRender(
|
||||
content = description,
|
||||
modifier = Modifier.secondaryItemAlpha(),
|
||||
annotator = descriptionAnnotator(
|
||||
loadImages = loadImages,
|
||||
linkStyle = getMarkdownLinkStyle().toSpanStyle(),
|
||||
),
|
||||
loadImages = loadImages,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -610,14 +677,11 @@ private fun MangaSummary(
|
||||
}
|
||||
},
|
||||
),
|
||||
) { (shrunk, expanded, actual, scrim), constraints ->
|
||||
) { (shrunk, actual, scrim), constraints ->
|
||||
val shrunkHeight = shrunk.single()
|
||||
.measure(constraints)
|
||||
.height
|
||||
val expandedHeight = expanded.single()
|
||||
.measure(constraints)
|
||||
.height
|
||||
val heightDelta = expandedHeight - shrunkHeight
|
||||
val heightDelta = infoHeight - shrunkHeight
|
||||
val scrimHeight = 24.dp.roundToPx()
|
||||
|
||||
val actualPlaceable = actual.single()
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package eu.kanade.presentation.manga.components
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import com.mohamedrejeb.richeditor.model.rememberRichTextState
|
||||
import com.mohamedrejeb.richeditor.ui.material3.RichText
|
||||
|
||||
private val FADE_TIME = tween<Float>(500)
|
||||
|
||||
@Composable
|
||||
fun MangaNotesDisplay(
|
||||
content: String,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val alpha = remember { Animatable(1f) }
|
||||
var contentUpdatedOnce by remember { mutableStateOf(false) }
|
||||
|
||||
val richTextState = rememberRichTextState()
|
||||
val primaryColor = MaterialTheme.colorScheme.primary
|
||||
LaunchedEffect(content) {
|
||||
richTextState.setMarkdown(content)
|
||||
|
||||
if (!contentUpdatedOnce) {
|
||||
contentUpdatedOnce = true
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
alpha.snapTo(targetValue = 0f)
|
||||
alpha.animateTo(targetValue = 1f, animationSpec = FADE_TIME)
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
richTextState.config.unorderedListIndent = 4
|
||||
richTextState.config.orderedListIndent = 20
|
||||
}
|
||||
LaunchedEffect(primaryColor) {
|
||||
richTextState.config.linkColor = primaryColor
|
||||
}
|
||||
|
||||
SelectionContainer {
|
||||
RichText(
|
||||
modifier = modifier
|
||||
// Only animate size if the notes changes
|
||||
.then(if (contentUpdatedOnce) Modifier.animateContentSize() else Modifier)
|
||||
.alpha(alpha.value),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
state = richTextState,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package eu.kanade.presentation.manga.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.EditNote
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Button
|
||||
import tachiyomi.presentation.core.components.material.ButtonDefaults
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
@Composable
|
||||
fun MangaNotesSection(
|
||||
content: String,
|
||||
expanded: Boolean,
|
||||
onEditNotes: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (content.isBlank()) return
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
MangaNotesDisplay(
|
||||
content = content,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
)
|
||||
if (expanded) {
|
||||
Button(
|
||||
onClick = onEditNotes,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.EditNote,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(16.dp),
|
||||
)
|
||||
Text(
|
||||
stringResource(MR.strings.action_edit_notes),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = if (expanded) 0.dp else 12.dp,
|
||||
bottom = if (expanded) 16.dp else 12.dp,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun MangaNotesSectionPreview() {
|
||||
MangaNotesSection(
|
||||
onEditNotes = {},
|
||||
expanded = true,
|
||||
content = "# Hello world\ntest1234 hi there!",
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package eu.kanade.presentation.manga.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
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.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted
|
||||
import androidx.compose.material.icons.outlined.FormatBold
|
||||
import androidx.compose.material.icons.outlined.FormatItalic
|
||||
import androidx.compose.material.icons.outlined.FormatListNumbered
|
||||
import androidx.compose.material.icons.outlined.FormatUnderlined
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.mohamedrejeb.richeditor.model.rememberRichTextState
|
||||
import com.mohamedrejeb.richeditor.ui.material3.RichTextEditor
|
||||
import com.mohamedrejeb.richeditor.ui.material3.RichTextEditorDefaults.richTextEditorColors
|
||||
import eu.kanade.tachiyomi.ui.manga.notes.MangaNotesScreen
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Composable
|
||||
fun MangaNotesTextArea(
|
||||
state: MangaNotesScreen.State,
|
||||
onUpdate: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val richTextState = rememberRichTextState()
|
||||
val primaryColor = MaterialTheme.colorScheme.primary
|
||||
|
||||
DisposableEffect(scope, richTextState) {
|
||||
snapshotFlow { richTextState.annotatedString }
|
||||
.debounce(0.25.seconds)
|
||||
.distinctUntilChanged()
|
||||
.map { richTextState.toMarkdown() }
|
||||
.onEach { onUpdate(it) }
|
||||
.launchIn(scope)
|
||||
|
||||
onDispose {
|
||||
onUpdate(richTextState.toMarkdown())
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
richTextState.setMarkdown(state.notes)
|
||||
richTextState.config.unorderedListIndent = 4
|
||||
richTextState.config.orderedListIndent = 20
|
||||
}
|
||||
LaunchedEffect(primaryColor) {
|
||||
richTextState.config.linkColor = primaryColor
|
||||
}
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(focusRequester) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
val textLength = remember(richTextState.annotatedString) { richTextState.toText().length }
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(horizontal = MaterialTheme.padding.small)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
RichTextEditor(
|
||||
state = richTextState,
|
||||
textStyle = MaterialTheme.typography.bodyLarge,
|
||||
placeholder = {
|
||||
Text(text = stringResource(MR.strings.notes_placeholder))
|
||||
},
|
||||
colors = richTextEditorColors(
|
||||
containerColor = Color.Transparent,
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
contentPadding = PaddingValues(
|
||||
horizontal = MaterialTheme.padding.medium,
|
||||
),
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester),
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier
|
||||
.padding(vertical = MaterialTheme.padding.small)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
LazyRow(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
item {
|
||||
MangaNotesTextAreaButton(
|
||||
onClick = { richTextState.toggleSpanStyle(SpanStyle(fontWeight = FontWeight.Bold)) },
|
||||
isSelected = richTextState.currentSpanStyle.fontWeight == FontWeight.Bold,
|
||||
icon = Icons.Outlined.FormatBold,
|
||||
)
|
||||
}
|
||||
item {
|
||||
MangaNotesTextAreaButton(
|
||||
onClick = { richTextState.toggleSpanStyle(SpanStyle(fontStyle = FontStyle.Italic)) },
|
||||
isSelected = richTextState.currentSpanStyle.fontStyle == FontStyle.Italic,
|
||||
icon = Icons.Outlined.FormatItalic,
|
||||
)
|
||||
}
|
||||
item {
|
||||
MangaNotesTextAreaButton(
|
||||
onClick = {
|
||||
richTextState.toggleSpanStyle(SpanStyle(textDecoration = TextDecoration.Underline))
|
||||
},
|
||||
isSelected = richTextState.currentSpanStyle.textDecoration
|
||||
?.contains(TextDecoration.Underline)
|
||||
?: false,
|
||||
icon = Icons.Outlined.FormatUnderlined,
|
||||
)
|
||||
}
|
||||
item {
|
||||
VerticalDivider(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = MaterialTheme.padding.extraSmall)
|
||||
.height(MaterialTheme.padding.large),
|
||||
)
|
||||
}
|
||||
item {
|
||||
MangaNotesTextAreaButton(
|
||||
onClick = { richTextState.toggleUnorderedList() },
|
||||
isSelected = richTextState.isUnorderedList,
|
||||
icon = Icons.AutoMirrored.Outlined.FormatListBulleted,
|
||||
)
|
||||
}
|
||||
item {
|
||||
MangaNotesTextAreaButton(
|
||||
onClick = { richTextState.toggleOrderedList() },
|
||||
isSelected = richTextState.isOrderedList,
|
||||
icon = Icons.Outlined.FormatListNumbered,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = textLength.toString(),
|
||||
modifier = Modifier.padding(MaterialTheme.padding.extraSmall),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MangaNotesTextAreaButton(
|
||||
onClick: () -> Unit,
|
||||
icon: ImageVector,
|
||||
isSelected: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.clickable(
|
||||
onClick = onClick,
|
||||
enabled = true,
|
||||
role = Role.Button,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = icon.name,
|
||||
tint = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.background(color = if (isSelected) MaterialTheme.colorScheme.onBackground else Color.Transparent)
|
||||
.padding(MaterialTheme.padding.extraSmall),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.components.AppBarTitle
|
||||
import eu.kanade.presentation.components.DownloadDropdownMenu
|
||||
import eu.kanade.presentation.manga.DownloadAction
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.theme.active
|
||||
@@ -37,6 +36,7 @@ fun MangaToolbar(
|
||||
onClickEditCategory: (() -> Unit)?,
|
||||
onClickRefresh: () -> Unit,
|
||||
onClickMigrate: (() -> Unit)?,
|
||||
onClickEditNotes: () -> Unit,
|
||||
|
||||
// For action mode
|
||||
actionModeCounter: Int,
|
||||
@@ -75,7 +75,7 @@ fun MangaToolbar(
|
||||
|
||||
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
||||
AppBarActions(
|
||||
actions = persistentListOf<AppBar.AppBarAction>().builder().apply {
|
||||
actions = buildList {
|
||||
if (isActionMode) {
|
||||
add(
|
||||
AppBar.Action(
|
||||
@@ -91,7 +91,7 @@ fun MangaToolbar(
|
||||
onClick = onInvertSelection,
|
||||
),
|
||||
)
|
||||
return@apply
|
||||
return@buildList
|
||||
}
|
||||
if (onClickDownload != null) {
|
||||
add(
|
||||
@@ -140,8 +140,13 @@ fun MangaToolbar(
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
.build(),
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.action_notes),
|
||||
onClick = onClickEditNotes,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
isActionMode = isActionMode,
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
package eu.kanade.presentation.manga.components
|
||||
|
||||
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.text.InlineTextContent
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Image
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.FirstBaseline
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.mikepenz.markdown.coil3.Coil3ImageTransformerImpl
|
||||
import com.mikepenz.markdown.compose.LocalBulletListHandler
|
||||
import com.mikepenz.markdown.compose.Markdown
|
||||
import com.mikepenz.markdown.compose.components.markdownComponents
|
||||
import com.mikepenz.markdown.compose.elements.MarkdownBulletList
|
||||
import com.mikepenz.markdown.compose.elements.MarkdownDivider
|
||||
import com.mikepenz.markdown.compose.elements.MarkdownOrderedList
|
||||
import com.mikepenz.markdown.compose.elements.MarkdownTable
|
||||
import com.mikepenz.markdown.compose.elements.MarkdownTableHeader
|
||||
import com.mikepenz.markdown.compose.elements.MarkdownTableRow
|
||||
import com.mikepenz.markdown.compose.elements.MarkdownText
|
||||
import com.mikepenz.markdown.compose.elements.listDepth
|
||||
import com.mikepenz.markdown.model.DefaultMarkdownColors
|
||||
import com.mikepenz.markdown.model.DefaultMarkdownInlineContent
|
||||
import com.mikepenz.markdown.model.DefaultMarkdownTypography
|
||||
import com.mikepenz.markdown.model.MarkdownAnnotator
|
||||
import com.mikepenz.markdown.model.MarkdownColors
|
||||
import com.mikepenz.markdown.model.MarkdownPadding
|
||||
import com.mikepenz.markdown.model.MarkdownTypography
|
||||
import com.mikepenz.markdown.model.NoOpImageTransformerImpl
|
||||
import com.mikepenz.markdown.model.markdownAnnotator
|
||||
import com.mikepenz.markdown.model.rememberMarkdownState
|
||||
import org.intellij.markdown.MarkdownTokenTypes.Companion.HTML_TAG
|
||||
import org.intellij.markdown.flavours.MarkdownFlavourDescriptor
|
||||
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
|
||||
import org.intellij.markdown.flavours.commonmark.CommonMarkMarkerProcessor
|
||||
import org.intellij.markdown.flavours.gfm.table.GitHubTableMarkerProvider
|
||||
import org.intellij.markdown.parser.MarkerProcessor
|
||||
import org.intellij.markdown.parser.MarkerProcessorFactory
|
||||
import org.intellij.markdown.parser.ProductionHolder
|
||||
import org.intellij.markdown.parser.constraints.CommonMarkdownConstraints
|
||||
import org.intellij.markdown.parser.constraints.MarkdownConstraints
|
||||
import org.intellij.markdown.parser.markerblocks.MarkerBlockProvider
|
||||
import org.intellij.markdown.parser.markerblocks.providers.AtxHeaderProvider
|
||||
import org.intellij.markdown.parser.markerblocks.providers.BlockQuoteProvider
|
||||
import org.intellij.markdown.parser.markerblocks.providers.CodeBlockProvider
|
||||
import org.intellij.markdown.parser.markerblocks.providers.CodeFenceProvider
|
||||
import org.intellij.markdown.parser.markerblocks.providers.HorizontalRuleProvider
|
||||
import org.intellij.markdown.parser.markerblocks.providers.ListMarkerProvider
|
||||
import org.intellij.markdown.parser.markerblocks.providers.SetextHeaderProvider
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
|
||||
const val MARKDOWN_INLINE_IMAGE_TAG = "MARKDOWN_INLINE_IMAGE"
|
||||
|
||||
@Composable
|
||||
fun MarkdownRender(
|
||||
content: String,
|
||||
modifier: Modifier = Modifier,
|
||||
flavour: MarkdownFlavourDescriptor = SimpleMarkdownFlavourDescriptor,
|
||||
annotator: MarkdownAnnotator = remember { markdownAnnotator() },
|
||||
loadImages: Boolean = true,
|
||||
) {
|
||||
Markdown(
|
||||
markdownState = rememberMarkdownState(
|
||||
content = content,
|
||||
flavour = flavour,
|
||||
immediate = true,
|
||||
),
|
||||
annotator = annotator,
|
||||
colors = getMarkdownColors(),
|
||||
typography = getMarkdownTypography(),
|
||||
padding = markdownPadding,
|
||||
components = markdownComponents,
|
||||
imageTransformer = remember(loadImages) {
|
||||
if (loadImages) Coil3ImageTransformerImpl else NoOpImageTransformerImpl()
|
||||
},
|
||||
inlineContent = getMarkdownInlineContent(),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun getMarkdownColors(): MarkdownColors {
|
||||
val codeBackground = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)
|
||||
return DefaultMarkdownColors(
|
||||
text = MaterialTheme.colorScheme.onSurface,
|
||||
codeBackground = codeBackground,
|
||||
inlineCodeBackground = codeBackground,
|
||||
dividerColor = MaterialTheme.colorScheme.outlineVariant,
|
||||
tableBackground = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
fun getMarkdownLinkStyle() = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun getMarkdownTypography(): MarkdownTypography {
|
||||
val link = getMarkdownLinkStyle()
|
||||
return DefaultMarkdownTypography(
|
||||
h1 = MaterialTheme.typography.headlineMedium,
|
||||
h2 = MaterialTheme.typography.headlineSmall,
|
||||
h3 = MaterialTheme.typography.titleLarge,
|
||||
h4 = MaterialTheme.typography.titleMedium,
|
||||
h5 = MaterialTheme.typography.titleSmall,
|
||||
h6 = MaterialTheme.typography.bodyLarge,
|
||||
text = MaterialTheme.typography.bodyMedium,
|
||||
code = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace),
|
||||
inlineCode = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace),
|
||||
quote = MaterialTheme.typography.bodyMedium.plus(SpanStyle(fontStyle = FontStyle.Italic)),
|
||||
paragraph = MaterialTheme.typography.bodyMedium,
|
||||
ordered = MaterialTheme.typography.bodyMedium,
|
||||
bullet = MaterialTheme.typography.bodyMedium,
|
||||
list = MaterialTheme.typography.bodyMedium,
|
||||
textLink = TextLinkStyles(style = link.toSpanStyle()),
|
||||
table = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
|
||||
private val markdownPadding = object : MarkdownPadding {
|
||||
override val block: Dp = 2.dp
|
||||
override val blockQuote: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 0.dp)
|
||||
override val blockQuoteBar: PaddingValues.Absolute = PaddingValues.Absolute(
|
||||
left = 4.dp,
|
||||
top = 2.dp,
|
||||
right = 4.dp,
|
||||
bottom = 2.dp,
|
||||
)
|
||||
override val blockQuoteText: PaddingValues = PaddingValues(vertical = 4.dp)
|
||||
override val codeBlock: PaddingValues = PaddingValues(8.dp)
|
||||
override val list: Dp = 0.dp
|
||||
override val listIndent: Dp = 8.dp
|
||||
override val listItemBottom: Dp = 0.dp
|
||||
override val listItemTop: Dp = 0.dp
|
||||
}
|
||||
|
||||
private val markdownComponents = markdownComponents(
|
||||
horizontalRule = {
|
||||
MarkdownDivider(
|
||||
modifier = Modifier
|
||||
.padding(vertical = MaterialTheme.padding.extraSmall)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
},
|
||||
orderedList = { ol ->
|
||||
Column(modifier = Modifier.padding(start = MaterialTheme.padding.small)) {
|
||||
MarkdownOrderedList(
|
||||
content = ol.content,
|
||||
node = ol.node,
|
||||
style = ol.typography.ordered,
|
||||
depth = ol.listDepth,
|
||||
markerModifier = { Modifier.alignBy(FirstBaseline) },
|
||||
listModifier = { Modifier.alignBy(FirstBaseline) },
|
||||
)
|
||||
}
|
||||
},
|
||||
unorderedList = { ul ->
|
||||
val markers = listOf("•", "◦", "▸", "▹")
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalBulletListHandler provides { _, _, _, _, _ -> "${markers[ul.listDepth % markers.size]} " },
|
||||
) {
|
||||
Column(modifier = Modifier.padding(start = MaterialTheme.padding.small)) {
|
||||
MarkdownBulletList(
|
||||
content = ul.content,
|
||||
node = ul.node,
|
||||
style = ul.typography.bullet,
|
||||
markerModifier = { Modifier.alignBy(FirstBaseline) },
|
||||
listModifier = { Modifier.alignBy(FirstBaseline) },
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
table = { t ->
|
||||
MarkdownTable(
|
||||
content = t.content,
|
||||
node = t.node,
|
||||
style = t.typography.text,
|
||||
headerBlock = { content, header, tableWidth, style ->
|
||||
MarkdownTableHeader(
|
||||
content = content,
|
||||
header = header,
|
||||
tableWidth = tableWidth,
|
||||
style = style,
|
||||
maxLines = Int.MAX_VALUE,
|
||||
)
|
||||
},
|
||||
rowBlock = { content, header, tableWidth, style ->
|
||||
MarkdownTableRow(
|
||||
content = content,
|
||||
header = header,
|
||||
tableWidth = tableWidth,
|
||||
style = style,
|
||||
maxLines = Int.MAX_VALUE,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
custom = { type, model ->
|
||||
if (type in DISALLOWED_MARKDOWN_TYPES) {
|
||||
MarkdownText(
|
||||
content = model.content.substring(model.node.startOffset, model.node.endOffset),
|
||||
node = model.node,
|
||||
style = model.typography.text,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun getMarkdownInlineContent() = DefaultMarkdownInlineContent(
|
||||
inlineContent = mapOf(
|
||||
MARKDOWN_INLINE_IMAGE_TAG to InlineTextContent(
|
||||
placeholder = Placeholder(
|
||||
width = MaterialTheme.typography.bodyMedium.fontSize * 1.25,
|
||||
height = MaterialTheme.typography.bodyMedium.fontSize * 1.25,
|
||||
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter,
|
||||
),
|
||||
children = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Image,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private object SimpleMarkdownFlavourDescriptor : CommonMarkFlavourDescriptor() {
|
||||
override val markerProcessorFactory: MarkerProcessorFactory = SimpleMarkdownProcessFactory
|
||||
}
|
||||
|
||||
private object SimpleMarkdownProcessFactory : MarkerProcessorFactory {
|
||||
override fun createMarkerProcessor(productionHolder: ProductionHolder): MarkerProcessor<*> {
|
||||
return SimpleMarkdownMarkerProcessor(productionHolder, CommonMarkdownConstraints.BASE)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `CommonMarkFlavour`, but with html blocks and reference links removed and
|
||||
* table support added
|
||||
*/
|
||||
private class SimpleMarkdownMarkerProcessor(
|
||||
productionHolder: ProductionHolder,
|
||||
constraints: MarkdownConstraints,
|
||||
) : CommonMarkMarkerProcessor(productionHolder, constraints) {
|
||||
private val markerBlockProviders = listOf(
|
||||
CodeBlockProvider(),
|
||||
HorizontalRuleProvider(),
|
||||
CodeFenceProvider(),
|
||||
SetextHeaderProvider(),
|
||||
BlockQuoteProvider(),
|
||||
ListMarkerProvider(),
|
||||
AtxHeaderProvider(),
|
||||
GitHubTableMarkerProvider(),
|
||||
)
|
||||
|
||||
override fun getMarkerBlockProviders(): List<MarkerBlockProvider<StateInfo>> {
|
||||
return markerBlockProviders
|
||||
}
|
||||
}
|
||||
|
||||
val DISALLOWED_MARKDOWN_TYPES = arrayOf(HTML_TAG)
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.kanade.presentation.more
|
||||
|
||||
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.layout.size
|
||||
@@ -15,7 +16,9 @@ import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
@Composable
|
||||
fun LogoHeader() {
|
||||
fun LogoHeader(
|
||||
iconPadding: PaddingValues = PaddingValues(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
@@ -25,7 +28,7 @@ fun LogoHeader() {
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier
|
||||
.padding(vertical = 56.dp)
|
||||
.padding(iconPadding)
|
||||
.size(64.dp),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package eu.kanade.presentation.more
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.automirrored.outlined.Label
|
||||
import androidx.compose.material.icons.filled.VolunteerActivism
|
||||
import androidx.compose.material.icons.outlined.CloudOff
|
||||
import androidx.compose.material.icons.outlined.GetApp
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
@@ -12,10 +13,10 @@ import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material.icons.outlined.Storage
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||
import eu.kanade.tachiyomi.R
|
||||
@@ -39,16 +40,17 @@ fun MoreScreen(
|
||||
onClickStats: () -> Unit,
|
||||
onClickDataAndStorage: () -> Unit,
|
||||
onClickSettings: () -> Unit,
|
||||
onClickSupport: () -> Unit,
|
||||
onClickAbout: () -> Unit,
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
Scaffold { contentPadding ->
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
) {
|
||||
ScrollbarLazyColumn(contentPadding = contentPadding) {
|
||||
item {
|
||||
LogoHeader()
|
||||
LogoHeader(
|
||||
iconPadding = PaddingValues(vertical = 32.dp),
|
||||
)
|
||||
}
|
||||
item {
|
||||
SwitchPreferenceWidget(
|
||||
@@ -131,6 +133,13 @@ fun MoreScreen(
|
||||
onPreferenceClick = onClickSettings,
|
||||
)
|
||||
}
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.label_support_us),
|
||||
icon = Icons.Default.VolunteerActivism,
|
||||
onPreferenceClick = onClickSupport,
|
||||
)
|
||||
}
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(MR.strings.pref_category_about),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package eu.kanade.presentation.more
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -13,13 +14,10 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import com.halilibo.richtext.markdown.Markdown
|
||||
import com.halilibo.richtext.ui.RichTextStyle
|
||||
import com.halilibo.richtext.ui.material3.RichText
|
||||
import com.halilibo.richtext.ui.string.RichTextStringStyle
|
||||
import eu.kanade.presentation.manga.components.MarkdownRender
|
||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
@@ -42,17 +40,15 @@ fun NewUpdateScreen(
|
||||
rejectText = stringResource(MR.strings.action_not_now),
|
||||
onRejectClick = onRejectUpdate,
|
||||
) {
|
||||
RichText(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = MaterialTheme.padding.large),
|
||||
style = RichTextStyle(
|
||||
stringStyle = RichTextStringStyle(
|
||||
linkStyle = SpanStyle(color = MaterialTheme.colorScheme.primary),
|
||||
),
|
||||
),
|
||||
) {
|
||||
Markdown(content = changelogInfo)
|
||||
MarkdownRender(
|
||||
content = changelogInfo,
|
||||
flavour = GFMFlavourDescriptor(),
|
||||
)
|
||||
|
||||
TextButton(
|
||||
onClick = onOpenInBrowser,
|
||||
|
||||
@@ -42,7 +42,9 @@ fun OnboardingScreen(
|
||||
}
|
||||
val isLastStep = currentStep == steps.lastIndex
|
||||
|
||||
BackHandler(enabled = currentStep != 0, onBack = { currentStep-- })
|
||||
BackHandler(enabled = currentStep != 0) {
|
||||
currentStep--
|
||||
}
|
||||
|
||||
InfoScreen(
|
||||
icon = Icons.Outlined.RocketLaunch,
|
||||
|
||||
@@ -125,7 +125,7 @@ internal class PermissionStep : OnboardingStep {
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
|
||||
val crashlyticsPref = privacyPreferences.crashlytics()
|
||||
val crashlyticsPref = privacyPreferences.crashlytics
|
||||
val crashlytics by crashlyticsPref.collectAsState()
|
||||
PermissionSwitch(
|
||||
title = stringResource(MR.strings.onboarding_permission_crashlytics),
|
||||
@@ -134,7 +134,7 @@ internal class PermissionStep : OnboardingStep {
|
||||
onToggleChange = crashlyticsPref::set,
|
||||
)
|
||||
|
||||
val analyticsPref = privacyPreferences.analytics()
|
||||
val analyticsPref = privacyPreferences.analytics
|
||||
val analytics by analyticsPref.collectAsState()
|
||||
PermissionSwitch(
|
||||
title = stringResource(MR.strings.onboarding_permission_analytics),
|
||||
|
||||
@@ -30,7 +30,7 @@ import uy.kohesive.injekt.api.get
|
||||
|
||||
internal class StorageStep : OnboardingStep {
|
||||
|
||||
private val storagePref = Injekt.get<StoragePreferences>().baseStorageDirectory()
|
||||
private val storagePref = Injekt.get<StoragePreferences>().baseStorageDirectory
|
||||
|
||||
private var _isComplete by mutableStateOf(false)
|
||||
|
||||
|
||||
@@ -19,13 +19,13 @@ internal class ThemeStep : OnboardingStep {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val themeModePref = uiPreferences.themeMode()
|
||||
val themeModePref = uiPreferences.themeMode
|
||||
val themeMode by themeModePref.collectAsState()
|
||||
|
||||
val appThemePref = uiPreferences.appTheme()
|
||||
val appThemePref = uiPreferences.appTheme
|
||||
val appTheme by appThemePref.collectAsState()
|
||||
|
||||
val amoledPref = uiPreferences.themeDarkAmoled()
|
||||
val amoledPref = uiPreferences.themeDarkAmoled
|
||||
val amoled by amoledPref.collectAsState()
|
||||
|
||||
Column {
|
||||
|
||||
@@ -5,8 +5,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.core.common.preference.Preference as PreferenceData
|
||||
@@ -15,10 +13,10 @@ sealed class Preference {
|
||||
abstract val title: String
|
||||
abstract val enabled: Boolean
|
||||
|
||||
sealed class PreferenceItem<T> : Preference() {
|
||||
sealed class PreferenceItem<T, R> : Preference() {
|
||||
abstract val subtitle: String?
|
||||
abstract val icon: ImageVector?
|
||||
abstract val onValueChanged: suspend (value: T) -> Boolean
|
||||
abstract val onValueChanged: suspend (value: T) -> R
|
||||
|
||||
/**
|
||||
* A basic [PreferenceItem] that only displays texts.
|
||||
@@ -27,10 +25,11 @@ sealed class Preference {
|
||||
override val title: String,
|
||||
override val subtitle: String? = null,
|
||||
override val enabled: Boolean = true,
|
||||
val widget: @Composable (() -> Unit)? = null,
|
||||
val onClick: (() -> Unit)? = null,
|
||||
) : PreferenceItem<String>() {
|
||||
) : PreferenceItem<String, Unit>() {
|
||||
override val icon: ImageVector? = null
|
||||
override val onValueChanged: suspend (value: String) -> Boolean = { true }
|
||||
override val onValueChanged: suspend (value: String) -> Unit = {}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,7 +41,7 @@ sealed class Preference {
|
||||
override val subtitle: String? = null,
|
||||
override val enabled: Boolean = true,
|
||||
override val onValueChanged: suspend (value: Boolean) -> Boolean = { true },
|
||||
) : PreferenceItem<Boolean>() {
|
||||
) : PreferenceItem<Boolean, Boolean>() {
|
||||
override val icon: ImageVector? = null
|
||||
}
|
||||
|
||||
@@ -52,12 +51,13 @@ sealed class Preference {
|
||||
data class SliderPreference(
|
||||
val value: Int,
|
||||
override val title: String,
|
||||
override val subtitle: String? = null,
|
||||
val valueString: String? = null,
|
||||
val valueRange: IntProgression = 0..1,
|
||||
@IntRange(from = 0) val steps: Int = with(valueRange) { (last - first) - 1 },
|
||||
override val subtitle: String? = null,
|
||||
override val enabled: Boolean = true,
|
||||
override val onValueChanged: suspend (value: Int) -> Boolean = { true },
|
||||
) : PreferenceItem<Int>() {
|
||||
override val onValueChanged: suspend (value: Int) -> Unit = {},
|
||||
) : PreferenceItem<Int, Unit>() {
|
||||
override val icon: ImageVector? = null
|
||||
}
|
||||
|
||||
@@ -67,21 +67,21 @@ sealed class Preference {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
data class ListPreference<T>(
|
||||
val preference: PreferenceData<T>,
|
||||
val entries: ImmutableMap<T, String>,
|
||||
val entries: Map<T, String>,
|
||||
override val title: String,
|
||||
override val subtitle: String? = "%s",
|
||||
val subtitleProvider: @Composable (value: T, entries: ImmutableMap<T, String>) -> String? =
|
||||
val subtitleProvider: @Composable (value: T, entries: Map<T, String>) -> String? =
|
||||
{ v, e -> subtitle?.format(e[v]) },
|
||||
override val icon: ImageVector? = null,
|
||||
override val enabled: Boolean = true,
|
||||
override val onValueChanged: suspend (value: T) -> Boolean = { true },
|
||||
) : PreferenceItem<T>() {
|
||||
) : PreferenceItem<T, Boolean>() {
|
||||
internal fun internalSet(value: Any) = preference.set(value as T)
|
||||
internal suspend fun internalOnValueChanged(value: Any) = onValueChanged(value as T)
|
||||
|
||||
@Composable
|
||||
internal fun internalSubtitleProvider(value: Any?, entries: ImmutableMap<out Any?, String>) =
|
||||
subtitleProvider(value as T, entries as ImmutableMap<T, String>)
|
||||
internal fun internalSubtitleProvider(value: Any?, entries: Map<out Any?, String>) =
|
||||
subtitleProvider(value as T, entries as Map<T, String>)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,15 +89,15 @@ sealed class Preference {
|
||||
*/
|
||||
data class BasicListPreference(
|
||||
val value: String,
|
||||
val entries: ImmutableMap<String, String>,
|
||||
val entries: Map<String, String>,
|
||||
override val title: String,
|
||||
override val subtitle: String? = "%s",
|
||||
val subtitleProvider: @Composable (value: String, entries: ImmutableMap<String, String>) -> String? =
|
||||
val subtitleProvider: @Composable (value: String, entries: Map<String, String>) -> String? =
|
||||
{ v, e -> subtitle?.format(e[v]) },
|
||||
override val icon: ImageVector? = null,
|
||||
override val enabled: Boolean = true,
|
||||
override val onValueChanged: suspend (value: String) -> Boolean = { true },
|
||||
) : PreferenceItem<String>()
|
||||
override val onValueChanged: suspend (value: String) -> Unit = {},
|
||||
) : PreferenceItem<String, Unit>()
|
||||
|
||||
/**
|
||||
* A [PreferenceItem] that displays a list of entries as a dialog.
|
||||
@@ -105,10 +105,10 @@ sealed class Preference {
|
||||
*/
|
||||
data class MultiSelectListPreference(
|
||||
val preference: PreferenceData<Set<String>>,
|
||||
val entries: ImmutableMap<String, String>,
|
||||
val entries: Map<String, String>,
|
||||
override val title: String,
|
||||
override val subtitle: String? = "%s",
|
||||
val subtitleProvider: @Composable (value: Set<String>, entries: ImmutableMap<String, String>) -> String? =
|
||||
val subtitleProvider: @Composable (value: Set<String>, entries: Map<String, String>) -> String? =
|
||||
{ v, e ->
|
||||
val combined = remember(v, e) {
|
||||
v.mapNotNull { e[it] }
|
||||
@@ -121,7 +121,7 @@ sealed class Preference {
|
||||
override val icon: ImageVector? = null,
|
||||
override val enabled: Boolean = true,
|
||||
override val onValueChanged: suspend (value: Set<String>) -> Boolean = { true },
|
||||
) : PreferenceItem<Set<String>>()
|
||||
) : PreferenceItem<Set<String>, Boolean>()
|
||||
|
||||
/**
|
||||
* A [PreferenceItem] that shows a EditText in the dialog.
|
||||
@@ -132,7 +132,7 @@ sealed class Preference {
|
||||
override val subtitle: String? = "%s",
|
||||
override val enabled: Boolean = true,
|
||||
override val onValueChanged: suspend (value: String) -> Boolean = { true },
|
||||
) : PreferenceItem<String>() {
|
||||
) : PreferenceItem<String, Boolean>() {
|
||||
override val icon: ImageVector? = null
|
||||
}
|
||||
|
||||
@@ -143,31 +143,31 @@ sealed class Preference {
|
||||
val tracker: Tracker,
|
||||
val login: () -> Unit,
|
||||
val logout: () -> Unit,
|
||||
) : PreferenceItem<String>() {
|
||||
) : PreferenceItem<String, Unit>() {
|
||||
override val title: String = ""
|
||||
override val enabled: Boolean = true
|
||||
override val subtitle: String? = null
|
||||
override val icon: ImageVector? = null
|
||||
override val onValueChanged: suspend (value: String) -> Boolean = { true }
|
||||
override val onValueChanged: suspend (value: String) -> Unit = {}
|
||||
}
|
||||
|
||||
data class InfoPreference(
|
||||
override val title: String,
|
||||
) : PreferenceItem<String>() {
|
||||
) : PreferenceItem<String, Unit>() {
|
||||
override val enabled: Boolean = true
|
||||
override val subtitle: String? = null
|
||||
override val icon: ImageVector? = null
|
||||
override val onValueChanged: suspend (value: String) -> Boolean = { true }
|
||||
override val onValueChanged: suspend (value: String) -> Unit = {}
|
||||
}
|
||||
|
||||
data class CustomPreference(
|
||||
override val title: String,
|
||||
val content: @Composable () -> Unit,
|
||||
) : PreferenceItem<Unit>() {
|
||||
) : PreferenceItem<Unit, Unit>() {
|
||||
override val enabled: Boolean = true
|
||||
override val subtitle: String? = null
|
||||
override val icon: ImageVector? = null
|
||||
override val onValueChanged: suspend (value: Unit) -> Boolean = { true }
|
||||
override val onValueChanged: suspend (value: Unit) -> Unit = {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +175,6 @@ sealed class Preference {
|
||||
override val title: String,
|
||||
override val enabled: Boolean = true,
|
||||
|
||||
val preferenceItems: ImmutableList<PreferenceItem<out Any>>,
|
||||
val preferenceItems: List<PreferenceItem<out Any, out Any>>,
|
||||
) : Preference()
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) {
|
||||
|
||||
@Composable
|
||||
fun StatusWrapper(
|
||||
item: Preference.PreferenceItem<*>,
|
||||
item: Preference.PreferenceItem<*, *>,
|
||||
highlightKey: String?,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
@@ -56,7 +56,7 @@ fun StatusWrapper(
|
||||
|
||||
@Composable
|
||||
internal fun PreferenceItem(
|
||||
item: Preference.PreferenceItem<*>,
|
||||
item: Preference.PreferenceItem<*, *>,
|
||||
highlightKey: String?,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -83,17 +83,18 @@ internal fun PreferenceItem(
|
||||
}
|
||||
is Preference.PreferenceItem.SliderPreference -> {
|
||||
BaseSliderItem(
|
||||
label = item.title,
|
||||
value = item.value,
|
||||
valueRange = item.valueRange,
|
||||
valueText = item.subtitle.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
|
||||
steps = item.steps,
|
||||
labelStyle = MaterialTheme.typography.titleLarge.copy(fontSize = TitleFontSize),
|
||||
title = item.title,
|
||||
subtitle = item.subtitle,
|
||||
valueString = item.valueString.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
|
||||
onChange = {
|
||||
scope.launch {
|
||||
item.onValueChanged(it)
|
||||
}
|
||||
},
|
||||
titleStyle = MaterialTheme.typography.titleLarge.copy(fontSize = TitleFontSize),
|
||||
modifier = Modifier.padding(
|
||||
horizontal = PrefsHorizontalPadding,
|
||||
vertical = PrefsVerticalPadding,
|
||||
@@ -146,6 +147,7 @@ internal fun PreferenceItem(
|
||||
title = item.title,
|
||||
subtitle = item.subtitle,
|
||||
icon = item.icon,
|
||||
widget = item.widget,
|
||||
onPreferenceClick = item.onClick,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ fun PreferenceScreen(
|
||||
}
|
||||
|
||||
// Create Preference Item
|
||||
is Preference.PreferenceItem<*> -> item {
|
||||
is Preference.PreferenceItem<*, *> -> item {
|
||||
PreferenceItem(
|
||||
item = preference,
|
||||
highlightKey = highlightKey,
|
||||
|
||||
+33
-24
@@ -53,9 +53,6 @@ import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
||||
import eu.kanade.tachiyomi.util.system.powerManager
|
||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import okhttp3.Headers
|
||||
@@ -63,6 +60,7 @@ import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||
import tachiyomi.core.common.util.lang.withUIContext
|
||||
import tachiyomi.core.common.util.system.ImageUtil
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
@@ -85,6 +83,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
|
||||
val basePreferences = remember { Injekt.get<BasePreferences>() }
|
||||
val networkPreferences = remember { Injekt.get<NetworkPreferences>() }
|
||||
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
||||
|
||||
return listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
@@ -97,7 +96,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = networkPreferences.verboseLogging(),
|
||||
preference = networkPreferences.verboseLogging,
|
||||
title = stringResource(MR.strings.pref_verbose_logging),
|
||||
subtitle = stringResource(MR.strings.pref_verbose_logging_summary),
|
||||
onValueChanged = {
|
||||
@@ -125,7 +124,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
getBackgroundActivityGroup(),
|
||||
getDataGroup(),
|
||||
getNetworkGroup(networkPreferences = networkPreferences),
|
||||
getLibraryGroup(),
|
||||
getLibraryGroup(libraryPreferences = libraryPreferences),
|
||||
getReaderGroup(basePreferences = basePreferences),
|
||||
getExtensionsGroup(basePreferences = basePreferences),
|
||||
)
|
||||
@@ -138,7 +137,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.label_background_activity),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.pref_disable_battery_optimization),
|
||||
subtitle = stringResource(MR.strings.pref_disable_battery_optimization_summary),
|
||||
@@ -177,7 +176,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.label_data),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.pref_invalidate_download_cache),
|
||||
subtitle = stringResource(MR.strings.pref_invalidate_download_cache_summary),
|
||||
@@ -202,12 +201,12 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
val context = LocalContext.current
|
||||
val networkHelper = remember { Injekt.get<NetworkHelper>() }
|
||||
|
||||
val userAgentPref = networkPreferences.defaultUserAgent()
|
||||
val userAgentPref = networkPreferences.defaultUserAgent
|
||||
val userAgent by userAgentPref.collectAsState()
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.label_network),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.pref_clear_cookies),
|
||||
onClick = {
|
||||
@@ -236,8 +235,8 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = networkPreferences.dohProvider(),
|
||||
entries = persistentMapOf(
|
||||
preference = networkPreferences.dohProvider,
|
||||
entries = mapOf(
|
||||
-1 to stringResource(MR.strings.disabled),
|
||||
PREF_DOH_CLOUDFLARE to "Cloudflare",
|
||||
PREF_DOH_GOOGLE to "Google",
|
||||
@@ -286,13 +285,15 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getLibraryGroup(): Preference.PreferenceGroup {
|
||||
private fun getLibraryGroup(
|
||||
libraryPreferences: LibraryPreferences,
|
||||
): Preference.PreferenceGroup {
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.label_library),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.pref_refresh_library_covers),
|
||||
onClick = { MetadataUpdateJob.startNow(context) },
|
||||
@@ -314,6 +315,16 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
}
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = libraryPreferences.updateMangaTitles,
|
||||
title = stringResource(MR.strings.pref_update_library_manga_titles),
|
||||
subtitle = stringResource(MR.strings.pref_update_library_manga_titles_summary),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = libraryPreferences.disallowNonAsciiFilenames,
|
||||
title = stringResource(MR.strings.pref_disallow_non_ascii_filenames),
|
||||
subtitle = stringResource(MR.strings.pref_disallow_non_ascii_filenames_details),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -329,14 +340,14 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
uri?.let {
|
||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
basePreferences.displayProfile().set(uri.toString())
|
||||
basePreferences.displayProfile.set(uri.toString())
|
||||
}
|
||||
}
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_reader),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = basePreferences.hardwareBitmapThreshold(),
|
||||
preference = basePreferences.hardwareBitmapThreshold,
|
||||
entries = GLUtil.CUSTOM_TEXTURE_LIMIT_OPTIONS
|
||||
.mapIndexed { index, option ->
|
||||
val display = if (index == 0) {
|
||||
@@ -346,8 +357,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
}
|
||||
option to display
|
||||
}
|
||||
.toMap()
|
||||
.toImmutableMap(),
|
||||
.toMap(),
|
||||
title = stringResource(MR.strings.pref_hardware_bitmap_threshold),
|
||||
subtitleProvider = { value, options ->
|
||||
stringResource(MR.strings.pref_hardware_bitmap_threshold_summary, options[value].orEmpty())
|
||||
@@ -356,13 +366,13 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
GLUtil.DEVICE_TEXTURE_LIMIT > GLUtil.SAFE_TEXTURE_LIMIT,
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = basePreferences.alwaysDecodeLongStripWithSSIV(),
|
||||
preference = basePreferences.alwaysDecodeLongStripWithSSIV,
|
||||
title = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_2),
|
||||
subtitle = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_summary),
|
||||
),
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.pref_display_profile),
|
||||
subtitle = basePreferences.displayProfile().get(),
|
||||
subtitle = basePreferences.displayProfile.get(),
|
||||
onClick = {
|
||||
chooseColorProfile.launch(arrayOf("*/*"))
|
||||
},
|
||||
@@ -377,7 +387,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
): Preference.PreferenceGroup {
|
||||
val context = LocalContext.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val extensionInstallerPref = basePreferences.extensionInstaller()
|
||||
val extensionInstallerPref = basePreferences.extensionInstaller
|
||||
var shizukuMissing by rememberSaveable { mutableStateOf(false) }
|
||||
val trustExtension = remember { Injekt.get<TrustExtension>() }
|
||||
|
||||
@@ -406,7 +416,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
}
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.label_extensions),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = extensionInstallerPref,
|
||||
entries = extensionInstallerPref.entries
|
||||
@@ -418,8 +428,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
true
|
||||
}
|
||||
}
|
||||
.associateWith { stringResource(it.titleRes) }
|
||||
.toImmutableMap(),
|
||||
.associateWith { stringResource(it.titleRes) },
|
||||
title = stringResource(MR.strings.ext_installer_pref),
|
||||
onValueChanged = {
|
||||
if (it == BasePreferences.ExtensionInstaller.SHIZUKU &&
|
||||
|
||||
+15
-15
@@ -19,8 +19,6 @@ import eu.kanade.presentation.more.settings.screen.appearance.AppLanguageScreen
|
||||
import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget
|
||||
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
@@ -50,18 +48,18 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
): Preference.PreferenceGroup {
|
||||
val context = LocalContext.current
|
||||
|
||||
val themeModePref = uiPreferences.themeMode()
|
||||
val themeModePref = uiPreferences.themeMode
|
||||
val themeMode by themeModePref.collectAsState()
|
||||
|
||||
val appThemePref = uiPreferences.appTheme()
|
||||
val appThemePref = uiPreferences.appTheme
|
||||
val appTheme by appThemePref.collectAsState()
|
||||
|
||||
val amoledPref = uiPreferences.themeDarkAmoled()
|
||||
val amoledPref = uiPreferences.themeDarkAmoled
|
||||
val amoled by amoledPref.collectAsState()
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_theme),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.CustomPreference(
|
||||
title = stringResource(MR.strings.pref_app_theme),
|
||||
) {
|
||||
@@ -103,23 +101,22 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
|
||||
val now = remember { LocalDate.now() }
|
||||
|
||||
val dateFormat by uiPreferences.dateFormat().collectAsState()
|
||||
val dateFormat by uiPreferences.dateFormat.collectAsState()
|
||||
val formattedNow = remember(dateFormat) {
|
||||
UiPreferences.dateFormat(dateFormat).format(now)
|
||||
}
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_display),
|
||||
preferenceItems = persistentListOf(
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.pref_app_language),
|
||||
onClick = { navigator.push(AppLanguageScreen()) },
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = uiPreferences.tabletUiMode(),
|
||||
preference = uiPreferences.tabletUiMode,
|
||||
entries = TabletUiMode.entries
|
||||
.associateWith { stringResource(it.titleRes) }
|
||||
.toImmutableMap(),
|
||||
.associateWith { stringResource(it.titleRes) },
|
||||
title = stringResource(MR.strings.pref_tablet_ui_mode),
|
||||
onValueChanged = {
|
||||
context.toast(MR.strings.requires_app_restart)
|
||||
@@ -127,17 +124,16 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
preference = uiPreferences.dateFormat(),
|
||||
preference = uiPreferences.dateFormat,
|
||||
entries = DateFormats
|
||||
.associateWith {
|
||||
val formattedDate = UiPreferences.dateFormat(it).format(now)
|
||||
"${it.ifEmpty { stringResource(MR.strings.label_default) }} ($formattedDate)"
|
||||
}
|
||||
.toImmutableMap(),
|
||||
},
|
||||
title = stringResource(MR.strings.pref_date_format),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = uiPreferences.relativeTime(),
|
||||
preference = uiPreferences.relativeTime,
|
||||
title = stringResource(MR.strings.pref_relative_format),
|
||||
subtitle = stringResource(
|
||||
MR.strings.pref_relative_format_summary,
|
||||
@@ -145,6 +141,10 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
formattedNow,
|
||||
),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = uiPreferences.imagesInDescription,
|
||||
title = stringResource(MR.strings.pref_display_images_description),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user